diff --git a/Dockerfile b/Dockerfile index bc4441291d3..f9f7214d603 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,29 +1,44 @@ -# The base image already builds the libtorrent dependency so only Python pip packages -# are necessary to be installed to run Tribler core process. -FROM triblercore/libtorrent:1.2.10-x +# libtorrent-1.2.19 does not support python 3.11 yet +FROM python:3.10-slim -# Update the base system and install required libsodium and Python pip -RUN apt update && apt upgrade -y -RUN apt install -y libsodium23 python3-pip git - -RUN useradd -ms /bin/bash user -USER user -WORKDIR /home/user +RUN apt-get update \ + && apt-get install -y --no-install-recommends libsodium23=1.0.18-1 \ + && rm -rf /var/lib/apt/lists/* # Then, install pip dependencies so that it can be cached and does not # need to be built every time the source code changes. # This reduces the docker build time. -RUN mkdir requirements -COPY ./requirements-core.txt requirements/core-requirements.txt -RUN pip3 install -r requirements/core-requirements.txt +COPY ./requirements-core.txt /app/tribler/core-requirements.txt +RUN pip3 install -r /app/tribler/core-requirements.txt + +RUN useradd -ms /bin/bash user + +# Create default state and download directories and set the permissions +RUN chown -R user:user /app +RUN mkdir /state /downloads && chown -R user:user /state /downloads # Copy the source code and set the working directory -COPY ./ tribler -WORKDIR /home/user/tribler +COPY ./src /app/tribler/src/ +WORKDIR /app/tribler/ -# Set the REST API port and expose it +# Set to -1 to use the default ENV CORE_API_PORT=20100 -EXPOSE 20100 +ENV IPV8_PORT=7759 +ENV TORRENT_PORT=-1 +ENV DOWNLOAD_DIR=/downloads +ENV TSTATEDIR=/state +ENV HTTP_HOST=127.0.0.1 +ENV HTTPS_HOST=127.0.0.1 + +VOLUME /state +VOLUME /downloads + +USER user -# Only run the core process with --core switch -CMD ["./src/tribler.sh", "--core"] +CMD exec python3 /app/tribler/src/run_tribler_headless.py \ + --ipv8=${IPV8_PORT} \ + --libtorrent=${TORRENT_PORT} \ + --restapi_http_host=${HTTP_HOST} \ + --restapi_https_host=${HTTPS_HOST} \ + "--statedir=${TSTATEDIR}" \ + "--download_dir=${DOWNLOAD_DIR}" diff --git a/README.rst b/README.rst index 8f9a9602946..5bbfef25f1a 100644 --- a/README.rst +++ b/README.rst @@ -85,6 +85,13 @@ To run the built docker image: Note that by default, the REST API is bound to localhost inside the container so to access the APIs, network needs to be set to host (--net="host"). +To use the local state directory and downloads directory, the volumes can be mounted: + +.. code-block:: bash + + docker run -p 20100:20100 --net="host" -v ~/.Tribler:/state -v ~/downloads/TriblerDownloads:/downloads triblercore/triblercore:latest + + The REST APIs are now accessible at: http://localhost:20100/docs diff --git a/docker-compose.yml b/docker-compose.yml index 5c2a3c59504..135a531b1bb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,10 +4,15 @@ services: tribler-core: image: triblercore/triblercore:latest container_name: triblercore - network_mode: "host" build: . volumes: - - ~/.Tribler:/home/user/.Tribler + - "~/.Tribler:/state" + - "~/Downloads/TriblerDownloads:/downloads" + ports: + - "20100:20100" environment: - CORE_API_PORT: 20100 - + - CORE_API_PORT=20100 + - CORE_API_KEY=TEST + - TORRENT_PORT=7000 + - TSTATEDIR=/state + - HTTP_HOST=0.0.0.0 diff --git a/src/run_tribler_headless.py b/src/run_tribler_headless.py index bc1c6c2227f..215a7f98b02 100644 --- a/src/run_tribler_headless.py +++ b/src/run_tribler_headless.py @@ -18,6 +18,7 @@ from tribler.core.components.session import Session from tribler.core.config.tribler_config import TriblerConfig from tribler.core.start_core import components_gen +from tribler.core.upgrade.version_manager import VersionHistory from tribler.core.utilities.exit_codes.tribler_exit_codes import EXITCODE_ANOTHER_CORE_PROCESS_IS_RUNNING from tribler.core.utilities.osutils import get_appstate_dir, get_root_state_directory from tribler.core.utilities.path_util import Path @@ -78,11 +79,13 @@ async def signal_handler(sig): signal.signal(signal.SIGINT, lambda sig, _: ensure_future(signal_handler(sig))) signal.signal(signal.SIGTERM, lambda sig, _: ensure_future(signal_handler(sig))) - statedir = Path(options.statedir or Path(get_appstate_dir(), '.Tribler')) - config = TriblerConfig.load(state_dir=statedir) + if options.statedir: + os.environ['TSTATEDIR'] = options.statedir - # Check if we are already running a Tribler instance root_state_dir = get_root_state_directory(create=True) + version_history = VersionHistory(root_state_dir) + statedir = version_history.code_version.directory + config = TriblerConfig.load(state_dir=statedir) self.process_lock = try_acquire_file_lock(root_state_dir / CORE_LOCK_FILENAME) current_process_owns_lock = bool(self.process_lock) @@ -96,9 +99,30 @@ async def signal_handler(sig): print("Starting Tribler") - if options.restapi > 0: + http_port = options.restapi_http_port or int(os.environ.get('CORE_API_PORT', "0")) + + if 'CORE_API_PORT' in os.environ and (http_port := int(os.environ.get('CORE_API_PORT'))) > 0: + config.api.http_port = http_port + elif options.restapi_http_port > 0: + config.api.http_port = options.restapi_http_port + + if options.restapi_http_host: + config.api.http_host = options.restapi_http_host + + if options.restapi_https_port > 0: + config.api.https_port = options.restapi_https_port + + if options.restapi_https_host: + config.api.https_host = options.restapi_https_host + + if config.api.http_port > 0: config.api.http_enabled = True - config.api.http_port = options.restapi + + if config.api.https_port > 0: + config.api.https_enabled = True + + if api_key := os.environ.get('CORE_API_KEY'): + config.api.key = api_key if options.ipv8 > 0: config.ipv8.port = options.ipv8 @@ -108,6 +132,9 @@ async def signal_handler(sig): if options.libtorrent != -1 and options.libtorrent > 0: config.libtorrent.port = options.libtorrent + if options.download_dir: + config.download_defaults.saveas = options.download_dir + if options.ipv8_bootstrap_override is not None: config.ipv8.bootstrap_override = options.ipv8_bootstrap_override @@ -135,7 +162,15 @@ def main(argv): parser.add_argument('--help', '-h', action='help', default=argparse.SUPPRESS, help='Show this help message and exit') parser.add_argument('--statedir', '-s', default=None, help='Use an alternate statedir') - parser.add_argument('--restapi', '-p', default=-1, type=int, help='Use an alternate port for REST API') + parser.add_argument('--download_dir', default=None, help='Use an alternative download directory') + parser.add_argument('--restapi_http_port', '--restapi', '-p', default=-1, type=int, + help='Use an alternate port for http REST API') + parser.add_argument('--restapi_http_host', default=None, type=str, + help='Use an alternate listen address for http REST API') + parser.add_argument('--restapi_https_port', default=-1, type=int, + help='Use an alternate port for https REST API') + parser.add_argument('--restapi_https_host', default=None, type=str, + help='Use an alternate listen address for https REST API') parser.add_argument('--ipv8', '-i', default=-1, type=int, help='Use an alternate port for the IPv8') parser.add_argument('--libtorrent', '-l', default=-1, type=int, help='Use an alternate port for libtorrent') parser.add_argument('--ipv8_bootstrap_override', '-b', default=None, type=str, @@ -152,7 +187,7 @@ def main(argv): coro = service.start_tribler(args) ensure_future(coro) - if sys.platform == 'win32': + if sys.platform == 'win32' and sys.version_info < (3, 8): # Unfortunately, this is needed on Windows for Ctrl+C to work consistently. # Should no longer be needed in Python 3.8. async def wakeup(): diff --git a/src/tribler/core/components/restapi/rest/rest_manager.py b/src/tribler/core/components/restapi/rest/rest_manager.py index 2302e448a56..60c9a3cad3f 100644 --- a/src/tribler/core/components/restapi/rest/rest_manager.py +++ b/src/tribler/core/components/restapi/rest/rest_manager.py @@ -93,8 +93,6 @@ def __init__(self, config: APISettings, root_endpoint: RootEndpoint, state_dir=N self.config = config self.state_dir = state_dir - self.http_host = '127.0.0.1' - self.https_host = '0.0.0.0' self.shutdown_timeout = shutdown_timeout def get_endpoint(self, name): @@ -146,13 +144,13 @@ async def start(self): self._logger.info('Https enabled') await self.start_https_site() - self._logger.info(f'Swagger docs: http://{self.http_host}:{self.config.http_port}/docs') - self._logger.info(f'Swagger JSON: http://{self.http_host}:{self.config.http_port}/docs/swagger.json') + self._logger.info(f'Swagger docs: http://{self.config.http_host}:{self.config.http_port}/docs') + self._logger.info(f'Swagger JSON: http://{self.config.http_host}:{self.config.http_port}/docs/swagger.json') async def start_http_site(self): api_port = max(self.config.http_port, 0) # if the value in config is -1 we convert it to 0 - self.site = web.TCPSite(self.runner, self.http_host, api_port, shutdown_timeout=self.shutdown_timeout) + self.site = web.TCPSite(self.runner, self.config.http_host, api_port, shutdown_timeout=self.shutdown_timeout) self._logger.info(f"Starting HTTP REST API server on port {api_port}...") try: @@ -174,7 +172,7 @@ async def start_https_site(self): ssl_context.load_cert_chain(cert) port = self.config.https_port - self.site_https = web.TCPSite(self.runner, self.https_host, port, ssl_context=ssl_context) + self.site_https = web.TCPSite(self.runner, self.config.https_host, port, ssl_context=ssl_context) await self.site_https.start() self._logger.info("Started HTTPS REST API: %s", self.site_https.name) diff --git a/src/tribler/core/components/restapi/rest/settings.py b/src/tribler/core/components/restapi/rest/settings.py index 399b9c18385..bedfe26fa67 100644 --- a/src/tribler/core/components/restapi/rest/settings.py +++ b/src/tribler/core/components/restapi/rest/settings.py @@ -9,7 +9,9 @@ class APISettings(TriblerConfigSection): http_enabled: bool = False http_port: int = -1 + http_host: str = "127.0.0.1" https_enabled: bool = False + https_host: str = "127.0.0.1" https_port: int = -1 https_certfile: str = '' key: Optional[str] = None