From b13d2290276f10921978103a07f31fb0653da692 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 6 Dec 2022 19:48:54 +0000 Subject: [PATCH 01/33] feat: add docker image --- .dockerignore | 7 +++++++ Dockerfile | 48 +++++++++++++++++++++++++++++++++++++++++++ bin/install.sh | 14 +++++++++++++ config.toml.local | 31 ++++++++++++++++++++++++++++ docker/README.md | 33 +++++++++++++++++++++++++++++ docker/bin/build.sh | 10 +++++++++ docker/bin/install.sh | 4 ++++ docker/bin/run.sh | 10 +++++++++ 8 files changed, 157 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100755 bin/install.sh create mode 100644 config.toml.local create mode 100644 docker/README.md create mode 100755 docker/bin/build.sh create mode 100755 docker/bin/install.sh create mode 100755 docker/bin/run.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..24b645df2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +.git +.github +.vscode +config.toml +data.db +docker/commands/ +target/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..e54c028b8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,48 @@ +FROM clux/muslrust:stable AS chef +WORKDIR /app +RUN cargo install cargo-chef + + +FROM chef AS planner +WORKDIR /app +COPY . . +RUN cargo chef prepare --recipe-path recipe.json + + +FROM chef AS builder +WORKDIR /app +ARG UID=1000 +# Create the app user +ENV USER=appuser +ENV UID=$UID +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + "${USER}" +# Build dependencies +COPY --from=planner /app/recipe.json recipe.json +RUN cargo chef cook --release --target x86_64-unknown-linux-musl --recipe-path recipe.json +# Build the application +# todo: it seems the previous cache layer is not working. The dependencies are compiled always. +COPY . . +RUN cargo build --release --target x86_64-unknown-linux-musl --bin torrust-tracker + + +FROM alpine:latest +WORKDIR /app +RUN apk --no-cache add ca-certificates +ENV TZ=Etc/UTC APP_USER=appuser +COPY --from=builder /etc/passwd /etc/passwd +COPY --from=builder /etc/group /etc/group +COPY --from=builder --chown=$APP_USER \ + /app/target/x86_64-unknown-linux-musl/release/torrust-tracker \ + /app/torrust-tracker +RUN chown -R $APP_USER:$APP_USER /app +USER $APP_USER:$APP_USER +EXPOSE 6969 +EXPOSE 1212 +ENTRYPOINT ["/app/torrust-tracker"] \ No newline at end of file diff --git a/bin/install.sh b/bin/install.sh new file mode 100755 index 000000000..d2a0a5745 --- /dev/null +++ b/bin/install.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Generate the default settings file if it does not exist +if ! [ -f "./config.toml" ]; then + cp ./config.toml.local ./config.toml +fi + +# Generate the sqlite database if it does not exist +if ! [ -f "./data.db" ]; then + # todo: it should get the path from config.toml and only do it when we use sqlite + touch ./data.db + echo ";" | sqlite3 ./data.db +fi + diff --git a/config.toml.local b/config.toml.local new file mode 100644 index 000000000..df88bac06 --- /dev/null +++ b/config.toml.local @@ -0,0 +1,31 @@ +log_level = "info" +mode = "public" +db_driver = "Sqlite3" +db_path = "data.db" +announce_interval = 120 +min_announce_interval = 120 +max_peer_timeout = 900 +on_reverse_proxy = false +external_ip = "0.0.0.0" +tracker_usage_statistics = true +persistent_torrent_completed_stat = false +inactive_peer_cleanup_interval = 600 +remove_peerless_torrents = true + +[[udp_trackers]] +enabled = false +bind_address = "0.0.0.0:6969" + +[[http_trackers]] +enabled = false +bind_address = "0.0.0.0:6969" +ssl_enabled = false +ssl_cert_path = "" +ssl_key_path = "" + +[http_api] +enabled = true +bind_address = "127.0.0.1:1212" + +[http_api.access_tokens] +admin = "MyAccessToken" diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 000000000..d9111cf0a --- /dev/null +++ b/docker/README.md @@ -0,0 +1,33 @@ +# Docker + +## Requirements + +- Docker version 20.10.21 + +## Dev environment + +Build and run locally: + +```s +export TORRUST_TRACKER_USER_UID=1000 +./docker/bin/build.sh $TORRUST_TRACKER_USER_UID +./bin/install.sh +./docker/bin/run.sh $TORRUST_TRACKER_USER_UID +``` + +Run using the pre-built public docker image: + +```s +export TORRUST_TRACKER_USER_UID=1000 +docker run -it \ + --user="$TORRUST_TRACKER_USER_UID" \ + -p 6969:6969 -p 1212:1212 \ + --mount type=bind,source="$(pwd)/data.db",target=/app/data.db \ + --mount type=bind,source="$(pwd)/config.toml",target=/app/config.toml \ + josecelano/torrust-tracker +``` + +> NOTES: +> +> - You have to create the SQLite DB (`data.db`) and configuration (`config.toml`) before running the tracker. See `bin/install.sh`. +> - You have to replace the user UID (`1000`) with yours. diff --git a/docker/bin/build.sh b/docker/bin/build.sh new file mode 100755 index 000000000..2a1c7b23d --- /dev/null +++ b/docker/bin/build.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +TORRUST_TRACKER_USER_UID=${TORRUST_TRACKER_USER_UID:-1000} + +echo "Building docker image ..." +echo "TORRUST_TRACKER_USER_UID: $TORRUST_TRACKER_USER_UID" + +docker build \ + --build-arg UID="$TORRUST_TRACKER_USER_UID" \ + -t torrust-tracker . diff --git a/docker/bin/install.sh b/docker/bin/install.sh new file mode 100755 index 000000000..a58969378 --- /dev/null +++ b/docker/bin/install.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +./docker/bin/build.sh +./bin/install.sh diff --git a/docker/bin/run.sh b/docker/bin/run.sh new file mode 100755 index 000000000..0fdeba422 --- /dev/null +++ b/docker/bin/run.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +TORRUST_TRACKER_USER_UID=${TORRUST_TRACKER_USER_UID:-1000} + +docker run -it \ + --user="$TORRUST_TRACKER_USER_UID" \ + -p 6969:6969 -p 1212:1212 \ + --mount type=bind,source="$(pwd)/data.db",target=/app/data.db \ + --mount type=bind,source="$(pwd)/config.toml",target=/app/config.toml \ + torrust-tracker From 0cb5423a525ceba018c0c1e3577038cf6de7ea8f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 8 Dec 2022 12:07:39 +0000 Subject: [PATCH 02/33] feat: publish docker image on dockerhub --- .github/workflows/publish-docker-image.yml | 59 ++++++++++++++++++++++ cSpell.json | 2 + 2 files changed, 61 insertions(+) create mode 100644 .github/workflows/publish-docker-image.yml diff --git a/.github/workflows/publish-docker-image.yml b/.github/workflows/publish-docker-image.yml new file mode 100644 index 000000000..a2ea8c6e5 --- /dev/null +++ b/.github/workflows/publish-docker-image.yml @@ -0,0 +1,59 @@ +name: Publish docker image + +on: + push: + branches: + # todo: Replace with `main` in the final version + - "docker" + - "releases/v*" + tags: + - "v*" + pull_request: + branches: + # todo: Replace with `main` in the final version + - "docker" + - "releases/v*" + +env: + # todo: Replace with `torrust/torrust-tracker` in the final version + DOCKER_IMAGE: josecelano/torrust-tracker + +jobs: + dockerhub: + runs-on: ubuntu-latest + environment: dockerhub-josecelano + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: | + ${{ env.DOCKER_IMAGE }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Build and push + uses: docker/build-push-action@v3 + with: + context: . + file: ./Dockerfile + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/cSpell.json b/cSpell.json index cc3359d58..8b10ff78a 100644 --- a/cSpell.json +++ b/cSpell.json @@ -9,12 +9,14 @@ "Bitflu", "bools", "bufs", + "Buildx", "byteorder", "canonicalize", "canonicalized", "chrono", "clippy", "completei", + "dockerhub", "downloadedi", "filesd", "Freebox", From 4312d5a7c0d3d60eaf16f89b89d7bbdebafe486a Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 8 Dec 2022 16:28:00 +0000 Subject: [PATCH 03/33] feat: move persistent state to storage folder Azure Container Instances (ACI) do not allow to mount a single file. There is no support for mounting a single file, or mounting a subfolder from an Azure File Share. More info: https://docs.docker.com/cloud/aci-container-features/#persistent-volumes Due to that I had to move both files (data.db and config.toml) to a folder. THis way we can run the container with: ``` docker run -it \ -p 6969:6969 -p 1212:1212 \ --volume "$(pwd)/storage":"/app/storage" \ josecelano/torrust-tracker ``` BREAKING CHANGE: `config.toml` file was moved to `./storage/config/`. --- .gitignore | 10 +++++----- bin/install.sh | 11 +++++------ cSpell.json | 2 ++ config.toml.local | 2 +- docker/README.md | 28 ++++++++++++++++++++++++++-- docker/bin/run.sh | 3 +-- src/config.rs | 6 +++--- src/main.rs | 2 +- 8 files changed, 44 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index ba9ceeb53..5bf5e6456 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,9 @@ -/target **/*.rs.bk -/database.json.bz2 -/database.db /.idea/ +/.vscode/launch.json /config.toml /data.db -/.vscode/launch.json - +/database.db +/database.json.bz2 +/storage +/target diff --git a/bin/install.sh b/bin/install.sh index d2a0a5745..3667ffb55 100755 --- a/bin/install.sh +++ b/bin/install.sh @@ -1,14 +1,13 @@ #!/bin/bash # Generate the default settings file if it does not exist -if ! [ -f "./config.toml" ]; then - cp ./config.toml.local ./config.toml +if ! [ -f "./storage/config/config.toml" ]; then + cp ./config.toml.local ./storage/config/config.toml fi # Generate the sqlite database if it does not exist -if ! [ -f "./data.db" ]; then +if ! [ -f "./storage/database/data.db" ]; then # todo: it should get the path from config.toml and only do it when we use sqlite - touch ./data.db - echo ";" | sqlite3 ./data.db + touch ./storage/database/data.db + echo ";" | sqlite3 ./storage/database/data.db fi - diff --git a/cSpell.json b/cSpell.json index 8b10ff78a..8275076d0 100644 --- a/cSpell.json +++ b/cSpell.json @@ -30,6 +30,7 @@ "libtorrent", "Lphant", "mockall", + "myacicontext", "nanos", "nextest", "nocapture", @@ -52,6 +53,7 @@ "thiserror", "Torrentstorm", "torrust", + "torrustracker", "typenum", "Unamed", "untuple", diff --git a/config.toml.local b/config.toml.local index df88bac06..33c9bba5d 100644 --- a/config.toml.local +++ b/config.toml.local @@ -1,7 +1,7 @@ log_level = "info" mode = "public" db_driver = "Sqlite3" -db_path = "data.db" +db_path = "./storage/database/data.db" announce_interval = 120 min_announce_interval = 120 max_peer_timeout = 900 diff --git a/docker/README.md b/docker/README.md index d9111cf0a..4a173b438 100644 --- a/docker/README.md +++ b/docker/README.md @@ -22,8 +22,7 @@ export TORRUST_TRACKER_USER_UID=1000 docker run -it \ --user="$TORRUST_TRACKER_USER_UID" \ -p 6969:6969 -p 1212:1212 \ - --mount type=bind,source="$(pwd)/data.db",target=/app/data.db \ - --mount type=bind,source="$(pwd)/config.toml",target=/app/config.toml \ + --volume "$(pwd)/storage":"/app/storage" \ josecelano/torrust-tracker ``` @@ -31,3 +30,28 @@ docker run -it \ > > - You have to create the SQLite DB (`data.db`) and configuration (`config.toml`) before running the tracker. See `bin/install.sh`. > - You have to replace the user UID (`1000`) with yours. +> - Remember to switch to your default docker context `docker context use default`. + +## Prod environment + +Deploy to Azure following [docker documentation](https://docs.docker.com/cloud/aci-integration/). + +```s +docker context create aci myacicontext +docker context use myacicontext +docker volume create test-volume --storage-account torrustracker +docker run \ + -p 80:80 \ + -v torrustracker/test-volume:/app/storage \ + registry.hub.docker.com/josecelano/torrust-tracker +``` + +> NOTES: +> +> - [There is no support for mounting a single file](https://docs.docker.com/cloud/aci-container-features/#persistent-volumes), or mounting a subfolder from an `Azure File Share`. +> - [ACI does not allow port mapping](https://docs.docker.com/cloud/aci-integration/#exposing-ports). + +## Links + +- [Deploying Docker containers on Azure](https://docs.docker.com/cloud/aci-integration/). +- [Docker run options for ACI containers](https://docs.docker.com/cloud/aci-container-features/). diff --git a/docker/bin/run.sh b/docker/bin/run.sh index 0fdeba422..961a82327 100755 --- a/docker/bin/run.sh +++ b/docker/bin/run.sh @@ -5,6 +5,5 @@ TORRUST_TRACKER_USER_UID=${TORRUST_TRACKER_USER_UID:-1000} docker run -it \ --user="$TORRUST_TRACKER_USER_UID" \ -p 6969:6969 -p 1212:1212 \ - --mount type=bind,source="$(pwd)/data.db",target=/app/data.db \ - --mount type=bind,source="$(pwd)/config.toml",target=/app/config.toml \ + --volume "$(pwd)/storage":"/app/storage" \ torrust-tracker diff --git a/src/config.rs b/src/config.rs index a7e7e9df6..2ad9c61b7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -99,7 +99,7 @@ impl Configuration { log_level: Option::from(String::from("info")), mode: mode::Mode::Public, db_driver: Driver::Sqlite3, - db_path: String::from("data.db"), + db_path: String::from("./storage/database/data.db"), announce_interval: 120, min_announce_interval: 120, max_peer_timeout: 900, @@ -154,7 +154,7 @@ impl Configuration { let config = Configuration::default(); config.save_to_file(path)?; return Err(Error::Message( - "Please edit the config.TOML in the root folder and restart the tracker.".to_string(), + "Please edit the config.TOML in ./storage/config folder and restart the tracker.".to_string(), )); } @@ -183,7 +183,7 @@ mod tests { let config = r#"log_level = "info" mode = "public" db_driver = "Sqlite3" - db_path = "data.db" + db_path = "./storage/database/data.db" announce_interval = 120 min_announce_interval = 120 max_peer_timeout = 900 diff --git a/src/main.rs b/src/main.rs index a7316cef2..a2d498139 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use torrust_tracker::{ephemeral_instance_keys, logging, setup, static_time, trac #[tokio::main] async fn main() { - const CONFIG_PATH: &str = "config.toml"; + const CONFIG_PATH: &str = "./storage/config/config.toml"; // Set the time of Torrust app starting lazy_static::initialize(&static_time::TIME_AT_APP_START); From ff33e710936d9b86bf98557c696c164d8ec96e2d Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 8 Dec 2022 16:36:15 +0000 Subject: [PATCH 04/33] fix: publish docker images only for tags --- .github/workflows/publish-docker-image.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/publish-docker-image.yml b/.github/workflows/publish-docker-image.yml index a2ea8c6e5..39de19728 100644 --- a/.github/workflows/publish-docker-image.yml +++ b/.github/workflows/publish-docker-image.yml @@ -2,17 +2,8 @@ name: Publish docker image on: push: - branches: - # todo: Replace with `main` in the final version - - "docker" - - "releases/v*" tags: - "v*" - pull_request: - branches: - # todo: Replace with `main` in the final version - - "docker" - - "releases/v*" env: # todo: Replace with `torrust/torrust-tracker` in the final version From 6911c203d16b64ab6f7a02171a89f74bd4ad0795 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 8 Dec 2022 17:22:23 +0000 Subject: [PATCH 05/33] fix: run docker as root due to ACI limitations Azure file share volume mount requires the Linux container run as root. https://learn.microsoft.com/en-us/azure/container-instances/container-instances-volume-azure-files#limitations --- .github/workflows/publish-docker-image.yml | 3 +++ Dockerfile | 10 ++++++---- docker/README.md | 19 ++++++++++++++++--- docker/bin/build.sh | 3 +++ 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish-docker-image.yml b/.github/workflows/publish-docker-image.yml index 39de19728..eb335f8cb 100644 --- a/.github/workflows/publish-docker-image.yml +++ b/.github/workflows/publish-docker-image.yml @@ -8,6 +8,9 @@ on: env: # todo: Replace with `torrust/torrust-tracker` in the final version DOCKER_IMAGE: josecelano/torrust-tracker + # Azure file share volume mount requires the Linux container run as root + # https://learn.microsoft.com/en-us/azure/container-instances/container-instances-volume-azure-files#limitations + TORRUST_TRACKER_RUN_AS_USER: root jobs: dockerhub: diff --git a/Dockerfile b/Dockerfile index e54c028b8..a17244081 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,15 +34,17 @@ RUN cargo build --release --target x86_64-unknown-linux-musl --bin torrust-track FROM alpine:latest WORKDIR /app +ARG RUN_AS_USER=appuser RUN apk --no-cache add ca-certificates -ENV TZ=Etc/UTC APP_USER=appuser +ENV TZ=Etc/UTC +ENV RUN_AS_USER=$RUN_AS_USER COPY --from=builder /etc/passwd /etc/passwd COPY --from=builder /etc/group /etc/group -COPY --from=builder --chown=$APP_USER \ +COPY --from=builder --chown=$RUN_AS_USER \ /app/target/x86_64-unknown-linux-musl/release/torrust-tracker \ /app/torrust-tracker -RUN chown -R $APP_USER:$APP_USER /app -USER $APP_USER:$APP_USER +RUN chown -R $RUN_AS_USER:$RUN_AS_USER /app +USER $RUN_AS_USER:$RUN_AS_USER EXPOSE 6969 EXPOSE 1212 ENTRYPOINT ["/app/torrust-tracker"] \ No newline at end of file diff --git a/docker/README.md b/docker/README.md index 4a173b438..4511d8465 100644 --- a/docker/README.md +++ b/docker/README.md @@ -9,6 +9,7 @@ Build and run locally: ```s +docker context use default export TORRUST_TRACKER_USER_UID=1000 ./docker/bin/build.sh $TORRUST_TRACKER_USER_UID ./bin/install.sh @@ -41,15 +42,27 @@ docker context create aci myacicontext docker context use myacicontext docker volume create test-volume --storage-account torrustracker docker run \ - -p 80:80 \ - -v torrustracker/test-volume:/app/storage \ - registry.hub.docker.com/josecelano/torrust-tracker + --name torrust-tracker \ + --port 80:80 \ + --volume torrustracker/test-volume:/app/storage \ + registry.hub.docker.com/josecelano/torrust-tracker:0.2.0 +``` + +Detach from container logs when container starts. By default, the command line stays attached and follows container logs. + +```s +docker run \ + --detach + --port 80:80 \ + --volume torrustracker/test-volume:/app/storage \ + registry.hub.docker.com/josecelano/torrust-tracker:0.2.0 ``` > NOTES: > > - [There is no support for mounting a single file](https://docs.docker.com/cloud/aci-container-features/#persistent-volumes), or mounting a subfolder from an `Azure File Share`. > - [ACI does not allow port mapping](https://docs.docker.com/cloud/aci-integration/#exposing-ports). +> - [Azure file share volume mount requires the Linux container run as root](https://learn.microsoft.com/en-us/azure/container-instances/container-instances-volume-azure-files#limitations). ## Links diff --git a/docker/bin/build.sh b/docker/bin/build.sh index 2a1c7b23d..d77d1ad34 100755 --- a/docker/bin/build.sh +++ b/docker/bin/build.sh @@ -1,10 +1,13 @@ #!/bin/bash TORRUST_TRACKER_USER_UID=${TORRUST_TRACKER_USER_UID:-1000} +TORRUST_TRACKER_RUN_AS_USER=${TORRUST_TRACKER_RUN_AS_USER:-appuser} echo "Building docker image ..." echo "TORRUST_TRACKER_USER_UID: $TORRUST_TRACKER_USER_UID" +echo "TORRUST_TRACKER_RUN_AS_USER: $TORRUST_TRACKER_RUN_AS_USER" docker build \ --build-arg UID="$TORRUST_TRACKER_USER_UID" \ + --build-arg RUN_AS_USER="$TORRUST_TRACKER_RUN_AS_USER" \ -t torrust-tracker . From a93d523b13f6983094258868b6bf16df20eb523e Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 8 Dec 2022 17:48:00 +0000 Subject: [PATCH 06/33] fix: pass missing build arg to docker build on CI --- .github/workflows/publish-docker-image.yml | 2 ++ docker/README.md | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-docker-image.yml b/.github/workflows/publish-docker-image.yml index eb335f8cb..ff0732e48 100644 --- a/.github/workflows/publish-docker-image.yml +++ b/.github/workflows/publish-docker-image.yml @@ -46,6 +46,8 @@ jobs: with: context: . file: ./Dockerfile + build-args: | + RUN_AS_USER=${{ env.TORRUST_TRACKER_RUN_AS_USER }} push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/docker/README.md b/docker/README.md index 4511d8465..25f743a74 100644 --- a/docker/README.md +++ b/docker/README.md @@ -45,7 +45,7 @@ docker run \ --name torrust-tracker \ --port 80:80 \ --volume torrustracker/test-volume:/app/storage \ - registry.hub.docker.com/josecelano/torrust-tracker:0.2.0 + registry.hub.docker.com/josecelano/torrust-tracker:0.4.0 ``` Detach from container logs when container starts. By default, the command line stays attached and follows container logs. @@ -55,7 +55,7 @@ docker run \ --detach --port 80:80 \ --volume torrustracker/test-volume:/app/storage \ - registry.hub.docker.com/josecelano/torrust-tracker:0.2.0 + registry.hub.docker.com/josecelano/torrust-tracker:0.4.0 ``` > NOTES: From 2853b4455e52310b9bce036b7c19caf2fad86f52 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 8 Dec 2022 18:59:48 +0000 Subject: [PATCH 07/33] docs: update README for docker --- cSpell.json | 1 + docker/README.md | 53 ++++++++++++++++++++++++++++++++++++++++++----- docker/bin/run.sh | 2 +- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/cSpell.json b/cSpell.json index 8275076d0..6a581c20e 100644 --- a/cSpell.json +++ b/cSpell.json @@ -37,6 +37,7 @@ "oneshot", "ostr", "Pando", + "Quickstart", "Rasterbar", "repr", "reqwest", diff --git a/docker/README.md b/docker/README.md index 25f743a74..8e67b25cc 100644 --- a/docker/README.md +++ b/docker/README.md @@ -22,7 +22,7 @@ Run using the pre-built public docker image: export TORRUST_TRACKER_USER_UID=1000 docker run -it \ --user="$TORRUST_TRACKER_USER_UID" \ - -p 6969:6969 -p 1212:1212 \ + -p 6969:6969/udp -p 1212:1212 \ --volume "$(pwd)/storage":"/app/storage" \ josecelano/torrust-tracker ``` @@ -37,34 +37,77 @@ docker run -it \ Deploy to Azure following [docker documentation](https://docs.docker.com/cloud/aci-integration/). +You have to create the ACI context and the storage: + ```s docker context create aci myacicontext docker context use myacicontext docker volume create test-volume --storage-account torrustracker +``` + +You need to create all the files needed by the application in the storage dir: + +- `storage/config/config.toml` +- `storage/database` + +And finally, you can run the container: + +```s docker run \ - --name torrust-tracker \ - --port 80:80 \ + --publish 80:80/udp \ + --publish 1212:1212 \ --volume torrustracker/test-volume:/app/storage \ registry.hub.docker.com/josecelano/torrust-tracker:0.4.0 ``` -Detach from container logs when container starts. By default, the command line stays attached and follows container logs. +Detach from container logs when the container starts. By default, the command line stays attached and follows container logs. ```s docker run \ --detach - --port 80:80 \ + --publish 80:80/udp \ + --publish 1212:1212 \ + --volume torrustracker/test-volume:/app/storage \ + registry.hub.docker.com/josecelano/torrust-tracker:0.4.0 +``` + +You should see something like this: + +```s +$ docker run \ \ + --publish 80:80/udp \ + --publish 1212:1212 \ --volume torrustracker/test-volume:/app/storage \ registry.hub.docker.com/josecelano/torrust-tracker:0.4.0 +[+] Running 2/2 + ⠿ Group intelligent-hawking Created 5.0s + ⠿ intelligent-hawking Created 41.7s +2022-12-08T18:39:19.697869300+00:00 [torrust_tracker::logging][INFO] logging initialized. +2022-12-08T18:39:19.712651100+00:00 [torrust_tracker::jobs::udp_tracker][INFO] Starting UDP server on: 0.0.0.0:80 +2022-12-08T18:39:19.712792700+00:00 [torrust_tracker::jobs::tracker_api][INFO] Starting Torrust API server on: 0.0.0.0:1212 +2022-12-08T18:39:19.725124+00:00 [torrust_tracker::jobs::tracker_api][INFO] Torrust API server started ``` +You can see the container with: + +```s +$ docker ps +CONTAINER ID IMAGE COMMAND STATUS PORTS +intelligent-hawking registry.hub.docker.com/josecelano/torrust-tracker:0.4.0 Running 4.236.213.57:80->80/udp, 4.236.213.57:1212->1212/tcp +``` + +After a while, you can use the tracker API `http://4.236.213.57:1212/api/stats?token=MyAccessToken` and the UDP tracker with your BitTorrent client using this tracker announce URL `udp://4.236.213.57:80/announce`. + > NOTES: > > - [There is no support for mounting a single file](https://docs.docker.com/cloud/aci-container-features/#persistent-volumes), or mounting a subfolder from an `Azure File Share`. > - [ACI does not allow port mapping](https://docs.docker.com/cloud/aci-integration/#exposing-ports). > - [Azure file share volume mount requires the Linux container run as root](https://learn.microsoft.com/en-us/azure/container-instances/container-instances-volume-azure-files#limitations). +> - It can take some minutes until the public IP for the ACI container is available. +> - You can use the Azure web UI to download files from the storage. For example, the SQLite database. ## Links - [Deploying Docker containers on Azure](https://docs.docker.com/cloud/aci-integration/). - [Docker run options for ACI containers](https://docs.docker.com/cloud/aci-container-features/). +- [Quickstart: Deploy a container instance in Azure using the Docker CLI](https://learn.microsoft.com/en-us/azure/container-instances/quickstart-docker-cli). diff --git a/docker/bin/run.sh b/docker/bin/run.sh index 961a82327..b5d56939f 100755 --- a/docker/bin/run.sh +++ b/docker/bin/run.sh @@ -4,6 +4,6 @@ TORRUST_TRACKER_USER_UID=${TORRUST_TRACKER_USER_UID:-1000} docker run -it \ --user="$TORRUST_TRACKER_USER_UID" \ - -p 6969:6969 -p 1212:1212 \ + -p 6969:6969/udp -p 1212:1212 \ --volume "$(pwd)/storage":"/app/storage" \ torrust-tracker From c10bed90456e7fda38e8d8a6f949ebbb74b0ffa8 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Thu, 8 Dec 2022 21:45:13 +0000 Subject: [PATCH 08/33] fix: expose udp port in dockerfile I think that could be the reason why the application does not work on the Azure Container Instance. --- Dockerfile | 2 +- docker/README.md | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index a17244081..de8f3c120 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,6 +45,6 @@ COPY --from=builder --chown=$RUN_AS_USER \ /app/torrust-tracker RUN chown -R $RUN_AS_USER:$RUN_AS_USER /app USER $RUN_AS_USER:$RUN_AS_USER -EXPOSE 6969 +EXPOSE 6969/udp EXPOSE 1212 ENTRYPOINT ["/app/torrust-tracker"] \ No newline at end of file diff --git a/docker/README.md b/docker/README.md index 8e67b25cc..19451663c 100644 --- a/docker/README.md +++ b/docker/README.md @@ -54,10 +54,10 @@ And finally, you can run the container: ```s docker run \ - --publish 80:80/udp \ + --publish 6969:6969/udp \ --publish 1212:1212 \ --volume torrustracker/test-volume:/app/storage \ - registry.hub.docker.com/josecelano/torrust-tracker:0.4.0 + registry.hub.docker.com/josecelano/torrust-tracker:0.5.0 ``` Detach from container logs when the container starts. By default, the command line stays attached and follows container logs. @@ -65,25 +65,25 @@ Detach from container logs when the container starts. By default, the command li ```s docker run \ --detach - --publish 80:80/udp \ + --publish 6969:6969/udp \ --publish 1212:1212 \ --volume torrustracker/test-volume:/app/storage \ - registry.hub.docker.com/josecelano/torrust-tracker:0.4.0 + registry.hub.docker.com/josecelano/torrust-tracker:0.5.0 ``` You should see something like this: ```s $ docker run \ \ - --publish 80:80/udp \ + --publish 6969:6969/udp \ --publish 1212:1212 \ --volume torrustracker/test-volume:/app/storage \ - registry.hub.docker.com/josecelano/torrust-tracker:0.4.0 + registry.hub.docker.com/josecelano/torrust-tracker:0.5.0 [+] Running 2/2 ⠿ Group intelligent-hawking Created 5.0s ⠿ intelligent-hawking Created 41.7s 2022-12-08T18:39:19.697869300+00:00 [torrust_tracker::logging][INFO] logging initialized. -2022-12-08T18:39:19.712651100+00:00 [torrust_tracker::jobs::udp_tracker][INFO] Starting UDP server on: 0.0.0.0:80 +2022-12-08T18:39:19.712651100+00:00 [torrust_tracker::jobs::udp_tracker][INFO] Starting UDP server on: 0.0.0.0:6969 2022-12-08T18:39:19.712792700+00:00 [torrust_tracker::jobs::tracker_api][INFO] Starting Torrust API server on: 0.0.0.0:1212 2022-12-08T18:39:19.725124+00:00 [torrust_tracker::jobs::tracker_api][INFO] Torrust API server started ``` @@ -93,10 +93,10 @@ You can see the container with: ```s $ docker ps CONTAINER ID IMAGE COMMAND STATUS PORTS -intelligent-hawking registry.hub.docker.com/josecelano/torrust-tracker:0.4.0 Running 4.236.213.57:80->80/udp, 4.236.213.57:1212->1212/tcp +intelligent-hawking registry.hub.docker.com/josecelano/torrust-tracker:0.5.0 Running 4.236.213.57:6969->6969/udp, 4.236.213.57:1212->1212/tcp ``` -After a while, you can use the tracker API `http://4.236.213.57:1212/api/stats?token=MyAccessToken` and the UDP tracker with your BitTorrent client using this tracker announce URL `udp://4.236.213.57:80/announce`. +After a while, you can use the tracker API `http://4.236.213.57:1212/api/stats?token=MyAccessToken` and the UDP tracker with your BitTorrent client using this tracker announce URL `udp://4.236.213.57:6969/announce`. > NOTES: > @@ -105,6 +105,7 @@ After a while, you can use the tracker API `http://4.236.213.57:1212/api/stats?t > - [Azure file share volume mount requires the Linux container run as root](https://learn.microsoft.com/en-us/azure/container-instances/container-instances-volume-azure-files#limitations). > - It can take some minutes until the public IP for the ACI container is available. > - You can use the Azure web UI to download files from the storage. For example, the SQLite database. +> - [It seems you can only expose web interfaces on port 80 on Azure Container Instances](https://stackoverflow.com/a/56768087/3012842). Not official documentation! ## Links From 59b4112f936b6c1080ac4ebaa258a5618ebc7c93 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Sat, 10 Dec 2022 13:52:34 +0000 Subject: [PATCH 09/33] feat: expose port 6969/tcp pm Dokerfile --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index de8f3c120..abb9a6764 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,5 +46,6 @@ COPY --from=builder --chown=$RUN_AS_USER \ RUN chown -R $RUN_AS_USER:$RUN_AS_USER /app USER $RUN_AS_USER:$RUN_AS_USER EXPOSE 6969/udp -EXPOSE 1212 +EXPOSE 6969/tcp +EXPOSE 1212/tcp ENTRYPOINT ["/app/torrust-tracker"] \ No newline at end of file From c57d4fcb90fae3bb5f83c2182d6f82b56b744957 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Sat, 10 Dec 2022 13:53:11 +0000 Subject: [PATCH 10/33] feat: use long option names for docker commands --- docker/bin/run.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/bin/run.sh b/docker/bin/run.sh index b5d56939f..30819fe9e 100755 --- a/docker/bin/run.sh +++ b/docker/bin/run.sh @@ -4,6 +4,7 @@ TORRUST_TRACKER_USER_UID=${TORRUST_TRACKER_USER_UID:-1000} docker run -it \ --user="$TORRUST_TRACKER_USER_UID" \ - -p 6969:6969/udp -p 1212:1212 \ + --publish 6969:6969/udp \ + --publish 1212:1212 \ --volume "$(pwd)/storage":"/app/storage" \ torrust-tracker From bdaa3b7471839d0573cac26fd8d0315e4eac1ba0 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Sat, 10 Dec 2022 13:55:58 +0000 Subject: [PATCH 11/33] feat: change log level for UDP server stdoutput messages The Cargo.toml file has these options: ``` [profile.release] debug = 1 opt-level = 3 lto = "fat" strip = true ``` I think that hides "debug" messages in release builds. SO when I use the docker image on Azure container or my local machine I do not see those messages. I think for the time being we can show them. On the other hand, there is a "debug" message that should be an "error" message: ``` Err(_) => { error!("could not write response to bytes."); } ``` That message shoudl be shown on production too. --- src/udp/server.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/udp/server.rs b/src/udp/server.rs index 5bd835365..4410a04e2 100644 --- a/src/udp/server.rs +++ b/src/udp/server.rs @@ -3,7 +3,7 @@ use std::net::SocketAddr; use std::sync::Arc; use aquatic_udp_protocol::Response; -use log::{debug, info}; +use log::{debug, error, info}; use tokio::net::UdpSocket; use crate::tracker; @@ -45,8 +45,8 @@ impl Udp { Ok((valid_bytes, remote_addr)) = socket.recv_from(&mut data) => { let payload = data[..valid_bytes].to_vec(); - debug!("Received {} bytes from {}", payload.len(), remote_addr); - debug!("{:?}", payload); + info!("Received {} bytes from {}", payload.len(), remote_addr); + info!("{:?}", payload); let response = handle_packet(remote_addr, payload, tracker).await; Udp::send_response(socket, remote_addr, response).await; @@ -56,7 +56,7 @@ impl Udp { } async fn send_response(socket: Arc, remote_addr: SocketAddr, response: Response) { - debug!("sending response to: {:?}", &remote_addr); + info!("sending response to: {:?}", &remote_addr); let buffer = vec![0u8; MAX_PACKET_SIZE]; let mut cursor = Cursor::new(buffer); @@ -71,7 +71,7 @@ impl Udp { Udp::send_packet(socket, &remote_addr, &inner[..position]).await; } Err(_) => { - debug!("could not write response to bytes."); + error!("could not write response to bytes."); } } } From db9b166ff1e5e2e5772f5112f476c2caff43edd0 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Sat, 10 Dec 2022 14:00:24 +0000 Subject: [PATCH 12/33] feat: new release v0.6.0 --- docker/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker/README.md b/docker/README.md index 19451663c..958a1d317 100644 --- a/docker/README.md +++ b/docker/README.md @@ -39,7 +39,7 @@ Deploy to Azure following [docker documentation](https://docs.docker.com/cloud/a You have to create the ACI context and the storage: -```s +```s0.6.0 docker context create aci myacicontext docker context use myacicontext docker volume create test-volume --storage-account torrustracker @@ -57,7 +57,7 @@ docker run \ --publish 6969:6969/udp \ --publish 1212:1212 \ --volume torrustracker/test-volume:/app/storage \ - registry.hub.docker.com/josecelano/torrust-tracker:0.5.0 + registry.hub.docker.com/josecelano/torrust-tracker:0.6.0 ``` Detach from container logs when the container starts. By default, the command line stays attached and follows container logs. @@ -68,7 +68,7 @@ docker run \ --publish 6969:6969/udp \ --publish 1212:1212 \ --volume torrustracker/test-volume:/app/storage \ - registry.hub.docker.com/josecelano/torrust-tracker:0.5.0 + registry.hub.docker.com/josecelano/torrust-tracker:0.6.0 ``` You should see something like this: @@ -78,7 +78,7 @@ $ docker run \ \ --publish 6969:6969/udp \ --publish 1212:1212 \ --volume torrustracker/test-volume:/app/storage \ - registry.hub.docker.com/josecelano/torrust-tracker:0.5.0 + registry.hub.docker.com/josecelano/torrust-tracker:0.6.0 [+] Running 2/2 ⠿ Group intelligent-hawking Created 5.0s ⠿ intelligent-hawking Created 41.7s @@ -93,7 +93,7 @@ You can see the container with: ```s $ docker ps CONTAINER ID IMAGE COMMAND STATUS PORTS -intelligent-hawking registry.hub.docker.com/josecelano/torrust-tracker:0.5.0 Running 4.236.213.57:6969->6969/udp, 4.236.213.57:1212->1212/tcp +intelligent-hawking registry.hub.docker.com/josecelano/torrust-tracker:0.6.0 Running 4.236.213.57:6969->6969/udp, 4.236.213.57:1212->1212/tcp ``` After a while, you can use the tracker API `http://4.236.213.57:1212/api/stats?token=MyAccessToken` and the UDP tracker with your BitTorrent client using this tracker announce URL `udp://4.236.213.57:6969/announce`. From 672dca74538d9774de71fb4f01246e32a39193b5 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Sat, 10 Dec 2022 22:07:10 +0000 Subject: [PATCH 13/33] feat: udpate .dockerignore --- .dockerignore | 10 ++++++++++ Dockerfile | 1 - 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index 24b645df2..2ca06b6ba 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,17 @@ .git +.git-blame-ignore .github +.gitignore .vscode +bin/ config.toml +config.toml.local +cSpell.json data.db +docker/ docker/commands/ +NOTICE +README.md +rustfmt.toml +storage/ target/ diff --git a/Dockerfile b/Dockerfile index abb9a6764..79b53ee3b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,6 @@ RUN adduser \ COPY --from=planner /app/recipe.json recipe.json RUN cargo chef cook --release --target x86_64-unknown-linux-musl --recipe-path recipe.json # Build the application -# todo: it seems the previous cache layer is not working. The dependencies are compiled always. COPY . . RUN cargo build --release --target x86_64-unknown-linux-musl --bin torrust-tracker From 87b11a8071e2f7d3b33f65da28af7c6cfc3084c6 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Sat, 10 Dec 2022 22:07:36 +0000 Subject: [PATCH 14/33] feat: expose HTTP tracker port --- docker/README.md | 13 +++++++++---- docker/bin/run.sh | 3 ++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/docker/README.md b/docker/README.md index 958a1d317..9cd2514a1 100644 --- a/docker/README.md +++ b/docker/README.md @@ -22,7 +22,9 @@ Run using the pre-built public docker image: export TORRUST_TRACKER_USER_UID=1000 docker run -it \ --user="$TORRUST_TRACKER_USER_UID" \ - -p 6969:6969/udp -p 1212:1212 \ + --publish 6969:6969/udp \ + --publish 6969:6969/tcp \ + --publish 1212:1212/tcp \ --volume "$(pwd)/storage":"/app/storage" \ josecelano/torrust-tracker ``` @@ -55,7 +57,8 @@ And finally, you can run the container: ```s docker run \ --publish 6969:6969/udp \ - --publish 1212:1212 \ + --publish 6969:6969/tcp \ + --publish 1212:1212/tcp \ --volume torrustracker/test-volume:/app/storage \ registry.hub.docker.com/josecelano/torrust-tracker:0.6.0 ``` @@ -66,7 +69,8 @@ Detach from container logs when the container starts. By default, the command li docker run \ --detach --publish 6969:6969/udp \ - --publish 1212:1212 \ + --publish 6969:6969/tcp \ + --publish 1212:1212/tcp \ --volume torrustracker/test-volume:/app/storage \ registry.hub.docker.com/josecelano/torrust-tracker:0.6.0 ``` @@ -76,7 +80,8 @@ You should see something like this: ```s $ docker run \ \ --publish 6969:6969/udp \ - --publish 1212:1212 \ + --publish 6969:6969/tcp \ + --publish 1212:1212/tcp \ --volume torrustracker/test-volume:/app/storage \ registry.hub.docker.com/josecelano/torrust-tracker:0.6.0 [+] Running 2/2 diff --git a/docker/bin/run.sh b/docker/bin/run.sh index 30819fe9e..1bc182aef 100755 --- a/docker/bin/run.sh +++ b/docker/bin/run.sh @@ -5,6 +5,7 @@ TORRUST_TRACKER_USER_UID=${TORRUST_TRACKER_USER_UID:-1000} docker run -it \ --user="$TORRUST_TRACKER_USER_UID" \ --publish 6969:6969/udp \ - --publish 1212:1212 \ + --publish 6969:6969/tcp \ + --publish 1212:1212/tcp \ --volume "$(pwd)/storage":"/app/storage" \ torrust-tracker From 8333929d0e2876425c822b99a4e60be3269b07b2 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Dec 2022 12:33:19 +0000 Subject: [PATCH 15/33] fix: docker cache for cargo dependencies The problem with the cargo dependencies cache in docker is the option `strip` in the release profile: ``` [profile.release] debug = 1 opt-level = 3 lto = "fat" strip = true ``` Removing the option `strip = true` fixes the problem as described here: https://github.com/LukeMathWalker/cargo-chef/issues/172 --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 80e9009f1..6e835bcb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ lto = "thin" debug = 1 opt-level = 3 lto = "fat" -strip = true [dependencies] tokio = { version = "1", features = [ From 46971a208eeffe31318f4dd517c6742b3494f174 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Dec 2022 16:20:23 +0000 Subject: [PATCH 16/33] feat: enable HTTPS for the API Now you can also serve the API using https. It was already implemented for the HTTP trackers. I think it could be useful if you want to deploy the tracker using only the application wihtout needing an Nginx instance to serve the API using HTTPS. --- config.toml.local | 3 + docker/README.md | 59 +++++++ src/api/mod.rs | 18 +++ src/api/routes.rs | 307 ++++++++++++++++++++++++++++++++++++ src/api/server.rs | 333 +++------------------------------------ src/config.rs | 12 ++ src/jobs/http_tracker.rs | 4 +- src/jobs/tracker_api.rs | 26 +-- src/jobs/udp_tracker.rs | 4 +- src/setup.rs | 2 +- tests/api.rs | 2 +- 11 files changed, 440 insertions(+), 330 deletions(-) create mode 100644 src/api/routes.rs diff --git a/config.toml.local b/config.toml.local index 33c9bba5d..9002361ac 100644 --- a/config.toml.local +++ b/config.toml.local @@ -26,6 +26,9 @@ ssl_key_path = "" [http_api] enabled = true bind_address = "127.0.0.1:1212" +ssl_enabled = false +ssl_cert_path = "" +ssl_key_path = "" [http_api.access_tokens] admin = "MyAccessToken" diff --git a/docker/README.md b/docker/README.md index 9cd2514a1..d59569021 100644 --- a/docker/README.md +++ b/docker/README.md @@ -3,6 +3,21 @@ ## Requirements - Docker version 20.10.21 +- You need to create the `storage` directory with this structure and files: + +```s +$ tree storage/ +storage/ +├── config +│   └── config.toml +├── database +│   └── data.db +└── ssl_certificates + ├── localhost.crt + └── localhost.key +``` + +> NOTE: you only need the `ssl_certificates` directory and certificates in case you have enabled SSL for the one HTTP tracker or the API. ## Dev environment @@ -35,6 +50,50 @@ docker run -it \ > - You have to replace the user UID (`1000`) with yours. > - Remember to switch to your default docker context `docker context use default`. +### SSL Certificates + +You can use a certificate for localhost. You can create your [localhost certificate](https://letsencrypt.org/docs/certificates-for-localhost/#making-and-trusting-your-own-certificates) and use it in the `storage` folder and the configuration file (`config.toml`). For example: + +The storage folder must contain your certificates: + +```s +$ tree storage/ +storage/ +├── config +│   └── config.toml +├── database +│   └── data.db +└── ssl_certificates + ├── localhost.crt + └── localhost.key +``` + +You have not enabled it in your `config.toml` file: + +```toml +... +[[http_trackers]] +enabled = true +bind_address = "0.0.0.0:6969" +ssl_enabled = true +ssl_cert_path = "./storage/ssl_certificates/localhost.crt" +ssl_key_path = "./storage/ssl_certificates/localhost.key" + +[http_api] +enabled = true +bind_address = "0.0.0.0:1212" +ssl_enabled = true +ssl_cert_path = "./storage/ssl_certificates/localhost.crt" +ssl_key_path = "./storage/ssl_certificates/localhost.key" +... +``` + +> NOTE: you can enable it independently for each HTTP tracker or the API. + +If you enable the SSL certificate for the API, for example, you can load the API with this URL: + + + ## Prod environment Deploy to Azure following [docker documentation](https://docs.docker.com/cloud/aci-integration/). diff --git a/src/api/mod.rs b/src/api/mod.rs index 16abb8e27..d254c91ac 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,2 +1,20 @@ pub mod resource; +pub mod routes; pub mod server; + +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Debug)] +pub struct TorrentInfoQuery { + offset: Option, + limit: Option, +} + +#[derive(Serialize, Debug)] +#[serde(tag = "status", rename_all = "snake_case")] +enum ActionStatus<'a> { + Ok, + Err { reason: std::borrow::Cow<'a, str> }, +} + +impl warp::reject::Reject for ActionStatus<'static> {} diff --git a/src/api/routes.rs b/src/api/routes.rs new file mode 100644 index 000000000..76b449e9b --- /dev/null +++ b/src/api/routes.rs @@ -0,0 +1,307 @@ +use std::cmp::min; +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; +use std::time::Duration; + +use serde::Deserialize; +use warp::{filters, reply, Filter}; + +use super::resource::auth_key::AuthKey; +use super::resource::peer; +use super::resource::stats::Stats; +use super::resource::torrent::{ListItem, Torrent}; +use super::{ActionStatus, TorrentInfoQuery}; +use crate::protocol::info_hash::InfoHash; +use crate::tracker; + +fn authenticate(tokens: HashMap) -> impl Filter + Clone { + #[derive(Deserialize)] + struct AuthToken { + token: Option, + } + + let tokens: HashSet = tokens.into_values().collect(); + + let tokens = Arc::new(tokens); + warp::filters::any::any() + .map(move || tokens.clone()) + .and(filters::query::query::()) + .and_then(|tokens: Arc>, token: AuthToken| async move { + match token.token { + Some(token) => { + if !tokens.contains(&token) { + return Err(warp::reject::custom(ActionStatus::Err { + reason: "token not valid".into(), + })); + } + + Ok(()) + } + None => Err(warp::reject::custom(ActionStatus::Err { + reason: "unauthorized".into(), + })), + } + }) + .untuple_one() +} + +#[allow(clippy::too_many_lines)] +#[must_use] +pub fn routes(tracker: &Arc) -> impl Filter + Clone { + // GET /api/torrents?offset=:u32&limit=:u32 + // View torrent list + let api_torrents = tracker.clone(); + let view_torrent_list = filters::method::get() + .and(filters::path::path("torrents")) + .and(filters::path::end()) + .and(filters::query::query()) + .map(move |limits| { + let tracker = api_torrents.clone(); + (limits, tracker) + }) + .and_then(|(limits, tracker): (TorrentInfoQuery, Arc)| async move { + let offset = limits.offset.unwrap_or(0); + let limit = min(limits.limit.unwrap_or(1000), 4000); + + let db = tracker.get_torrents().await; + let results: Vec<_> = db + .iter() + .map(|(info_hash, torrent_entry)| { + let (seeders, completed, leechers) = torrent_entry.get_stats(); + ListItem { + info_hash: info_hash.to_string(), + seeders, + completed, + leechers, + peers: None, + } + }) + .skip(offset as usize) + .take(limit as usize) + .collect(); + + Result::<_, warp::reject::Rejection>::Ok(reply::json(&results)) + }); + + // GET /api/stats + // View tracker status + let api_stats = tracker.clone(); + let view_stats_list = filters::method::get() + .and(filters::path::path("stats")) + .and(filters::path::end()) + .map(move || api_stats.clone()) + .and_then(|tracker: Arc| async move { + let mut results = Stats { + torrents: 0, + seeders: 0, + completed: 0, + leechers: 0, + tcp4_connections_handled: 0, + tcp4_announces_handled: 0, + tcp4_scrapes_handled: 0, + tcp6_connections_handled: 0, + tcp6_announces_handled: 0, + tcp6_scrapes_handled: 0, + udp4_connections_handled: 0, + udp4_announces_handled: 0, + udp4_scrapes_handled: 0, + udp6_connections_handled: 0, + udp6_announces_handled: 0, + udp6_scrapes_handled: 0, + }; + + let db = tracker.get_torrents().await; + + db.values().for_each(|torrent_entry| { + let (seeders, completed, leechers) = torrent_entry.get_stats(); + results.seeders += seeders; + results.completed += completed; + results.leechers += leechers; + results.torrents += 1; + }); + + let stats = tracker.get_stats().await; + + #[allow(clippy::cast_possible_truncation)] + { + results.tcp4_connections_handled = stats.tcp4_connections_handled as u32; + results.tcp4_announces_handled = stats.tcp4_announces_handled as u32; + results.tcp4_scrapes_handled = stats.tcp4_scrapes_handled as u32; + results.tcp6_connections_handled = stats.tcp6_connections_handled as u32; + results.tcp6_announces_handled = stats.tcp6_announces_handled as u32; + results.tcp6_scrapes_handled = stats.tcp6_scrapes_handled as u32; + results.udp4_connections_handled = stats.udp4_connections_handled as u32; + results.udp4_announces_handled = stats.udp4_announces_handled as u32; + results.udp4_scrapes_handled = stats.udp4_scrapes_handled as u32; + results.udp6_connections_handled = stats.udp6_connections_handled as u32; + results.udp6_announces_handled = stats.udp6_announces_handled as u32; + results.udp6_scrapes_handled = stats.udp6_scrapes_handled as u32; + } + + Result::<_, warp::reject::Rejection>::Ok(reply::json(&results)) + }); + + // GET /api/torrent/:info_hash + // View torrent info + let t2 = tracker.clone(); + let view_torrent_info = filters::method::get() + .and(filters::path::path("torrent")) + .and(filters::path::param()) + .and(filters::path::end()) + .map(move |info_hash: InfoHash| { + let tracker = t2.clone(); + (info_hash, tracker) + }) + .and_then(|(info_hash, tracker): (InfoHash, Arc)| async move { + let db = tracker.get_torrents().await; + let torrent_entry_option = db.get(&info_hash); + + let torrent_entry = match torrent_entry_option { + Some(torrent_entry) => torrent_entry, + None => { + return Result::<_, warp::reject::Rejection>::Ok(reply::json(&"torrent not known")); + } + }; + let (seeders, completed, leechers) = torrent_entry.get_stats(); + + let peers = torrent_entry.get_peers(None); + + let peer_resources = peers.iter().map(|peer| peer::Peer::from(**peer)).collect(); + + Ok(reply::json(&Torrent { + info_hash: info_hash.to_string(), + seeders, + completed, + leechers, + peers: Some(peer_resources), + })) + }); + + // DELETE /api/whitelist/:info_hash + // Delete info hash from whitelist + let t3 = tracker.clone(); + let delete_torrent = filters::method::delete() + .and(filters::path::path("whitelist")) + .and(filters::path::param()) + .and(filters::path::end()) + .map(move |info_hash: InfoHash| { + let tracker = t3.clone(); + (info_hash, tracker) + }) + .and_then(|(info_hash, tracker): (InfoHash, Arc)| async move { + match tracker.remove_torrent_from_whitelist(&info_hash).await { + Ok(_) => Ok(warp::reply::json(&ActionStatus::Ok)), + Err(_) => Err(warp::reject::custom(ActionStatus::Err { + reason: "failed to remove torrent from whitelist".into(), + })), + } + }); + + // POST /api/whitelist/:info_hash + // Add info hash to whitelist + let t4 = tracker.clone(); + let add_torrent = filters::method::post() + .and(filters::path::path("whitelist")) + .and(filters::path::param()) + .and(filters::path::end()) + .map(move |info_hash: InfoHash| { + let tracker = t4.clone(); + (info_hash, tracker) + }) + .and_then(|(info_hash, tracker): (InfoHash, Arc)| async move { + match tracker.add_torrent_to_whitelist(&info_hash).await { + Ok(..) => Ok(warp::reply::json(&ActionStatus::Ok)), + Err(..) => Err(warp::reject::custom(ActionStatus::Err { + reason: "failed to whitelist torrent".into(), + })), + } + }); + + // POST /api/key/:seconds_valid + // Generate new key + let t5 = tracker.clone(); + let create_key = filters::method::post() + .and(filters::path::path("key")) + .and(filters::path::param()) + .and(filters::path::end()) + .map(move |seconds_valid: u64| { + let tracker = t5.clone(); + (seconds_valid, tracker) + }) + .and_then(|(seconds_valid, tracker): (u64, Arc)| async move { + match tracker.generate_auth_key(Duration::from_secs(seconds_valid)).await { + Ok(auth_key) => Ok(warp::reply::json(&AuthKey::from(auth_key))), + Err(..) => Err(warp::reject::custom(ActionStatus::Err { + reason: "failed to generate key".into(), + })), + } + }); + + // DELETE /api/key/:key + // Delete key + let t6 = tracker.clone(); + let delete_key = filters::method::delete() + .and(filters::path::path("key")) + .and(filters::path::param()) + .and(filters::path::end()) + .map(move |key: String| { + let tracker = t6.clone(); + (key, tracker) + }) + .and_then(|(key, tracker): (String, Arc)| async move { + match tracker.remove_auth_key(&key).await { + Ok(_) => Ok(warp::reply::json(&ActionStatus::Ok)), + Err(_) => Err(warp::reject::custom(ActionStatus::Err { + reason: "failed to delete key".into(), + })), + } + }); + + // GET /api/whitelist/reload + // Reload whitelist + let t7 = tracker.clone(); + let reload_whitelist = filters::method::get() + .and(filters::path::path("whitelist")) + .and(filters::path::path("reload")) + .and(filters::path::end()) + .map(move || t7.clone()) + .and_then(|tracker: Arc| async move { + match tracker.load_whitelist().await { + Ok(_) => Ok(warp::reply::json(&ActionStatus::Ok)), + Err(_) => Err(warp::reject::custom(ActionStatus::Err { + reason: "failed to reload whitelist".into(), + })), + } + }); + + // GET /api/keys/reload + // Reload whitelist + let t8 = tracker.clone(); + let reload_keys = filters::method::get() + .and(filters::path::path("keys")) + .and(filters::path::path("reload")) + .and(filters::path::end()) + .map(move || t8.clone()) + .and_then(|tracker: Arc| async move { + match tracker.load_keys().await { + Ok(_) => Ok(warp::reply::json(&ActionStatus::Ok)), + Err(_) => Err(warp::reject::custom(ActionStatus::Err { + reason: "failed to reload keys".into(), + })), + } + }); + + let api_routes = filters::path::path("api").and( + view_torrent_list + .or(delete_torrent) + .or(view_torrent_info) + .or(view_stats_list) + .or(add_torrent) + .or(create_key) + .or(delete_key) + .or(reload_whitelist) + .or(reload_keys), + ); + + api_routes.and(authenticate(tracker.config.http_api.access_tokens.clone())) +} diff --git a/src/api/server.rs b/src/api/server.rs index 5967a8be4..5d6a3cdfd 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -1,327 +1,32 @@ -use std::cmp::min; -use std::collections::{HashMap, HashSet}; use std::net::SocketAddr; use std::sync::Arc; -use std::time::Duration; -use serde::{Deserialize, Serialize}; -use warp::{filters, reply, serve, Filter}; +use warp::serve; -use super::resource::auth_key::AuthKey; -use super::resource::peer; -use super::resource::stats::Stats; -use super::resource::torrent::{ListItem, Torrent}; -use crate::protocol::info_hash::InfoHash; +use super::routes::routes; use crate::tracker; -#[derive(Deserialize, Debug)] -struct TorrentInfoQuery { - offset: Option, - limit: Option, -} - -#[derive(Serialize, Debug)] -#[serde(tag = "status", rename_all = "snake_case")] -enum ActionStatus<'a> { - Ok, - Err { reason: std::borrow::Cow<'a, str> }, -} - -impl warp::reject::Reject for ActionStatus<'static> {} - -fn authenticate(tokens: HashMap) -> impl Filter + Clone { - #[derive(Deserialize)] - struct AuthToken { - token: Option, - } - - let tokens: HashSet = tokens.into_iter().map(|(_, v)| v).collect(); - - let tokens = Arc::new(tokens); - warp::filters::any::any() - .map(move || tokens.clone()) - .and(filters::query::query::()) - .and_then(|tokens: Arc>, token: AuthToken| async move { - match token.token { - Some(token) => { - if !tokens.contains(&token) { - return Err(warp::reject::custom(ActionStatus::Err { - reason: "token not valid".into(), - })); - } - - Ok(()) - } - None => Err(warp::reject::custom(ActionStatus::Err { - reason: "unauthorized".into(), - })), - } - }) - .untuple_one() -} - -#[allow(clippy::too_many_lines)] pub fn start(socket_addr: SocketAddr, tracker: &Arc) -> impl warp::Future { - // GET /api/torrents?offset=:u32&limit=:u32 - // View torrent list - let api_torrents = tracker.clone(); - let view_torrent_list = filters::method::get() - .and(filters::path::path("torrents")) - .and(filters::path::end()) - .and(filters::query::query()) - .map(move |limits| { - let tracker = api_torrents.clone(); - (limits, tracker) - }) - .and_then(|(limits, tracker): (TorrentInfoQuery, Arc)| async move { - let offset = limits.offset.unwrap_or(0); - let limit = min(limits.limit.unwrap_or(1000), 4000); - - let db = tracker.get_torrents().await; - let results: Vec<_> = db - .iter() - .map(|(info_hash, torrent_entry)| { - let (seeders, completed, leechers) = torrent_entry.get_stats(); - ListItem { - info_hash: info_hash.to_string(), - seeders, - completed, - leechers, - peers: None, - } - }) - .skip(offset as usize) - .take(limit as usize) - .collect(); - - Result::<_, warp::reject::Rejection>::Ok(reply::json(&results)) - }); - - // GET /api/stats - // View tracker status - let api_stats = tracker.clone(); - let view_stats_list = filters::method::get() - .and(filters::path::path("stats")) - .and(filters::path::end()) - .map(move || api_stats.clone()) - .and_then(|tracker: Arc| async move { - let mut results = Stats { - torrents: 0, - seeders: 0, - completed: 0, - leechers: 0, - tcp4_connections_handled: 0, - tcp4_announces_handled: 0, - tcp4_scrapes_handled: 0, - tcp6_connections_handled: 0, - tcp6_announces_handled: 0, - tcp6_scrapes_handled: 0, - udp4_connections_handled: 0, - udp4_announces_handled: 0, - udp4_scrapes_handled: 0, - udp6_connections_handled: 0, - udp6_announces_handled: 0, - udp6_scrapes_handled: 0, - }; - - let db = tracker.get_torrents().await; - - db.values().for_each(|torrent_entry| { - let (seeders, completed, leechers) = torrent_entry.get_stats(); - results.seeders += seeders; - results.completed += completed; - results.leechers += leechers; - results.torrents += 1; - }); - - let stats = tracker.get_stats().await; - - #[allow(clippy::cast_possible_truncation)] - { - results.tcp4_connections_handled = stats.tcp4_connections_handled as u32; - results.tcp4_announces_handled = stats.tcp4_announces_handled as u32; - results.tcp4_scrapes_handled = stats.tcp4_scrapes_handled as u32; - results.tcp6_connections_handled = stats.tcp6_connections_handled as u32; - results.tcp6_announces_handled = stats.tcp6_announces_handled as u32; - results.tcp6_scrapes_handled = stats.tcp6_scrapes_handled as u32; - results.udp4_connections_handled = stats.udp4_connections_handled as u32; - results.udp4_announces_handled = stats.udp4_announces_handled as u32; - results.udp4_scrapes_handled = stats.udp4_scrapes_handled as u32; - results.udp6_connections_handled = stats.udp6_connections_handled as u32; - results.udp6_announces_handled = stats.udp6_announces_handled as u32; - results.udp6_scrapes_handled = stats.udp6_scrapes_handled as u32; - } - - Result::<_, warp::reject::Rejection>::Ok(reply::json(&results)) - }); - - // GET /api/torrent/:info_hash - // View torrent info - let t2 = tracker.clone(); - let view_torrent_info = filters::method::get() - .and(filters::path::path("torrent")) - .and(filters::path::param()) - .and(filters::path::end()) - .map(move |info_hash: InfoHash| { - let tracker = t2.clone(); - (info_hash, tracker) - }) - .and_then(|(info_hash, tracker): (InfoHash, Arc)| async move { - let db = tracker.get_torrents().await; - let torrent_entry_option = db.get(&info_hash); - - let torrent_entry = match torrent_entry_option { - Some(torrent_entry) => torrent_entry, - None => { - return Result::<_, warp::reject::Rejection>::Ok(reply::json(&"torrent not known")); - } - }; - let (seeders, completed, leechers) = torrent_entry.get_stats(); - - let peers = torrent_entry.get_peers(None); - - let peer_resources = peers.iter().map(|peer| peer::Peer::from(**peer)).collect(); - - Ok(reply::json(&Torrent { - info_hash: info_hash.to_string(), - seeders, - completed, - leechers, - peers: Some(peer_resources), - })) - }); - - // DELETE /api/whitelist/:info_hash - // Delete info hash from whitelist - let t3 = tracker.clone(); - let delete_torrent = filters::method::delete() - .and(filters::path::path("whitelist")) - .and(filters::path::param()) - .and(filters::path::end()) - .map(move |info_hash: InfoHash| { - let tracker = t3.clone(); - (info_hash, tracker) - }) - .and_then(|(info_hash, tracker): (InfoHash, Arc)| async move { - match tracker.remove_torrent_from_whitelist(&info_hash).await { - Ok(_) => Ok(warp::reply::json(&ActionStatus::Ok)), - Err(_) => Err(warp::reject::custom(ActionStatus::Err { - reason: "failed to remove torrent from whitelist".into(), - })), - } - }); - - // POST /api/whitelist/:info_hash - // Add info hash to whitelist - let t4 = tracker.clone(); - let add_torrent = filters::method::post() - .and(filters::path::path("whitelist")) - .and(filters::path::param()) - .and(filters::path::end()) - .map(move |info_hash: InfoHash| { - let tracker = t4.clone(); - (info_hash, tracker) - }) - .and_then(|(info_hash, tracker): (InfoHash, Arc)| async move { - match tracker.add_torrent_to_whitelist(&info_hash).await { - Ok(..) => Ok(warp::reply::json(&ActionStatus::Ok)), - Err(..) => Err(warp::reject::custom(ActionStatus::Err { - reason: "failed to whitelist torrent".into(), - })), - } - }); - - // POST /api/key/:seconds_valid - // Generate new key - let t5 = tracker.clone(); - let create_key = filters::method::post() - .and(filters::path::path("key")) - .and(filters::path::param()) - .and(filters::path::end()) - .map(move |seconds_valid: u64| { - let tracker = t5.clone(); - (seconds_valid, tracker) - }) - .and_then(|(seconds_valid, tracker): (u64, Arc)| async move { - match tracker.generate_auth_key(Duration::from_secs(seconds_valid)).await { - Ok(auth_key) => Ok(warp::reply::json(&AuthKey::from(auth_key))), - Err(..) => Err(warp::reject::custom(ActionStatus::Err { - reason: "failed to generate key".into(), - })), - } - }); - - // DELETE /api/key/:key - // Delete key - let t6 = tracker.clone(); - let delete_key = filters::method::delete() - .and(filters::path::path("key")) - .and(filters::path::param()) - .and(filters::path::end()) - .map(move |key: String| { - let tracker = t6.clone(); - (key, tracker) - }) - .and_then(|(key, tracker): (String, Arc)| async move { - match tracker.remove_auth_key(&key).await { - Ok(_) => Ok(warp::reply::json(&ActionStatus::Ok)), - Err(_) => Err(warp::reject::custom(ActionStatus::Err { - reason: "failed to delete key".into(), - })), - } - }); + let (_addr, api_server) = serve(routes(tracker)).bind_with_graceful_shutdown(socket_addr, async move { + tokio::signal::ctrl_c().await.expect("Failed to listen to shutdown signal."); + }); - // GET /api/whitelist/reload - // Reload whitelist - let t7 = tracker.clone(); - let reload_whitelist = filters::method::get() - .and(filters::path::path("whitelist")) - .and(filters::path::path("reload")) - .and(filters::path::end()) - .map(move || t7.clone()) - .and_then(|tracker: Arc| async move { - match tracker.load_whitelist().await { - Ok(_) => Ok(warp::reply::json(&ActionStatus::Ok)), - Err(_) => Err(warp::reject::custom(ActionStatus::Err { - reason: "failed to reload whitelist".into(), - })), - } - }); + api_server +} - // GET /api/keys/reload - // Reload whitelist - let t8 = tracker.clone(); - let reload_keys = filters::method::get() - .and(filters::path::path("keys")) - .and(filters::path::path("reload")) - .and(filters::path::end()) - .map(move || t8.clone()) - .and_then(|tracker: Arc| async move { - match tracker.load_keys().await { - Ok(_) => Ok(warp::reply::json(&ActionStatus::Ok)), - Err(_) => Err(warp::reject::custom(ActionStatus::Err { - reason: "failed to reload keys".into(), - })), - } +pub fn start_tls( + socket_addr: SocketAddr, + ssl_cert_path: String, + ssl_key_path: String, + tracker: &Arc, +) -> impl warp::Future { + let (_addr, api_server) = serve(routes(tracker)) + .tls() + .cert_path(ssl_cert_path) + .key_path(ssl_key_path) + .bind_with_graceful_shutdown(socket_addr, async move { + tokio::signal::ctrl_c().await.expect("Failed to listen to shutdown signal."); }); - let api_routes = filters::path::path("api").and( - view_torrent_list - .or(delete_torrent) - .or(view_torrent_info) - .or(view_stats_list) - .or(add_torrent) - .or(create_key) - .or(delete_key) - .or(reload_whitelist) - .or(reload_keys), - ); - - let server = api_routes.and(authenticate(tracker.config.http_api.access_tokens.clone())); - - let (_addr, api_server) = serve(server).bind_with_graceful_shutdown(socket_addr, async move { - tokio::signal::ctrl_c().await.expect("Failed to listen to shutdown signal."); - }); - api_server } diff --git a/src/config.rs b/src/config.rs index 2ad9c61b7..3aa222f0e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -30,10 +30,16 @@ pub struct HttpTracker { pub ssl_key_path: Option, } +#[serde_as] #[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] pub struct HttpApi { pub enabled: bool, pub bind_address: String, + pub ssl_enabled: bool, + #[serde_as(as = "NoneAsEmptyString")] + pub ssl_cert_path: Option, + #[serde_as(as = "NoneAsEmptyString")] + pub ssl_key_path: Option, pub access_tokens: HashMap, } @@ -114,6 +120,9 @@ impl Configuration { http_api: HttpApi { enabled: true, bind_address: String::from("127.0.0.1:1212"), + ssl_enabled: false, + ssl_cert_path: None, + ssl_key_path: None, access_tokens: [(String::from("admin"), String::from("MyAccessToken"))] .iter() .cloned() @@ -208,6 +217,9 @@ mod tests { [http_api] enabled = true bind_address = "127.0.0.1:1212" + ssl_enabled = false + ssl_cert_path = "" + ssl_key_path = "" [http_api.access_tokens] admin = "MyAccessToken" diff --git a/src/jobs/http_tracker.rs b/src/jobs/http_tracker.rs index b8f031f5a..c62bc5cc9 100644 --- a/src/jobs/http_tracker.rs +++ b/src/jobs/http_tracker.rs @@ -22,10 +22,10 @@ pub fn start_job(config: &HttpTracker, tracker: Arc) -> JoinHa let http_tracker = Http::new(tracker); if !ssl_enabled { - info!("Starting HTTP server on: {}", bind_addr); + info!("Starting HTTP server on: http://{}", bind_addr); http_tracker.start(bind_addr).await; } else if ssl_enabled && ssl_cert_path.is_some() && ssl_key_path.is_some() { - info!("Starting HTTPS server on: {} (TLS)", bind_addr); + info!("Starting HTTPS server on: https://{} (TLS)", bind_addr); http_tracker .start_tls(bind_addr, ssl_cert_path.unwrap(), ssl_key_path.unwrap()) .await; diff --git a/src/jobs/tracker_api.rs b/src/jobs/tracker_api.rs index 2c00aa453..211174f35 100644 --- a/src/jobs/tracker_api.rs +++ b/src/jobs/tracker_api.rs @@ -5,7 +5,7 @@ use tokio::sync::oneshot; use tokio::task::JoinHandle; use crate::api::server; -use crate::config::Configuration; +use crate::config::HttpApi; use crate::tracker; #[derive(Debug)] @@ -14,24 +14,30 @@ pub struct ApiServerJobStarted(); /// # Panics /// /// It would panic if unable to send the `ApiServerJobStarted` notice. -pub async fn start_job(config: &Configuration, tracker: Arc) -> JoinHandle<()> { +pub async fn start_job(config: &HttpApi, tracker: Arc) -> JoinHandle<()> { let bind_addr = config - .http_api .bind_address .parse::() .expect("Tracker API bind_address invalid."); - - info!("Starting Torrust API server on: {}", bind_addr); + let ssl_enabled = config.ssl_enabled; + let ssl_cert_path = config.ssl_cert_path.clone(); + let ssl_key_path = config.ssl_key_path.clone(); let (tx, rx) = oneshot::channel::(); // Run the API server let join_handle = tokio::spawn(async move { - let handel = server::start(bind_addr, &tracker); - - tx.send(ApiServerJobStarted()).expect("the start job dropped"); - - handel.await; + if !ssl_enabled { + info!("Starting Torrust API server on: http://{}", bind_addr); + let handle = server::start(bind_addr, &tracker); + tx.send(ApiServerJobStarted()).expect("the start job dropped"); + handle.await; + } else if ssl_enabled && ssl_cert_path.is_some() && ssl_key_path.is_some() { + info!("Starting Torrust API server on: https://{}", bind_addr); + let handle = server::start_tls(bind_addr, ssl_cert_path.unwrap(), ssl_key_path.unwrap(), &tracker); + tx.send(ApiServerJobStarted()).expect("the start job dropped"); + handle.await; + } }); // Wait until the API server job is running diff --git a/src/jobs/udp_tracker.rs b/src/jobs/udp_tracker.rs index 57369f660..d0907c976 100644 --- a/src/jobs/udp_tracker.rs +++ b/src/jobs/udp_tracker.rs @@ -14,11 +14,11 @@ pub fn start_job(config: &UdpTracker, tracker: Arc) -> JoinHan tokio::spawn(async move { match Udp::new(tracker, &bind_addr).await { Ok(udp_server) => { - info!("Starting UDP server on: {}", bind_addr); + info!("Starting UDP server on: udp://{}", bind_addr); udp_server.start().await; } Err(e) => { - warn!("Could not start UDP tracker on: {}", bind_addr); + warn!("Could not start UDP tracker on: udp://{}", bind_addr); error!("{}", e); } } diff --git a/src/setup.rs b/src/setup.rs index a7b7c5a82..c045310bb 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -49,7 +49,7 @@ pub async fn setup(config: &Configuration, tracker: Arc) -> Ve // Start HTTP API server if config.http_api.enabled { - jobs.push(tracker_api::start_job(config, tracker.clone()).await); + jobs.push(tracker_api::start_job(&config.http_api, tracker.clone()).await); } // Remove torrents without peers, every interval diff --git a/tests/api.rs b/tests/api.rs index 706cd0b8d..42b9f3075 100644 --- a/tests/api.rs +++ b/tests/api.rs @@ -290,7 +290,7 @@ mod tracker_api { logging::setup(&configuration); // Start the HTTP API job - self.job = Some(tracker_api::start_job(&configuration, tracker).await); + self.job = Some(tracker_api::start_job(&configuration.http_api, tracker).await); self.started.store(true, Ordering::Relaxed); } From b0243a5056898a396e54edec47c253928af9b051 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Dec 2022 17:06:38 +0000 Subject: [PATCH 17/33] docs: update docker README --- docker/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docker/README.md b/docker/README.md index d59569021..2b2736067 100644 --- a/docker/README.md +++ b/docker/README.md @@ -96,7 +96,11 @@ If you enable the SSL certificate for the API, for example, you can load the API ## Prod environment -Deploy to Azure following [docker documentation](https://docs.docker.com/cloud/aci-integration/). +In this section, you will learn how to deploy the tracker to a single docker container in Azure Container Instances. + +> NOTE: Azure Container Instances is a solution when you want to run an isolated container. If you need full container orchestration, including service discovery across multiple containers, automatic scaling, and coordinated application upgrades, we recommend [Kubernetes](https://kubernetes.io/). + +Deploy to Azure Container Instance following [docker documentation](https://docs.docker.com/cloud/aci-integration/). You have to create the ACI context and the storage: From a1e9b6a7256d63734c6a5d44f14d152295bca98f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Dec 2022 17:07:25 +0000 Subject: [PATCH 18/33] feat: new release 0.7.0 --- docker/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/README.md b/docker/README.md index 2b2736067..27f6cd1fb 100644 --- a/docker/README.md +++ b/docker/README.md @@ -104,7 +104,7 @@ Deploy to Azure Container Instance following [docker documentation](https://docs You have to create the ACI context and the storage: -```s0.6.0 +```s0.7.0 docker context create aci myacicontext docker context use myacicontext docker volume create test-volume --storage-account torrustracker @@ -123,7 +123,7 @@ docker run \ --publish 6969:6969/tcp \ --publish 1212:1212/tcp \ --volume torrustracker/test-volume:/app/storage \ - registry.hub.docker.com/josecelano/torrust-tracker:0.6.0 + registry.hub.docker.com/josecelano/torrust-tracker:0.7.0 ``` Detach from container logs when the container starts. By default, the command line stays attached and follows container logs. @@ -133,9 +133,9 @@ docker run \ --detach --publish 6969:6969/udp \ --publish 6969:6969/tcp \ - --publish 1212:1212/tcp \ + --publish 1212:1212/tcp \0.7.0 --volume torrustracker/test-volume:/app/storage \ - registry.hub.docker.com/josecelano/torrust-tracker:0.6.0 + registry.hub.docker.com/josecelano/torrust-tracker:0.7.0 ``` You should see something like this: @@ -146,7 +146,7 @@ $ docker run \ \ --publish 6969:6969/tcp \ --publish 1212:1212/tcp \ --volume torrustracker/test-volume:/app/storage \ - registry.hub.docker.com/josecelano/torrust-tracker:0.6.0 + registry.hub.docker.com/josecelano/torrust-tracker:0.7.0 [+] Running 2/2 ⠿ Group intelligent-hawking Created 5.0s ⠿ intelligent-hawking Created 41.7s @@ -161,7 +161,7 @@ You can see the container with: ```s $ docker ps CONTAINER ID IMAGE COMMAND STATUS PORTS -intelligent-hawking registry.hub.docker.com/josecelano/torrust-tracker:0.6.0 Running 4.236.213.57:6969->6969/udp, 4.236.213.57:1212->1212/tcp +intelligent-hawking registry.hub.docker.com/josecelano/torrust-tracker:0.7.0 Running 4.236.213.57:6969->6969/udp, 4.236.213.57:1212->1212/tcp ``` After a while, you can use the tracker API `http://4.236.213.57:1212/api/stats?token=MyAccessToken` and the UDP tracker with your BitTorrent client using this tracker announce URL `udp://4.236.213.57:6969/announce`. From 0dd5b5ccb57cd239a24a82213a7f40791f39a239 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Dec 2022 17:26:15 +0000 Subject: [PATCH 19/33] fix: strip binary after build --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 79b53ee3b..f40d40bba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,6 +29,9 @@ RUN cargo chef cook --release --target x86_64-unknown-linux-musl --recipe-path r # Build the application COPY . . RUN cargo build --release --target x86_64-unknown-linux-musl --bin torrust-tracker +# Strip the binary +# More info: https://github.com/LukeMathWalker/cargo-chef/issues/149 +RUN strip /app/target/x86_64-unknown-linux-musl/release/torrust-tracker FROM alpine:latest From 54aa7ef139a3fab8f1f153bd0886fef6570c4666 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Dec 2022 17:26:28 +0000 Subject: [PATCH 20/33] docs: update docker README --- docker/README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docker/README.md b/docker/README.md index 27f6cd1fb..541563e00 100644 --- a/docker/README.md +++ b/docker/README.md @@ -141,12 +141,6 @@ docker run \ You should see something like this: ```s -$ docker run \ \ - --publish 6969:6969/udp \ - --publish 6969:6969/tcp \ - --publish 1212:1212/tcp \ - --volume torrustracker/test-volume:/app/storage \ - registry.hub.docker.com/josecelano/torrust-tracker:0.7.0 [+] Running 2/2 ⠿ Group intelligent-hawking Created 5.0s ⠿ intelligent-hawking Created 41.7s From 7b8762b9c882b0888ccd496dc1d92c781243d833 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Dec 2022 18:59:22 +0000 Subject: [PATCH 21/33] feat: change default HTTP tracker port to 7070 On Azure Container Intances we are getting an error if we tried to publish a port wiht both DP and TCP. The error: ``` containerinstance.ContainerGroupsClient#CreateOrUpdate: Failure sending request: StatusCode=400 -- Original Error: Code="DuplicateContainerPorts" Message="Duplicate ports '6969' found in container group 'sweet-gates' container 'sweet-gates'." ``` --- Dockerfile | 2 +- README.md | 2 +- config.toml.local | 2 +- docker/README.md | 10 +++++----- docker/bin/run.sh | 2 +- src/config.rs | 6 +++--- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index f40d40bba..3a9a87d2b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,6 +48,6 @@ COPY --from=builder --chown=$RUN_AS_USER \ RUN chown -R $RUN_AS_USER:$RUN_AS_USER /app USER $RUN_AS_USER:$RUN_AS_USER EXPOSE 6969/udp -EXPOSE 6969/tcp +EXPOSE 7070/tcp EXPOSE 1212/tcp ENTRYPOINT ["/app/torrust-tracker"] \ No newline at end of file diff --git a/README.md b/README.md index beb2591ea..4e464dd68 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ bind_address = "0.0.0.0:6969" [[http_trackers]] enabled = true -bind_address = "0.0.0.0:6969" +bind_address = "0.0.0.0:7070" ssl_enabled = false ssl_cert_path = "" ssl_key_path = "" diff --git a/config.toml.local b/config.toml.local index 9002361ac..baf272d5a 100644 --- a/config.toml.local +++ b/config.toml.local @@ -18,7 +18,7 @@ bind_address = "0.0.0.0:6969" [[http_trackers]] enabled = false -bind_address = "0.0.0.0:6969" +bind_address = "0.0.0.0:7070" ssl_enabled = false ssl_cert_path = "" ssl_key_path = "" diff --git a/docker/README.md b/docker/README.md index 541563e00..f47105c0a 100644 --- a/docker/README.md +++ b/docker/README.md @@ -38,7 +38,7 @@ export TORRUST_TRACKER_USER_UID=1000 docker run -it \ --user="$TORRUST_TRACKER_USER_UID" \ --publish 6969:6969/udp \ - --publish 6969:6969/tcp \ + --publish 7070:7070/tcp \ --publish 1212:1212/tcp \ --volume "$(pwd)/storage":"/app/storage" \ josecelano/torrust-tracker @@ -74,7 +74,7 @@ You have not enabled it in your `config.toml` file: ... [[http_trackers]] enabled = true -bind_address = "0.0.0.0:6969" +bind_address = "0.0.0.0:7070" ssl_enabled = true ssl_cert_path = "./storage/ssl_certificates/localhost.crt" ssl_key_path = "./storage/ssl_certificates/localhost.key" @@ -120,7 +120,7 @@ And finally, you can run the container: ```s docker run \ --publish 6969:6969/udp \ - --publish 6969:6969/tcp \ + --publish 7070:7070/tcp \ --publish 1212:1212/tcp \ --volume torrustracker/test-volume:/app/storage \ registry.hub.docker.com/josecelano/torrust-tracker:0.7.0 @@ -132,7 +132,7 @@ Detach from container logs when the container starts. By default, the command li docker run \ --detach --publish 6969:6969/udp \ - --publish 6969:6969/tcp \ + --publish 7070:7070/tcp \ --publish 1212:1212/tcp \0.7.0 --volume torrustracker/test-volume:/app/storage \ registry.hub.docker.com/josecelano/torrust-tracker:0.7.0 @@ -158,7 +158,7 @@ CONTAINER ID IMAGE intelligent-hawking registry.hub.docker.com/josecelano/torrust-tracker:0.7.0 Running 4.236.213.57:6969->6969/udp, 4.236.213.57:1212->1212/tcp ``` -After a while, you can use the tracker API `http://4.236.213.57:1212/api/stats?token=MyAccessToken` and the UDP tracker with your BitTorrent client using this tracker announce URL `udp://4.236.213.57:6969/announce`. +After a while, you can use the tracker API `http://4.236.213.57:1212/api/stats?token=MyAccessToken` and the UDP tracker with your BitTorrent client using this tracker announce URL `udp://4.236.213.57:6969`. > NOTES: > diff --git a/docker/bin/run.sh b/docker/bin/run.sh index 1bc182aef..81b8d8260 100755 --- a/docker/bin/run.sh +++ b/docker/bin/run.sh @@ -5,7 +5,7 @@ TORRUST_TRACKER_USER_UID=${TORRUST_TRACKER_USER_UID:-1000} docker run -it \ --user="$TORRUST_TRACKER_USER_UID" \ --publish 6969:6969/udp \ - --publish 6969:6969/tcp \ + --publish 7070:7070/tcp \ --publish 1212:1212/tcp \ --volume "$(pwd)/storage":"/app/storage" \ torrust-tracker diff --git a/src/config.rs b/src/config.rs index 3aa222f0e..0bdb7e69e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -131,11 +131,11 @@ impl Configuration { }; configuration.udp_trackers.push(UdpTracker { enabled: false, - bind_address: String::from("0.0.0.0:6969"), + bind_address: String::from("0.0.0.0:7070"), }); configuration.http_trackers.push(HttpTracker { enabled: false, - bind_address: String::from("0.0.0.0:6969"), + bind_address: String::from("0.0.0.0:7070"), ssl_enabled: false, ssl_cert_path: None, ssl_key_path: None, @@ -209,7 +209,7 @@ mod tests { [[http_trackers]] enabled = false - bind_address = "0.0.0.0:6969" + bind_address = "0.0.0.0:7070" ssl_enabled = false ssl_cert_path = "" ssl_key_path = "" From d8fc5c69ebbd3392fcc653d70f1120784c250bfd Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Dec 2022 19:04:32 +0000 Subject: [PATCH 22/33] feat: use arguments for port in Dockerfile --- Dockerfile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3a9a87d2b..23535db04 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,6 +37,9 @@ RUN strip /app/target/x86_64-unknown-linux-musl/release/torrust-tracker FROM alpine:latest WORKDIR /app ARG RUN_AS_USER=appuser +ARG TRACKER_UDP_PORT=6969 +ARG TRACKER_HTTP_PORT=7070 +ARG TRACKER_API_PORT=1212 RUN apk --no-cache add ca-certificates ENV TZ=Etc/UTC ENV RUN_AS_USER=$RUN_AS_USER @@ -47,7 +50,7 @@ COPY --from=builder --chown=$RUN_AS_USER \ /app/torrust-tracker RUN chown -R $RUN_AS_USER:$RUN_AS_USER /app USER $RUN_AS_USER:$RUN_AS_USER -EXPOSE 6969/udp -EXPOSE 7070/tcp -EXPOSE 1212/tcp +EXPOSE $TRACKER_UDP_PORT/udp +EXPOSE $TRACKER_HTTP_PORT/tcp +EXPOSE $TRACKER_API_PORT/tcp ENTRYPOINT ["/app/torrust-tracker"] \ No newline at end of file From f8ff4152af790fd73d1d7599d1bfe47dd78ef95d Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Dec 2022 19:08:34 +0000 Subject: [PATCH 23/33] feat: push also docker image for develop branch to docker registry --- .github/workflows/publish-docker-image.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish-docker-image.yml b/.github/workflows/publish-docker-image.yml index ff0732e48..5da0b1c32 100644 --- a/.github/workflows/publish-docker-image.yml +++ b/.github/workflows/publish-docker-image.yml @@ -2,6 +2,8 @@ name: Publish docker image on: push: + branches: + - 'develop' tags: - "v*" From 63ff9f79aa6f23ab38534bdd2d912a2b84db7a91 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Dec 2022 19:15:34 +0000 Subject: [PATCH 24/33] docs: use latest docker image tag in the README --- docker/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker/README.md b/docker/README.md index f47105c0a..292eedb71 100644 --- a/docker/README.md +++ b/docker/README.md @@ -104,7 +104,7 @@ Deploy to Azure Container Instance following [docker documentation](https://docs You have to create the ACI context and the storage: -```s0.7.0 +```slatest docker context create aci myacicontext docker context use myacicontext docker volume create test-volume --storage-account torrustracker @@ -123,7 +123,7 @@ docker run \ --publish 7070:7070/tcp \ --publish 1212:1212/tcp \ --volume torrustracker/test-volume:/app/storage \ - registry.hub.docker.com/josecelano/torrust-tracker:0.7.0 + registry.hub.docker.com/josecelano/torrust-tracker:latest ``` Detach from container logs when the container starts. By default, the command line stays attached and follows container logs. @@ -133,9 +133,9 @@ docker run \ --detach --publish 6969:6969/udp \ --publish 7070:7070/tcp \ - --publish 1212:1212/tcp \0.7.0 + --publish 1212:1212/tcp \latest --volume torrustracker/test-volume:/app/storage \ - registry.hub.docker.com/josecelano/torrust-tracker:0.7.0 + registry.hub.docker.com/josecelano/torrust-tracker:latest ``` You should see something like this: @@ -155,7 +155,7 @@ You can see the container with: ```s $ docker ps CONTAINER ID IMAGE COMMAND STATUS PORTS -intelligent-hawking registry.hub.docker.com/josecelano/torrust-tracker:0.7.0 Running 4.236.213.57:6969->6969/udp, 4.236.213.57:1212->1212/tcp +intelligent-hawking registry.hub.docker.com/josecelano/torrust-tracker:latest Running 4.236.213.57:6969->6969/udp, 4.236.213.57:1212->1212/tcp ``` After a while, you can use the tracker API `http://4.236.213.57:1212/api/stats?token=MyAccessToken` and the UDP tracker with your BitTorrent client using this tracker announce URL `udp://4.236.213.57:6969`. From 94ac06322ca6b0541c74ce3d1f6034cff4aae942 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Dec 2022 19:19:10 +0000 Subject: [PATCH 25/33] feat: temporarily publish docker imamge for docker branch --- .github/workflows/publish-docker-image.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-docker-image.yml b/.github/workflows/publish-docker-image.yml index 5da0b1c32..1f1dcadff 100644 --- a/.github/workflows/publish-docker-image.yml +++ b/.github/workflows/publish-docker-image.yml @@ -3,7 +3,9 @@ name: Publish docker image on: push: branches: - - 'develop' + - 'develop' + # todo: only during development of issue 11 + - 'docker' tags: - "v*" From cdb92d13216fe5054a584b527810f5eb4889c9fe Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Dec 2022 19:39:39 +0000 Subject: [PATCH 26/33] fix: test after changing default port --- src/config.rs | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/config.rs b/src/config.rs index 0bdb7e69e..fe697583e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -87,20 +87,8 @@ impl std::fmt::Display for Error { impl std::error::Error for Error {} -impl Configuration { - #[must_use] - pub fn get_ext_ip(&self) -> Option { - match &self.external_ip { - None => None, - Some(external_ip) => match IpAddr::from_str(external_ip) { - Ok(external_ip) => Some(external_ip), - Err(_) => None, - }, - } - } - - #[must_use] - pub fn default() -> Configuration { +impl Default for Configuration { + fn default() -> Self { let mut configuration = Configuration { log_level: Option::from(String::from("info")), mode: mode::Mode::Public, @@ -131,7 +119,7 @@ impl Configuration { }; configuration.udp_trackers.push(UdpTracker { enabled: false, - bind_address: String::from("0.0.0.0:7070"), + bind_address: String::from("0.0.0.0:6969"), }); configuration.http_trackers.push(HttpTracker { enabled: false, @@ -142,6 +130,19 @@ impl Configuration { }); configuration } +} + +impl Configuration { + #[must_use] + pub fn get_ext_ip(&self) -> Option { + match &self.external_ip { + None => None, + Some(external_ip) => match IpAddr::from_str(external_ip) { + Ok(external_ip) => Some(external_ip), + Err(_) => None, + }, + } + } /// # Errors /// From 0184ac138d2266f8e893aa0afdea446f57b341bf Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 12 Dec 2022 19:39:55 +0000 Subject: [PATCH 27/33] feat: build docker images only if tests pass --- .github/workflows/publish-docker-image.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/publish-docker-image.yml b/.github/workflows/publish-docker-image.yml index 1f1dcadff..7fe89fc9b 100644 --- a/.github/workflows/publish-docker-image.yml +++ b/.github/workflows/publish-docker-image.yml @@ -17,7 +17,21 @@ env: TORRUST_TRACKER_RUN_AS_USER: root jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + components: llvm-tools-preview + - uses: Swatinem/rust-cache@v1 + - name: Run Tests + run: cargo test + dockerhub: + needs: test runs-on: ubuntu-latest environment: dockerhub-josecelano steps: From 19a083913decc3a94766d23b540643d9eb9bc6a8 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 13 Dec 2022 17:42:26 +0000 Subject: [PATCH 28/33] feat: docker-compose support for development --- .env.local | 1 + .github/workflows/test_docker.yml | 15 ++++++ .gitignore | 1 + Dockerfile | 32 ++++++++++-- cSpell.json | 4 ++ compose.yaml | 48 ++++++++++++++++++ docker/README.md | 81 ++++++++++++++++++++++++++++++- 7 files changed, 177 insertions(+), 5 deletions(-) create mode 100644 .env.local create mode 100644 .github/workflows/test_docker.yml create mode 100644 compose.yaml diff --git a/.env.local b/.env.local new file mode 100644 index 000000000..fefed56c4 --- /dev/null +++ b/.env.local @@ -0,0 +1 @@ +TORRUST_TRACKER_USER_UID=1000 \ No newline at end of file diff --git a/.github/workflows/test_docker.yml b/.github/workflows/test_docker.yml new file mode 100644 index 000000000..15ae9917b --- /dev/null +++ b/.github/workflows/test_docker.yml @@ -0,0 +1,15 @@ +name: Publish docker image + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build docker image + run: ./docker/bin/build.sh + - name: Build docker-compose images + run: docker compose build diff --git a/.gitignore b/.gitignore index 5bf5e6456..299c0e4ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.env **/*.rs.bk /.idea/ /.vscode/launch.json diff --git a/Dockerfile b/Dockerfile index 23535db04..152ab9980 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,10 +9,34 @@ COPY . . RUN cargo chef prepare --recipe-path recipe.json +FROM chef as development +WORKDIR /app +ARG UID=1000 +ARG RUN_AS_USER=appuser +ARG TRACKER_UDP_PORT=6969 +ARG TRACKER_HTTP_PORT=7070 +ARG TRACKER_API_PORT=1212 +# Add the app user for development +ENV USER=appuser +ENV UID=$UID +RUN adduser --uid "${UID}" "${USER}" +# Build dependencies +COPY --from=planner /app/recipe.json recipe.json +RUN cargo chef cook --recipe-path recipe.json +# Build the application +COPY . . +RUN cargo build --bin torrust-tracker +USER $RUN_AS_USER:$RUN_AS_USER +EXPOSE $TRACKER_UDP_PORT/udp +EXPOSE $TRACKER_HTTP_PORT/tcp +EXPOSE $TRACKER_API_PORT/tcp +CMD ["cargo", "run"] + + FROM chef AS builder WORKDIR /app ARG UID=1000 -# Create the app user +# Add the app user for production ENV USER=appuser ENV UID=$UID RUN adduser \ @@ -22,7 +46,7 @@ RUN adduser \ --shell "/sbin/nologin" \ --no-create-home \ --uid "${UID}" \ - "${USER}" + "${USER}" # Build dependencies COPY --from=planner /app/recipe.json recipe.json RUN cargo chef cook --release --target x86_64-unknown-linux-musl --recipe-path recipe.json @@ -43,8 +67,8 @@ ARG TRACKER_API_PORT=1212 RUN apk --no-cache add ca-certificates ENV TZ=Etc/UTC ENV RUN_AS_USER=$RUN_AS_USER -COPY --from=builder /etc/passwd /etc/passwd -COPY --from=builder /etc/group /etc/group +COPY --from=base /etc/passwd /etc/passwd +COPY --from=base /etc/group /etc/group COPY --from=builder --chown=$RUN_AS_USER \ /app/target/x86_64-unknown-linux-musl/release/torrust-tracker \ /app/torrust-tracker diff --git a/cSpell.json b/cSpell.json index 6a581c20e..5bc67a0c8 100644 --- a/cSpell.json +++ b/cSpell.json @@ -22,8 +22,10 @@ "Freebox", "hasher", "hexlify", + "hlocalhost", "Hydranode", "incompletei", + "infoschema", "intervali", "leecher", "leechers", @@ -37,6 +39,7 @@ "oneshot", "ostr", "Pando", + "proot", "Quickstart", "Rasterbar", "repr", @@ -58,6 +61,7 @@ "typenum", "Unamed", "untuple", + "uroot", "Vagaa", "Xtorrent", "Xunlei" diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 000000000..d11f9c8ae --- /dev/null +++ b/compose.yaml @@ -0,0 +1,48 @@ +name: torrust +services: + + tracker: + build: + context: . + target: development + user: ${TORRUST_TRACKER_USER_UID:-1000}:${TORRUST_TRACKER_USER_UID:-1000} + tty: true + networks: + - server_side + ports: + - 6969:6969/udp + - 7070:7070 + - 1212:1212 + volumes: + - ./:/app + - ~/.cargo:/home/appuser/.cargo + depends_on: + - mysql + + mysql: + image: mysql:8.0 + command: '--default-authentication-plugin=mysql_native_password' + restart: always + healthcheck: + test: ['CMD-SHELL', 'mysqladmin ping -h 127.0.0.1 --password="$$(cat /run/secrets/db-password)" --silent'] + interval: 3s + retries: 5 + start_period: 30s + environment: + - MYSQL_ROOT_HOST=% + - MYSQL_ROOT_PASSWORD=root_secret_password + - MYSQL_DATABASE=torrust_tracker + - MYSQL_USER=db_user + - MYSQL_PASSWORD=db_user_secret_password + networks: + - server_side + ports: + - 3306:3306 + volumes: + - mysql_data:/var/lib/mysql + +networks: + server_side: {} + +volumes: + mysql_data: {} \ No newline at end of file diff --git a/docker/README.md b/docker/README.md index 292eedb71..8c6b2a474 100644 --- a/docker/README.md +++ b/docker/README.md @@ -21,6 +21,8 @@ storage/ ## Dev environment +### With docker + Build and run locally: ```s @@ -50,6 +52,83 @@ docker run -it \ > - You have to replace the user UID (`1000`) with yours. > - Remember to switch to your default docker context `docker context use default`. +### With docker-compose + +The docker-compose configuration includes the MySQL service configuration. If you want to use MySQL instead of SQLite you have to change your `config.toml` configuration: + +```toml +db_driver = "MySQL" +db_path = "mysql://db_user:db_user_secret_password@mysql:3306/torrust_tracker" +``` + +Build and run it locally: + +```s +docker compose up --build +``` + +After running the "up" command you will have two running containers: + +```s +$ docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +06feacb91a9e torrust-tracker "cargo run" 18 minutes ago Up 4 seconds 0.0.0.0:1212->1212/tcp, :::1212->1212/tcp, 0.0.0.0:7070->7070/tcp, :::7070->7070/tcp, 0.0.0.0:6969->6969/udp, :::6969->6969/udp torrust-tracker-1 +34d29e792ee2 mysql:8.0 "docker-entrypoint.s…" 18 minutes ago Up 5 seconds (healthy) 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp torrust-mysql-1 +``` + +And you should be able to use the application, for example making a request to the API: + + + +You can stop the containers with: + +```s +docker compose down +``` + +Additionally, you can delete all resources (containers, volumes, networks) with: + +```s +docker compose down -v +``` + +### Access Mysql with docker + +These are some useful commands for MySQL. + +Open a shell in the MySQL container using docker or docker-compose. + +```s +docker exec -it torrust-mysql-1 /bin/bash +docker compose exec mysql /bin/bash +``` + +Connect to MySQL from inside the MySQL container or from the host: + +```s +mysql -h127.0.0.1 -uroot -proot_secret_password +``` + +The when MySQL container is started the first time, it creates the database, user, and permissions needed. +If you see the error "Host is not allowed to connect to this MySQL server" you can check that users have the right permissions in the database. Make sure the user `root` and `db_user` can connect from any host (`%`). + +```s +mysql> SELECT host, user FROM mysql.user; ++-----------+------------------+ +| host | user | ++-----------+------------------+ +| % | db_user | +| % | root | +| localhost | mysql.infoschema | +| localhost | mysql.session | +| localhost | mysql.sys | +| localhost | root | ++-----------+------------------+ +6 rows in set (0.00 sec) +``` + +If the database, user or permissions are not created the reason could be the MySQL container volume can be corrupted. Delete it and start again the containers. + ### SSL Certificates You can use a certificate for localhost. You can create your [localhost certificate](https://letsencrypt.org/docs/certificates-for-localhost/#making-and-trusting-your-own-certificates) and use it in the `storage` folder and the configuration file (`config.toml`). For example: @@ -104,7 +183,7 @@ Deploy to Azure Container Instance following [docker documentation](https://docs You have to create the ACI context and the storage: -```slatest +```s docker context create aci myacicontext docker context use myacicontext docker volume create test-volume --storage-account torrustracker From 25c1cc9ad90e08d58e694c711dc2b505df8d3658 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 13 Dec 2022 17:43:59 +0000 Subject: [PATCH 29/33] reafactor: rename workflow --- .../{publish-docker-image.yml => publish_docker_image.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{publish-docker-image.yml => publish_docker_image.yml} (100%) diff --git a/.github/workflows/publish-docker-image.yml b/.github/workflows/publish_docker_image.yml similarity index 100% rename from .github/workflows/publish-docker-image.yml rename to .github/workflows/publish_docker_image.yml From ece2fd7625adc2f915c6a8bb4673962f1bf59a94 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 13 Dec 2022 17:44:54 +0000 Subject: [PATCH 30/33] fix: workflow name --- .github/workflows/test_docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_docker.yml b/.github/workflows/test_docker.yml index 15ae9917b..abe461465 100644 --- a/.github/workflows/test_docker.yml +++ b/.github/workflows/test_docker.yml @@ -1,4 +1,4 @@ -name: Publish docker image +name: Test docker build on: push: From 78850743f8bd116eb63eeea81971d435bce84799 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 13 Dec 2022 17:51:02 +0000 Subject: [PATCH 31/33] feat: use GH action for docker build --- .github/workflows/test_docker.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_docker.yml b/.github/workflows/test_docker.yml index abe461465..2cfa4de5c 100644 --- a/.github/workflows/test_docker.yml +++ b/.github/workflows/test_docker.yml @@ -9,7 +9,18 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Build docker image - run: ./docker/bin/build.sh + uses: docker/build-push-action@v3 + with: + context: . + file: ./Dockerfile + push: false + cache-from: type=gha + cache-to: type=gha,mode=max + - name: Build docker-compose images run: docker compose build From 6ebbaf3823eb4fc6cf42341b193d709612f88ba1 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 13 Dec 2022 17:55:42 +0000 Subject: [PATCH 32/33] fix: import user from builder User in production and development docker images is different. --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 152ab9980..96d21fa84 100644 --- a/Dockerfile +++ b/Dockerfile @@ -67,8 +67,8 @@ ARG TRACKER_API_PORT=1212 RUN apk --no-cache add ca-certificates ENV TZ=Etc/UTC ENV RUN_AS_USER=$RUN_AS_USER -COPY --from=base /etc/passwd /etc/passwd -COPY --from=base /etc/group /etc/group +COPY --from=builder /etc/passwd /etc/passwd +COPY --from=builder /etc/group /etc/group COPY --from=builder --chown=$RUN_AS_USER \ /app/target/x86_64-unknown-linux-musl/release/torrust-tracker \ /app/torrust-tracker From a94b1ca38e441cfa53094b38b2aac60e79dcb2ec Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 13 Dec 2022 18:03:58 +0000 Subject: [PATCH 33/33] docs: update docker README --- docker/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/README.md b/docker/README.md index 8c6b2a474..acfd417de 100644 --- a/docker/README.md +++ b/docker/README.md @@ -61,6 +61,8 @@ db_driver = "MySQL" db_path = "mysql://db_user:db_user_secret_password@mysql:3306/torrust_tracker" ``` +If you want to inject an environment variable into docker-compose you can use the file `.env`. There is a template `.env.local`. + Build and run it locally: ```s