diff --git a/.github/skills/dev/git-workflow/commit-changes/skill.md b/.github/skills/dev/git-workflow/commit-changes/skill.md index ad30e79c8..1208a6763 100644 --- a/.github/skills/dev/git-workflow/commit-changes/skill.md +++ b/.github/skills/dev/git-workflow/commit-changes/skill.md @@ -19,8 +19,8 @@ This skill guides you through the complete commit process for the Torrust Tracke # 2. Stage changes git add -# 3. Commit with conventional format -git commit -m "{type}: [#{issue}] {description}" +# 3. Commit with conventional format and GPG signature (MANDATORY) +git commit -S -m "{type}: [#{issue}] {description}" ``` ## Conventional Commit Format @@ -60,6 +60,16 @@ When working on a branch with an issue number, include it in your commit message | `ci` | CI/CD related changes | `ci: [#23] add workflow for testing provisioning` | | `perf` | Performance improvements | `perf: [#52] optimize container startup time` | +## GPG Commit Signing (MANDATORY) + +**All commits must be GPG signed.** Use the `-S` flag: + +```bash +git commit -S -m "your commit message" +``` + +Ensure GPG is configured (see Troubleshooting section if signing fails). + ## Pre-commit Verification (MANDATORY) **Before committing any changes**, you **MUST** run: @@ -207,8 +217,8 @@ vim src/main.rs # 4. Stage changes git add src/main.rs -# 5. Commit with conventional format -git commit -m "feat: [#42] add new CLI command" +# 5. Commit with conventional format and GPG signature (MANDATORY) +git commit -S -m "feat: [#42] add new CLI command" # 6. Push to remote git push origin 42-add-new-cli-command @@ -216,6 +226,21 @@ git push origin 42-add-new-cli-command ## Troubleshooting +### GPG Signing Fails + +**Problem**: `git commit -S` fails with "gpg failed to sign the data" + +**Solution**: + +1. Verify GPG is installed: `gpg --version` +2. List your GPG keys: `gpg --list-keys` +3. If no keys exist, create one: `gpg --gen-key` +4. Configure Git to use your GPG key: `git config --global user.signingkey ` +5. Test signing: `echo "test" | gpg --clearsign` +6. Retry commit: `git commit -S -m "your message"` + +If still failing, check that your GPG agent is running and has proper pinentry configured. + ### Pre-commit Script Fails **Problem**: One or more checks fail in `./scripts/pre-commit.sh` @@ -261,8 +286,9 @@ Note: This is only supported in local environments with proper LXD networking an ## Key Reminders -1. **Always run `./scripts/pre-commit.sh` before committing** - This is non-negotiable -2. **Use issue numbers consistently** - Follow the `[#{issue}]` format -3. **Be careful with hashtags** - Only use `#NUMBER` when referencing issues -4. **Keep commits atomic** - One logical change per commit -5. **Write descriptive messages** - Future you will thank present you +1. **Always sign commits with `-S`** - GPG signing is mandatory for audit trail +2. **Always run `./scripts/pre-commit.sh` before committing** - This is non-negotiable +3. **Use issue numbers consistently** - Follow the `[#{issue}]` format +4. **Be careful with hashtags** - Only use `#NUMBER` when referencing issues +5. **Keep commits atomic** - One logical change per commit +6. **Write descriptive messages** - Future you will thank present you diff --git a/.github/skills/dev/git-workflow/open-pull-request/skill.md b/.github/skills/dev/git-workflow/open-pull-request/skill.md new file mode 100644 index 000000000..4277f1b39 --- /dev/null +++ b/.github/skills/dev/git-workflow/open-pull-request/skill.md @@ -0,0 +1,100 @@ +--- +name: open-pull-request +description: Open a pull request from a feature branch using GitHub CLI (preferred) or GitHub MCP tools. Covers pre-flight checks, correct base/head configuration for fork workflows, title/body conventions, and post-creation validation. Use when asked to "open PR", "create pull request", or "submit branch for review". +metadata: + author: torrust + version: "1.0" +--- + +# Open a Pull Request + +This skill explains how to create a pull request for this repository in a repeatable way. + +## CLI vs MCP decision rule + +Use the tool that matches the loop: + +- **Inner loop (fast local branch work):** prefer GitHub CLI (`gh`) because it is fast and low overhead. +- **Outer loop (cross-system coordination):** use MCP when you need structured/authenticated access across shared systems. + +For opening a PR from the current local branch, prefer `gh pr create`. + +## Pre-flight checks + +Before opening a PR: + +- [ ] Working tree is clean (`git status`) +- [ ] Branch is pushed to remote +- [ ] Commits are signed (`git log --show-signature -n 1`) +- [ ] Required checks have been run (`./scripts/pre-commit.sh`) + +## Title and description convention + +Use conventional commit style in the PR title when possible, including issue reference. + +Examples: + +- `ci: [#448] add crate publish workflow` +- `docs: [#448] define release process` + +Include in PR body: + +- Summary of changes +- Files/workflows touched +- Validation performed +- Issue link (`Closes #`) + +## Option A (Preferred): GitHub CLI + +### Same-repo branch + +```bash +gh pr create \ + --repo torrust/torrust-tracker-deployer \ + --base main \ + --head \ + --title "" \ + --body "<body>" +``` + +### Fork branch (common maintainer flow) + +```bash +gh pr create \ + --repo torrust/torrust-tracker-deployer \ + --base main \ + --head <fork-owner>:<branch-name> \ + --title "<title>" \ + --body "<body>" +``` + +If successful, `gh` prints the PR URL. + +## Option B: GitHub MCP tools + +When MCP pull request management tools are available: + +1. Create branch remotely if needed +2. Open PR with base `main` and correct head branch +3. Capture and share resulting PR URL + +## Post-creation validation + +After PR creation: + +- [ ] Verify PR points to `torrust/torrust-tracker-deployer:main` +- [ ] Verify head branch is correct +- [ ] Confirm CI workflows started +- [ ] Confirm issue is linked in description + +## Troubleshooting + +- `fatal: ... does not appear to be a git repository`: push to correct remote (`git remote -v`) +- `A pull request already exists`: open existing PR URL instead of creating a new one +- Permission errors on upstream repo: create PR from your fork branch (`owner:branch`) + +## References + +- [`docs/contributing/commit-process.md`](../../../../../docs/contributing/commit-process.md) +- [`docs/contributing/pr-review-guide.md`](../../../../../docs/contributing/pr-review-guide.md) +- Existing branch skill: `.github/skills/dev/git-workflow/create-feature-branch/skill.md` diff --git a/.github/skills/dev/git-workflow/release-new-version/skill.md b/.github/skills/dev/git-workflow/release-new-version/skill.md new file mode 100644 index 000000000..410b225ee --- /dev/null +++ b/.github/skills/dev/git-workflow/release-new-version/skill.md @@ -0,0 +1,122 @@ +--- +name: release-new-version +description: Guide for releasing a new version of the deployer using the standard branch/tag workflow. Covers version bump, signed release commit, pushing main, creating signed tag, creating release branch, and verifying Docker + crate publication workflows. Use when asked to "release", "cut a version", "publish a new version", or "create release vX.Y.Z". +metadata: + author: torrust + version: "1.0" +--- + +# Release New Version + +This skill provides the canonical workflow to release a new version of the Torrust Tracker Deployer. + +Primary reference: [`docs/release-process.md`](../../../../../docs/release-process.md) + +## Release Order (Mandatory) + +Execute these steps in order: + +1. Update versions in manifests +2. Create release commit +3. Push release commit to `main` +4. Create and push signed tag `vX.Y.Z` +5. Create and push release branch `releases/vX.Y.Z` +6. Verify release workflows +7. Create GitHub release + +Do not reorder these steps. + +## Version and Naming Rules + +- Git tag: `vX.Y.Z` +- Release branch: `releases/vX.Y.Z` +- Docker release tag: `X.Y.Z` (no `v` prefix) +- Crate version: `X.Y.Z` + +## Pre-Flight Checklist + +Before starting: + +- [ ] Clean working tree (`git status`) +- [ ] Up to date with `origin/main` +- [ ] GitHub environment `dockerhub-torrust` configured +- [ ] GitHub environment `crates-io` configured with `CARGO_REGISTRY_TOKEN` scoped to + **all four** crate names (`torrust-tracker-deployer-types`, + `torrust-tracker-deployer-dependency-installer`, `torrust-tracker-deployer`, + `torrust-tracker-deployer-sdk`) — a token scoped to only one will cause 403 on the others +- [ ] Releaser has permissions for `main`, tags, and release branches +- [ ] All four `Cargo.toml` files have `description`, `license`, `repository`, `readme` +- [ ] Every internal path dependency also declares an explicit `version` constraint +- [ ] `Cargo.lock` is committed and up to date + +## Commands + +### 1) Update versions + +Update `version` in all four manifests, and update the `version` constraint on every +internal path dependency in each file: + +- `Cargo.toml` +- `packages/deployer-types/Cargo.toml` +- `packages/dependency-installer/Cargo.toml` +- `packages/sdk/Cargo.toml` + +### 2) Commit and push + +```bash +git add Cargo.toml \ + packages/deployer-types/Cargo.toml \ + packages/dependency-installer/Cargo.toml \ + packages/sdk/Cargo.toml \ + Cargo.lock +git commit -S -m "release: version vX.Y.Z" +git push origin main +``` + +### 3) Tag and release branch + +```bash +git tag -s -a vX.Y.Z -m "Release vX.Y.Z" +git push origin vX.Y.Z + +git checkout -b releases/vX.Y.Z +git push origin releases/vX.Y.Z +``` + +### 4) Verify workflows + +- **Container** workflow: publishes Docker image from release branch +- **Publish Crate** workflow: publishes all four crates in dependency order: + 1. `torrust-tracker-deployer-types` + 2. `torrust-tracker-deployer-dependency-installer` + 3. `torrust-tracker-deployer` + 4. `torrust-tracker-deployer-sdk` + + Each crate's dry-run runs only after its prerequisites are indexed on crates.io. + Do not attempt to publish out of order. + +Workflow files: + +- `.github/workflows/container.yaml` +- `.github/workflows/publish-crate.yaml` + +### 5) Create GitHub release + +Create the release manually from tag `vX.Y.Z` after both workflows pass. + +## Failure Handling + +- Docker failed, crate not started: fix Docker workflow and rerun on same release branch +- Docker passed, crate failed before upload: fix issue and rerun crate workflow on same release branch +- Crate already published: do not republish same version; cut a patch release +- Ref already exists (tag/branch): stop and investigate partial release state before continuing + +## Quick Validation + +```bash +# Verify refs exist remotely +git ls-remote --tags origin vX.Y.Z +git ls-remote --heads origin releases/vX.Y.Z +``` + +For full operational guidance, troubleshooting, and rollback/yank policy, use [`docs/release-process.md`](../../../../../docs/release-process.md). diff --git a/.github/skills/dev/maintenance/update-dependencies/skill.md b/.github/skills/dev/maintenance/update-dependencies/skill.md new file mode 100644 index 000000000..3b2688cb6 --- /dev/null +++ b/.github/skills/dev/maintenance/update-dependencies/skill.md @@ -0,0 +1,317 @@ +--- +name: update-dependencies +description: Guide for updating project dependencies using the update-dependencies.sh automation script. Automates the cargo update workflow including branch creation, commit, push, and optional PR creation. Use when updating dependencies, running cargo update, or automating the dependency lifecycle. Triggers on "update dependencies", "cargo update", "update deps", "bump dependencies", or "run dependency update". +metadata: + author: torrust + version: "1.0" +--- + +# Updating Dependencies + +This skill guides you through updating project dependencies using the `scripts/update-dependencies.sh` automation script, which handles the complete dependency update workflow from branch creation to PR submission. + +## Quick Reference + +```bash +# Simple update (no issue) +./scripts/update-dependencies.sh \ + --branch update-dependencies \ + --push-remote {fork-remote} \ + --create-pr + +# Complex update with issue +./scripts/update-dependencies.sh \ + --branch {issue-number}-update-dependencies \ + --push-remote {fork-remote} \ + --create-pr +``` + +## Workflow Overview + +The `update-dependencies.sh` script automates the following steps: + +1. Ensures a clean working tree (no uncommitted changes) +2. Fetches and fast-forwards the base branch from upstream +3. Creates a feature branch with the specified name +4. Runs `cargo update` and captures the full output +5. Exits early if no `Cargo.lock` changes are produced +6. Optionally runs `./scripts/pre-commit.sh` (default: enabled) +7. **Commits** the `Cargo.lock` changes with full `cargo update` output in commit body +8. **Pushes** the branch to the fork remote +9. **Creates a PR** on GitHub (optional, default: disabled) + +## Usage + +### Basic Invocation + +```bash +./scripts/update-dependencies.sh \ + --branch update-dependencies \ + --push-remote {fork-remote} +``` + +### With PR Creation + +```bash +./scripts/update-dependencies.sh \ + --branch update-dependencies \ + --push-remote {fork-remote} \ + --create-pr +``` + +### Signing Commits + +Commits are **always signed** with `git commit -S` (GPG signing is mandatory): + +```bash +./scripts/update-dependencies.sh \ + --branch update-dependencies \ + --push-remote {fork-remote} +``` + +Unsigned commits are not permitted in this workflow. + +### Skipping Pre-Commit Checks + +Pre-commit checks are run by default. Skip them if needed (not recommended): + +```bash +./scripts/update-dependencies.sh \ + --branch update-dependencies \ + --push-remote {fork-remote} \ + --skip-pre-commit +``` + +### Deleting Existing Branch + +If a branch with the same name already exists, delete it first: + +```bash +./scripts/update-dependencies.sh \ + --branch update-dependencies \ + --push-remote {fork-remote} \ + --delete-existing-branch +``` + +## All Options + +```bash +./scripts/update-dependencies.sh --help +``` + +| Option | Default | Description | +| -------------------------- | ---------------------------- | ------------------------------------------------------ | +| `--branch` | **required** | Feature branch name (e.g., `123-update-deps`) | +| `--base-branch` | `main` | Target branch for merge base | +| `--base-remote` | Auto-detected | Remote for base branch (prefers `torrust` → `origin`) | +| `--push-remote` | Auto-detected | Remote to push the branch to | +| `--repo` | Auto-detected | GitHub repo slug (owner/repo) | +| `--commit-title` | `chore: update dependencies` | First line of commit message | +| `--pr-title` | `chore: update dependencies` | Pull request title | +| `--skip-pre-commit` | disabled | Skip `./scripts/pre-commit.sh` after update | +| `--create-pr` | disabled | Create a PR after pushing | +| `--delete-existing-branch` | disabled | Delete the branch locally and remotely before starting | +| `--help` | — | Show full usage and all options | + +## When to Create an Issue + +- **Simple updates**: Just running `cargo update` with no special handling → **No issue needed**, use branch name `update-dependencies` +- **Complex updates**: Dependency updates requiring additional changes (migrations, API updates, refactoring) → **Create an issue** and use branch name `{issue-number}-update-dependencies` + +This keeps the issue tracker focused on substantial work while allowing for routine maintenance tasks without issue clutter. + +## Step-by-Step Example (Simple Update) + +### 1. Run the Script (No Issue) + +```bash +./scripts/update-dependencies.sh \ + --branch update-dependencies \ + --push-remote {fork-remote} \ + --create-pr +``` + +## Step-by-Step Example (Complex Update with Issue) + +### 1. Create an Issue + +```bash +gh issue create \ + --title "chore: update dependencies and migrate to new API" \ + --body "Update dependencies and handle breaking changes in async library." +``` + +Note the issue number (e.g., `#456`). + +### 2. Run the Script + +```bash +./scripts/update-dependencies.sh \ + --branch {issue-number}-update-dependencies \ + --push-remote {fork-remote} \ + --create-pr +``` + +### 3. Observe the Output + +The script will: + +- Fetch the latest `main` from `torrust` remote +- Create branch `456-update-dependencies` +- Run `cargo update` and show the output +- If dependencies changed: run pre-commit, commit with full output, push, create PR +- If no changes: clean up branch and exit (no-op, which is fine) + +### 4. Review and Merge + +- Visit the PR created by the script (URL printed to stdout) +- Review the `cargo update` output in the commit body +- Let CI checks pass +- Merge when ready + +## Commit Message Format + +The script generates commit messages in this format: + +```text +chore: update dependencies + +[Full cargo update output] + +- run `cargo update` +- commit the resulting `Cargo.lock` changes +``` + +The full `cargo update` output is included in the commit body for traceability. Example: + +```text + Updating crates.io index + Locking 14 packages to latest compatible versions + Updating hyper-rustls from 0.27.7 to 0.27.8 + ... +``` + +## Pre-Commit Checks + +Before committing, the script optionally runs `./scripts/pre-commit.sh` (enabled by default), which verifies: + +1. **Unused dependencies**: `cargo machete` +2. **All linters**: markdown, YAML, TOML, spelling, Clippy, rustfmt, shellcheck +3. **Tests**: `cargo test` +4. **Documentation**: `cargo doc --no-deps` +5. **E2E infrastructure tests**: provisioning and destruction +6. **E2E deployment tests**: full workflow + +If pre-commit fails, the script exits before committing. Fix issues and run the script again. + +### Skip Pre-Commit (Not Recommended) + +```bash +./scripts/update-dependencies.sh \ + --branch {issue-number}-update-dependencies \ + --push-remote {fork-remote} \ + --skip-pre-commit +``` + +## When Dependencies Don't Change + +If `cargo update` produces no changes to `Cargo.lock`, the script will: + +1. Print: `No dependency changes were produced by cargo update` +2. Clean up the feature branch (delete it locally) +3. Exit cleanly with code 0 (success) + +This is **not an error** — it means all dependencies are at their latest compatible versions. + +## Troubleshooting + +### Error: Working tree has unstaged changes + +The script requires a clean working tree. Stage or remove all uncommitted changes: + +```bash +git status # See what's uncommitted +git add <files> # Stage changes +git commit -m "..." # Or commit them +git stash # Or stash them +``` + +Then retry the script. + +### Error: Branch already exists + +The branch exists either locally or on the remote: + +```bash +# Option 1: Use a different branch name +./scripts/update-dependencies.sh \ + --branch {issue-number}-update-dependencies-retry \ + --push-remote {fork-remote} + +# Option 2: Delete the existing branch first +./scripts/update-dependencies.sh \ + --branch {issue-number}-update-dependencies \ + --push-remote {fork-remote} \ + --delete-existing-branch +``` + +### Commit Signing Failures + +GPG signing is mandatory. If commit signing fails: + +```bash +# Check GPG setup +gpg --list-keys + +# Fix GPG configuration, then retry +gh auth logout && gh auth login # Re-authenticate if needed +``` + +Ensure GPG is properly configured before running the script. Unsigned commits are not permitted. + +### PR Creation Fails + +Ensure `gh` CLI is authenticated: + +```bash +gh auth status # Check authentication +gh auth login # Log in if needed +``` + +Then retry with `--create-pr`. + +## After Merge + +Once the PR is merged to `main`: + +1. The updated `Cargo.lock` is now in the base branch +2. All future branches will build with the new dependencies +3. Repeat the workflow for the next update cycle (typically monthly or as needed) + +## Integration with CI + +When the PR is created, GitHub Actions will automatically run: + +- All linters (stable + nightly) +- Full test suite +- E2E infrastructure tests +- E2E deployment tests +- Coverage analysis + +All checks must pass before merging. + +## Best Practices + +- **Run regularly**: Update dependencies monthly or quarterly +- **Always sign commits**: Use GPG signing (default behavior) for audit trails +- **Review the output**: Check the commit message to see which packages were updated +- **Run pre-commit**: Never skip this step (use default behavior) +- **Use issue numbers**: Prefix branch names with issue numbers (`--branch {issue}-update-dependencies`) +- **One branch per run**: Each run creates one branch and optionally one PR +- **Wait for CI**: Never merge until all checks pass + +## See Also + +- [Committing Changes](../../git-workflow/commit-changes/skill.md) — General commit workflow +- [Creating Feature Branches](../../git-workflow/create-feature-branch/skill.md) — Branch naming conventions +- [Pre-Commit Checks](../../git-workflow/run-pre-commit-checks/skill.md) — Understanding the 6-step verification process diff --git a/.github/skills/dev/testing/run-local-e2e-test/skill.md b/.github/skills/dev/testing/run-local-e2e-test/skill.md index aed47156d..af0329e63 100644 --- a/.github/skills/dev/testing/run-local-e2e-test/skill.md +++ b/.github/skills/dev/testing/run-local-e2e-test/skill.md @@ -21,13 +21,13 @@ This skill walks you through a complete manual end-to-end test of the deployer u ```bash # Verify all required tools are installed -cargo run -p torrust-dependency-installer --bin dependency-installer -- check +cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer -- check # Install missing tools (LXD, OpenTofu, Ansible, Docker) -cargo run -p torrust-dependency-installer --bin dependency-installer -- install +cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer -- install ``` -> **Note**: `cargo run --bin dependency-installer` does not work from the workspace root because the binary lives in a sub-package. Always use `-p torrust-dependency-installer`. +> **Note**: `cargo run --bin dependency-installer` does not work from the workspace root because the binary lives in a sub-package. Always use `-p torrust-tracker-deployer-dependency-installer`. ## Complete Workflow diff --git a/.github/skills/usage/operations/check-system-dependencies/skill.md b/.github/skills/usage/operations/check-system-dependencies/skill.md index a8f8f9f29..e89f8ed85 100644 --- a/.github/skills/usage/operations/check-system-dependencies/skill.md +++ b/.github/skills/usage/operations/check-system-dependencies/skill.md @@ -14,16 +14,16 @@ Use the built-in `dependency-installer` package to verify all required tools are ```bash # Check all dependencies -cargo run -p torrust-dependency-installer --bin dependency-installer check +cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer check # Check a specific dependency -cargo run -p torrust-dependency-installer --bin dependency-installer check --dependency opentofu +cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer check --dependency opentofu # List all dependencies with status -cargo run -p torrust-dependency-installer --bin dependency-installer list +cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer list # Install all missing dependencies -cargo run -p torrust-dependency-installer --bin dependency-installer install +cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer install ``` ## Required Dependencies @@ -60,7 +60,7 @@ ERROR ... dependency is not installed dependency="opentofu" - In CI/CD pipelines use `--log-level off` to suppress output and rely on exit code only: ```bash - cargo run -p torrust-dependency-installer --bin dependency-installer check --log-level off + cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer check --log-level off ``` - To install missing tools automatically, use the `install` subcommand (requires system package manager access) diff --git a/.github/skills/usage/operations/install-system-dependencies/skill.md b/.github/skills/usage/operations/install-system-dependencies/skill.md index 2a3791b7f..ddb5c966b 100644 --- a/.github/skills/usage/operations/install-system-dependencies/skill.md +++ b/.github/skills/usage/operations/install-system-dependencies/skill.md @@ -14,26 +14,26 @@ Use the built-in `dependency-installer` package to install all tools required to ```bash # 1. Check what is already installed -cargo run -p torrust-dependency-installer --bin dependency-installer check +cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer check # 2. Install everything missing in one command -cargo run -p torrust-dependency-installer --bin dependency-installer install +cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer install # 3. Verify all dependencies are now present -cargo run -p torrust-dependency-installer --bin dependency-installer check +cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer check ``` ## Commands ```bash # Install all missing dependencies -cargo run -p torrust-dependency-installer --bin dependency-installer install +cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer install # Install a specific dependency -cargo run -p torrust-dependency-installer --bin dependency-installer install --dependency opentofu +cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer install --dependency opentofu # Install with verbose output (shows download/install steps) -cargo run -p torrust-dependency-installer --bin dependency-installer install --verbose +cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer install --verbose ``` ## Dependencies Installed diff --git a/.github/workflows/cargo-security-audit.yml b/.github/workflows/cargo-security-audit.yml new file mode 100644 index 000000000..fb2dfe786 --- /dev/null +++ b/.github/workflows/cargo-security-audit.yml @@ -0,0 +1,44 @@ +name: Cargo Security Audit + +on: + push: + branches: [main, develop] + paths: + - "**/Cargo.toml" + - "**/Cargo.lock" + - ".github/workflows/cargo-security-audit.yml" + + pull_request: + paths: + - "**/Cargo.toml" + - "**/Cargo.lock" + - ".github/workflows/cargo-security-audit.yml" + + # Scheduled scans are important because new RustSec advisories can appear + # even when the codebase and lockfile do not change. + schedule: + - cron: "0 6 * * *" # Daily at 6 AM UTC + + workflow_dispatch: + +jobs: + cargo-audit: + name: Audit Rust Dependencies + runs-on: ubuntu-latest + timeout-minutes: 10 + + # cspell:ignore rustsec + # rustsec/audit-check can create issues and checks on scheduled runs. + permissions: + contents: read + checks: write + issues: write + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Run cargo audit via RustSec action + uses: rustsec/audit-check@v2.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..c1a60f39c --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,91 @@ +name: "CodeQL Advanced" + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + schedule: + - cron: "24 23 * * 5" + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + include: + - language: actions + build-mode: none + - language: rust + build-mode: none + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Add any setup steps before running the `github/codeql-action/init` action. + # This includes steps like installing compilers or runtimes (`actions/setup-node` + # or others). This is typically only required for manual builds. + # - name: Setup runtime (example) + # uses: actions/setup-example@v1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: + # https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - name: Run manual build steps + if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/container.yaml b/.github/workflows/container.yaml index 1f76a66b2..83704aabe 100644 --- a/.github/workflows/container.yaml +++ b/.github/workflows/container.yaml @@ -4,13 +4,15 @@ # Following patterns from torrust/torrust-tracker container.yaml workflow. # # Triggers: -# - Push to main/develop branches +# - Push to main/develop/releases/** branches # - Pull requests to main/develop # - Manual dispatch # # Publishing: -# - Images are pushed to Docker Hub on push to main/develop (not PRs) -# - Requires Docker Hub credentials in repository secrets +# - Images are pushed to Docker Hub on push to main/develop/release branches (not PRs) +# - Release branches (releases/vX.Y.Z or releases/vX.Y.Z-pre.N) publish versioned Docker tags +# - Release Docker tags use bare semver without the v prefix +# - Requires Docker Hub credentials in the dockerhub-torrust GitHub Environment name: Container @@ -19,12 +21,7 @@ on: branches: - "develop" - "main" - paths: - - "src/**" - - "Cargo.toml" - - "Cargo.lock" - - "docker/deployer/**" - - ".github/workflows/container.yaml" + - "releases/**/*" pull_request: branches: @@ -100,6 +97,7 @@ jobs: outputs: continue: ${{ steps.check.outputs.continue }} type: ${{ steps.check.outputs.type }} + version: ${{ steps.check.outputs.version }} steps: - name: Check Context @@ -108,10 +106,15 @@ jobs: if [[ "${{ github.repository }}" == "torrust/torrust-tracker-deployer" ]]; then if [[ "${{ github.event_name }}" == "push" ]]; then if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then - echo "type=production" >> $GITHUB_OUTPUT + echo "type=main" >> $GITHUB_OUTPUT echo "continue=true" >> $GITHUB_OUTPUT elif [[ "${{ github.ref }}" == "refs/heads/develop" ]]; then - echo "type=development" >> $GITHUB_OUTPUT + echo "type=develop" >> $GITHUB_OUTPUT + echo "continue=true" >> $GITHUB_OUTPUT + elif [[ $(echo "${{ github.ref }}" | grep -P '^refs/heads/releases/v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[a-zA-Z0-9][a-zA-Z0-9.-]*)?$') ]]; then + version=$(echo "${{ github.ref }}" | sed -n -E 's/^refs\/heads\/releases\///p') + echo "version=$version" >> $GITHUB_OUTPUT + echo "type=release" >> $GITHUB_OUTPUT echo "continue=true" >> $GITHUB_OUTPUT fi fi @@ -122,11 +125,11 @@ jobs: echo "continue=false" >> $GITHUB_OUTPUT fi - publish_development: - name: Publish (Development) + publish: + name: Publish Image environment: dockerhub-torrust needs: context - if: needs.context.outputs.continue == 'true' && needs.context.outputs.type == 'development' + if: needs.context.outputs.continue == 'true' runs-on: ubuntu-latest timeout-minutes: 30 @@ -134,48 +137,34 @@ jobs: - name: Checkout uses: actions/checkout@v5 - - name: Docker Meta - id: meta - uses: docker/metadata-action@v5 - with: - images: | - ${{ env.DOCKER_HUB_USERNAME }}/tracker-deployer - tags: | - type=ref,event=branch - type=sha,prefix=dev- - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ env.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - - - name: Setup Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Build and Push - uses: docker/build-push-action@v6 - with: - context: . - file: ./docker/deployer/Dockerfile - target: release - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - publish_production: - name: Publish (Production) - environment: dockerhub-torrust - needs: context - if: needs.context.outputs.continue == 'true' && needs.context.outputs.type == 'production' - runs-on: ubuntu-latest - timeout-minutes: 30 - - steps: - - name: Checkout - uses: actions/checkout@v5 + - name: Configure Docker Tag Strategy + id: tag_config + run: | + if [[ "${{ needs.context.outputs.type }}" == "develop" ]]; then + { + echo "tags<<EOF" + echo "type=ref,event=branch" + echo "type=sha,prefix=dev-" + echo "EOF" + } >> "$GITHUB_OUTPUT" + elif [[ "${{ needs.context.outputs.type }}" == "main" ]]; then + { + echo "tags<<EOF" + echo "type=raw,value=latest" + echo "type=ref,event=branch" + echo "type=sha" + echo "EOF" + } >> "$GITHUB_OUTPUT" + elif [[ "${{ needs.context.outputs.type }}" == "release" ]]; then + { + echo "tags<<EOF" + echo "type=semver,value=${{ needs.context.outputs.version }},pattern={{version}}" + echo "EOF" + } >> "$GITHUB_OUTPUT" + else + echo "Unsupported publish type: ${{ needs.context.outputs.type }}" >&2 + exit 1 + fi - name: Docker Meta id: meta @@ -183,10 +172,7 @@ jobs: with: images: | ${{ env.DOCKER_HUB_USERNAME }}/tracker-deployer - tags: | - type=raw,value=latest - type=ref,event=branch - type=sha + tags: ${{ steps.tag_config.outputs.tags }} - name: Login to Docker Hub uses: docker/login-action@v3 @@ -208,3 +194,10 @@ jobs: labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max + + - name: Inspect Published Image + if: needs.context.outputs.type == 'release' + run: | + version=$(echo "${{ needs.context.outputs.version }}" | sed 's/^v//') + docker pull ${{ env.DOCKER_HUB_USERNAME }}/tracker-deployer:"$version" + docker image inspect ${{ env.DOCKER_HUB_USERNAME }}/tracker-deployer:"$version" diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 42e20526b..adb340182 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -35,7 +35,7 @@ jobs: - name: Build dependency-installer binary run: | - cargo build --release -p torrust-dependency-installer --bin dependency-installer + cargo build --release -p torrust-tracker-deployer-dependency-installer --bin dependency-installer - name: Install all development dependencies run: | diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 359aac390..ed75d1fa2 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -38,7 +38,7 @@ jobs: name: Build All Binaries run: | cargo build --bins - cargo build -p torrust-dependency-installer --bin dependency-installer + cargo build -p torrust-tracker-deployer-dependency-installer --bin dependency-installer - id: coverage-text name: Generate Text Coverage Summary diff --git a/.github/workflows/docker-security-scan.yml b/.github/workflows/docker-security-scan.yml index 4eaf5b684..c592eae56 100644 --- a/.github/workflows/docker-security-scan.yml +++ b/.github/workflows/docker-security-scan.yml @@ -6,16 +6,18 @@ on: paths: - "docker/**" - "templates/docker-compose/**" + - "src/**" - ".github/workflows/docker-security-scan.yml" pull_request: paths: - "docker/**" - "templates/docker-compose/**" + - "src/**" - ".github/workflows/docker-security-scan.yml" # Scheduled scans are important because new CVEs appear - # even if the code or images didn’t change + # even if the code or images didn't change schedule: - cron: "0 6 * * *" # Daily at 6 AM UTC @@ -60,7 +62,7 @@ jobs: ${{ matrix.image.context }} # Human-readable output in logs - # This NEVER fails the job; it’s only for visibility + # This NEVER fails the job; it's only for visibility - name: Display vulnerabilities (table format) uses: aquasecurity/trivy-action@0.35.0 with: @@ -93,24 +95,113 @@ jobs: path: trivy-${{ matrix.image.name }}.sarif retention-days: 30 + extract-images: + name: Extract Third-Party Docker Images from Source + runs-on: ubuntu-latest + timeout-minutes: 10 + outputs: + # JSON array of Docker image references for use in scan matrix + # Example: ["torrust/tracker:develop","mysql:8.4","prom/prometheus:v3.11.2","grafana/grafana:13.0.0","caddy:2.11.2"] + images: ${{ steps.extract.outputs.images }} + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + + - name: Build deployer CLI + run: cargo build --release + + # Creates a minimal environment config with all optional services + # enabled so that all third-party Docker images appear in the output. + # - MySQL: enables mysql image in docker_images output + # - Prometheus: enables prometheus image in docker_images output + # - Grafana: enables grafana image in docker_images output + # Uses fixture SSH keys (already committed to the repository). + - name: Create environment config for image extraction + run: | + cat > /tmp/ci-images-env.json <<EOF + { + "environment": { "name": "ci-images" }, + "ssh_credentials": { + "private_key_path": "$GITHUB_WORKSPACE/fixtures/testing_rsa", + "public_key_path": "$GITHUB_WORKSPACE/fixtures/testing_rsa.pub" + }, + "provider": { + "provider": "lxd", + "profile_name": "ci-profile" + }, + "tracker": { + "core": { + "database": { + "driver": "mysql", + "host": "mysql", + "port": 3306, + "database_name": "torrust_tracker", + "username": "tracker_user", + "password": "tracker_password" + }, + "private": false + }, + "udp_trackers": [{ "bind_address": "0.0.0.0:6969" }], + "http_trackers": [{ "bind_address": "0.0.0.0:7070" }], + "http_api": { "bind_address": "0.0.0.0:1212", "admin_token": "ci-token" }, + "health_check_api": { "bind_address": "127.0.0.1:1313" } + }, + "prometheus": { "scrape_interval_in_secs": 15 }, + "grafana": { "admin_user": "admin", "admin_password": "admin" } + } + EOF + + - name: Create minimal environment (no infrastructure provisioned) + run: | + ./target/release/torrust-tracker-deployer \ + --working-dir /tmp/ci-workspace \ + create environment \ + --env-file /tmp/ci-images-env.json + + # Extract Docker images from show command JSON output. + # The show command lists all configured service images in docker_images. + # Caddy is always in the docker-compose stack but is not tracked as + # a domain service, so it is appended to the list manually. + - name: Extract Docker images + id: extract + run: | + show_output=$(./target/release/torrust-tracker-deployer \ + --working-dir /tmp/ci-workspace \ + show ci-images) + + images=$(echo "$show_output" | \ + jq -c '[ + .docker_images.tracker, + .docker_images.mysql, + .docker_images.prometheus, + .docker_images.grafana + ] | map(select(. != null)) + ["caddy:2.11.2"]') + + echo "Detected images: $images" + echo "images=$images" >> "$GITHUB_OUTPUT" + scan-third-party-images: name: Scan Third-Party Docker Images + needs: extract-images runs-on: ubuntu-latest timeout-minutes: 15 permissions: contents: read + security-events: write strategy: fail-fast: false matrix: - # These must match docker-compose templates - # in templates/docker-compose/docker-compose.yml.tera - image: - - torrust/tracker:develop - - mysql:8.0 - - grafana/grafana:11.4.0 - - prom/prometheus:v3.0.1 - - caddy:2.10 + # Dynamic image list extracted from the deployer CLI at build time. + # Images come from domain config constants — no manual maintenance needed. + image: ${{ fromJson(needs.extract-images.outputs.images) }} steps: - name: Display vulnerabilities (table format) @@ -147,14 +238,23 @@ jobs: path: trivy.sarif retention-days: 30 + # Use the supported CodeQL upload action so category tracking works + # for dynamic third-party image configurations. + - name: Upload third-party SARIF + if: always() + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: trivy.sarif + category: docker-third-party-${{ steps.sanitize.outputs.name }} + continue-on-error: true + upload-sarif-results: name: Upload SARIF Results to GitHub Security runs-on: ubuntu-latest needs: - scan-project-images - - scan-third-party-images - # Always run so we don’t lose security visibility + # Always run so we don't lose security visibility if: always() permissions: @@ -164,11 +264,10 @@ jobs: - name: Download all SARIF artifacts uses: actions/download-artifact@v7 with: - pattern: sarif-*-${{ github.run_id }} + pattern: sarif-project-*-${{ github.run_id }} # Upload each SARIF file with CodeQL Action using unique categories. # The category parameter enables proper alert tracking per image. - # Must use CodeQL Action (not gh API) - API doesn't support category field. # # VIEWING RESULTS: # - For pull requests: /security/code-scanning?query=pr:NUMBER+is:open @@ -191,43 +290,3 @@ jobs: sarif_file: sarif-project-ssh-server-${{ github.run_id }}/trivy-ssh-server.sarif category: docker-project-ssh-server continue-on-error: true - - - name: Upload third-party mysql SARIF - if: always() - uses: github/codeql-action/upload-sarif@v4 - with: - sarif_file: sarif-third-party-mysql-8.0-${{ github.run_id }}/trivy.sarif - category: docker-third-party-mysql-8.0 - continue-on-error: true - - - name: Upload third-party tracker SARIF - if: always() - uses: github/codeql-action/upload-sarif@v4 - with: - sarif_file: sarif-third-party-torrust-tracker-develop-${{ github.run_id }}/trivy.sarif - category: docker-third-party-torrust-tracker-develop - continue-on-error: true - - - name: Upload third-party grafana SARIF - if: always() - uses: github/codeql-action/upload-sarif@v4 - with: - sarif_file: sarif-third-party-grafana-grafana-11.4.0-${{ github.run_id }}/trivy.sarif - category: docker-third-party-grafana-grafana-11.4.0 - continue-on-error: true - - - name: Upload third-party prometheus SARIF - if: always() - uses: github/codeql-action/upload-sarif@v4 - with: - sarif_file: sarif-third-party-prom-prometheus-v3.0.1-${{ github.run_id }}/trivy.sarif - category: docker-third-party-prom-prometheus-v3.0.1 - continue-on-error: true - - - name: Upload third-party caddy SARIF - if: always() - uses: github/codeql-action/upload-sarif@v4 - with: - sarif_file: sarif-third-party-caddy-2.10-${{ github.run_id }}/trivy.sarif - category: docker-third-party-caddy-2.10 - continue-on-error: true diff --git a/.github/workflows/publish-crate.yaml b/.github/workflows/publish-crate.yaml new file mode 100644 index 000000000..eb736ebe5 --- /dev/null +++ b/.github/workflows/publish-crate.yaml @@ -0,0 +1,232 @@ +# Crate publication workflow for Torrust Tracker Deployer +# +# Publishes all four workspace crates in dependency order when a release branch is pushed: +# 1. torrust-tracker-deployer-types (no internal deps) +# 2. torrust-tracker-deployer-dependency-installer (no internal deps) +# 3. torrust-tracker-deployer (depends on 1 and 2) +# 4. torrust-tracker-deployer-sdk (depends on 1 and 3) +# +# Trigger branch format: releases/vX.Y.Z or releases/vX.Y.Z-pre.N (pre-release) + +name: Publish Crate + +on: + push: + branches: + - "releases/**/*" + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + DEPLOYER_TYPES_CRATE: torrust-tracker-deployer-types + DEPENDENCY_INSTALLER_CRATE: torrust-tracker-deployer-dependency-installer + MAIN_CRATE: torrust-tracker-deployer + SDK_CRATE: torrust-tracker-deployer-sdk + +jobs: + publish_all: + name: Publish All Crates + environment: crates-io + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - name: Checkout Repository + uses: actions/checkout@v5 + + - name: Setup Rust Toolchain + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + + - name: Enable Workflow Cache + uses: Swatinem/rust-cache@v2 + + - name: Extract Release Version + id: release + run: | + if [[ "${{ github.ref_name }}" =~ ^releases/v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[a-zA-Z0-9][a-zA-Z0-9.-]*)?$ ]]; then + version="${GITHUB_REF_NAME#releases/v}" + echo "version=$version" >> "$GITHUB_OUTPUT" + else + echo "Invalid release branch name: ${{ github.ref_name }}" >&2 + echo "Expected format: releases/vX.Y.Z or releases/vX.Y.Z-pre.N" >&2 + exit 1 + fi + + - name: Verify Release Version Matches All Cargo Manifests + run: | + release_version="${{ steps.release.outputs.version }}" + + root_version=$(grep '^version = ' Cargo.toml | head -1 | sed -E 's/version = "([^"]+)"/\1/') + sdk_version=$(grep '^version = ' packages/sdk/Cargo.toml | head -1 | sed -E 's/version = "([^"]+)"/\1/') + types_version=$(grep '^version = ' packages/deployer-types/Cargo.toml | head -1 | sed -E 's/version = "([^"]+)"/\1/') + installer_version=$(grep '^version = ' packages/dependency-installer/Cargo.toml | head -1 | sed -E 's/version = "([^"]+)"/\1/') + + for pair in "Root:$root_version" "SDK:$sdk_version" "DeployerTypes:$types_version" "DependencyInstaller:$installer_version"; do + label="${pair%%:*}" + version="${pair#*:}" + if [[ "$version" != "$release_version" ]]; then + echo "$label Cargo.toml version mismatch: $version != $release_version" >&2 + exit 1 + fi + done + + - name: Verify Crates.io Metadata for All Crates + run: | + for manifest in Cargo.toml packages/sdk/Cargo.toml packages/deployer-types/Cargo.toml packages/dependency-installer/Cargo.toml; do + for field in description license repository readme; do + if ! grep -q "^$field = " "$manifest"; then + echo "Missing required field '$field' in $manifest" >&2 + exit 1 + fi + done + done + + - name: Run All Tests + run: cargo test + + - name: Inspect Packaged Files + run: | + for crate in \ + "${{ env.DEPLOYER_TYPES_CRATE }}" \ + "${{ env.DEPENDENCY_INSTALLER_CRATE }}" \ + "${{ env.MAIN_CRATE }}" \ + "${{ env.SDK_CRATE }}"; do + echo "=== Packaged files for $crate ===" + cargo package --list -p "$crate" + done + + - name: Dry Run Publish torrust-tracker-deployer-types + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish --dry-run -p ${{ env.DEPLOYER_TYPES_CRATE }} + + - name: Check If Versions Already Published + run: | + release_version="${{ steps.release.outputs.version }}" + for crate in \ + "${{ env.DEPLOYER_TYPES_CRATE }}" \ + "${{ env.DEPENDENCY_INSTALLER_CRATE }}" \ + "${{ env.MAIN_CRATE }}" \ + "${{ env.SDK_CRATE }}"; do + set +e + http_status=$(curl -s -o /tmp/crate-check.json -w "%{http_code}" "https://crates.io/api/v1/crates/$crate/$release_version") + set -e + if [[ "$http_status" == "200" ]]; then + echo "Already published: $crate $release_version — do not republish, cut a patch release instead." >&2 + exit 1 + fi + done + + - name: Publish torrust-tracker-deployer-types + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish -p ${{ env.DEPLOYER_TYPES_CRATE }} + + - name: Wait for torrust-tracker-deployer-types to Be Indexed + run: | + release_version="${{ steps.release.outputs.version }}" + for attempt in $(seq 1 10); do + status=$(curl -s -o /tmp/crate-check.json -w "%{http_code}" "https://crates.io/api/v1/crates/${{ env.DEPLOYER_TYPES_CRATE }}/$release_version") + if [[ "$status" == "200" ]]; then + echo "${{ env.DEPLOYER_TYPES_CRATE }} $release_version is indexed on crates.io" + break + fi + echo "Waiting for crates.io index (attempt $attempt/10)..." + sleep 20 + done + + - name: Dry Run Publish torrust-tracker-deployer-dependency-installer + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish --dry-run -p ${{ env.DEPENDENCY_INSTALLER_CRATE }} + + - name: Publish torrust-tracker-deployer-dependency-installer + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish -p ${{ env.DEPENDENCY_INSTALLER_CRATE }} + + - name: Wait for torrust-tracker-deployer-dependency-installer to Be Indexed + run: | + release_version="${{ steps.release.outputs.version }}" + for attempt in $(seq 1 10); do + status=$(curl -s -o /tmp/crate-check.json -w "%{http_code}" "https://crates.io/api/v1/crates/${{ env.DEPENDENCY_INSTALLER_CRATE }}/$release_version") + if [[ "$status" == "200" ]]; then + echo "${{ env.DEPENDENCY_INSTALLER_CRATE }} $release_version is indexed on crates.io" + break + fi + echo "Waiting for crates.io index update (attempt $attempt/10)..." + sleep 20 + done + + - name: Dry Run Publish torrust-tracker-deployer + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish --dry-run -p ${{ env.MAIN_CRATE }} + + - name: Publish torrust-tracker-deployer + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish -p ${{ env.MAIN_CRATE }} + + - name: Wait for torrust-tracker-deployer to Be Indexed + run: | + release_version="${{ steps.release.outputs.version }}" + for attempt in $(seq 1 10); do + status=$(curl -s -o /tmp/crate-check.json -w "%{http_code}" "https://crates.io/api/v1/crates/${{ env.MAIN_CRATE }}/$release_version") + if [[ "$status" == "200" ]]; then + echo "${{ env.MAIN_CRATE }} $release_version is indexed on crates.io" + break + fi + echo "Waiting for crates.io index update (attempt $attempt/10)..." + sleep 20 + done + + - name: Dry Run Publish torrust-tracker-deployer-sdk + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish --dry-run -p ${{ env.SDK_CRATE }} + + - name: Publish torrust-tracker-deployer-sdk + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish -p ${{ env.SDK_CRATE }} + + - name: Verify SDK Is Available on crates.io + run: | + release_version="${{ steps.release.outputs.version }}" + for attempt in $(seq 1 30); do + status=$(curl -s -o /tmp/crate-publish-check.json -w "%{http_code}" "https://crates.io/api/v1/crates/${{ env.SDK_CRATE }}/$release_version") + if [[ "$status" == "200" ]]; then + echo "SDK crate is available on crates.io" + exit 0 + fi + echo "Waiting for crates.io index update (attempt $attempt/30)..." + sleep 20 + done + + # crates.io indexing can be slow under load. The cargo publish step above + # already returned zero — the crate is published. This check is best-effort. + echo "SDK crate not yet visible after 10 minutes — verify manually:" >&2 + echo " https://crates.io/crates/${{ env.SDK_CRATE }}/$release_version" >&2 + echo "This is expected on slow crates.io days; it does not mean the publish failed." + exit 0 + + - name: Verify docs.rs Build for SDK + run: | + docs_url="https://docs.rs/${{ env.SDK_CRATE }}/${{ steps.release.outputs.version }}" + for attempt in $(seq 1 6); do + status=$(curl -s -o /tmp/docs-rs-check.html -w "%{http_code}" "$docs_url") + if [[ "$status" == "200" ]]; then + echo "docs.rs page is available: $docs_url" + exit 0 + fi + echo "Waiting for docs.rs build (attempt $attempt/6)..." + sleep 20 + done + + echo "docs.rs page is not available yet: $docs_url" >&2 + echo "The crate may still be building on docs.rs; verify manually later." >&2 + echo "This is expected shortly after a first publish. Verify manually: $docs_url" + exit 0 diff --git a/.github/workflows/test-dependency-installer.yml b/.github/workflows/test-dependency-installer.yml index e23b76c5e..54d12334d 100644 --- a/.github/workflows/test-dependency-installer.yml +++ b/.github/workflows/test-dependency-installer.yml @@ -48,7 +48,7 @@ jobs: - name: Build dependency-installer binary run: | echo "🔨 Building dependency-installer binary..." - cargo build --release -p torrust-dependency-installer --bin dependency-installer + cargo build --release -p torrust-tracker-deployer-dependency-installer --bin dependency-installer echo "✅ Binary built successfully" - name: Install all development dependencies diff --git a/.github/workflows/test-e2e-deployment.yml b/.github/workflows/test-e2e-deployment.yml index b3d0e2e18..9c8df0f17 100644 --- a/.github/workflows/test-e2e-deployment.yml +++ b/.github/workflows/test-e2e-deployment.yml @@ -43,8 +43,8 @@ jobs: - name: Install dependencies run: | - cargo build -p torrust-dependency-installer --bin dependency-installer - cargo run -p torrust-dependency-installer --bin dependency-installer -- install + cargo build -p torrust-tracker-deployer-dependency-installer --bin dependency-installer + cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer -- install - name: Setup Docker uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/test-e2e-infrastructure.yml b/.github/workflows/test-e2e-infrastructure.yml index 8d870715f..0b949d84b 100644 --- a/.github/workflows/test-e2e-infrastructure.yml +++ b/.github/workflows/test-e2e-infrastructure.yml @@ -39,8 +39,8 @@ jobs: - name: Install dependencies run: | - cargo build -p torrust-dependency-installer --bin dependency-installer - cargo run -p torrust-dependency-installer --bin dependency-installer -- install + cargo build -p torrust-tracker-deployer-dependency-installer --bin dependency-installer + cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer -- install - name: Verify installations run: | diff --git a/.github/workflows/test-lxd-provision.yml b/.github/workflows/test-lxd-provision.yml index ab6661bf5..0f405cbd6 100644 --- a/.github/workflows/test-lxd-provision.yml +++ b/.github/workflows/test-lxd-provision.yml @@ -54,8 +54,8 @@ jobs: - name: Install dependencies run: | - cargo build -p torrust-dependency-installer --bin dependency-installer - cargo run -p torrust-dependency-installer --bin dependency-installer -- install + cargo build -p torrust-tracker-deployer-dependency-installer --bin dependency-installer + cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer -- install - name: Render template configurations run: | diff --git a/.gitignore b/.gitignore index c595b5b1d..0cb919598 100644 --- a/.gitignore +++ b/.gitignore @@ -51,7 +51,6 @@ repomix-output.xml # Rust build artifacts target/ -Cargo.lock rustc-ice-*.txt # Template build directory (runtime-generated configs) diff --git a/AGENTS.md b/AGENTS.md index f1d16c84d..e645b3a3c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,7 +44,7 @@ This is a deployment infrastructure proof-of-concept for the Torrust ecosystem. - `cspell.json` - Spell checking configuration - `project-words.txt` - Project-specific dictionary -## Essential Principles +## 🧭 Development Principles The development of this application is guided by fundamental principles that ensure quality, maintainability, and user experience. For detailed information, see [`docs/development-principles.md`](docs/development-principles.md). @@ -80,6 +80,22 @@ Reference: [Beck Design Rules](https://martinfowler.com/bliki/BeckDesignRules.ht These principles should guide all development decisions, code reviews, and feature implementations. +## 🤝 Collaboration Principles + +These rules apply repository-wide to every assistant, including custom agents. + +When acting as an assistant in this repository: + +- Do not flatter the user or agree with weak ideas by default. +- Push back when a request, diff, or proposed commit looks wrong. +- Flag unclear but important points before they become problems. +- Ask a clarifying question instead of making a random choice when the decision matters. +- Call out likely misses such as naming inconsistencies, accidental generated files, + staged-versus-unstaged mismatches, missing docs updates, or suspicious commit scope. + +When raising a likely mistake or blocker, say so clearly and early instead of +burying it after routine status updates. + ## 🔧 Essential Rules 1. **CRITICAL — `data/` is READ ONLY** (⚠️ **MOST FREQUENTLY VIOLATED**): Never create or edit files in `data/` — it contains application-managed deployment state. User configs belong in `envs/`. These are completely different JSON structures with different purposes. See the `create-environment-config` skill for details. @@ -134,6 +150,7 @@ For detailed information about working with deployed instances, see [`docs/user- - Individual linters: `cargo run --bin linter {markdown|yaml|toml|cspell|clippy|rustfmt|shellcheck}` - Alternative: `./scripts/lint.sh` (wrapper that calls the Rust binary) - **Dependencies**: `cargo machete` (mandatory before commits - no unused dependencies) +- **Commit Signing**: All commits **must** be signed with GPG (`git commit -S`) for audit trail - **Build**: `cargo build` - **Test**: `cargo test` - **Unit Tests**: When writing unit tests, follow conventions described in [`docs/contributing/testing/`](docs/contributing/testing/) @@ -186,8 +203,10 @@ Available skills: | Handling user output | `.github/skills/dev/cli/handle-user-output/skill.md` | | Implementing domain types | `.github/skills/dev/rust-code-quality/implement-domain-types/skill.md` | | Installing system dependencies | `.github/skills/usage/operations/install-system-dependencies/skill.md` | +| Opening pull requests | `.github/skills/dev/git-workflow/open-pull-request/skill.md` | | Organizing Rust modules | `.github/skills/dev/rust-code-quality/organize-rust-modules/skill.md` | | Placing code in DDD layers | `.github/skills/dev/rust-code-quality/place-code-in-ddd-layers/skill.md` | +| Releasing a new version | `.github/skills/dev/git-workflow/release-new-version/skill.md` | | Regenerating CLI docs | `.github/skills/dev/cli/regenerate-cli-docs/skill.md` | | Rendering tracker artifacts | `.github/skills/usage/operations/render-tracker-artifacts/skill.md` | | Reviewing pull requests | `.github/skills/dev/git-workflow/review-pr/skill.md` | diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..cb655d1fe --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3886 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "astral-tokio-tar" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c23f3af104b40a3430ccb90ed5f7bd877a8dc5c26fc92fde51a22b40890dcf9" +dependencies = [ + "filetime", + "futures-core", + "libc", + "portable-atomic", + "rustc-hash", + "tokio", + "tokio-stream", + "xattr", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bollard" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee04c4c84f1f811b017f2fbb7dd8815c976e7ca98593de9c1e2afad0f636bff4" +dependencies = [ + "async-stream", + "base64 0.22.1", + "bitflags", + "bollard-buildkit-proto", + "bollard-stubs", + "bytes", + "futures-core", + "futures-util", + "hex", + "home", + "http", + "http-body-util", + "hyper", + "hyper-named-pipe", + "hyper-rustls", + "hyper-util", + "hyperlocal", + "log", + "num", + "pin-project-lite", + "rand 0.9.4", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_derive", + "serde_json", + "serde_urlencoded", + "thiserror 2.0.18", + "time", + "tokio", + "tokio-stream", + "tokio-util", + "tonic", + "tower-service", + "url", + "winapi", +] + +[[package]] +name = "bollard-buildkit-proto" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a885520bf6249ab931a764ffdb87b0ceef48e6e7d807cfdb21b751e086e1ad" +dependencies = [ + "prost", + "prost-types", + "tonic", + "tonic-prost", + "ureq", +] + +[[package]] +name = "bollard-stubs" +version = "1.52.1-rc.29.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0a8ca8799131c1837d1282c3f81f31e76ceb0ce426e04a7fe1ccee3287c066" +dependencies = [ + "base64 0.22.1", + "bollard-buildkit-proto", + "bytes", + "prost", + "serde", + "serde_json", + "serde_repr", + "time", +] + +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "chrono-tz" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + +[[package]] +name = "clap" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "deunicode" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "docker_credential" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d89dfcba45b4afad7450a99b39e751590463e45c04728cf555d36bb66940de8" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +dependencies = [ + "serde", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "etcetera" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96" +dependencies = [ + "cfg-if", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "ferroid" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb330bbd4cb7a5b9f559427f06f98a4f853a137c8298f3bd3f8ca57663e21986" +dependencies = [ + "portable-atomic", + "rand 0.9.4", + "web-time", +] + +[[package]] +name = "figment" +version = "0.10.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +dependencies = [ + "atomic", + "serde", + "serde_json", + "uncased", + "version_check", +] + +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +dependencies = [ + "bitflags", + "ignore", + "walkdir", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-named-pipe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +dependencies = [ + "hex", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "hyperlocal" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" +dependencies = [ + "hex", + "http-body-util", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "bitflags", + "libc", + "plain", + "redox_syscall 0.7.4", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "openssl" +version = "0.10.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe4646e360ec77dff7dde40ed3d6c5fee52d156ef4a62f53973d38294dad87f" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad2f2c0eba47118757e4c6d2bff2838f3e0523380021356e7875e858372ce644" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "parse-display" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" +dependencies = [ + "parse-display-derive", + "regex", + "regex-syntax", +] + +[[package]] +name = "parse-display-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "regex-syntax", + "structmeta", + "syn", +] + +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rstest" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros", +] + +[[package]] +name = "rstest_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn", + "unicode-ident", +] + +[[package]] +name = "rust-embed" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" +dependencies = [ + "sha2", + "walkdir", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slug" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "structmeta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" +dependencies = [ + "proc-macro2", + "quote", + "structmeta-derive", + "syn", +] + +[[package]] +name = "structmeta-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tera" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8004bca281f2d32df3bacd59bc67b312cb4c70cea46cbd79dbe8ac5ed206722" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static", + "percent-encoding", + "pest", + "pest_derive", + "rand 0.8.5", + "regex", + "serde", + "serde_json", + "slug", + "unicode-segmentation", +] + +[[package]] +name = "testcontainers" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bd36b06a2a6c0c3c81a83be1ab05fe86460d054d4d51bf513bc56b3e15bdc22" +dependencies = [ + "astral-tokio-tar", + "async-trait", + "bollard", + "bytes", + "docker_credential", + "either", + "etcetera", + "ferroid", + "futures", + "http", + "itertools", + "log", + "memchr", + "parse-display", + "pin-project-lite", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "url", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "tonic" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" +dependencies = [ + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "socket2", + "sync_wrapper", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-prost" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "torrust-linting" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a0f3caf9aa1e1d4488fe34559ea406bb9622ed0b61605b0eec914e4bb8c338" +dependencies = [ + "anyhow", + "clap", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "torrust-tracker-deployer" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64 0.22.1", + "chrono", + "clap", + "derive_more", + "figment", + "parking_lot", + "percent-encoding", + "rand 0.9.4", + "regex", + "reqwest", + "rstest", + "rust-embed", + "schemars 1.2.1", + "secrecy", + "serde", + "serde_json", + "tempfile", + "tera", + "testcontainers", + "thiserror 2.0.18", + "tokio", + "torrust-linting", + "torrust-tracker-deployer-dependency-installer", + "torrust-tracker-deployer-types", + "tracing", + "tracing-appender", + "tracing-subscriber", + "tracing-test", + "url", + "uuid", +] + +[[package]] +name = "torrust-tracker-deployer-dependency-installer" +version = "0.1.0" +dependencies = [ + "async-trait", + "clap", + "testcontainers", + "thiserror 1.0.69", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "torrust-tracker-deployer-sdk" +version = "0.1.0" +dependencies = [ + "tempfile", + "thiserror 2.0.18", + "tokio", + "torrust-tracker-deployer", + "torrust-tracker-deployer-types", +] + +[[package]] +name = "torrust-tracker-deployer-types" +version = "0.1.0" +dependencies = [ + "chrono", + "email_address", + "schemars 1.2.1", + "secrecy", + "serde", + "serde_json", + "thiserror 2.0.18", + "tracing", + "url", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 2.14.0", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +dependencies = [ + "crossbeam-channel", + "thiserror 2.0.18", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "tracing-test" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a4c448db514d4f24c5ddb9f73f2ee71bfb24c526cf0c570ba142d1119e0051" +dependencies = [ + "tracing-core", + "tracing-subscriber", + "tracing-test-macro", +] + +[[package]] +name = "tracing-test-macro" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad06847b7afb65c7866a36664b75c40b895e318cea4f71299f013fb22965329d" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" +dependencies = [ + "base64 0.22.1", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "ureq-proto", + "utf8-zero", +] + +[[package]] +name = "ureq-proto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" +dependencies = [ + "base64 0.22.1", + "http", + "httparse", + "log", +] + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.14.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.14.0", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap 2.14.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.14.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 89829787a..7197c37e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,8 @@ version = "0.1.0" edition = "2021" description = "Torrust Tracker Deployer - Deployment Infrastructure with Ansible and OpenTofu" license = "MIT" +repository = "https://github.com/torrust/torrust-tracker-deployer" +readme = "README.md" default-run = "torrust-tracker-deployer" [lib] @@ -60,10 +62,10 @@ serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" tempfile = "3.0" tera = "1.0" -testcontainers = { version = "0.26", features = [ "blocking" ] } +testcontainers = { version = "0.27", features = [ "blocking" ] } thiserror = "2.0" -torrust-dependency-installer = { path = "packages/dependency-installer" } -torrust-deployer-types = { path = "packages/deployer-types" } +torrust-tracker-deployer-dependency-installer = { path = "packages/dependency-installer", version = "0.1.0" } +torrust-tracker-deployer-types = { path = "packages/deployer-types", version = "0.1.0" } torrust-linting = "0.1.0" tracing = { version = "0.1", features = [ "attributes" ] } tracing-appender = "0.2" diff --git a/README.md b/README.md index 6a743143b..b36b23637 100644 --- a/README.md +++ b/README.md @@ -1,135 +1,97 @@ -[![Linting](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/linting.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/linting.yml) [![Testing](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/testing.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/testing.yml) [![Test Dependency Installer](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-dependency-installer.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-dependency-installer.yml) [![E2E Infrastructure Tests](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-e2e-infrastructure.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-e2e-infrastructure.yml) [![E2E Deployment Tests](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-e2e-deployment.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-e2e-deployment.yml) [![SDK Examples](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-sdk-examples.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-sdk-examples.yml) [![Test LXD Container Provisioning](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-lxd-provision.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-lxd-provision.yml) [![Coverage](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/coverage.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/coverage.yml) [![Container](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/container.yaml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/container.yaml) [![Backup Container](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/backup-container.yaml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/backup-container.yaml) [![Docker Security Scan](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/docker-security-scan.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/docker-security-scan.yml) [![Code Statistics](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/code-statistics.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/code-statistics.yml) +[![Linting](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/linting.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/linting.yml) +[![Testing](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/testing.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/testing.yml) +[![E2E Infrastructure Tests](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-e2e-infrastructure.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-e2e-infrastructure.yml) +[![E2E Deployment Tests](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-e2e-deployment.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-e2e-deployment.yml) +[![Coverage](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/coverage.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/coverage.yml) +[![SDK Examples Tests](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-sdk-examples.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-sdk-examples.yml) +[![Test LXD Container Provisioning](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-lxd-provision.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-lxd-provision.yml) +[![Test Dependency Installer](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-dependency-installer.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/test-dependency-installer.yml) +[![Cargo Security Audit](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/cargo-security-audit.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/cargo-security-audit.yml) +[![Docker Security Scan](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/docker-security-scan.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/docker-security-scan.yml) +[![Container](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/container.yaml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/container.yaml) +[![Publish Crate](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/publish-crate.yaml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/publish-crate.yaml) +[![Backup Container](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/backup-container.yaml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/backup-container.yaml) +[![Code Statistics](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/code-statistics.yml/badge.svg)](https://github.com/torrust/torrust-tracker-deployer/actions/workflows/code-statistics.yml) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/torrust/torrust-tracker-deployer?quickstart=1) # Torrust Tracker Deployer -> ⚠️ **DEVELOPMENT STATUS: Early Production Phase** -> -> This project is in **active development** with initial cloud provider support now available. -> -> **Current Scope:** -> -> - ✅ Local LXD virtual machine provisioning -> - ✅ **Hetzner Cloud support** for production deployments -> - ✅ Development and testing workflows -> - ✅ Multi-provider architecture (provider selection via configuration) -> - ✅ **Application deployment** (Torrust Tracker stack with Docker Compose) -> -> 📋 **MVP Goal:** After completing the [roadmap](docs/roadmap.md), we will have a fully automated deployment solution for Torrust Tracker with complete application stack management and multi-cloud provider support. +Deployment automation for [Torrust Tracker](https://github.com/torrust/torrust-tracker) environments using [OpenTofu](https://opentofu.org), [Ansible](https://www.ansible.com), and [Rust](https://www.rust-lang.org). -This Rust application provides automated deployment infrastructure for Torrust tracker projects. It supports **local development** with LXD and **production deployments** with Hetzner Cloud. The multi-provider architecture allows easy extension to additional cloud providers. +## Release Status -## 🎯 Project Goals +Version 0.1.0 is the first fully functional release line of the deployer. -**Current Development Phase:** +Current status: -- ✅ **Create local VMs supporting cloud-init** for development and CI testing -- ✅ **Test cloud-init execution and verification** in controlled environments -- ✅ **Support Docker Compose** inside VMs for application stacks -- ✅ **Fast, easy to install and use** local development solution -- ✅ **No nested virtualization dependency** (CI compatibility) -- ✅ **Multi-provider support** (LXD for local, Hetzner Cloud for production) -- ✅ **Application stack deployment** (Torrust Tracker with Docker Compose) +- End-to-end workflow is implemented: create, provision, configure, test, release, run, destroy +- Multi-provider architecture is implemented +- Providers currently supported: LXD (local development) and Hetzner Cloud (cloud deployments) +- CI includes linting, unit/integration tests, and split E2E workflows -**Future MVP Goals:** (See [roadmap](docs/roadmap.md)) +## What This Project Does -- 🔄 **Additional cloud providers** (AWS, GCP, Azure) -- 🔄 **Multi-environment management** -- 🔄 **Enhanced observability** (monitoring, alerting, metrics) +The deployer provisions and configures VM infrastructure, then deploys and runs the Torrust Tracker stack. -## 🔧 Local Development Approach +Workflow: -This repository uses LXD virtual machines for local virtualization and development: +1. OpenTofu provisions infrastructure and cloud-init setup. +2. Ansible configures the provisioned host. +3. The deployer releases tracker artifacts and starts services. +4. Built-in commands support verification and teardown. -### ☁️ **LXD Virtual Machines (`templates/tofu/lxd/`)** - **LOCAL DEVELOPMENT** +## Quick Start -- **Technology**: Virtual machines with cloud-init support -- **Status**: ✅ Production-ready for local development and CI testing -- **Best for**: Local development, CI/CD environments, fast iteration -- **Requirements**: No special virtualization needed +### 1. Install dependencies -**[📖 See detailed documentation →](docs/tofu-lxd-configuration.md)** - -## 📊 LXD Benefits - -**[📖 See detailed comparison →](docs/vm-providers.md)** - -| Feature | LXD Virtual Machines | -| -------------------------- | -------------------- | -| **GitHub Actions Support** | ✅ Guaranteed | -| **Nested Virtualization** | ❌ Not needed | -| **Boot Time** | ✅ Fast (~5-10s) | -| **Resource Usage** | ✅ Efficient | -| **Installation** | ✅ Simple setup | - -## 🚀 Quick Start - -### 📋 Prerequisites - -This is a Rust application that automates deployment infrastructure using OpenTofu and Ansible. - -#### Automated Setup (Recommended) - -The project provides a dependency installer tool that automatically detects and installs required dependencies: +Recommended (automatic dependency installer): ```bash -# Install all required dependencies cargo run --bin dependency-installer install - -# Check which dependencies are installed cargo run --bin dependency-installer check - -# List all dependencies with status -cargo run --bin dependency-installer list ``` -The installer supports: **OpenTofu**, **Ansible**, **LXD**, and **cargo-machete**. - -For detailed information, see **[📖 Dependency Installer →](packages/dependency-installer/README.md)**. - -#### Manual Setup - -If you prefer manual installation or need to troubleshoot: - -**Check installations:** +### 2. Build and run the CLI ```bash -lxd version && tofu version && ansible --version && cargo --version +cargo run ``` -**Missing tools?** See detailed installation guides: - -- **[📖 OpenTofu Setup Guide →](docs/tech-stack/opentofu.md)** -- **[📖 Ansible Setup Guide →](docs/tech-stack/ansible.md)** - -**Quick manual install:** +### 3. Create and deploy an environment ```bash -# Install Rust (if not already installed) -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +# Generate environment config template +cargo run -- create template my-env.json + +# Edit config values, then create the environment from the file +cargo run -- create environment --env-file my-env.json -# Install LXD -sudo snap install lxd && sudo lxd init --auto && sudo usermod -a -G lxd $USER && newgrp lxd +# Provision and configure +cargo run -- provision my-environment +cargo run -- configure my-environment -# Install OpenTofu -curl -fsSL https://get.opentofu.org/install-opentofu.sh | sudo bash +# Verify and deploy application +cargo run -- test my-environment +cargo run -- release my-environment +cargo run -- run my-environment -# Install Ansible -sudo apt install ansible +# Tear down when done +cargo run -- destroy my-environment ``` -### 💻 Usage +Important: -#### 🐳 Docker (Recommended for Cloud Deployments) +- Keep your environment JSON files in [`envs/`](envs/). +- The [`data/`](data/) directory is application-managed deployment state and should not be edited manually. -The easiest way to use the deployer for **cloud provider deployments** (Hetzner) is with Docker - no local dependency installation required: +## Docker Usage + +For cloud-provider deployments, you can run the deployer via container image: ```bash -# Pull the image docker pull torrust/tracker-deployer:latest -# Run a command (example: show help) docker run --rm \ -v $(pwd)/data:/var/lib/torrust/deployer/data \ -v $(pwd)/build:/var/lib/torrust/deployer/build \ @@ -139,287 +101,63 @@ docker run --rm \ --help ``` -> ⚠️ **Important**: Docker only supports **cloud providers** (Hetzner). For **LXD local development**, install the deployer directly on your host. - -**[📖 See Docker documentation →](docker/deployer/README.md)** - -#### 🚀 Main Application - -The main application provides usage instructions: - -```bash -# Build and run the application -cargo run - -# Or install and run directly -cargo install --path . -torrust-tracker-deployer -``` - -For detailed usage instructions, command reference, and examples, see the **[👤 User Guide](docs/user-guide/README.md)**. - -The application includes comprehensive logging with configurable format, output mode, and directory. See **[📖 Logging Guide](docs/user-guide/logging.md)** for details on logging configuration options. - -#### 🔧 Development Tasks +Note: Docker workflow supports cloud providers. For local LXD usage, run the deployer natively on the host. -This project includes convenient scripts for common development tasks: +## Development Commands ```bash -# Run all linters (markdown, YAML, TOML, shell scripts, Rust) +# Comprehensive linting cargo run --bin linter all -``` - -Or run individual linters: - -```bash -cargo run --bin linter markdown # Markdown linting -cargo run --bin linter yaml # YAML linting -cargo run --bin linter toml # TOML linting -cargo run --bin linter cspell # Spell checking -cargo run --bin linter clippy # Rust code analysis -cargo run --bin linter rustfmt # Rust formatting check -cargo run --bin linter shellcheck # Shell script linting -``` -**[📖 See linting documentation →](docs/linting.md)** +# Run test suite +cargo test -#### 🧪 Running E2E Tests +# E2E suites +cargo run --bin e2e-infrastructure-lifecycle-tests +cargo run --bin e2e-deployment-workflow-tests -Use the E2E test binaries to run automated infrastructure tests with hardcoded environments: - -```bash -# Run comprehensive E2E tests (LOCAL ONLY - connectivity issues in GitHub runners) +# Full E2E workflow (local only) cargo run --bin e2e-complete-workflow-tests +``` -# Run individual E2E test suites -cargo run --bin e2e-deployment-workflow-tests # Configuration, release, and run workflow tests -cargo run --bin e2e-infrastructure-lifecycle-tests # Infrastructure provisioning tests +## Documentation Map -# Keep the test environment after completion for inspection -cargo run --bin e2e-complete-workflow-tests -- --keep -cargo run --bin e2e-infrastructure-lifecycle-tests -- --keep +For detailed guides, use the docs index and user guide: -# Use custom templates directory -cargo run --bin e2e-complete-workflow-tests -- --templates-dir ./custom/templates +- [Documentation Index](docs/README.md) +- [User Guide](docs/user-guide/README.md) +- [Quick Start Guides](docs/user-guide/quick-start/README.md) +- [Commands](docs/user-guide/commands/) +- [Providers](docs/user-guide/providers/README.md) +- [E2E Testing](docs/e2e-testing/README.md) +- [Contributing](docs/contributing/README.md) +- [Architecture Overview](docs/codebase-architecture.md) +- [Roadmap](docs/roadmap.md) -# See all available options -cargo run --bin e2e-complete-workflow-tests -- --help -``` +## Related Repository -> **⚠️ Important Notes:** -> -> - E2E tests create **hardcoded environments** with predefined configurations -> - Use `--keep` flag to inspect generated `data/` and `build/` directories after tests -> - `e2e-complete-workflow-tests` can **only run locally** due to connectivity issues in GitHub runners -> - To see final OpenTofu and Ansible templates, check `build/` directories after running with `--keep` - -### 📖 Manual Deployment Steps - -> **✅ Complete deployment workflow is now available!** You can create, provision, configure, test, deploy, run, and destroy Torrust Tracker environments using the CLI. -> -> **Current Status:** -> -> - ✅ **Environment Management**: Create and manage deployment environments -> - ✅ **Infrastructure Provisioning**: Provision VM infrastructure with LXD or Hetzner Cloud -> - ✅ **Configuration**: Configure provisioned infrastructure (Docker, Docker Compose) -> - ✅ **Verification**: Test deployment infrastructure -> - ✅ **Application Deployment**: Deploy Torrust Tracker configuration and database -> - ✅ **Service Management**: Start and manage tracker services -> -> **Available Commands:** -> -> ```bash -> # 1. Generate configuration template -> torrust-tracker-deployer create template my-env.json -> -> # 2. Edit my-env.json with your settings -> -> # 3. Create environment from configuration -> torrust-tracker-deployer create environment -f my-env.json -> -> # 4. Provision VM infrastructure -> torrust-tracker-deployer provision my-environment -> -> # 5. Configure infrastructure (install Docker, Docker Compose) -> torrust-tracker-deployer configure my-environment -> -> # 6. Verify deployment infrastructure -> torrust-tracker-deployer test my-environment -> -> # 7. Deploy tracker application configuration -> torrust-tracker-deployer release my-environment -> -> # 8. Start tracker services -> torrust-tracker-deployer run my-environment -> -> # 9. Destroy environment when done -> torrust-tracker-deployer destroy my-environment -> ``` -> -> **📖 For detailed command documentation and guides, see:** -> -> - **[Quick Start Guides](docs/user-guide/quick-start/README.md)** - Docker and native installation guides -> - **[Commands Reference](docs/user-guide/commands/)** - Detailed guide for each command _(coming soon)_ -> - **[Console Commands](docs/console-commands.md)** - Technical reference -> - **[Advanced: Manual Commands](docs/user-guide/advanced-manual-commands.md)** - Manual OpenTofu and Ansible commands (advanced users only) - -## 🎭 Infrastructure Workflow - -1. **Provision**: OpenTofu creates and configures VMs with cloud-init -2. **Configure**: Ansible connects to VMs and executes management tasks -3. **Verify**: Automated checks ensure proper setup and functionality - -| Phase | Tool | Purpose | -| ------------------ | ----------------- | ------------------------------------------- | -| **Infrastructure** | OpenTofu | VM provisioning and cloud-init setup | -| **Configuration** | Ansible | Task execution and configuration management | -| **Verification** | Ansible Playbooks | System checks and validation | - -**[📖 See detailed Ansible documentation →](docs/tech-stack/ansible.md)** - -## 🧪 Testing in GitHub Actions - -The repository includes comprehensive GitHub Actions workflows for CI testing: - -- **`.github/workflows/linting.yml`** - **Code Quality** - Runs all linters (markdown, YAML, TOML, Rust, shell scripts) -- **`.github/workflows/testing.yml`** - **Unit Tests** - Runs Rust unit tests and basic validation -- **`.github/workflows/test-e2e-infrastructure.yml`** - **E2E Infrastructure Tests** - Tests infrastructure provisioning and destruction -- **`.github/workflows/test-e2e-deployment.yml`** - **E2E Deployment Tests** - Tests software installation, configuration, release, and run workflows -- **`.github/workflows/test-lxd-provision.yml`** - **LXD Provisioning** - Tests LXD VM provisioning specifically - -> **Note:** The complete E2E workflow tests (`e2e-complete-workflow-tests`) can only be executed locally due to connectivity issues documented in [`docs/e2e-testing/`](docs/e2e-testing/). - -## 🗺️ Roadmap - -This project follows a structured development roadmap to evolve from the current local development focus to a production-ready deployment solution. - -**Current Development Status:** - -- ✅ **Local LXD Infrastructure**: VM provisioning, cloud-init, E2E testing -- ✅ **Development Workflows**: Linting, testing, CI/CD automation -- ✅ **Foundation Layer**: OpenTofu + Ansible + Docker integration - -**Next Major Milestones:** - -- 🔄 **Main Application Commands**: `create`, `deploy`, `destroy`, `status` -- ☁️ **Real Cloud Providers**: Starting with Hetzner, expanding to AWS/GCP/Azure -- 🔄 **Production Features**: HTTPS, backups, monitoring stack - -**[📖 See complete roadmap →](docs/roadmap.md)** - -## 📁 Repository Structure - -```text -├── .github/ # CI/CD workflows and GitHub configuration -│ └── workflows/ # GitHub Actions workflow files -├── build/ # 📁 Generated runtime configs (git-ignored) -│ ├── e2e-complete/ # E2E complete workflow test runtime files -│ ├── e2e-deployment/ # E2E deployment test runtime files -│ ├── e2e-infrastructure/ # E2E infrastructure test runtime files -│ └── manual-test-*/ # Manual test environment runtime files -├── data/ # Environment-specific data and configurations -│ ├── e2e-complete/ # E2E complete workflow test environment data -│ ├── e2e-deployment/ # E2E deployment test environment data -│ ├── e2e-infrastructure/ # E2E infrastructure test environment data -│ ├── manual-test-*/ # Manual test environment data -│ └── logs/ # Application logs -├── docker/ # Docker-related configurations -│ └── provisioned-instance/ # Docker setup for provisioned instances -├── docs/ # 📖 Detailed documentation -│ ├── tech-stack/ # Technology-specific documentation -│ │ ├── opentofu.md # OpenTofu installation and usage -│ │ ├── ansible.md # Ansible installation and usage -│ │ └── lxd.md # LXD virtual machines -│ ├── user-guide/ # User documentation -│ │ ├── commands/ # Command reference documentation -│ │ └── providers/ # Provider-specific guides (LXD, Hetzner) -│ ├── decisions/ # Architecture Decision Records (ADRs) -│ ├── contributing/ # Contributing guidelines and conventions -│ │ ├── README.md # Main contributing guide -│ │ ├── branching.md # Git branching conventions -│ │ ├── commit-process.md # Commit process and pre-commit checks -│ │ ├── error-handling.md # Error handling principles -│ │ ├── module-organization.md # Module organization conventions -│ │ └── testing/ # Testing conventions and guides -│ ├── features/ # Feature specifications and documentation -│ ├── research/ # Research and analysis documents -│ └── *.md # Various documentation files -├── envs/ # 📁 User environment configurations (git-ignored) -│ └── *.json # Environment configuration files for CLI -├── fixtures/ # Test fixtures and sample data -│ ├── testing_rsa* # SSH key pair for testing -│ └── tofu/ # OpenTofu test fixtures -├── packages/ # Rust workspace packages -│ ├── dependency-installer/ # Dependency detection and installation -│ └── linting/ # Linting utilities package -│ └── src/ # Linting implementation source code -├── scripts/ # Development and utility scripts -│ └── setup/ # Installation scripts for dependencies -├── src/ # 🦀 Main Rust application source code (DDD Architecture) -│ ├── main.rs # Main application binary entry point -│ ├── lib.rs # Library root module -│ ├── container.rs # Dependency injection container -│ ├── logging.rs # Logging configuration -│ ├── bin/ # Binary executables -│ │ ├── linter.rs # Unified linting command interface -│ │ └── e2e*.rs # End-to-end testing binaries -│ ├── application/ # Application layer (use cases, commands) -│ ├── domain/ # Domain layer (business logic, entities) -│ │ └── provider/ # Provider types (LXD, Hetzner) -│ ├── infrastructure/ # Infrastructure layer (external systems) -│ ├── presentation/ # Presentation layer (CLI interface) -│ ├── adapters/ # External tool adapters (OpenTofu, Ansible, SSH, LXD) -│ ├── shared/ # Shared utilities and common code -│ ├── testing/ # Testing utilities and mocks -│ ├── config/ # Configuration handling -│ ├── bootstrap/ # Application bootstrapping -│ └── e2e/ # End-to-end testing infrastructure -├── templates/ # 📁 Template configurations (git-tracked) -│ ├── tofu/ # 🏗️ OpenTofu/Terraform templates -│ │ ├── lxd/ # LXD VM template configuration -│ │ └── hetzner/ # Hetzner Cloud template configuration -│ └── ansible/ # 🤖 Ansible playbook templates -├── tests/ # Integration and system tests -├── target/ # 🦀 Rust build artifacts (git-ignored) -├── Cargo.toml # Rust workspace configuration -├── Cargo.lock # Rust dependency lock file -├── cspell.json # Spell checking configuration -├── project-words.txt # Custom dictionary for spell checking -├── .markdownlint.json # Markdown linting configuration -├── .prettierignore # Prettier ignore rules (for Tera templates) -├── .taplo.toml # TOML formatting configuration -├── .yamllint-ci.yml # YAML linting configuration for CI -├── README.md # This file - project overview -├── LICENSE # Project license -└── .gitignore # Git ignore rules -``` +The deployer focuses on provisioning, configuring, and deploying Torrust Tracker. +For example application operations and maintenance after deployment, see: -## 📚 Documentation +- [Torrust Tracker Demo](https://github.com/torrust/torrust-tracker-demo) -- **[👤 User Guide](docs/user-guide/README.md)** - Getting started, command reference, and usage examples -- **[☁️ Provider Guides](docs/user-guide/providers/README.md)** - LXD and Hetzner Cloud provider configuration -- **[🤝 Contributing Guide](docs/contributing/README.md)** - Git workflow, commit process, and linting conventions -- **[🗺️ Roadmap](docs/roadmap.md)** - Development roadmap and MVP goals -- **[📖 Documentation Organization Guide](docs/documentation.md)** - How documentation is organized and where to contribute -- **[📖 OpenTofu Setup Guide](docs/tech-stack/opentofu.md)** - Installation, common commands, and best practices -- **[📖 Ansible Setup Guide](docs/tech-stack/ansible.md)** - Installation, configuration, and project usage -- **[📖 VM Providers Comparison](docs/vm-providers.md)** - Detailed comparison and decision rationale +## Repository Layout -## 🔮 Next Steps +Top-level directories: -This project now supports multiple infrastructure providers. The path to production-ready deployment is outlined in our [📋 **Roadmap**](docs/roadmap.md). +- [`src/`](src/): Rust codebase using DDD layers (domain, application, infrastructure, presentation) +- [`templates/`](templates/): OpenTofu and Ansible templates +- [`docs/`](docs/README.md): user and contributor documentation +- [`envs/`](envs/): user environment configuration files (git-ignored) +- `build/`: generated runtime files (git-ignored) +- [`data/`](data/): application-managed deployment state -**Recent achievements:** +## Roadmap After 0.1.0 -- ✅ **Multi-Provider Support**: LXD for local development, Hetzner Cloud for production deployments -- ✅ **Provider Selection**: Choose your provider via `provider_config` in environment configuration -- ✅ **Complete CLI Commands**: `create`, `provision`, `configure`, `test`, and `destroy` commands +The 0.1.0 line establishes the functional baseline. Upcoming improvements are tracked in the roadmap, including broader provider support and deployment UX refinements. -**Key upcoming milestones:** +See: [Roadmap](docs/roadmap.md) -- **Application Stack Management**: Complete Docker Compose stacks with Torrust Tracker, MySQL, Prometheus, and Grafana -- **HTTPS Support**: SSL/TLS configuration for all services -- **Backup & Recovery**: Database backups and disaster recovery procedures -- **Additional Cloud Providers**: AWS, GCP, and Azure support +## License -**[📖 See full roadmap →](docs/roadmap.md)** +This project is licensed under the [MIT License](LICENSE). diff --git a/docker/backup/Dockerfile b/docker/backup/Dockerfile index 94aca25e9..76bd62093 100644 --- a/docker/backup/Dockerfile +++ b/docker/backup/Dockerfile @@ -39,6 +39,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ sqlite3 \ gzip \ tar \ + && apt-get upgrade -y \ && rm -rf /var/lib/apt/lists/* # ============================================================================= diff --git a/docker/deployer/Dockerfile b/docker/deployer/Dockerfile index 7cdea9cec..d3639a97c 100644 --- a/docker/deployer/Dockerfile +++ b/docker/deployer/Dockerfile @@ -78,7 +78,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ openssh-client \ # Required for downloading tools curl \ - gnupg \ # Python for Ansible python3 \ python3-pip \ @@ -89,6 +88,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ # Additional utilities sudo \ + && apt-get upgrade -y \ && rm -rf /var/lib/apt/lists/* # Install Ansible via pipx (isolated environment) @@ -105,10 +105,13 @@ RUN pipx runpip ansible-core install ansible \ # Install OpenTofu # Using the official installation script with deb method for Debian -RUN curl -fsSL https://get.opentofu.org/install-opentofu.sh -o install-opentofu.sh \ +RUN apt-get update && apt-get install -y --no-install-recommends gnupg \ + && curl -fsSL https://get.opentofu.org/install-opentofu.sh -o install-opentofu.sh \ && chmod +x install-opentofu.sh \ && ./install-opentofu.sh --install-method deb \ - && rm install-opentofu.sh + && rm install-opentofu.sh \ + && apt-get purge -y --auto-remove gnupg dirmngr \ + && rm -rf /var/lib/apt/lists/* # Build arguments for customization ARG USER_ID=1000 diff --git a/docker/provisioned-instance/Dockerfile b/docker/provisioned-instance/Dockerfile index 2d867a543..37242d2dd 100644 --- a/docker/provisioned-instance/Dockerfile +++ b/docker/provisioned-instance/Dockerfile @@ -16,7 +16,7 @@ ENV DEBIAN_FRONTEND=noninteractive ENV TZ=UTC # Update package list and install essential packages -RUN apt-get update && apt-get install -y \ +RUN apt-get update && apt-get install -y --no-install-recommends \ # SSH server for Ansible connectivity openssh-server \ # Sudo for privilege escalation @@ -35,6 +35,7 @@ RUN apt-get update && apt-get install -y \ apt-transport-https \ # iptables for Docker networking iptables \ + && apt-get upgrade -y \ # Clean up package cache && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/docker/ssh-server/Dockerfile b/docker/ssh-server/Dockerfile index ef7e04dab..2b7eb7eba 100644 --- a/docker/ssh-server/Dockerfile +++ b/docker/ssh-server/Dockerfile @@ -20,7 +20,8 @@ RUN apk add --no-cache \ curl \ wget \ # Network tools for testing - net-tools + net-tools \ + && apk upgrade --no-cache # Generate SSH host keys RUN ssh-keygen -A @@ -56,14 +57,12 @@ RUN echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCw16sai+XVnawp/P/Q23kcXKekygZ6AL chmod 600 /home/testuser/.ssh/authorized_keys && \ chown testuser:testuser /home/testuser/.ssh/authorized_keys -# Generate SSH host keys -RUN ssh-keygen -A - # Create a simple entrypoint script -RUN echo '#!/bin/sh\n\ -# Start SSH daemon in foreground\n\ -exec /usr/sbin/sshd -D\n\ -' > /entrypoint.sh && chmod +x /entrypoint.sh +RUN printf '%s\n' \ + '#!/bin/sh' \ + 'exec /usr/sbin/sshd -D' \ + > /entrypoint.sh \ + && chmod +x /entrypoint.sh # Expose SSH port EXPOSE 22 diff --git a/docs/README.md b/docs/README.md index 31384576d..d75379485 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,6 +12,7 @@ Welcome to the Torrust Tracker Deployer documentation! This index helps you quic | Place code in the correct layer | [`contributing/ddd-layer-placement.md`](contributing/ddd-layer-placement.md) - **CRITICAL** decision flowchart | | Follow development principles | [`development-principles.md`](development-principles.md) - Observability, Testability, User Friendliness, Actionability | | Commit code | [`contributing/commit-process.md`](contributing/commit-process.md) - Pre-commit checks, conventional commits | +| Cut a release | [`release-process.md`](release-process.md) - Standard release sequence, branch/tag rules, Docker + crate publication | | Handle errors properly | [`contributing/error-handling.md`](contributing/error-handling.md) - Explicit enums, actionable messages | | Handle output properly | [`contributing/output-handling.md`](contributing/output-handling.md) - **CRITICAL** UserOutput, never `println!` | | Organize Rust code | [`contributing/module-organization.md`](contributing/module-organization.md) - **CRITICAL** import conventions | @@ -83,6 +84,7 @@ docs/ | Write unit tests | [`contributing/testing/unit-testing.md`](contributing/testing/unit-testing.md) | | Understand a decision | [`decisions/README.md`](decisions/README.md) | | Plan a new feature | [`features/README.md`](features/README.md) | +| Perform a release | [`release-process.md`](release-process.md) | | Fix external tool issues | [`external-issues/README.md`](external-issues/README.md) | | Work with templates | [`contributing/templates/`](contributing/templates/) | | Handle errors properly | [`contributing/error-handling.md`](contributing/error-handling.md) | diff --git a/docs/ai-training/dataset/rendered-templates/03-minimal-hetzner/tofu/hetzner/main.tf b/docs/ai-training/dataset/rendered-templates/03-minimal-hetzner/tofu/hetzner/main.tf index b78486960..2f88f9cbb 100644 --- a/docs/ai-training/dataset/rendered-templates/03-minimal-hetzner/tofu/hetzner/main.tf +++ b/docs/ai-training/dataset/rendered-templates/03-minimal-hetzner/tofu/hetzner/main.tf @@ -107,7 +107,7 @@ variable "server_labels" { # Root SSH provides a recovery/debugging path. # # SECURITY NOTE: For production deployments, consider disabling root SSH access -# after verifying deployment succeeded. See docs/security/ssh-root-access-hetzner.md +# after verifying deployment succeeded. See docs/security/user-security/ssh-root-access-hetzner.md # # This key will appear in Hetzner Console → Security → SSH Keys. resource "hcloud_ssh_key" "torrust" { diff --git a/docs/ai-training/dataset/rendered-templates/04-full-stack-hetzner/tofu/hetzner/main.tf b/docs/ai-training/dataset/rendered-templates/04-full-stack-hetzner/tofu/hetzner/main.tf index b78486960..2f88f9cbb 100644 --- a/docs/ai-training/dataset/rendered-templates/04-full-stack-hetzner/tofu/hetzner/main.tf +++ b/docs/ai-training/dataset/rendered-templates/04-full-stack-hetzner/tofu/hetzner/main.tf @@ -107,7 +107,7 @@ variable "server_labels" { # Root SSH provides a recovery/debugging path. # # SECURITY NOTE: For production deployments, consider disabling root SSH access -# after verifying deployment succeeded. See docs/security/ssh-root-access-hetzner.md +# after verifying deployment succeeded. See docs/security/user-security/ssh-root-access-hetzner.md # # This key will appear in Hetzner Console → Security → SSH Keys. resource "hcloud_ssh_key" "torrust" { diff --git a/docs/ai-training/dataset/rendered-templates/12-high-availability/tofu/hetzner/main.tf b/docs/ai-training/dataset/rendered-templates/12-high-availability/tofu/hetzner/main.tf index b78486960..2f88f9cbb 100644 --- a/docs/ai-training/dataset/rendered-templates/12-high-availability/tofu/hetzner/main.tf +++ b/docs/ai-training/dataset/rendered-templates/12-high-availability/tofu/hetzner/main.tf @@ -107,7 +107,7 @@ variable "server_labels" { # Root SSH provides a recovery/debugging path. # # SECURITY NOTE: For production deployments, consider disabling root SSH access -# after verifying deployment succeeded. See docs/security/ssh-root-access-hetzner.md +# after verifying deployment succeeded. See docs/security/user-security/ssh-root-access-hetzner.md # # This key will appear in Hetzner Console → Security → SSH Keys. resource "hcloud_ssh_key" "torrust" { diff --git a/docs/ai-training/dataset/rendered-templates/14-lightweight-production/tofu/hetzner/main.tf b/docs/ai-training/dataset/rendered-templates/14-lightweight-production/tofu/hetzner/main.tf index b78486960..2f88f9cbb 100644 --- a/docs/ai-training/dataset/rendered-templates/14-lightweight-production/tofu/hetzner/main.tf +++ b/docs/ai-training/dataset/rendered-templates/14-lightweight-production/tofu/hetzner/main.tf @@ -107,7 +107,7 @@ variable "server_labels" { # Root SSH provides a recovery/debugging path. # # SECURITY NOTE: For production deployments, consider disabling root SSH access -# after verifying deployment succeeded. See docs/security/ssh-root-access-hetzner.md +# after verifying deployment succeeded. See docs/security/user-security/ssh-root-access-hetzner.md # # This key will appear in Hetzner Console → Security → SSH Keys. resource "hcloud_ssh_key" "torrust" { diff --git a/docs/codebase-architecture.md b/docs/codebase-architecture.md index 5ca8c226d..9aae4d292 100644 --- a/docs/codebase-architecture.md +++ b/docs/codebase-architecture.md @@ -219,7 +219,7 @@ Independently versioned Cargo workspace packages in `packages/`: - ✅ [`torrust-linting`](https://crates.io/crates/torrust-linting) (external crate) - Unified linting framework (runs markdownlint, yamllint, taplo, cspell, clippy, rustfmt, shellcheck) - ✅ `packages/dependency-installer/` - Dependency detection and installation for development setup (OpenTofu, Ansible, LXD, cargo-machete) -- ✅ `packages/deployer-types/` - Shared value objects and traits (`torrust-deployer-types`) — cross-cutting foundational types (e.g., `EnvironmentName`, `DomainName`, `Username`, `Clock`, `ErrorKind`) shared by the root crate and SDK +- ✅ `packages/deployer-types/` - Shared value objects and traits (`torrust-tracker-deployer-types`) — cross-cutting foundational types (e.g., `EnvironmentName`, `DomainName`, `Username`, `Clock`, `ErrorKind`) shared by the root crate and SDK - ✅ `packages/sdk/` - Programmatic SDK (`torrust-tracker-deployer-sdk`) — independently consumable Rust crate for deploying Torrust Tracker instances without the CLI ### Presentation Layer diff --git a/docs/contributing/README.md b/docs/contributing/README.md index af71f0d59..ae42afba4 100644 --- a/docs/contributing/README.md +++ b/docs/contributing/README.md @@ -62,6 +62,24 @@ git commit -m "feat: [#42] add new testing feature" git push origin 42-add-your-feature-name ``` +## Dependency Update Automation + +For dependency-only updates, you can automate the repetitive git and PR workflow with: + +```bash +./scripts/update-dependencies.sh \ + --branch 445-update-dependencies \ + --push-remote josecelano \ + --create-pr +``` + +Notes: + +- The script signs commits by default with `git commit -S`. +- The push remote and branch name are explicit so the workflow works with different forks. +- Reuse `--delete-existing-branch` only when you intentionally want to replace an older update branch. +- Use `--help` to see all options. + ## 📖 Additional Resources - [Main Documentation](../documentation.md) - Project documentation organization diff --git a/docs/decisions/README.md b/docs/decisions/README.md index a50a1f57d..37e7d67d9 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -4,54 +4,55 @@ This directory contains architectural decision records for the Torrust Tracker D ## Decision Index -| Status | Date | Decision | Summary | -| ------------- | ---------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | -| ✅ Accepted | 2026-04-07 | [SSH Key Passphrase Detection](./ssh-key-passphrase-detection.md) | Detect passphrase-protected keys via byte inspection; reject ssh-keygen probe approach | -| ✅ Accepted | 2026-02-26 | [SDK Package Naming](./sdk-package-naming.md) | Keep "SDK" name for packages/sdk — the modern API-wrapper meaning is industry-standard | -| ✅ Accepted | 2026-02-24 | [SDK Presentation Layer Interface Design](./sdk-presentation-layer-interface-design.md) | Return () from SDK operations; no domain types or typestate pattern in the SDK public API | -| ✅ Accepted | 2026-02-24 | [Docker Compose Local Validation Placement](./docker-compose-local-validation-placement.md) | Validate rendered docker-compose.yml in the infrastructure generator, not the app layer | -| ✅ Accepted | 2026-02-17 | [Application-Layer Progress Reporting Trait](./application-layer-progress-reporting-trait.md) | Use trait-based progress reporting with Dependency Inversion for testable, reusable design | -| ✅ Accepted | 2026-02-13 | [Concurrent Docker Image Builds in Tests](./concurrent-docker-image-builds-in-tests.md) | Treat "already exists" tagging error as success when parallel tests build same image | -| ✅ Accepted | 2026-02-06 | [Agent Skills Content Strategy](./skill-content-strategy-duplication-vs-linking.md) | Three-tier content strategy: self-contained workflows, progressive disclosure, linked docs | -| ✅ Accepted | 2026-01-27 | [Atomic Ansible Playbooks](./atomic-ansible-playbooks.md) | Require one-responsibility playbooks with Rust-side gating and registered static templates | -| ✅ Accepted | 2026-01-24 | [Bind Mount Standardization](./bind-mount-standardization.md) | Use bind mounts exclusively for all Docker Compose volumes for observability and backup | -| ✅ Accepted | 2026-01-21 | [TryFrom for DTO to Domain Conversion](./tryfrom-for-dto-to-domain-conversion.md) | Use standard TryFrom trait for self-documenting, discoverable DTO→Domain conversions | -| ✅ Accepted | 2026-01-21 | [Validated Deserialization for Domain Types](./validated-deserialization-for-domain-types.md) | Use custom Deserialize impl with Raw struct to enforce domain invariants during parsing | -| ✅ Accepted | 2026-01-20 | [Grafana Default Port 3000](./grafana-default-port-3000.md) | Use Grafana's default port 3000 instead of 3100 for dedicated VM deployments | -| ✅ Accepted | 2026-01-20 | [Caddy for TLS Termination](./caddy-for-tls-termination.md) | Use Caddy v2.10 as TLS proxy for automatic HTTPS with WebSocket support | -| ✅ Accepted | 2026-01-20 | [Per-Service TLS Configuration](./per-service-tls-configuration.md) | Use domain + use_tls_proxy fields instead of nested tls section for explicit TLS opt-in | -| ✅ Accepted | 2026-01-20 | [Uniform HTTP Tracker TLS Requirement](./uniform-http-tracker-tls-requirement.md) | All HTTP trackers must use same TLS setting due to tracker's global on_reverse_proxy | -| ✅ Accepted | 2026-01-10 | [Hetzner SSH Key Dual Injection Pattern](./hetzner-ssh-key-dual-injection.md) | Use both OpenTofu SSH key and cloud-init for debugging capability with manual hardening | -| ✅ Accepted | 2026-01-10 | [Configuration and Data Directories as Secrets](./configuration-directories-as-secrets.md) | Treat envs/, data/, build/ as secrets; no env var injection; users secure via permissions | -| ✅ Accepted | 2026-01-07 | [Configuration DTO Layer Placement](./configuration-dto-layer-placement.md) | Keep configuration DTOs in application layer, not domain; defer package extraction | -| ✅ Accepted | 2025-12-23 | [Docker Security Scan Exit Code Zero](./docker-security-scan-exit-code-zero.md) | Use exit-code 0 for security scanning - Trivy detects, GitHub Security decides, CI green | -| ✅ Accepted | 2025-12-20 | [Grafana Integration Pattern](./grafana-integration-pattern.md) | Enable Grafana by default with hard Prometheus dependency and environment variable config | -| ✅ Accepted | 2025-12-17 | [Secrecy Crate for Sensitive Data Handling](./secrecy-crate-for-sensitive-data.md) | Use secrecy crate for type-safe secret handling with memory zeroing | -| ✅ Accepted | 2025-12-14 | [Database Configuration Structure in Templates](./database-configuration-structure-in-templates.md) | Expose structured database fields in templates rather than pre-resolved connection strings | -| ✅ Accepted | 2025-12-13 | [Environment Variable Injection in Docker Compose](./environment-variable-injection-in-docker-compose.md) | Use .env file injection instead of hardcoded values for runtime configuration changes | -| ✅ Accepted | 2025-12-11 | [Cloud-Init SSH Port Configuration with Reboot](./cloud-init-ssh-port-reboot.md) | Use cloud-init with reboot pattern to configure custom SSH ports during VM provisioning | -| ✅ Accepted | 2025-12-10 | [Single Docker Image for Sequential E2E Command Testing](./single-docker-image-sequential-testing.md) | Use single Docker image with sequential command execution instead of multi-image phases | -| ✅ Accepted | 2025-12-09 | [Register Command SSH Port Override](./register-ssh-port-override.md) | Add optional --ssh-port argument to register command for non-standard SSH ports | -| ✅ Accepted | 2025-11-19 | [Disable MD060 Table Formatting Rule](./md060-table-formatting-disabled.md) | Disable MD060 to allow flexible table formatting and emoji usage | -| ✅ Accepted | 2025-11-19 | [Test Command as Smoke Test](./test-command-as-smoke-test.md) | Test command validates running services, not infrastructure components | -| ✅ Accepted | 2025-11-13 | [Migration to AGENTS.md Standard](./agents-md-migration.md) | Adopt open AGENTS.md standard for multi-agent compatibility while keeping GitHub redirect | -| ✅ Accepted | 2025-11-11 | [Use ReentrantMutex Pattern for UserOutput Reentrancy](./reentrant-mutex-useroutput-pattern.md) | Use Arc<ReentrantMutex<RefCell<UserOutput>>> to fix same-thread deadlock in issue #164 | -| ❌ Superseded | 2025-11-11 | [Remove UserOutput Mutex](./user-output-mutex-removal.md) | Remove Arc<Mutex<UserOutput>> pattern for simplified, deadlock-free architecture | -| ✅ Accepted | 2025-11-07 | [ExecutionContext Wrapper Pattern](./execution-context-wrapper.md) | Use ExecutionContext wrapper around Container for future-proof command signatures | -| ✅ Accepted | 2025-11-03 | [Environment Variable Prefix](./environment-variable-prefix.md) | Use `TORRUST_TD_` prefix for all environment variables | -| ✅ Accepted | 2025-10-15 | [External Tool Adapters Organization](./external-tool-adapters-organization.md) | Consolidate external tool wrappers in `src/adapters/` for better discoverability | -| ✅ Accepted | 2025-10-10 | [Repository Rename to Deployer](./repository-rename-to-deployer.md) | Rename from "Torrust Tracker Deploy" to "Torrust Tracker Deployer" for production use | -| ✅ Accepted | 2025-10-03 | [Error Context Strategy](./error-context-strategy.md) | Use structured error context with trace files for complete error information | -| ✅ Accepted | 2025-10-03 | [Command State Return Pattern](./command-state-return-pattern.md) | Commands return typed states (Environment<S> → Environment<T>) for compile-time safety | -| ✅ Accepted | 2025-10-03 | [Actionable Error Messages](./actionable-error-messages.md) | Use tiered help system with brief tips + .help() method for detailed troubleshooting | -| ✅ Accepted | 2025-10-01 | [Type Erasure for Environment States](./type-erasure-for-environment-states.md) | Use enum-based type erasure to enable runtime handling and serialization of typed states | -| ✅ Accepted | 2025-09-29 | [Test Context vs Deployment Environment Naming](./test-context-vs-deployment-environment-naming.md) | Rename TestEnvironment to TestContext to avoid conflicts with multi-environment feature | -| ✅ Accepted | 2025-09-10 | [LXD VMs over Containers](./lxd-vm-over-containers.md) | Use LXD virtual machines instead of containers for production alignment | -| ✅ Accepted | 2025-09-09 | [Tera Minimal Templating Strategy](./tera-minimal-templating-strategy.md) | Use Tera with minimal variables and templates to avoid complexity and delimiter conflicts | -| ✅ Accepted | 2025-07-08 | [MySQL over MariaDB](./mysql-over-mariadb.md) | Use MySQL 8.0 as default database backend over MariaDB for upstream compatibility | -| ✅ Accepted | - | [LXD over Multipass](./lxd-over-multipass.md) | Choose LXD containers over Multipass VMs for deployment testing | -| ✅ Resolved | - | [Docker Testing Evolution](./docker-testing-evolution.md) | Evolution from Docker rejection to hybrid approach for split E2E testing | -| ✅ Accepted | - | [Meson Removal](./meson-removal.md) | Remove Meson build system from the project | +| Status | Date | Decision | Summary | +| ------------- | ---------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | +| ✅ Accepted | 2026-04-10 | [Track Cargo.lock for Application Reproducibility](./cargo-lock-tracking-for-application-reproducibility.md) | Track Cargo.lock in Git for deterministic app and CI dependency resolution | +| ✅ Accepted | 2026-04-07 | [SSH Key Passphrase Detection](./ssh-key-passphrase-detection.md) | Detect passphrase-protected keys via byte inspection; reject ssh-keygen probe approach | +| ✅ Accepted | 2026-02-26 | [SDK Package Naming](./sdk-package-naming.md) | Keep "SDK" name for packages/sdk — the modern API-wrapper meaning is industry-standard | +| ✅ Accepted | 2026-02-24 | [SDK Presentation Layer Interface Design](./sdk-presentation-layer-interface-design.md) | Return () from SDK operations; no domain types or typestate pattern in the SDK public API | +| ✅ Accepted | 2026-02-24 | [Docker Compose Local Validation Placement](./docker-compose-local-validation-placement.md) | Validate rendered docker-compose.yml in the infrastructure generator, not the app layer | +| ✅ Accepted | 2026-02-17 | [Application-Layer Progress Reporting Trait](./application-layer-progress-reporting-trait.md) | Use trait-based progress reporting with Dependency Inversion for testable, reusable design | +| ✅ Accepted | 2026-02-13 | [Concurrent Docker Image Builds in Tests](./concurrent-docker-image-builds-in-tests.md) | Treat "already exists" tagging error as success when parallel tests build same image | +| ✅ Accepted | 2026-02-06 | [Agent Skills Content Strategy](./skill-content-strategy-duplication-vs-linking.md) | Three-tier content strategy: self-contained workflows, progressive disclosure, linked docs | +| ✅ Accepted | 2026-01-27 | [Atomic Ansible Playbooks](./atomic-ansible-playbooks.md) | Require one-responsibility playbooks with Rust-side gating and registered static templates | +| ✅ Accepted | 2026-01-24 | [Bind Mount Standardization](./bind-mount-standardization.md) | Use bind mounts exclusively for all Docker Compose volumes for observability and backup | +| ✅ Accepted | 2026-01-21 | [TryFrom for DTO to Domain Conversion](./tryfrom-for-dto-to-domain-conversion.md) | Use standard TryFrom trait for self-documenting, discoverable DTO→Domain conversions | +| ✅ Accepted | 2026-01-21 | [Validated Deserialization for Domain Types](./validated-deserialization-for-domain-types.md) | Use custom Deserialize impl with Raw struct to enforce domain invariants during parsing | +| ✅ Accepted | 2026-01-20 | [Grafana Default Port 3000](./grafana-default-port-3000.md) | Use Grafana's default port 3000 instead of 3100 for dedicated VM deployments | +| ✅ Accepted | 2026-01-20 | [Caddy for TLS Termination](./caddy-for-tls-termination.md) | Use Caddy v2.10 as TLS proxy for automatic HTTPS with WebSocket support | +| ✅ Accepted | 2026-01-20 | [Per-Service TLS Configuration](./per-service-tls-configuration.md) | Use domain + use_tls_proxy fields instead of nested tls section for explicit TLS opt-in | +| ✅ Accepted | 2026-01-20 | [Uniform HTTP Tracker TLS Requirement](./uniform-http-tracker-tls-requirement.md) | All HTTP trackers must use same TLS setting due to tracker's global on_reverse_proxy | +| ✅ Accepted | 2026-01-10 | [Hetzner SSH Key Dual Injection Pattern](./hetzner-ssh-key-dual-injection.md) | Use both OpenTofu SSH key and cloud-init for debugging capability with manual hardening | +| ✅ Accepted | 2026-01-10 | [Configuration and Data Directories as Secrets](./configuration-directories-as-secrets.md) | Treat envs/, data/, build/ as secrets; no env var injection; users secure via permissions | +| ✅ Accepted | 2026-01-07 | [Configuration DTO Layer Placement](./configuration-dto-layer-placement.md) | Keep configuration DTOs in application layer, not domain; defer package extraction | +| ✅ Accepted | 2025-12-23 | [Docker Security Scan Exit Code Zero](./docker-security-scan-exit-code-zero.md) | Use exit-code 0 for security scanning - Trivy detects, GitHub Security decides, CI green | +| ✅ Accepted | 2025-12-20 | [Grafana Integration Pattern](./grafana-integration-pattern.md) | Enable Grafana by default with hard Prometheus dependency and environment variable config | +| ✅ Accepted | 2025-12-17 | [Secrecy Crate for Sensitive Data Handling](./secrecy-crate-for-sensitive-data.md) | Use secrecy crate for type-safe secret handling with memory zeroing | +| ✅ Accepted | 2025-12-14 | [Database Configuration Structure in Templates](./database-configuration-structure-in-templates.md) | Expose structured database fields in templates rather than pre-resolved connection strings | +| ✅ Accepted | 2025-12-13 | [Environment Variable Injection in Docker Compose](./environment-variable-injection-in-docker-compose.md) | Use .env file injection instead of hardcoded values for runtime configuration changes | +| ✅ Accepted | 2025-12-11 | [Cloud-Init SSH Port Configuration with Reboot](./cloud-init-ssh-port-reboot.md) | Use cloud-init with reboot pattern to configure custom SSH ports during VM provisioning | +| ✅ Accepted | 2025-12-10 | [Single Docker Image for Sequential E2E Command Testing](./single-docker-image-sequential-testing.md) | Use single Docker image with sequential command execution instead of multi-image phases | +| ✅ Accepted | 2025-12-09 | [Register Command SSH Port Override](./register-ssh-port-override.md) | Add optional --ssh-port argument to register command for non-standard SSH ports | +| ✅ Accepted | 2025-11-19 | [Disable MD060 Table Formatting Rule](./md060-table-formatting-disabled.md) | Disable MD060 to allow flexible table formatting and emoji usage | +| ✅ Accepted | 2025-11-19 | [Test Command as Smoke Test](./test-command-as-smoke-test.md) | Test command validates running services, not infrastructure components | +| ✅ Accepted | 2025-11-13 | [Migration to AGENTS.md Standard](./agents-md-migration.md) | Adopt open AGENTS.md standard for multi-agent compatibility while keeping GitHub redirect | +| ✅ Accepted | 2025-11-11 | [Use ReentrantMutex Pattern for UserOutput Reentrancy](./reentrant-mutex-useroutput-pattern.md) | Use Arc<ReentrantMutex<RefCell<UserOutput>>> to fix same-thread deadlock in issue #164 | +| ❌ Superseded | 2025-11-11 | [Remove UserOutput Mutex](./user-output-mutex-removal.md) | Remove Arc<Mutex<UserOutput>> pattern for simplified, deadlock-free architecture | +| ✅ Accepted | 2025-11-07 | [ExecutionContext Wrapper Pattern](./execution-context-wrapper.md) | Use ExecutionContext wrapper around Container for future-proof command signatures | +| ✅ Accepted | 2025-11-03 | [Environment Variable Prefix](./environment-variable-prefix.md) | Use `TORRUST_TD_` prefix for all environment variables | +| ✅ Accepted | 2025-10-15 | [External Tool Adapters Organization](./external-tool-adapters-organization.md) | Consolidate external tool wrappers in `src/adapters/` for better discoverability | +| ✅ Accepted | 2025-10-10 | [Repository Rename to Deployer](./repository-rename-to-deployer.md) | Rename from "Torrust Tracker Deploy" to "Torrust Tracker Deployer" for production use | +| ✅ Accepted | 2025-10-03 | [Error Context Strategy](./error-context-strategy.md) | Use structured error context with trace files for complete error information | +| ✅ Accepted | 2025-10-03 | [Command State Return Pattern](./command-state-return-pattern.md) | Commands return typed states (Environment<S> → Environment<T>) for compile-time safety | +| ✅ Accepted | 2025-10-03 | [Actionable Error Messages](./actionable-error-messages.md) | Use tiered help system with brief tips + .help() method for detailed troubleshooting | +| ✅ Accepted | 2025-10-01 | [Type Erasure for Environment States](./type-erasure-for-environment-states.md) | Use enum-based type erasure to enable runtime handling and serialization of typed states | +| ✅ Accepted | 2025-09-29 | [Test Context vs Deployment Environment Naming](./test-context-vs-deployment-environment-naming.md) | Rename TestEnvironment to TestContext to avoid conflicts with multi-environment feature | +| ✅ Accepted | 2025-09-10 | [LXD VMs over Containers](./lxd-vm-over-containers.md) | Use LXD virtual machines instead of containers for production alignment | +| ✅ Accepted | 2025-09-09 | [Tera Minimal Templating Strategy](./tera-minimal-templating-strategy.md) | Use Tera with minimal variables and templates to avoid complexity and delimiter conflicts | +| ✅ Accepted | 2025-07-08 | [MySQL over MariaDB](./mysql-over-mariadb.md) | Use MySQL 8.0 as default database backend over MariaDB for upstream compatibility | +| ✅ Accepted | - | [LXD over Multipass](./lxd-over-multipass.md) | Choose LXD containers over Multipass VMs for deployment testing | +| ✅ Resolved | - | [Docker Testing Evolution](./docker-testing-evolution.md) | Evolution from Docker rejection to hybrid approach for split E2E testing | +| ✅ Accepted | - | [Meson Removal](./meson-removal.md) | Remove Meson build system from the project | ## ADR Template diff --git a/docs/decisions/cargo-lock-tracking-for-application-reproducibility.md b/docs/decisions/cargo-lock-tracking-for-application-reproducibility.md new file mode 100644 index 000000000..60e008a59 --- /dev/null +++ b/docs/decisions/cargo-lock-tracking-for-application-reproducibility.md @@ -0,0 +1,79 @@ +# Decision: Track Cargo.lock for Application Reproducibility + +## Status + +✅ Accepted + +## Date + +2026-04-10 + +## Context + +The repository is a Rust workspace that includes reusable packages and executable +application binaries. CI workflows clone the repository and run tasks that depend +on deterministic dependency resolution. + +`Cargo.lock` was excluded via `.gitignore`, so GitHub runners did not receive it +on checkout. This caused workflow failures and made dependency resolution +non-deterministic across developer machines and CI runs. + +For Rust libraries published to crates.io, omitting `Cargo.lock` is often +recommended. For applications, committing `Cargo.lock` is recommended to keep +builds reproducible. + +This repository is app-first from an operations perspective: users clone the repo +and run deployer binaries and automation workflows directly. + +## Decision + +Stop ignoring `Cargo.lock` and commit it to the repository. + +Specifically: + +- Remove the `Cargo.lock` ignore rule from `.gitignore`. +- Keep `Cargo.lock` versioned in Git so all environments resolve to the same + dependency graph. + +## Consequences + +Positive: + +- Reproducible dependency resolution in local development and CI. +- GitHub Actions runners receive `Cargo.lock` after checkout, preventing missing + lockfile failures. +- Fewer "works on my machine" differences caused by floating transitive versions. + +Negative / Risks: + +- Lockfile updates create larger diffs and may need periodic refreshes. +- Contributors touching dependencies may need to resolve lockfile merge conflicts. + +## Alternatives Considered + +1. Keep ignoring `Cargo.lock` and generate it in CI. + +Rejected because: + +- CI and local builds can drift as transitive dependencies change. +- Adds avoidable complexity to workflows and troubleshooting. + +1. Keep ignoring `Cargo.lock` because the workspace includes library crates. + +Rejected because: + +- The repository's primary usage is as a runnable application and deployment tool. +- Application reproducibility is more important than library-only conventions. + +## Related Decisions + +- [SDK Package Naming](./sdk-package-naming.md) +- [Application-Layer Progress Reporting Trait](./application-layer-progress-reporting-trait.md) + +## References + +- Rust Cargo book: https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +- Workflow affected by lockfile presence: + - `.github/workflows/cargo-security-audit.yml` + - `.github/workflows/test-dependency-installer.yml` + - `.github/workflows/container.yaml` diff --git a/docs/deployments/hetzner-demo-tracker/prerequisites.md b/docs/deployments/hetzner-demo-tracker/prerequisites.md index 7b4e55868..f875991c2 100644 --- a/docs/deployments/hetzner-demo-tracker/prerequisites.md +++ b/docs/deployments/hetzner-demo-tracker/prerequisites.md @@ -86,7 +86,7 @@ The image starts, prints tool versions, and shows the CLI help. Tool versions in All dependencies verified with: ```bash -cargo run -p torrust-dependency-installer --bin dependency-installer check +cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer check ``` - [x] **Rust toolchain** — `rustc 1.96.0-nightly (ec818fda3 2026-03-02)` diff --git a/docs/issues/250-epic-docker-image-vulnerability-scanning.md b/docs/issues/250-epic-docker-image-vulnerability-scanning.md deleted file mode 100644 index bb66b606e..000000000 --- a/docs/issues/250-epic-docker-image-vulnerability-scanning.md +++ /dev/null @@ -1,240 +0,0 @@ -# Implement Automated Docker Image Vulnerability Scanning - -**Issue**: #250 (Epic) -**Parent Epic**: N/A (Independent security initiative) -**Related**: - -- Security best practices -- CI/CD pipeline improvements -- Subissue #251: Implement Basic Trivy Scanning Workflow -- Subissue #252: Implement Dynamic Image Detection for Scanning - -## Overview - -This epic implements automated vulnerability scanning for all Docker images used in the project using Trivy. The implementation is divided into two phases: - -1. **Basic Scanning**: Hardcoded image list with periodic and PR-triggered scans -2. **Dynamic Scanning**: Automatically detect images from environment configuration - -## Problem Statement - -Currently, the project uses multiple Docker images without automated vulnerability scanning: - -- `docker/provisioned-instance/Dockerfile` -- `docker/ssh-server/Dockerfile` -- `templates/docker-compose/docker-compose.yml.tera` - -This creates security risks as vulnerabilities in these images go undetected until discovered manually or exploited. - -## Container Images in Scope - -### Project-Built Images - -These images are built from Dockerfiles in this repository: - -1. **Provisioned Instance**: `torrust-tracker-deployer/provisioned-instance` - - - Source: `docker/provisioned-instance/Dockerfile` - - Purpose: Test container for E2E deployment testing - -2. **SSH Server**: `torrust-tracker-deployer/ssh-server` - - Source: `docker/ssh-server/Dockerfile` - - Purpose: Mock SSH server for integration testing - -### Third-Party Images - -These images are referenced in Docker Compose templates: - -1. **Torrust Tracker**: `torrust/tracker:develop` - - - Purpose: BitTorrent tracker application - - Source template: `templates/docker-compose/docker-compose.yml.tera` - -2. **MySQL Database**: `mysql:8.0` - - - Purpose: Tracker database backend - - Source template: `templates/docker-compose/docker-compose.yml.tera` - -3. **Grafana**: `grafana/grafana:11.4.0` - - - Purpose: Metrics visualization dashboard - - Source template: `templates/docker-compose/docker-compose.yml.tera` - -4. **Prometheus**: `prom/prometheus:v3.0.1` - - Purpose: Metrics collection and monitoring - - Source template: `templates/docker-compose/docker-compose.yml.tera` - -**Total Images**: 6 (2 project-built + 4 third-party) - -## Current Vulnerability Status - -> **Scan Date**: 2025-12-22 -> **Scanner**: Trivy (latest) -> **Severity Filter**: HIGH, CRITICAL - -### Project-Built Images - -#### 1. torrust-tracker-deployer/provisioned-instance:latest - -```text -Status: ✅ CLEAN -Total: 0 (HIGH: 0, CRITICAL: 0) -Base OS: Ubuntu 24.04 -Notes: No HIGH or CRITICAL vulnerabilities detected -``` - -#### 2. torrust-tracker-deployer/ssh-server:latest - -```text -Status: ✅ CLEAN -Total: 0 (HIGH: 0, CRITICAL: 0) -Base OS: Alpine 3.22.2 -Notes: No HIGH or CRITICAL vulnerabilities detected -``` - -### Third-Party Images - -#### 3. torrust/tracker:develop - -```text -Status: ⚠️ VULNERABILITIES FOUND -Total: 5 (HIGH: 4, CRITICAL: 1) -Base OS: Debian 12.11 - -CRITICAL Vulnerabilities: -- CVE-2019-1010022 (libc6): glibc stack guard protection bypass - Installed: 2.36-9+deb12u10 - Fixed: Not available - -HIGH Vulnerabilities: -- CVE-2018-20796 (libc6): glibc uncontrolled recursion in check_dst_limits_calc_pos_1 - Installed: 2.36-9+deb12u10 - Fixed: Not available - -- CVE-2019-1010023 (libc6): glibc running ldd on malicious ELF leads to code execution - Installed: 2.36-9+deb12u10 - Fixed: Not available - -- CVE-2019-9192 (libc6): glibc uncontrolled recursion in check_dst_limits_calc_pos_1 - Installed: 2.36-9+deb12u10 - Fixed: Not available - -- CVE-2023-0286 (libssl3): X.400 address type confusion in X.509 GeneralName - Installed: 3.0.16-1~deb12u1 - Fixed: Not available - -⚠️ Action Required: These are known glibc/openssl CVEs without available patches. -Consider evaluating risk vs. functionality trade-offs. -``` - -#### 4. mysql:8.0 - -```text -Status: ✅ CLEAN -Total: 0 (HIGH: 0, CRITICAL: 0) -Base OS: Oracle Linux 9.5 -Notes: No HIGH or CRITICAL vulnerabilities detected -Warning: OS version no longer supported by distribution -``` - -#### 5. grafana/grafana:11.4.0 - -```text -Status: ✅ CLEAN -Total: 0 (HIGH: 0, CRITICAL: 0) -Base OS: Alpine 3.20.3 -Notes: No HIGH or CRITICAL vulnerabilities detected -Warning: OS version no longer supported by distribution -``` - -#### 6. prom/prometheus:v3.0.1 - -```text -Status: ✅ CLEAN -Total: 0 (HIGH: 0, CRITICAL: 0) -Notes: No HIGH or CRITICAL vulnerabilities detected -Warning: OS not detected (distroless or scratch-based image) -``` - -### Summary - -- **Clean Images**: 5/6 (83%) -- **Images with Vulnerabilities**: 1/6 (17%) -- **Total HIGH Vulnerabilities**: 4 -- **Total CRITICAL Vulnerabilities**: 1 -- **Images Requiring Attention**: torrust/tracker:develop - -### Recommended Actions - -1. **Immediate**: Document the known vulnerabilities in `torrust/tracker:develop` as accepted risks or plan mitigation -2. **Short-term**: Implement Phase 1 (basic scanning workflow) to prevent introduction of new vulnerable images -3. **Medium-term**: Monitor for patches to the identified CVEs in glibc and openssl -4. **Long-term**: Implement Phase 2 (dynamic detection) for maintainable scanning - -## Solution Approach - -### Phase 1: Basic Scanning (Subissue 1) - -Implement Trivy-based scanning workflow with: - -- Hardcoded list of images to scan -- Scan on PR and push events -- Periodic scanning (e.g., daily/weekly) -- Fail build on HIGH/CRITICAL vulnerabilities -- Generate vulnerability reports - -### Phase 2: Dynamic Scanning (Subissue 2) - -Make scanning dynamic and maintainable: - -- Extract Docker images from environment configuration -- Store image references in environment data structure -- Use `show` command to expose image information -- Update workflow to dynamically detect images -- Eliminate manual image list maintenance - -## Tasks - -- [ ] #X - Implement basic Trivy scanning workflow with hardcoded images -- [ ] #X - Implement dynamic image detection using environment configuration - -## Benefits - -**Security**: - -- Continuous vulnerability monitoring -- Early detection of security issues -- Automated security compliance - -**Maintainability**: - -- No manual image list updates (Phase 2) -- Consistent scanning across all images -- Integration with existing environment management - -**Development Workflow**: - -- Fail fast on vulnerable images -- Clear security status in PRs -- Periodic monitoring for new vulnerabilities - -## Related Documentation - -- GitHub Actions workflows: `.github/workflows/` -- Docker files: `docker/` -- Docker Compose template: `templates/docker-compose/docker-compose.yml.tera` -- Environment show command: `docs/issues/241-implement-environment-show-command.md` -- Trivy documentation: https://github.com/aquasecurity/trivy - -## Timeline - -- **Phase 1**: 2-4 hours - Immediate security improvement -- **Phase 2**: 4-6 hours - Long-term maintainability (depends on #241) - -## Success Criteria - -- [ ] All Docker images scanned automatically -- [ ] HIGH/CRITICAL vulnerabilities block builds -- [ ] Periodic scans detect new vulnerabilities -- [ ] Dynamic detection eliminates manual maintenance -- [ ] Documentation updated for security workflow diff --git a/docs/issues/252-implement-dynamic-image-detection-for-scanning.md b/docs/issues/252-implement-dynamic-image-detection-for-scanning.md deleted file mode 100644 index 421d4380a..000000000 --- a/docs/issues/252-implement-dynamic-image-detection-for-scanning.md +++ /dev/null @@ -1,586 +0,0 @@ -# Implement Dynamic Image Detection for Vulnerability Scanning - -**Issue**: #252 -**Parent Epic**: #250 - Implement Automated Docker Image Vulnerability Scanning -**Related**: - -- Epic specification: `docs/issues/250-epic-docker-image-vulnerability-scanning.md` -- Subissue 1: #251 - `docs/issues/251-implement-basic-trivy-scanning-workflow.md` -- Show command: #241 - `docs/issues/241-implement-environment-show-command.md` -- Docker Compose template: `templates/docker-compose/docker-compose.yml.tera` - -## Overview - -Enhance the Trivy scanning workflow to dynamically detect Docker images from environment configuration instead of using a hardcoded list. This eliminates manual maintenance and ensures the workflow automatically adapts when images change. - -The solution leverages the `show` command (issue #241) to expose Docker image information stored in the environment data structure. - -## Goals - -- [ ] Convert hardcoded Docker Compose image references to Tera variables -- [ ] Store Docker image references in environment data structure -- [ ] Expose image information through `show` command -- [ ] Update Trivy workflow to dynamically detect images -- [ ] Eliminate manual image list maintenance - -## 🏗️ Architecture Requirements - -**DDD Layers**: Domain + Application + Infrastructure (CI/CD) - -**Module Paths**: - -- `src/domain/environment/mod.rs` - Add image information to domain model -- `src/infrastructure/external_tools/ansible/template/renderer/` - Update template variables -- `src/application/commands/show/` - Expose image information -- `templates/docker-compose/docker-compose.yml.tera` - Use variables for images -- `.github/workflows/docker-security-scan.yml` - Dynamic image detection - -**Patterns**: - -- Domain Layer: Value Objects for Docker image references -- Application Layer: Extend show command with image information -- Infrastructure Layer: Template rendering with constants -- CI/CD: Dynamic workflow based on environment output - -### Module Structure Requirements - -- [ ] Follow DDD layer separation (see [docs/codebase-architecture.md](../codebase-architecture.md)) -- [ ] Domain model owns image configuration (immutable, validation) -- [ ] Application layer extracts and formats image data -- [ ] Infrastructure layer handles template rendering -- [ ] Use appropriate module organization (see [docs/contributing/module-organization.md](../contributing/module-organization.md)) - -### Architectural Constraints - -- [ ] Image versions are **not** user-configurable (compatibility concerns) -- [ ] Images stored as constants in code (single source of truth) -- [ ] Template variables injected from constants (not from user config) -- [ ] Show command uses public API only (no template internals) -- [ ] Workflow uses CLI interface only (no direct file access) - -### Anti-Patterns to Avoid - -- ❌ Allowing users to override Docker image versions (compatibility risk) -- ❌ Duplicating image information across multiple files -- ❌ Hardcoding images in both template and workflow -- ❌ Workflow parsing template files directly (use show command) -- ❌ Exposing template implementation details through show command - -## Specifications - -### Docker Image References to Extract - -From `templates/docker-compose/docker-compose.yml.tera`: - -1. **Tracker**: `torrust/tracker:develop` -2. **MySQL**: `mysql:8.0` -3. **Grafana**: `grafana/grafana:11.4.0` -4. **Prometheus**: `prom/prometheus:v3.0.1` - -### Domain Model Changes - -#### New Value Objects - -**Location**: `src/shared/docker_image.rs` (or `src/domain/docker_image.rs`) - -```rust -use std::fmt; - -/// Docker image reference with repository and tag -#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub struct DockerImage { - repository: String, - tag: String, -} - -impl DockerImage { - pub fn new(repository: impl Into<String>, tag: impl Into<String>) -> Self { - Self { - repository: repository.into(), - tag: tag.into(), - } - } - - pub fn repository(&self) -> &str { - &self.repository - } - - pub fn tag(&self) -> &str { - &self.tag - } - - /// Returns the full image reference (e.g., "torrust/tracker:develop") - pub fn full_reference(&self) -> String { - format!("{}:{}", self.repository, self.tag) - } -} - -impl fmt::Display for DockerImage { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}:{}", self.repository, self.tag) - } -} - -impl From<(&str, &str)> for DockerImage { - fn from((repository, tag): (&str, &str)) -> Self { - Self::new(repository, tag) - } -} -``` - -#### Update Service Configurations - -Each service configuration should own its Docker image information. - -**Location**: `src/domain/tracker/config.rs` - -```rust -use crate::shared::docker_image::DockerImage; - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct TrackerConfig { - // ... existing fields ... - pub docker_image: DockerImage, -} - -impl Default for TrackerConfig { - fn default() -> Self { - Self { - // ... existing defaults ... - docker_image: DockerImage::new("torrust/tracker", "develop"), - } - } -} -``` - -**Location**: `src/domain/tracker/database/mod.rs` - -```rust -use crate::shared::docker_image::DockerImage; - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct DatabaseConfig { - // ... existing fields ... - pub docker_image: DockerImage, -} - -impl DatabaseConfig { - pub fn default_mysql() -> Self { - Self { - // ... existing defaults ... - docker_image: DockerImage::new("mysql", "8.0"), - } - } -} -``` - -**Location**: `src/domain/prometheus/config.rs` - -```rust -use crate::shared::docker_image::DockerImage; - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct PrometheusConfig { - // ... existing fields ... - pub docker_image: DockerImage, -} - -impl Default for PrometheusConfig { - fn default() -> Self { - Self { - // ... existing defaults ... - docker_image: DockerImage::new("prom/prometheus", "v3.0.1"), - } - } -} -``` - -**Location**: `src/domain/grafana/config.rs` - -```rust -use crate::shared::docker_image::DockerImage; - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct GrafanaConfig { - // ... existing fields ... - pub docker_image: DockerImage, -} - -impl Default for GrafanaConfig { - fn default() -> Self { - Self { - // ... existing defaults ... - docker_image: DockerImage::new("grafana/grafana", "11.4.0"), - } - } -} -``` - -### Template Changes - -#### Update Docker Compose Template - -**Location**: `templates/docker-compose/docker-compose.yml.tera` - -The template context already provides service configurations through `DockerComposeContext`. -Update the template to use the new `docker_image` field from service configurations: - -```yaml -services: - tracker: - image: {{ tracker.docker_image.repository }}:{{ tracker.docker_image.tag }} - # ... rest of config ... - -{% if database.driver == "mysql" %} - mysql: - image: {{ database.docker_image.repository }}:{{ database.docker_image.tag }} - # ... rest of config ... -{% endif %} - -{% if prometheus_config %} - prometheus: - image: {{ prometheus_config.docker_image.repository }}:{{ prometheus_config.docker_image.tag }} - # ... rest of config ... -{% endif %} - -{% if grafana_config %} - grafana: - image: {{ grafana_config.docker_image.repository }}:{{ grafana_config.docker_image.tag }} - # ... rest of config ... -{% endif %} -``` - -#### Update Template Context - -**Location**: `src/infrastructure/templating/docker_compose/template/wrappers/docker_compose/context/mod.rs` - -The `DockerComposeContext` already has `prometheus_config` and `grafana_config` which will include the `docker_image` field. -For tracker and database, add dedicated image fields to the context: - -```rust -#[derive(Serialize, Debug, Clone)] -pub struct DockerComposeContext { - /// Database configuration (global - used by multiple services) - pub database: DatabaseConfig, - /// Tracker port configuration (global - used by multiple services) - pub ports: TrackerPorts, - /// Tracker configuration - includes docker_image field - pub tracker: TrackerConfig, - /// Prometheus configuration (optional) - includes docker_image field - #[serde(skip_serializing_if = "Option::is_none")] - pub prometheus_config: Option<PrometheusConfig>, - /// Grafana configuration (optional) - includes docker_image field - #[serde(skip_serializing_if = "Option::is_none")] - pub grafana_config: Option<GrafanaConfig>, -} -``` - -Update the builder in `context/builder.rs`: - -```rust -impl DockerComposeContextBuilder { - pub fn with_tracker(mut self, tracker: TrackerConfig) -> Self { - self.tracker = Some(tracker); - self - } -} -``` - -````rust -impl ProjectGenerator for DockerComposeProjectGenerator { - fn generate(&self, environment: &Environment) -> Result<(), ProjectGeneratorError> { - // ... existing code ... - - let mut context = tera::Context::new(); - - // Service configs already include docker_image field - context.insert("tracker", &environment.tracker_config); - context.insert("database", &environment.database_config); - context.insert("prometheus_config", &environment.prometheus_config); - context.insert("grafana_config", &environment.grafana_config); -### Show Command Enhancement - -#### Add Image Information to Output - -**Location**: `src/application/commands/show/formatter.rs` - -Add section for Docker images by extracting from service configurations: - -```rust -impl EnvironmentFormatter { - fn format_docker_images( - &self, - tracker: &TrackerConfig, - database: &DatabaseConfig, - prometheus: Option<&PrometheusConfig>, - grafana: Option<&GrafanaConfig>, - ) -> String { - let mut lines = vec![ - format!("Tracker: {}", tracker.docker_image.full_reference()), - format!("Database: {}", database.docker_image.full_reference()), - ]; - - if let Some(prom) = prometheus { - lines.push(format!("Prometheus: {}", prom.docker_image.full_reference())); - } - - if let Some(graf) = grafana { - lines.push(format!("Grafana: {}", graf.docker_image.full_reference())); - } - - format!("Docker Images:\n {}", lines.join("\n ")) - } -} -```` - -#### Example Output - -```bash -$ torrust-tracker-deployer show my-environment - -Environment: my-environment -State: Running -Provider: LXD - -# ... existing output ... - -Docker Images: - Tracker: torrust/tracker:develop - MySQL: mysql:8.0 - Grafana: grafana/grafana:11.4.0 - Prometheus: prom/prometheus:v3.0.1 - -# ... rest of output ... -``` - -### Workflow Update - -#### Update Trivy Workflow to Use Show Command - -**Location**: `.github/workflows/docker-security-scan.yml` - -Add dynamic image detection: - -```yaml -jobs: - extract-images: - name: Extract Docker Images from Environment - runs-on: ubuntu-latest - outputs: - images: ${{ steps.extract.outputs.images }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 - - - name: Build deployer CLI - run: cargo build --release - - - name: Create test environment - run: | - # Create minimal environment config for image extraction - cat > /tmp/test-env.json <<EOF - { - "name": "ci-image-scan", - "provider": { - "type": "lxd", - "ssh": { - "username": "ubuntu", - "private_key_path": "/tmp/key", - "public_key_path": "/tmp/key.pub" - } - } - } - EOF - - # Create environment (doesn't provision, just stores config) - ./target/release/torrust-tracker-deployer create --env-file /tmp/test-env.json - - - name: Extract Docker images - id: extract - run: | - # Run show command and parse output for Docker images - images=$(./target/release/torrust-tracker-deployer show ci-image-scan \ - | grep -A 5 "Docker Images:" \ - | grep -E "^\s+(Tracker|Database|Grafana|Prometheus):" \ - - scan-extracted-images: - name: Scan Extracted Docker Images - needs: extract-images - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - image: ${{ fromJson(needs.extract-images.outputs.images) }} - steps: - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@master - with: - image-ref: ${{ matrix.image }} - format: "sarif" - output: "trivy-results.sarif" - severity: "HIGH,CRITICAL" - exit-code: "1" - - - name: Upload Trivy results - uses: github/codeql-action/upload-sarif@v3 - if: always() - with: - sarif_file: "trivy-results.sarif" -``` - -## Implementation Plan - -### Phase 1: Domain Model Changes (1.5 hours) - -- [ ] Create `DockerImage` value object in `src/shared/docker_image.rs` -- [ ] Add `docker_image` field to `TrackerConfig` in `src/domain/tracker/config.rs` -- [ ] Add `docker_image` field to `DatabaseConfig` in `src/domain/tracker/database/mod.rs` -- [ ] Add `docker_image` field to `PrometheusConfig` in `src/domain/prometheus/config.rs` -- [ ] Add `docker_image` field to `GrafanaConfig` in `src/domain/grafana/config.rs` -- [ ] Implement `Default` trait with image constants for each service -- [ ] Add unit tests for `DockerImage` value object -- [ ] Update environment serialization tests - -### Phase 2: Template Updates (1 hour) - -- [ ] Update Docker Compose template to use service-specific image variables -- [ ] Verify template renderer already passes service configs to context -- [ ] Test template rendering produces correct output -- [ ] Verify existing E2E tests still pass -- [ ] Update template documentation - -### Phase 3: Show Command Enhancement (1 hour) - -- [ ] Extend show command formatter to extract and display Docker images from service configs -- [ ] Add unit tests for image formatting -- [ ] Test show command output includes images -- [ ] Update show command documentation -- [ ] Add E2E test for show command with images - -### Phase 4: Workflow Update (1.5 hours) - -- [ ] Add `extract-images` job to workflow -- [ ] Parse show command output for images -- [ ] Convert image list to JSON array -- [ ] Update `scan-extracted-images` job to use dynamic list -- [ ] Remove hardcoded image list from workflow -- [ ] Test workflow extracts correct images - -### Phase 5: Testing and Documentation (1 hour) - -- [ ] Test complete workflow end-to-end -- [ ] Verify workflow adapts when images change -- [ ] Update security documentation -- [ ] Add architecture decision record (ADR) if needed -- [ ] Update troubleshooting guide - -## Acceptance Criteria - -> **Note for Contributors**: These criteria define what the PR reviewer will check. Use this as your pre-review checklist before submitting the PR to minimize back-and-forth iterations. - -**Quality Checks**: - -- [ ] Pre-commit checks pass: `./scripts/pre-commit.sh` -- [ ] All unit tests pass -- [ ] All E2E tests pass -- [ ] Template rendering tests pass - -**Functional Requirements**: - -- [ ] Docker images stored in service configurations with constants -- [ ] Each service config (`TrackerConfig`, `DatabaseConfig`, `PrometheusConfig`, `GrafanaConfig`) has `docker_image` field -- [ ] Template uses service-specific image variables instead of hardcoded values -- [ ] Show command displays Docker image information from service configs -- [ ] Workflow dynamically extracts images from show command -- [ ] Changing an image constant in service config updates workflow automatically -- [ ] No manual image list maintenance required - -**Domain Model**: - -- [ ] `DockerImage` value object validates image references -- [ ] Images stored in each service configuration independently -- [ ] Default images set from constants in service `Default` implementations -- [ ] Images serialized/deserialized correctly as part of service configs - -**Show Command**: - -- [ ] Show command output includes "Docker Images:" section -- [ ] Images displayed with full reference (repo:tag) -- [ ] Output parseable by workflow script -- [ ] Works for all environment states - -**Workflow**: - -- [ ] `extract-images` job successfully creates environment -- [ ] Image extraction from show command works correctly -- [ ] JSON array conversion succeeds -- [ ] Workflow scans all extracted images -- [ ] No hardcoded image list remains - -**Testing**: - -- [ ] Unit tests for `DockerImage` value object -- [ ] Unit tests for show command image formatting -- [ ] E2E test for show command with images -- [ ] Manual test: change image constant and verify workflow updates - -**Documentation**: - -- [ ] Show command documentation updated -- [ ] Workflow documentation explains dynamic detection -- [ ] Architecture decision documented (if needed) -- [ ] Troubleshooting guide updated - -## Related Documentation - -- Show command specification: `docs/issues/241-implement-environment-show-command.md` -- Codebase architecture: `docs/codebase-architecture.md` -- DDD layer placement: `docs/contributing/ddd-layer-placement.md` -- Template system: `docs/contributing/templates/template-system-architecture.md` -- Docker Compose template: `templates/docker-compose/docker-compose.yml.tera` - -## Notes - -### Why Not Allow User Configuration? - -Docker image versions are **not** user-configurable for several reasons: - -1. **Compatibility**: Image versions must be tested together (tracker, database, monitoring) -2. **Support**: Troubleshooting requires known working combinations -3. **Security**: We control which versions are used and can enforce security updates - -Users who need custom images can modify the code (it's open source) but this is intentionally not exposed as a configuration option. - -### Alternative Approaches Considered - -#### 1. Parse Template Files Directly - -**Rejected**: Couples workflow to template implementation details. If we move or reorganize templates, workflow breaks. - -#### 2. Separate Configuration File - -**Rejected**: Creates duplication. Images would be defined in both template and config file. Single source of truth is better. - -#### 3. Workflow Script Extracts from Template - -**Rejected**: Requires workflow to understand Tera syntax. Using public CLI interface (show command) is cleaner. - -### Dependency on Issue #241 - -This task depends on issue #241 (implement show command) being completed first. The show command provides the public interface for exposing environment information. - -**If #241 is not ready**: - -- Implement minimal show command just for images -- Focus on domain model and template changes -- Defer workflow update to later - -### Future Enhancements - -1. **Image Version Validation**: Add checks for known vulnerable versions -2. **Image Pinning**: Use SHA256 digests instead of tags for reproducibility -3. **Custom Image Registries**: Support private registries for enterprise deployments -4. **Image Build Information**: Include build date, source commit in environment data diff --git a/docs/issues/304-clippy-large-stack-arrays-false-positive.md b/docs/issues/304-clippy-large-stack-arrays-false-positive.md deleted file mode 100644 index de82f998d..000000000 --- a/docs/issues/304-clippy-large-stack-arrays-false-positive.md +++ /dev/null @@ -1,114 +0,0 @@ -# Issue #304: Clippy `large_stack_arrays` False Positive with `vec![]` Macro - -**GitHub Issue**: <https://github.com/torrust/torrust-tracker-deployer/issues/304> - -## Problem - -Clippy reports a false positive `large_stack_arrays` lint when using the `vec![]` macro -to create vectors of `ServiceTopology` items in tests. - -### Error Message - -```text -error: allocating a local array larger than 16384 bytes - | - = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.93.0/index.html#large_stack_arrays - = note: `-D clippy::large-stack-arrays` implied by `-D clippy::pedantic` - = help: to override `-D clippy::pedantic` add `#[allow(clippy::large_stack_arrays)]` -``` - -### Root Cause - -This is a known clippy bug where the `vec![]` macro is incorrectly flagged for creating -a large stack array. The `vec![]` macro creates a `Vec<T>` which allocates on the heap, -not the stack. The lint is a false positive. - -**Upstream Issue**: <https://github.com/rust-lang/rust-clippy/issues/12586> - -### Affected Code - -Tests in `src/domain/topology/aggregate.rs` that create vectors of `ServiceTopology`: - -```rust -let topology = DockerComposeTopology::new(vec![ - ServiceTopology::with_networks(Service::Tracker, vec![Network::Database]), - ServiceTopology::with_networks(Service::MySQL, vec![Network::Database]), -]) -.unwrap(); -``` - -### Workarounds Attempted (Did Not Work) - -1. **Outer attribute on module**: `#[allow(clippy::large_stack_arrays)]` on `mod tests` -2. **Inner attribute on submodule**: `#![allow(clippy::large_stack_arrays)]` inside submodules -3. **Outer attribute on test function**: `#[allow(clippy::large_stack_arrays)]` on `#[test]` functions -4. **Inner attribute in function body**: `#![allow(clippy::large_stack_arrays)]` inside function - -None of these local suppression methods worked because the lint fires during macro -expansion before the allow attributes are processed. - -## Solution - -Add a **crate-level allow attribute** in `src/lib.rs`: - -```rust -// False positive: clippy reports large_stack_arrays for vec![] macro with ServiceTopology -// This is a known issue: https://github.com/rust-lang/rust-clippy/issues/12586 -#![allow(clippy::large_stack_arrays)] -``` - -This is the only approach that successfully suppresses the false positive. - -### Trade-offs - -- **Downside**: Suppresses the lint crate-wide, potentially hiding legitimate issues -- **Mitigation**: This lint is specifically for stack allocations. Since we use `Vec<T>` - (heap-allocated) throughout the codebase, legitimate triggers are unlikely -- **Future**: Remove this allow once the upstream clippy issue is fixed - -## Affected Rust Versions - -- Rust 1.93.0 stable (confirmed on GitHub Actions CI) -- Local nightly 1.95.0 does **not** reproduce the issue - -### Fix Timeline - -| Event | Date | Version | -| ---------------------------------------------------------------------------------------------- | -------------- | ---------------------------------- | -| Fix merged to clippy master ([PR #12624](https://github.com/rust-lang/rust-clippy/pull/12624)) | April 27, 2024 | - | -| Rust 1.79.0 released | June 13, 2024 | Nightly at merge time | -| Rust 1.80.0 released | July 25, 2024 | **Expected first stable with fix** | - -### Potential Regression - -The fix was merged in April 2024 and should be in Rust 1.80.0+. However, we're still -seeing this error on Rust 1.93.0 (January 2026). This suggests either: - -1. **Regression**: The bug may have regressed in a later clippy version -2. **Different code pattern**: Our `vec![]` with `ServiceTopology` (large struct with - `EnumSet` fields) might trigger a variant not covered by the original fix -3. **CI environment**: Some discrepancy in the clippy version used in CI - -Consider reporting this as a potential regression if the issue persists after verifying -the clippy version matches the expected behavior. - -## Related Clippy Issues - -Other `large_stack_arrays` issues (not directly related to our problem): - -- [#13774](https://github.com/rust-lang/rust-clippy/issues/13774) - No span for error (closed) -- [#13529](https://github.com/rust-lang/rust-clippy/issues/13529) - Nested const items (closed) -- [#9460](https://github.com/rust-lang/rust-clippy/issues/9460) - Static struct (closed) -- [#4520](https://github.com/rust-lang/rust-clippy/issues/4520) - Original lint creation (open) - -## References - -- Upstream clippy issue: <https://github.com/rust-lang/rust-clippy/issues/12586> -- Related PR: #303 (Phase 4 Service Topology DDD Alignment) - -## Labels - -- `bug` -- `workaround` -- `clippy` -- `technical-debt` diff --git a/docs/issues/405-deploy-hetzner-demo-tracker-and-document-process.md b/docs/issues/405-deploy-hetzner-demo-tracker-and-document-process.md deleted file mode 100644 index cf59f93ab..000000000 --- a/docs/issues/405-deploy-hetzner-demo-tracker-and-document-process.md +++ /dev/null @@ -1,130 +0,0 @@ -# Deploy Hetzner Demo Tracker and Document the Process - -**Issue**: #405 -**Parent Epic**: None -**Related**: [docs/user-guide/providers/hetzner/](../user-guide/providers/hetzner/), [docs/user-guide/quick-start/docker.md](../user-guide/quick-start/docker.md) - -## Overview - -Deploy a real Torrust Tracker demo instance to Hetzner Cloud using the deployer tool and document the entire process end-to-end. The documentation will serve two purposes: - -1. **Internal reference**: A deployment journal under `docs/deployments/hetzner-demo-tracker/` capturing every step, decision, and problem encountered during a real deployment. -2. **Blog post source**: The documented process will be adapted into a blog post for the [torrust.com](https://torrust.com) blog. - -## Domain Plan - -**Main domain**: `torrust-tracker-demo.com` - -Subdomains for individual services will be defined during the configuration phase. - -## Goals - -- [ ] Successfully deploy a Torrust Tracker demo instance to Hetzner Cloud -- [ ] Document every step of the deployment process with commands and outputs -- [ ] Capture all problems encountered with root causes and resolutions -- [ ] Produce documentation that can be adapted into a blog post - -## Documentation Structure - -A new `docs/deployments/` directory will be created for real-world deployment journals (distinct from the generic `docs/user-guide/` reference docs): - -```text -docs/deployments/ -└── hetzner-demo-tracker/ - ├── README.md # Main deployment journal (step-by-step walkthrough) - ├── prerequisites.md # Account setup, API tokens, SSH keys, tool installation - ├── configuration.md # Environment config decisions and sanitized examples - ├── problems.md # Issues encountered, root causes, and solutions - └── screenshots/ # Terminal output, Hetzner console, Grafana dashboards, etc. -``` - -### Why a separate `docs/deployments/` directory? - -- **Different nature**: User guides are reference docs; deployment journals are narratives with decisions, context, and real problems. -- **Blog-post-ready**: The journal format maps directly to a blog post structure. -- **Reusable pattern**: Future deployments (different providers, configs) get their own subdirectory. -- **No duplication**: Links to existing docs (Hetzner provider, command reference, quick-start) instead of repeating them. - -## Implementation Plan - -### Phase 1: Setup and Prerequisites - -- [x] Task 1.1: Create `docs/deployments/hetzner-demo-tracker/` directory structure -- [x] Task 1.2: Document prerequisites (Hetzner account, API token, SSH keys, tool versions) -- [x] Task 1.3: Verify all required tools are installed and working - -### Phase 2: Create and Configure Environment - -- [x] Task 2.1: Generate environment configuration template for Hetzner -- [x] Task 2.2: Document configuration decisions (server type, location, image, credentials) -- [x] Task 2.3: Create the environment using the deployer - -### Phase 3: Deploy the Tracker - -- [x] Task 3.1: Provision infrastructure (create Hetzner VM) -- [x] Task 3.2: Configure the instance (Docker, SSH, system setup) -- [x] Task 3.3: Release the application (deploy tracker files) -- [ ] Task 3.4: Run the services (start the tracker) - -### Phase 3.5: Post-Provision Manual Setup - -Steps required after provisioning and before running `configure`. -See [`docs/deployments/hetzner-demo-tracker/post-provision/`](../deployments/hetzner-demo-tracker/post-provision/README.md). - -**DNS Setup** ([dns-setup.md](../deployments/hetzner-demo-tracker/post-provision/dns-setup.md)): - -- [x] Task 3.5.1: Assign IPv4 floating IP (`116.202.176.169`) to the server in Hetzner Console -- [x] Task 3.5.2: Assign IPv6 floating IP (`2a01:4f8:1c0c:9aae::/64`) to the server in Hetzner Console -- [x] Task 3.5.3: Configure floating IPs permanently inside the VM (netplan) -- [x] Task 3.5.4: Create DNS records for all six subdomains via Hetzner Cloud API -- [x] Task 3.5.5: Verify all DNS records resolve correctly - -**Volume Setup** ([volume-setup.md](../deployments/hetzner-demo-tracker/post-provision/volume-setup.md)): - -- [x] Task 3.5.6: Create a 50 GB Hetzner volume (`torrust-tracker-demo-storage`) in `nbg1` -- [x] Task 3.5.7: Format the volume (`ext4`) and mount it at `/opt/torrust/storage` -- [x] Task 3.5.8: Add the volume to `/etc/fstab` for persistent mounting -- [x] Task 3.5.9: Verify volume is correctly mounted and writable - -### Phase 4: Verify and Document - -- [ ] Task 4.1: Verify tracker is accessible and functioning -- [ ] Task 4.2: Verify monitoring stack (Grafana, Prometheus) -- [ ] Task 4.3: Take screenshots of running services -- [ ] Task 4.4: Document any problems encountered during all phases - -### Phase 5: Finalize Documentation - -- [ ] Task 5.1: Write the main deployment journal (`README.md`) -- [ ] Task 5.2: Review and polish all documentation files -- [ ] Task 5.3: Update `docs/README.md` index with new deployments section - -## Acceptance Criteria - -> **Note for Contributors**: These criteria define what the PR reviewer will check. Use this as your pre-review checklist before submitting the PR to minimize back-and-forth iterations. - -**Quality Checks**: - -- [ ] Pre-commit checks pass: `./scripts/pre-commit.sh` - -**Task-Specific Criteria**: - -- [ ] A Torrust Tracker demo instance is running on Hetzner Cloud -- [ ] `docs/deployments/hetzner-demo-tracker/README.md` contains a complete step-by-step walkthrough -- [ ] All problems encountered are documented in `problems.md` with resolutions -- [ ] Configuration examples are sanitized (no real secrets/tokens) -- [ ] Documentation links to existing user-guide docs where appropriate (no duplication) -- [ ] `docs/README.md` updated to reference the new deployments section - -## Related Documentation - -- [Hetzner Cloud Provider](../user-guide/providers/hetzner/) -- [Quick Start: Docker Deployment](../user-guide/quick-start/docker.md) -- [Deployment Overview](../deployment-overview.md) -- [User Guide](../user-guide/README.md) - -## Notes - -- All secrets, API tokens, and passwords must be sanitized in the documentation. Use placeholders like `YOUR_HETZNER_API_TOKEN`. -- The blog post adaptation is out of scope for this issue — it will be done separately on the torrust.com repository. -- The demo tracker instance will incur Hetzner Cloud costs. Document the chosen server type and estimated monthly cost. diff --git a/docs/issues/407-submit-udp1-tracker-to-newtrackon.md b/docs/issues/407-submit-udp1-tracker-to-newtrackon.md deleted file mode 100644 index d398c1441..000000000 --- a/docs/issues/407-submit-udp1-tracker-to-newtrackon.md +++ /dev/null @@ -1,187 +0,0 @@ -# Submit UDP1 Tracker to newTrackon - -**Issue**: #407 -**Parent Epic**: None -**Related**: [#405 - Deploy Hetzner Demo Tracker](405-deploy-hetzner-demo-tracker-and-document-process.md), -[docs/deployments/hetzner-demo-tracker/tracker-registry.md](../deployments/hetzner-demo-tracker/tracker-registry.md) - -## Overview - -After deploying the Hetzner demo tracker (issue #405), the HTTP1 tracker was successfully submitted to -[newTrackon](https://newtrackon.com/). However, the UDP1 tracker submission failed to be accepted. - -Two prerequisites were missed during the initial submission: - -1. **BEP 34 DNS TXT records**: newTrackon requires a DNS TXT record on the tracker's domain following - [BEP 34](https://www.bittorrent.org/beps/bep_0034.html) to announce which ports the tracker uses. -2. **One tracker per IP policy**: newTrackon only accepts one tracker per IP address. The HTTP1 tracker - already occupies the two floating IPs assigned to the server - (`116.202.176.169` and `2a01:4f8:1c0c:9aae::1`), so two new floating IPs (IPv4 + IPv6) must be - provisioned and assigned to support the UDP1 tracker. - -This task documents and resolves both blockers so that the UDP1 tracker can be listed on newTrackon, -and updates the deployment documentation to make the newTrackon prerequisites explicit for future -deployments. - -## Goals - -- [x] Add BEP 34 DNS TXT records for `http1.torrust-tracker-demo.com` (port 443) and - `udp1.torrust-tracker-demo.com` (port 6969) -- [x] Provision two new Hetzner floating IPs (IPv4 + IPv6) and assign them to the existing server -- [x] Configure the new IPs permanently inside the VM (netplan) -- [x] Configure DNS A/AAAA records so `udp1.torrust-tracker-demo.com` resolves to the new IPs -- [x] Retry submission of `udp://udp1.torrust-tracker-demo.com:6969/announce` to newTrackon - (Attempt 3: ✅ Accepted — 2026-03-06; root causes: ufw blocking IPv6 UDP 6969 + asymmetric routing) -- [x] Verify UDP1 tracker appears in the newTrackon public list -- [x] Document the complete process (prerequisites, steps, outcomes) in the deployment docs - -## Specifications - -### BEP 34 DNS TXT Record - -[BEP 34](https://www.bittorrent.org/beps/bep_0034.html) defines a DNS-based method for announcing -tracker availability. newTrackon uses this to validate that a domain is intentionally serving a -BitTorrent tracker on the submitted port. - -The TXT record format is: - -```text -"BITTORRENT UDP:<port> TCP:<port>" -``` - -For example, the old demo tracker (`tracker.torrust-demo.com`) has: - -```text -"BITTORRENT UDP:6969 TCP:443" -``` - -Records to add for the new demo: - -| Domain | TXT value | -| -------------------------------- | --------------------- | -| `http1.torrust-tracker-demo.com` | `BITTORRENT TCP:443` | -| `udp1.torrust-tracker-demo.com` | `BITTORRENT UDP:6969` | - -### One IP Per Tracker (newTrackon Policy) - -newTrackon enforces that each listed tracker resolves to unique IP addresses not already used by -another listed tracker. The HTTP1 tracker already occupies: - -- IPv4: `116.202.176.169` -- IPv6: `2a01:4f8:1c0c:9aae::1` - -To add the UDP1 tracker, two new floating IPs must be provisioned in Hetzner and associated -exclusively with the `udp1` subdomain. - -### Floating IP Configuration - -New floating IPs must be made persistent inside the VM using netplan. The current floating IPs -were **not** configured with netplan — this task also covers making all four floating IPs -permanent (both existing and new ones). - -Netplan configuration path: `/etc/netplan/60-floating-ip.yaml` - -Example netplan stanza for a floating IPv4: - -```yaml -network: - version: 2 - renderer: networkd - ethernets: - eth0: - addresses: - - 116.202.176.169/32 -``` - -> **Note**: Hetzner uses `/64` prefix for IPv6 floating IPs (not `/128`). - -### DNS Records for New IPs - -Once the new floating IPs are provisioned, A and AAAA records must be created for -`udp1.torrust-tracker-demo.com` pointing to those new IPs via the Hetzner DNS API. - -## Implementation Plan - -### Phase 1: DNS BEP 34 TXT Records - -- [x] Task 1.1: Add TXT record `"BITTORRENT TCP:443"` to `http1.torrust-tracker-demo.com` via Hetzner DNS API -- [x] Task 1.2: Add TXT record `"BITTORRENT UDP:6969"` to `udp1.torrust-tracker-demo.com` via Hetzner DNS API -- [x] Task 1.3: Verify both TXT records resolve correctly with `dig TXT <domain>` -- [x] Task 1.4: Create `docs/deployments/hetzner-demo-tracker/post-provision/newtrackon-prerequisites.md` - documenting the BEP 34 requirement and the TXT records added -- [x] Task 1.5: Update `docs/deployments/hetzner-demo-tracker/README.md` to reference the new document - -### Phase 2: Provision New Floating IPs - -- [x] Task 2.1: Book a new IPv4 floating IP in Hetzner Cloud Console (region `nbg1`) — `udp1-ipv4`: `116.202.177.184` -- [x] Task 2.2: Book a new IPv6 floating IP in Hetzner Cloud Console (region `nbg1`) — `udp1-ipv6`: `2a01:4f8:1c0c:828e::1` -- [x] Task 2.3: Assign both new floating IPs to the existing demo server in Hetzner Console -- [x] Task 2.4: Add the "one tracker per IP" policy section to `newtrackon-prerequisites.md` - -### Phase 3: Configure New IPs Inside the VM - -- [x] Task 3.1: SSH into the server -- [x] Task 3.2: Add all four floating IPs to netplan configuration (`/etc/netplan/60-floating-ip.yaml`) - — both existing IPs (which were not previously configured via netplan) and the two new ones -- [x] Task 3.3: Apply netplan configuration (`sudo netplan apply`) and verify all IPs are active -- [x] Task 3.4: Confirm the new IPs receive traffic (IPv4 ping test from external host: ✅; - IPv6 not testable from local machine — confirmed active on `eth0` via `ip addr`) -- [x] Task 3.5: Document the netplan configuration steps and file content in `newtrackon-prerequisites.md` - -### Phase 4: Update DNS for UDP1 Subdomain - -- [x] Task 4.1: Update (or add) A record for `udp1.torrust-tracker-demo.com` pointing to the new IPv4 -- [x] Task 4.2: Update (or add) AAAA record for `udp1.torrust-tracker-demo.com` pointing to the new IPv6 -- [x] Task 4.3: Verify DNS resolution with `dig A udp1.torrust-tracker-demo.com` and - `dig AAAA udp1.torrust-tracker-demo.com` -- [x] Task 4.4: Update `docs/deployments/hetzner-demo-tracker/post-provision/dns-setup.md` with the - new A/AAAA records added for `udp1.torrust-tracker-demo.com` - -### Phase 5: Submit UDP1 Tracker to newTrackon - -- [x] Task 5.1: Go to <https://newtrackon.com/> and submit `udp://udp1.torrust-tracker-demo.com:6969/announce` -- [x] Task 5.2: Verify submission is accepted (no error message from newTrackon) -- [x] Task 5.3: Wait for the tracker to appear in the [newTrackon list](https://newtrackon.com/list) -- [x] Task 5.4: Verify via newTrackon API: `curl https://newtrackon.com/api/stable` -- [x] Task 5.5: Update `docs/deployments/hetzner-demo-tracker/tracker-registry.md` with the final - submission status for the UDP1 tracker and link to `newtrackon-prerequisites.md` - -## Acceptance Criteria - -> **Note for Contributors**: These criteria define what the PR reviewer will check. Use this as your -> pre-review checklist before submitting the PR to minimize back-and-forth iterations. - -**Quality Checks**: - -- [x] Pre-commit checks pass: `./scripts/pre-commit.sh` - -**Task-Specific Criteria**: - -- [x] BEP 34 TXT records are present and correct for both `http1` and `udp1` subdomains - (verified with `dig TXT`) -- [x] Two new floating IPs are provisioned in Hetzner and assigned to the server -- [x] All four floating IPs (existing + new) are configured permanently via netplan -- [x] `udp1.torrust-tracker-demo.com` resolves to the new IPs (A + AAAA records) -- [x] `udp://udp1.torrust-tracker-demo.com:6969/announce` appears in the newTrackon public list -- [x] `docs/deployments/hetzner-demo-tracker/post-provision/newtrackon-prerequisites.md` documents - the prerequisites clearly -- [x] `tracker-registry.md` is updated with the correct submission status - -## Related Documentation - -- [BEP 34 — DNS Tracker Preferences](https://www.bittorrent.org/beps/bep_0034.html) -- [newTrackon](https://newtrackon.com/) -- [Hetzner Demo Tracker — Deployment Journal](../deployments/hetzner-demo-tracker/README.md) -- [Hetzner Demo Tracker — Tracker Registry](../deployments/hetzner-demo-tracker/tracker-registry.md) -- [Hetzner Demo Tracker — DNS Setup](../deployments/hetzner-demo-tracker/post-provision/dns-setup.md) -- [Issue #405 — Deploy Hetzner Demo Tracker](405-deploy-hetzner-demo-tracker-and-document-process.md) - -## Notes - -The HTTP1 tracker (`https://http1.torrust-tracker-demo.com/announce`) was successfully submitted and -accepted by newTrackon on 2026-03-04. The newTrackon API can be used to verify current status: -`curl https://newtrackon.com/api/stable`. - -The UDP2 tracker (`udp://udp2.torrust-tracker-demo.com:6868/announce`) is intentionally **not** -submitted to any public registry — it is reserved as a low-traffic endpoint for manual testing -and debugging. diff --git a/docs/issues/409-fix-hardcoded-deploy-dir-in-ansible-templates.md b/docs/issues/409-fix-hardcoded-deploy-dir-in-ansible-templates.md deleted file mode 100644 index 0d197a698..000000000 --- a/docs/issues/409-fix-hardcoded-deploy-dir-in-ansible-templates.md +++ /dev/null @@ -1,130 +0,0 @@ -# Fix Hardcoded Deployment Directory in Ansible Templates - -**Issue**: #409 -**Parent Epic**: None -**Related**: #405 - Deploy Hetzner Demo Tracker and Document the Process - -## Overview - -Several Ansible playbook templates under `templates/ansible/` hardcode the deployment directory as `/opt/torrust` instead of using the `{{ deploy_dir }}` variable sourced from `variables.yml`. This causes deployment failures when a user configures a custom deployment directory other than the default `/opt/torrust`. - -The correct pattern — used by `create-grafana-storage.yml`, `create-mysql-storage.yml`, and `deploy-grafana-provisioning.yml` — is to load `variables.yml` via `vars_files` and reference `{{ deploy_dir }}` in all paths. - -This bug was discovered while deploying the Torrust Tracker demo to the Hetzner provider (issue #405). - -## Goals - -- [ ] Replace all hardcoded `/opt/torrust` path occurrences in `templates/ansible/` task definitions with `{{ deploy_dir }}` -- [ ] Ensure all affected playbooks load `variables.yml` via `vars_files` where not already done -- [ ] Standardize the variable name (`deploy_dir`) across all playbooks — `deploy-compose-files.yml` currently uses a different name (`remote_deploy_dir`) -- [ ] Verify the fix works for both the default value `/opt/torrust` and a custom deployment directory - -## Specifications - -### Affected Templates - -The following 10 templates need to be updated: - -#### 1. Templates with fully hardcoded paths (no variable usage at all) - -These templates use `/opt/torrust` directly in task `path:`, `dest:`, and loop items. None of them load `variables.yml`. - -| Template | Hardcoded occurrences | -| ------------------------------------------------- | ---------------------------------- | -| `templates/ansible/create-tracker-storage.yml` | 3 (loop items) | -| `templates/ansible/create-prometheus-storage.yml` | 1 (loop item) | -| `templates/ansible/create-backup-storage.yml` | 2 (`path:` params) | -| `templates/ansible/deploy-backup-config.yml` | 4 (`dest:` and `path:` params) | -| `templates/ansible/deploy-tracker-config.yml` | 2 (`dest:` and `path:` params) | -| `templates/ansible/deploy-prometheus-config.yml` | 2 (`dest:` and `path:` params) | -| `templates/ansible/init-tracker-database.yml` | 2 (`path:` params) | -| `templates/ansible/deploy-caddy-config.yml` | 6 (loop items + `dest:` + `path:`) | - -#### 2. Templates using a variable inline (not from `variables.yml`) - -These templates define the deployment directory as an inline playbook variable, bypassing the user-configured value from `variables.yml`. - -| Template | Issue | -| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | -| `templates/ansible/run-compose-services.yml` | Defines `deploy_dir: /opt/torrust` inline — does not load from `variables.yml` | -| `templates/ansible/deploy-compose-files.yml` | Defines `remote_deploy_dir: /opt/torrust` inline — different variable name AND does not load from `variables.yml` | - -#### 3. Templates already correct (reference) - -These correctly use `vars_files: variables.yml` and `{{ deploy_dir }}`: - -- `templates/ansible/create-grafana-storage.yml` ✓ -- `templates/ansible/create-mysql-storage.yml` ✓ -- `templates/ansible/deploy-grafana-provisioning.yml` ✓ - -### Required Fix Pattern - -Each affected playbook must be updated to follow the correct pattern: - -```yaml -- name: <playbook name> - hosts: all - become: true - vars_files: - - variables.yml - - tasks: - - name: <task name> - ansible.builtin.file: - path: "{{ deploy_dir }}/storage/..." -``` - -### Variable Naming - -The variable must consistently be named `deploy_dir` across all playbooks, matching the name defined in `variables.yml.tera`: - -```yaml -deploy_dir: /opt/torrust -``` - -The `deploy-compose-files.yml` template must be updated to rename `remote_deploy_dir` to `deploy_dir` for consistency. - -## Implementation Plan - -### Phase 1: Fix fully hardcoded templates - -- [ ] `create-tracker-storage.yml`: Add `vars_files: variables.yml` and replace all 3 hardcoded loop items with `{{ deploy_dir }}/storage/...` -- [ ] `create-prometheus-storage.yml`: Add `vars_files: variables.yml` and replace the hardcoded loop item -- [ ] `create-backup-storage.yml`: Add `vars_files: variables.yml` and replace 2 hardcoded `path:` values -- [ ] `deploy-backup-config.yml`: Add `vars_files: variables.yml` and replace all 4 hardcoded `dest:`/`path:` values -- [ ] `deploy-tracker-config.yml`: Add `vars_files: variables.yml` and replace 2 hardcoded `dest:`/`path:` values -- [ ] `deploy-prometheus-config.yml`: Add `vars_files: variables.yml` and replace 2 hardcoded `dest:`/`path:` values -- [ ] `init-tracker-database.yml`: Add `vars_files: variables.yml` and replace 2 hardcoded `path:` values -- [ ] `deploy-caddy-config.yml`: Add `vars_files: variables.yml` and replace all 6 hardcoded occurrences - -### Phase 2: Fix inline-variable templates - -- [ ] `run-compose-services.yml`: Remove inline `deploy_dir: /opt/torrust` from `vars` and add `vars_files: variables.yml` instead -- [ ] `deploy-compose-files.yml`: Remove inline `remote_deploy_dir: /opt/torrust` from `vars`, add `vars_files: variables.yml`, and rename all `remote_deploy_dir` references to `deploy_dir` - -### Phase 3: Update comments in affected templates - -- [ ] Update inline comments in all modified templates to reference `{{ deploy_dir }}` instead of `/opt/torrust` as the example path (where applicable) -- [ ] Run linters: `cargo run --bin linter all` - -## Acceptance Criteria - -> **Note for Contributors**: These criteria define what the PR reviewer will check. Use this as your pre-review checklist before submitting the PR to minimize back-and-forth iterations. - -**Quality Checks**: - -- [ ] Pre-commit checks pass: `./scripts/pre-commit.sh` - -**Task-Specific Criteria**: - -- [ ] No playbook task in `templates/ansible/` uses a hardcoded `/opt/torrust` path in `path:`, `dest:`, or loop items -- [ ] All playbooks that reference the deployment directory load `variables.yml` via `vars_files` -- [ ] The variable name `deploy_dir` is used consistently (no `remote_deploy_dir` or other aliases) -- [ ] Playbooks that previously had inline `vars:` blocks for the deploy directory no longer define it inline -- [ ] The default behavior (with `deploy_dir: /opt/torrust`) is unchanged - -## Related Documentation - -- [docs/issues/409-fix-hardcoded-deploy-dir-in-ansible-templates.md](409-fix-hardcoded-deploy-dir-in-ansible-templates.md) — this specification -- [templates/ansible/variables.yml.tera](../../templates/ansible/variables.yml.tera) — defines `deploy_dir` -- [docs/issues/405-deploy-hetzner-demo-tracker-and-document-process.md](405-deploy-hetzner-demo-tracker-and-document-process.md) — parent issue where this bug was discovered diff --git a/docs/issues/410-bug-multiple-mysql-configuration-issues.md b/docs/issues/410-bug-multiple-mysql-configuration-issues.md deleted file mode 100644 index 536b29482..000000000 --- a/docs/issues/410-bug-multiple-mysql-configuration-issues.md +++ /dev/null @@ -1,572 +0,0 @@ -# Bug: Multiple MySQL Configuration Issues in Tracker Deployer - -**Issue**: #410 -**Parent Epic**: None -**Related**: #405 - Deploy Hetzner Demo Tracker and Document the Process, -[torrust/torrust-tracker#1606](https://github.com/torrust/torrust-tracker/issues/1606) — Document DSN password URL-encoding requirement for MySQL connection string - -## Overview - -Three related MySQL configuration bugs were discovered during the Hetzner demo -deployment (#405). All three stem from the same area of the codebase (MySQL setup in -the deployer) and are best fixed together. - -**Bug 1 — MySQL DSN hardcoded in `tracker.toml` with no URL-encoding**: The tracker -configuration template builds the MySQL DSN by interpolating raw credential values -directly into the URL string. If the password contains URL-reserved characters the DSN -becomes malformed and the tracker fails to connect. Additionally, the DSN (including the -plaintext password) ends up in a mounted config file rather than an environment -variable, violating the project's secret-handling convention. - -**Bug 2 — MySQL root password is not user-configurable**: The deployer silently derives -the MySQL root password as `{app_password}_root` (hardcoded in -`src/application/services/rendering/docker_compose.rs`). Users have no way to supply -their own root password via the environment configuration JSON. This also means the root -password is entirely predictable from the app password. - -**Bug 3 — No validation that MySQL app username is not `"root"`**: The MySQL Docker -image reserves the `root` username for the built-in administrator account. If a user -supplies `"root"` as the app DB username in their environment JSON, Docker will refuse -to initialize the database (the `MYSQL_USER` variable cannot be set to `root`). The -domain type `MysqlConfig` validates for an empty username but not for this reserved -value. - -## Goals - -### Bug 1 — DSN in `tracker.toml` - -- [x] Move the MySQL DSN out of `tracker.toml` and into an environment variable override, - consistent with `TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER` and - `TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN` -- [x] Build the percent-encoded DSN in Rust and expose it in the `.env` file as - `TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__PATH` -- [x] Pass the new env var into the tracker container via `docker-compose.yml.tera` -- [x] Remove the raw DSN line from `tracker.toml.tera` for the MySQL case -- [x] Remove the now-unused `MysqlTemplateConfig` from `TrackerContext` - -### Bug 2 — Root password not configurable - -- [x] Add an optional `root_password` field to the MySQL section of the environment - configuration JSON schema -- [x] When a root password is provided by the user, use it; when omitted, generate a - strong random password at environment creation time (application layer) rather - than deriving it from the app password -- [x] Remove the `format!("{password}_root")` derivation from `create_mysql_contexts` - -### Bug 3 — Reserved username not rejected - -- [x] Add a `ReservedUsername` variant to `MysqlConfigError` -- [x] Reject `"root"` as the app DB username in `MysqlConfig::new()` with a clear, - actionable error message - -## Specifications - -### Root Cause - -Two distinct problems share the same fix: - -**Problem 1 — URL encoding**: The MySQL DSN is a URL. Per RFC 3986, the user-info -component (`user:password@`) must percent-encode any character from the reserved set. -The current template interpolates raw values. Base64-generated secrets (common from -secret managers and AI agents) always contain `+` and `/`, which are reserved characters. - -Common problematic characters: - -| Character | URL encoding | -| --------- | ------------ | -| `@` | `%40` | -| `:` | `%3A` | -| `/` | `%2F` | -| `+` | `%2B` | -| `#` | `%23` | -| `?` | `%3F` | -| `%` | `%25` | - -**Problem 2 — Secret in config file**: `tracker.toml` is written by the `deploy -tracker-config` step and mounted read-only into the container. Placing the raw DSN -(with password) there conflicts with the project convention of injecting secrets via -`.env`. The `.env` file already carries `MYSQL_PASSWORD` for the MySQL container — the -same secret should not also appear in a different form inside the tracker config file. - -### Solution: env var override for `core.database.path` - -The tracker supports runtime config overrides through env vars following the pattern -`TORRUST_TRACKER_CONFIG_OVERRIDE_<SECTION__KEY>`. This is already used for: - -- `TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER` -- `TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN` - -The fix adds `TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__PATH` for the MySQL case. -The Rust rendering layer (not the Tera template) constructs the full percent-encoded DSN -and places it in the `.env` file. The tracker container receives it through -`docker-compose.yml` environment injection, overriding whatever `tracker.toml` says -(or does not say) about `core.database.path`. - -### Affected Modules and Types - -#### `Cargo.toml` - -- Add `percent-encoding` crate dependency. - -#### `src/infrastructure/templating/docker_compose/template/wrappers/env/context.rs` - -- `TrackerServiceConfig`: add an optional field to carry the database path DSN - (populated only for MySQL; `None` for SQLite). -- `EnvContext::new` (SQLite constructor): leave the new field as `None`. -- `EnvContext::new_with_mysql`: use `percent_encoding::utf8_percent_encode` with - `NON_ALPHANUMERIC` to encode the username and password, construct the full DSN string, - and store it in the new `TrackerServiceConfig` field. - -#### `templates/docker-compose/.env.tera` - -- Inside the `{%- if mysql %}` block, add a line that renders - `TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__PATH` from the new - `tracker.database_path` (or equivalent) field. - -#### `templates/docker-compose/docker-compose.yml.tera` - -- In the tracker service `environment:` section, conditionally inject - `TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__PATH` from the `.env` file when - MySQL is configured (use `{%- if database.mysql %}` similar to the existing - `depends_on` block). - -#### `templates/tracker/tracker.toml.tera` - -- In the `{%- elif database_driver == "mysql" %}` block: remove the `path = ...` line. - Replace with a comment explaining that the connection path is injected via the - `TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__PATH` environment variable. - -#### `src/infrastructure/templating/tracker/template/wrapper/tracker_config/context.rs` - -- `MysqlTemplateConfig`: remove this struct entirely — none of its fields are used by - the tracker template once the `path` line is gone. -- `TrackerContext::from_config`: remove the MySQL branch that builds - `MysqlTemplateConfig`; the MySQL case reduces to setting `database_driver = "mysql"` - (already handled by the shared driver field). -- Remove or update the existing unit test that asserts `mysql_password` on - `MysqlTemplateConfig`. - -### What Does Not Change — Bug 1 - -- `templates/docker-compose/.env.tera`: the `MYSQL_PASSWORD` line for the MySQL service - is unchanged — it is used by the MySQL container, not the tracker. -- `TrackerContext` still carries `database_driver` and the SQLite config; only the - MySQL-specific struct is removed. -- The `.env.tera` and `docker-compose.yml.tera` templates' overall structure are - unchanged; only additive lines are inserted. - -### Bug 2 — MySQL Root Password Not Configurable (auto-derived) - -**Root cause**: In -`src/application/services/rendering/docker_compose.rs`, the `create_mysql_contexts` -function derives the root password unconditionally: - -```rust -let root_password = format!("{password}_root"); -``` - -This value is never read from the environment configuration JSON — the JSON schema has -no `root_password` field for MySQL. The deployer therefore always sets -`MYSQL_ROOT_PASSWORD` in `.env` to `{app_password}_root`, which: - -- Is entirely predictable from the app password (security concern). -- Cannot be rotated independently of the app password. -- Offers no escape hatch for environments that require a specific root password. - -**Fix**: Add an optional `root_password` to the MySQL section of the environment -configuration JSON. If the user provides it, use it. If they omit it, generate a -cryptographically random password at **environment creation time** (application layer) -instead of deriving it from the app password. Remove the `format!("{password}_root")` -derivation. - -**Key design decision — generate at creation time, not render time**: The root password -is a domain invariant that must remain stable across multiple renders (e.g. re-deploying -without reprovisioning). Generating it at render time would produce a different -`MYSQL_ROOT_PASSWORD` on each render, breaking MySQL container restarts. Instead, -generation happens once in the application layer (`TryFrom<DatabaseSection>`) when the -environment is first created, and the value is persisted alongside the rest of the -environment config. - -**Affected modules and types**: - -- `Cargo.toml`: add `rand = "0.9"` dependency. -- `schemas/environment-config.json`: add optional `root_password` string to the MySQL - database object. -- `src/shared/secrets/random.rs` (new file): `generate_random_password() -> Password` - using `rand::rng()` (ThreadRng seeded from OsRng), guaranteeing one character from each - class (lower, upper, digit, symbol), filled to 32 characters, then shuffled. Satisfies - MySQL `validate_password MEDIUM` policy. -- `src/shared/secrets/mod.rs` and `src/shared/mod.rs`: re-export - `generate_random_password`. -- `src/domain/tracker/config/core/database/mysql.rs` (`MysqlConfig`): `root_password` - field is `Password` (non-optional) — the domain type always has a value. Constructor - `new()` takes `root_password: Password`. Accessor `root_password() -> &Password` added. - `MysqlConfigRaw` (the serde deserialization intermediate) keeps - `root_password: Option<Password>` with `#[serde(default)]` for backward compatibility - with persisted environments that pre-date this field; missing values are filled by - calling `generate_random_password()` during deserialization. -- `src/application/command_handlers/create/config/tracker/tracker_core_section.rs` - (`TryFrom<DatabaseSection>`): generation happens here — the optional user-supplied - `root_password` is mapped to `Password` if present, or `generate_random_password()` is - called if absent. This is the single point of generation for new environments. -- `src/application/services/rendering/docker_compose.rs` (`create_mysql_contexts`): - `root_password` parameter is now `PlainPassword` (non-optional); call site passes - `mysql_config.root_password().expose_secret().to_string()`. No generation logic - remains here. - -### Bug 3 — Reserved MySQL Username `"root"` Not Rejected - -**Root cause**: The official MySQL Docker image initializes the database according to -several environment variables. The `MYSQL_USER` variable is used to create a regular -app user, but the MySQL image explicitly [rejects `root`](https://hub.docker.com/_/mysql) -for this variable because `root` is already created as the privileged superuser. If -`MYSQL_USER=root` is set, the container initialization will fail with an error like: - -```text -[ERROR] [Entrypoint]: MYSQL_USER="root", MYSQL_USER and MYSQL_ROOT_USER cannot be the same. -``` - -The domain type `MysqlConfig::new()` in -`src/domain/tracker/config/core/database/mysql.rs` validates for an empty username but -does not check for the reserved value `"root"`: - -```rust -if username.is_empty() { - return Err(MysqlConfigError::EmptyUsername); -} -// ← missing: if username == "root" { return Err(ReservedUsername); } -``` - -The error is therefore deferred until Docker container startup, far from the source of -the misconfiguration. - -**Fix**: Add `ReservedUsername` to `MysqlConfigError` and check `username == "root"` in -`MysqlConfig::new()`, before the `Ok(Self { ... })` return, with a clear actionable -error message. - -**Affected modules and types**: - -- `src/domain/tracker/config/core/database/mysql.rs`: - - Add `ReservedUsername` variant to `MysqlConfigError` with a `help()` message - explaining the constraint and directing the user to choose a different username (e.g. - `"tracker_user"`). - - In `MysqlConfig::new()`: add the reserved username check after the empty-username - check. - - Add a unit test `it_should_reject_root_as_username()` mirroring the existing - `it_should_reject_empty_username_when_creating_mysql_config` test. - -## Implementation Plan - -Tasks are ordered from simplest to most complex. - -### Phase 1: Reject reserved MySQL username (Bug 3) - -- [x] In `MysqlConfigError` (`mysql.rs`): add `ReservedUsername` variant -- [x] Add `help()` arm for `ReservedUsername` with actionable fix instructions -- [x] In `MysqlConfig::new()`: add `if username == "root"` guard returning - `Err(MysqlConfigError::ReservedUsername)` -- [x] Add unit test `it_should_reject_root_as_username` - -### Phase 2: Make root password configurable (Bug 2) - -- [x] `schemas/environment-config.json`: add optional `root_password` string to the - MySQL database object -- [x] `MysqlConfig` (`mysql.rs`): `root_password` is `Password` (non-optional) in the - domain — always has a value. `MysqlConfigRaw` uses `Option<Password>` for backward - compat with persisted environments lacking the field. -- [x] `src/shared/secrets/random.rs` (new): `generate_random_password() -> Password` - using mixed charset (lower + upper + digit + symbol), length 32, satisfies MySQL - MEDIUM password policy -- [x] `TryFrom<DatabaseSection>` (`tracker_core_section.rs`): generates root password at - environment creation time — not at render time — so it is stable across re-renders -- [x] `create_mysql_contexts` (`docker_compose.rs`): replaced `format!("{password}_root")` - with `mysql_config.root_password().expose_secret().to_string()`; no generation - logic remains here - -### Phase 3: Move DSN to env var override and add URL-encoding (Bug 1) - -- [x] Add `percent-encoding` to `Cargo.toml` -- [x] `TrackerServiceConfig` (`env/context.rs`): add optional database path field -- [x] `EnvContext::new_with_mysql`: percent-encode username and password with - `utf8_percent_encode(..., USERINFO_ENCODE)` (custom AsciiSet preserving RFC 3986 - unreserved chars), build the full DSN string, store in the new field -- [x] `TrackerContext` (`tracker_config/context.rs`): remove `MysqlTemplateConfig` and - the MySQL branch that builds it -- [x] `templates/docker-compose/.env.tera`: add - `TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__PATH` inside `{%- if mysql %}`, - placed in the Tracker Service Configuration section -- [x] `templates/docker-compose/docker-compose.yml.tera`: inject the new env var into - the tracker service `environment:` section, conditionally on `{%- if mysql %}` -- [x] `templates/tracker/tracker.toml.tera`: remove the MySQL `path =` line; add a - comment explaining that the connection path is injected via the env var override - -### Phase 4: Tests - -- [x] `mysql.rs`: add `it_should_reject_root_as_username` unit test (Phase 1) -- [x] `env/context.rs`: add test that `new_with_mysql` produces a correctly - percent-encoded DSN for a password containing special characters -- [x] `env/context.rs`: add test that `new` (SQLite) leaves the database path field as - `None` -- [x] `tracker_config/context.rs`: remove or update the test that referenced - `mysql_password` on `MysqlTemplateConfig` -- [x] Run `cargo test` to verify all tests pass (2314 passed) - -### Phase 5: Linting and pre-commit - -- [x] Run linters: `cargo run --bin linter all` -- [x] Run pre-commit: `./scripts/pre-commit.sh` - -> **Status**: Phases 1–5 complete and committed. - -## Acceptance Criteria - -> **Note for Contributors**: These criteria define what the PR reviewer will check. -> Use this as your pre-review checklist before submitting the PR to minimize -> back-and-forth iterations. - -**Quality Checks**: - -- [x] Pre-commit checks pass: `./scripts/pre-commit.sh` - -**Task-Specific Criteria — Bug 3 (reserved username)**: - -- [x] `MysqlConfig::new()` returns `Err(MysqlConfigError::ReservedUsername)` when - username is `"root"` -- [x] `MysqlConfigError::ReservedUsername` has a `help()` message with an actionable fix -- [x] A unit test for the reserved username rejection exists and passes - -**Task-Specific Criteria — Bug 2 (root password)**: - -- [x] The environment configuration JSON schema accepts an optional `root_password` field - in the MySQL database object -- [x] When `root_password` is provided in the env JSON it is used as `MYSQL_ROOT_PASSWORD` - in the rendered `.env` -- [x] When `root_password` is omitted, a randomly generated password is used — it is - **not** derived from the app password -- [x] `create_mysql_contexts` no longer contains `format!("{password}_root")` -- [x] The random password is generated once at environment creation time (not at render - time), ensuring stability across multiple renders -- [x] The domain type `MysqlConfig.root_password` is always populated (`Password`, - non-optional) - -**Task-Specific Criteria — Bug 1 (DSN in tracker.toml)**: - -- [x] The rendered `tracker.toml` for a MySQL deployment does **not** contain the - database password -- [x] The rendered `.env` file contains - `TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__PATH` with a correctly - percent-encoded DSN when MySQL is configured -- [x] The rendered `.env` file does **not** contain - `TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__PATH` when SQLite is configured -- [x] The rendered `docker-compose.yml` injects - `TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__PATH` into the tracker service - environment when MySQL is configured -- [x] A MySQL password containing URL-reserved characters (e.g. `@`, `+`, `/`) produces - a valid, correctly encoded DSN in the `.env` file (verified with `tracker_p@ss!word#1`) -- [x] A MySQL password with only alphanumeric characters is rendered unchanged -- [x] `MysqlTemplateConfig` no longer exists in `tracker_config/context.rs` -- [x] `cargo machete` reports no unused dependencies - -## Manual E2E Verification Test - -This test verifies the fix end-to-end on a local LXD VM, using a MySQL password that -contains URL-reserved characters. It validates that the rendered templates contain the -expected values and that the tracker connects to MySQL successfully. - -### Test Environment Configuration - -Create the environment file `envs/mysql-special-chars-test.json`: - -```json -{ - "environment": { - "name": "mysql-special-chars-test", - "instance_name": null - }, - "ssh_credentials": { - "private_key_path": "fixtures/testing_rsa", - "public_key_path": "fixtures/testing_rsa.pub", - "username": "torrust", - "port": 22 - }, - "provider": { - "provider": "lxd", - "profile_name": "torrust-profile-mysql-special-chars-test" - }, - "tracker": { - "core": { - "database": { - "driver": "mysql", - "host": "mysql", - "port": 3306, - "database_name": "tracker", - "username": "tracker_user", - "password": "p@ss:w/ord+1" - }, - "private": false - }, - "udp_trackers": [ - { - "bind_address": "0.0.0.0:6969" - } - ], - "http_trackers": [ - { - "bind_address": "0.0.0.0:7070" - } - ], - "http_api": { - "bind_address": "0.0.0.0:1212", - "admin_token": "MyAccessToken" - }, - "health_check_api": { - "bind_address": "127.0.0.1:1313" - } - } -} -``` - -> The password `p@ss:w/ord+1` contains `@` (`%40`), `:` (`%3A`), `/` (`%2F`), -> and `+` (`%2B`) — all URL-reserved characters that triggered the original bug. - -### Step 1: Render and Inspect Artifacts (No Infrastructure Needed) - -Before provisioning, verify the rendered templates have the correct values. - -```bash -# Create the environment -cargo run -- create environment --env-file envs/mysql-special-chars-test.json - -# Render artifacts to an output directory (use any placeholder IP) -cargo run -- render --env-name mysql-special-chars-test \ - --instance-ip 192.168.1.100 \ - --output-dir ./tmp/mysql-special-chars-test -``` - -**Verify `.env` contains the encoded DSN, NOT the raw password:** - -```bash -grep "DATABASE__PATH" ./tmp/mysql-special-chars-test/.env -``` - -Expected — the DSN must use percent-encoded values: - -```text -TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__PATH='mysql://tracker_user:p%40ss%3Aw%2Ford%2B1@mysql:3306/tracker' -``` - -**Verify `tracker.toml` does NOT contain the password:** - -```bash -grep -i "password\|p@ss\|p%40\|mysql://" ./tmp/mysql-special-chars-test/tracker/tracker.toml -``` - -Expected — no output (neither the raw password nor the DSN should appear in `tracker.toml`). - -**Verify `docker-compose.yml` injects the new env var into the tracker service:** - -```bash -grep "DATABASE__PATH" ./tmp/mysql-special-chars-test/docker-compose.yml -``` - -Expected: - -```text - - TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__PATH=${TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__PATH} -``` - -**Verify `MYSQL_PASSWORD` in `.env` still holds the raw (unencoded) password** — it is used by the MySQL container directly, not in a URL: - -```bash -grep "MYSQL_PASSWORD" ./tmp/mysql-special-chars-test/.env -``` - -Expected: - -```text -MYSQL_PASSWORD='p@ss:w/ord+1' -``` - -### Step 2: Full Deployment on LXD - -```bash -# Provision the VM -cargo run -- provision mysql-special-chars-test - -# Configure the OS (Docker, firewall, storage directories) -cargo run -- configure mysql-special-chars-test - -# Deploy compose files and config -cargo run -- release mysql-special-chars-test - -# Start services -cargo run -- run mysql-special-chars-test -``` - -### Step 3: Get the Instance IP - -```bash -export INSTANCE_IP=$(cat data/mysql-special-chars-test/environment.json \ - | jq -r '.Running.context.runtime_outputs.instance_ip') -echo "VM IP: $INSTANCE_IP" -``` - -### Step 4: Verify Containers Are Running and Healthy - -```bash -ssh -i fixtures/testing_rsa -o StrictHostKeyChecking=no torrust@$INSTANCE_IP \ - "docker ps --format 'table {{.Names}}\t{{.Status}}'" -``` - -Expected — both containers healthy: - -```text -NAMES STATUS -tracker Up X seconds (healthy) -mysql Up X seconds (healthy) -``` - -> If the tracker shows `(unhealthy)` or is restarting, the DSN was not encoded -> correctly and the bug is not fixed. - -### Step 5: Verify Tracker Connects to MySQL - -```bash -ssh -i fixtures/testing_rsa -o StrictHostKeyChecking=no torrust@$INSTANCE_IP \ - "docker logs tracker 2>&1 | head -20" -``` - -Expected — no `Access denied` or `parse error` messages, tracker shows it started -and is listening. - -### Step 6: Verify Tracker API is Reachable - -```bash -curl -s http://$INSTANCE_IP:1212/api/v1/stats?token=MyAccessToken -``` - -Expected — JSON response with tracker statistics (not an error). - -### Step 7: Cleanup - -```bash -cargo run -- destroy mysql-special-chars-test -rm envs/mysql-special-chars-test.json -rm -rf ./tmp/mysql-special-chars-test -``` - -Also clean up the LXD profile if it was created: - -```bash -lxc profile delete torrust-profile-mysql-special-chars-test -``` - -## Related Documentation - -- [templates/tracker/tracker.toml.tera](../../templates/tracker/tracker.toml.tera) — affected template -- [templates/docker-compose/.env.tera](../../templates/docker-compose/.env.tera) — where the DSN override is added -- [templates/docker-compose/docker-compose.yml.tera](../../templates/docker-compose/docker-compose.yml.tera) — where the env var is injected -- [src/infrastructure/templating/docker_compose/template/wrappers/env/context.rs](../../src/infrastructure/templating/docker_compose/template/wrappers/env/context.rs) — main Rust change -- [src/infrastructure/templating/tracker/template/wrapper/tracker_config/context.rs](../../src/infrastructure/templating/tracker/template/wrapper/tracker_config/context.rs) — cleanup -- [torrust/torrust-tracker#1606](https://github.com/torrust/torrust-tracker/issues/1606) — upstream issue documenting the DSN encoding requirement -- [docs/issues/405-deploy-hetzner-demo-tracker-and-document-process.md](405-deploy-hetzner-demo-tracker-and-document-process.md) — deployment issue where this bug was discovered diff --git a/docs/issues/411-bug-ssh-key-passphrase-breaks-automated-deployment.md b/docs/issues/411-bug-ssh-key-passphrase-breaks-automated-deployment.md deleted file mode 100644 index 23b3c0f4f..000000000 --- a/docs/issues/411-bug-ssh-key-passphrase-breaks-automated-deployment.md +++ /dev/null @@ -1,286 +0,0 @@ -# Bug: Passphrase-Protected SSH Key Silently Fails During Automated Deployment - -**Issue**: #411 -**Parent Epic**: None -**Related**: #405 - Deploy Hetzner Demo Tracker and Document the Process - -## Overview - -When a user configures a passphrase-protected SSH private key in `ssh_credentials`, the -deployer fails silently during the `provision` step with a misleading -`Permission denied (publickey,password)` error. The root cause — that the key is -encrypted and cannot be decrypted without a passphrase in an unattended environment — -is never surfaced to the user. - -This bug was triggered during the Hetzner demo deployment (#405) where a fresh -deployment key was created with a passphrase for security. In an interactive terminal -session the OS SSH agent transparently decrypts the key, so the error only appears when -running the deployer in an automated context (Docker container, CI/CD pipeline) where no -agent is available. - -There is no code fix required — passphrase-protected keys are a valid configuration for -some workflows (e.g. with SSH agent forwarding into the container). The two actions -needed are: - -1. **Add an early warning** in `create environment` when the private key file is detected - as passphrase-protected, so users can make an informed decision before reaching the - `provision` step. -2. **Add documentation** covering SSH key requirements and the three supported workflows. - -## Goals - -- [ ] Detect passphrase-protected private keys during `create environment` and emit a - user-visible warning (not an error — the choice belongs to the user) -- [ ] Add a documentation section to the user guide on SSH key handling, covering key - requirements and all supported workflows -- [ ] Update the Hetzner provider guide to call out the passphrase requirement for - Docker-based deployments - -## Specifications - -### Root Cause - -SSH private keys can be stored in two formats: - -- **Unencrypted**: the key material is in plaintext in the PEM file. -- **Encrypted (passphrase-protected)**: the key material is encrypted; decryption - requires the passphrase, an SSH agent holding the unlocked key, or a TTY for - interactive prompting. - -The deployer invokes the system `ssh` binary for connectivity probes and remote -commands. When running inside a Docker container, there is no SSH agent socket and no -TTY. If the key file is encrypted, `ssh` cannot authenticate and every attempt returns -`Permission denied (publickey,password)`. This is indistinguishable from a wrong key -or an unconfigured `authorized_keys` file — the log output reveals nothing about the -passphrase being the cause. - -An encrypted OpenSSH private key file contains `ENCRYPTED` in its PEM header: - -```text ------BEGIN OPENSSH PRIVATE KEY----- ← unencrypted ------BEGIN OPENSSH PRIVATE KEY----- ← also unencrypted (need to read body) -``` - -The reliable detection approach is to read the first line of the PEM file: - -- RSA/EC legacy PEM format: encrypted files contain `ENCRYPTED` in the header: - `-----BEGIN ENCRYPTED PRIVATE KEY-----` or `Proc-Type: 4,ENCRYPTED` -- OpenSSH format: the body begins with `bcrypt` if passphrase-protected; the header - alone is not sufficient — the first few bytes of the decoded body must be checked. - -### Detection Approaches Considered - -Two approaches were evaluated for detecting whether a key is passphrase-protected: - -#### Option A — Byte inspection (chosen) - -Read the raw bytes of the key file and check: - -- For legacy PEM: `ENCRYPTED` appears in the header -- For OpenSSH format: after the base64-decoded body, the string `bcrypt` appears near - the start (OpenSSH uses bcrypt KDF for passphrase derivation) - -| | | -| --- | ------------------------------------------------------------------------------- | -| ✅ | Pure Rust, no external tools required | -| ✅ | Fast — reads only the first ~64 bytes of the file | -| ✅ | Works in any environment, including minimal Docker images | -| ✅ | No process spawning overhead | -| ❌ | Must handle two PEM variants (legacy `ENCRYPTED` header, OpenSSH `bcrypt` body) | -| ❌ | False-negative risk for exotic or future key formats (acceptable per spec) | - -#### Option B — `ssh-keygen -y` probe - -Spawn `ssh-keygen -y -f <key> < /dev/null`: this command attempts to derive the public -key from the private key and exits non-zero with a passphrase prompt when the key is -encrypted. - -| | | -| --- | --------------------------------------------------------------------------------------------------- | -| ✅ | Handles all key formats transparently — no format-specific parsing | -| ✅ | Implementation is a single `std::process::Command` call | -| ❌ | Requires `ssh-keygen` to be present in the runtime environment | -| ❌ | Spawns an external process just for detection | -| ❌ | Must distinguish "encrypted" from "file not found" / "unsupported format" via exit codes and stderr | -| ❌ | Slower than reading a few bytes from a file | - -**Chosen approach**: Option A (byte inspection). The deployer already targets Docker -containers where `ssh-keygen` may not be installed, and the detection is best-effort -(a missed warning is acceptable — a false positive is not). An ADR documents this -choice in full (see Implementation Plan). - -The check only needs to be a best-effort heuristic — it is used to emit a warning, not -to block the user. A false negative (missing the warning) is acceptable; a false -positive (warning for an unencrypted key) would be confusing and should be avoided. - -### Warning Behavior - -The warning should be emitted during the `create environment` command, after the -configuration is loaded and the private key path is resolved but before the environment -state is persisted. It must: - -- Be non-blocking — the environment is still created normally. -- Be clearly labelled as a warning, not an error. -- Explain the consequence (automated runs without an SSH agent will fail). -- Describe all three resolution options (see below). - -Example warning text: - -```text -⚠ Warning: SSH private key appears to be passphrase-protected. - Key: /home/deployer/.ssh/torrust_tracker_deployer_ed25519 - - Automated deployment (e.g. Docker, CI/CD) requires an SSH key that can be used - without interactive input. A passphrase-protected key will cause the `provision` - step to fail with "Permission denied" unless one of the following is arranged: - - Option 1 — Remove the passphrase (recommended for dedicated deployment keys): - ssh-keygen -p -f /path/to/your/private_key - - Option 2 — Forward your SSH agent socket into the Docker container: - docker run ... -v "$SSH_AUTH_SOCK:/tmp/ssh-agent.sock" \ - -e SSH_AUTH_SOCK=/tmp/ssh-agent.sock ... - - Option 3 — Use a separate passphrase-free deployment key and configure it in - ssh_credentials.private_key_path. - - You can continue now — the environment will be created. If you plan to run - the deployer without an SSH agent, resolve this before running `provision`. -``` - -### Affected Modules and Types - -#### Detection utility - -A small free function (or method on `SshCredentials`) to check whether a key file -appears to be passphrase-protected. Location: `src/adapters/ssh/ssh/credentials.rs` or -a new `src/adapters/ssh/ssh/key_inspector.rs`. - -The function signature could be: - -```rust -/// Returns `true` if the private key at `path` appears to be passphrase-protected. -/// Returns `false` if the key is unencrypted or if the file cannot be read/parsed. -pub fn is_passphrase_protected(path: &Path) -> bool -``` - -This is best-effort: it returns `false` on any I/O or parse error (no key found, -unrecognized format) to avoid blocking normal flow with spurious warnings. - -#### `create environment` handler - -`src/presentation/cli/controllers/create/subcommands/environment/handler.rs`: - -After configuration is loaded (the `LoadConfiguration` step), call the detection -function on `config.ssh_credentials.ssh_priv_key_path`. If it returns `true`, emit the -warning through `user_output` before proceeding to the `CreateEnvironment` step. - -No changes are needed in the application or domain layers — this is a pure -presentation-layer concern. - -### Documentation - -#### New page: SSH Key Handling - -Create `docs/user-guide/ssh-keys.md` covering: - -- Why the deployer requires SSH keys (remote provisioning, configuration, release, run) -- Key requirements for unattended automation (no passphrase, or agent forwarding) -- The three workflows: - 1. Passphrase-free dedicated deployment key (recommended) - 2. SSH agent forwarding into Docker - 3. Direct (non-Docker) execution with an SSH agent running on the host -- How to generate a deployment key pair: - - ```bash - ssh-keygen -t ed25519 -C "torrust-tracker-deployer" \ - -f ~/.ssh/torrust_tracker_deployer_ed25519 - # Leave passphrase empty for automated use - ``` - -- How to remove an existing passphrase: - - ```bash - ssh-keygen -p -f ~/.ssh/torrust_tracker_deployer_ed25519 - ``` - -- Security notes: dedicated deployment keys, key rotation after use, filesystem - permissions (`0600`) -- Reference to the `ssh_credentials` fields in the environment config JSON schema - -#### Update: Hetzner provider guide - -`docs/user-guide/providers/hetzner/` — add a "SSH Key Requirements" section or -callout box noting that Docker-based deployments require a passphrase-free key (or agent -forwarding) and linking to the new SSH keys page. - -#### Update: `create environment` command docs - -`docs/user-guide/commands/create.md` — mention that a warning is shown if the -configured private key appears to be passphrase-protected. - -## Implementation Plan - -### Phase 1: Detection and warning (code change) - -- [ ] Implement `is_passphrase_protected(path: &Path) -> bool` in - `src/adapters/ssh/ssh/credentials.rs` (or a new `key_inspector.rs` module) - - Check for `ENCRYPTED` in PEM header (legacy format) - - Check for `bcrypt` near the start of the decoded OpenSSH body - - Return `false` on any I/O or parse error -- [ ] In the `create environment` handler - (`handler.rs`): after `LoadConfiguration`, call the detection function and emit - a warning via `user_output` if the key appears to be passphrase-protected -- [ ] Add unit test `it_detects_passphrase_protected_key` (using a test fixture key - with and without passphrase if available, or by constructing the minimal PEM - structure in the test) - -### Phase 2: ADR - -- [ ] Create `docs/decisions/XXX-ssh-key-passphrase-detection.md` documenting: - - Why byte inspection was chosen over the `ssh-keygen -y` probe - - Pros and cons of each approach - - Consequences and limitations (best-effort, false-negative acceptable) -- [ ] Register the new ADR in `docs/decisions/README.md` - -### Phase 3: Documentation - -- [ ] Create `docs/user-guide/ssh-keys.md` covering all workflows and security notes -- [ ] Update `docs/user-guide/providers/hetzner/` with an SSH key requirements note -- [ ] Update `docs/user-guide/commands/create.md` to mention the passphrase warning -- [ ] Update `docs/user-guide/README.md` to link to the new `ssh-keys.md` page - -### Phase 4: Linting and pre-commit - -- [ ] Run linters: `cargo run --bin linter all` -- [ ] Run pre-commit: `./scripts/pre-commit.sh` - -## Acceptance Criteria - -> **Note for Contributors**: These criteria define what the PR reviewer will check. -> Use this as your pre-review checklist before submitting the PR to minimize -> back-and-forth iterations. - -**Quality Checks**: - -- [ ] Pre-commit checks pass: `./scripts/pre-commit.sh` - -**Task-Specific Criteria**: - -- [ ] `create environment` emits a visible warning (not an error) when the configured - private key file is passphrase-protected -- [ ] `create environment` still succeeds (environment is created) even when the warning - is emitted — the user is not blocked -- [ ] `create environment` emits no warning when the key is unencrypted -- [ ] The warning message names all three resolution options (remove passphrase, agent - forwarding, separate key) -- [ ] `docs/user-guide/ssh-keys.md` exists and covers key requirements, workflows, and - security notes -- [ ] `docs/user-guide/providers/hetzner/` references the SSH key requirements - -## Related Documentation - -- [docs/deployments/hetzner-demo-tracker/commands/provision/problems.md](../deployments/hetzner-demo-tracker/commands/provision/problems.md) — root cause analysis and resolution for the Hetzner deployment failure -- [src/adapters/ssh/ssh/credentials.rs](../../src/adapters/ssh/ssh/credentials.rs) — `SshCredentials` struct -- [src/presentation/cli/controllers/create/subcommands/environment/handler.rs](../../src/presentation/cli/controllers/create/subcommands/environment/handler.rs) — where the warning is added -- [docs/user-guide/providers/hetzner/](../user-guide/providers/hetzner/) — Hetzner provider guide diff --git a/docs/issues/412-bug-udp-tracker-domains-missing-from-provision-output.md b/docs/issues/412-bug-udp-tracker-domains-missing-from-provision-output.md deleted file mode 100644 index 3372a4787..000000000 --- a/docs/issues/412-bug-udp-tracker-domains-missing-from-provision-output.md +++ /dev/null @@ -1,188 +0,0 @@ -# Bug: UDP Tracker Domains Missing from `provision` Output - -**Issue**: #412 -**Parent Epic**: None -**Related**: #405 - Deploy Hetzner Demo Tracker and Document the Process - -## Overview - -The `provision` command output includes a `domains` array listing the configured domain -names for the deployed environment. When UDP trackers have domains configured (via the -`domain` field of `udp_trackers[].domain` in the environment JSON), those domains are -absent from the list. Only HTTP-based service domains appear. - -Example observed output (Hetzner demo deployment #405): - -```json -{ - "domains": [ - "http1.torrust-tracker-demo.com", - "http2.torrust-tracker-demo.com", - "api.torrust-tracker-demo.com", - "grafana.torrust-tracker-demo.com" - ] -} -``` - -Expected — UDP domains should also appear: - -```json -{ - "domains": [ - "http1.torrust-tracker-demo.com", - "http2.torrust-tracker-demo.com", - "udp1.torrust-tracker-demo.com", - "udp2.torrust-tracker-demo.com", - "api.torrust-tracker-demo.com", - "grafana.torrust-tracker-demo.com" - ] -} -``` - -The `domains` list is used as a DNS-setup reminder — it tells the operator which -`A`/`AAAA` records need to point at the server IP. A missing UDP domain means the -operator does not know they need to create that DNS record. - -## Goals - -- [ ] Include UDP tracker domain names in the `domains` list of the `provision` output -- [ ] Ensure the `dns_reminder` view also includes UDP domains -- [ ] Add or update tests to cover UDP domains appearing in both views - -## Specifications - -### Root Cause - -The `domains` field in `ProvisionDetailsData` is populated in -`src/presentation/cli/views/commands/provision/view_data/provision_details.rs` by -calling `services.tls_domain_names()`: - -```rust -let domains = if let Some(ip) = environment.instance_ip() { - let tracker_config = environment.tracker_config(); - let grafana_config = environment.grafana_config(); - let services = ServiceInfo::from_tracker_config(tracker_config, ip, grafana_config); - services - .tls_domain_names() // ← only returns TLS service domains - .iter() - .map(|s| (*s).to_string()) - .collect() -} else { - vec![] -}; -``` - -`tls_domain_names()` in -`src/application/command_handlers/show/info/tracker.rs` returns only the `tls_domains` -vector — domains associated with HTTPS services (HTTP trackers with TLS proxy, API with -TLS proxy, Grafana). UDP trackers are not TLS services and are never added to -`tls_domains`. - -`ServiceInfo` already stores UDP tracker URLs in `udp_trackers: Vec<String>` (built by -`build_udp_tracker_urls`), but these are full announce URLs -(`udp://udp1.example.com:6969/announce`), not bare domain names. The domain name needs -to be extracted from those URLs, or a separate accessor for UDP domains needs to be -added. - -The same issue exists in `dns_reminder.rs` which also calls `tls_domain_names()` to -build the DNS setup hint. - -### Solution - -Add a method `all_domain_names() -> Vec<&str>` (or rename the existing one) to -`ServiceInfo` that returns: - -1. All TLS service domain names (HTTP trackers, API, Grafana) — currently in `tls_domains` -2. All UDP tracker domain names — extracted from `UdpTrackerConfig::domain()` (if set) - -The `provision_details.rs` and `dns_reminder.rs` callers are then updated to call the -new method instead of `tls_domain_names()`. - -An alternative is to keep `tls_domain_names()` unchanged (it is used for the HTTPS -hint that reads "configure these domains in /etc/hosts or your DNS before enabling TLS") -and introduce a separate `all_domain_names()` accessor used only for the DNS reminder -and provision output `domains` field. This keeps the TLS-specific semantic intact while -fixing the provision output. - -### Affected Modules and Types - -#### `src/application/command_handlers/show/info/tracker.rs` - -- `ServiceInfo`: add `all_domain_names() -> Vec<&str>` that returns TLS domains plus - UDP tracker domains that have a `domain` configured. -- `build_udp_tracker_urls`: no change needed; UDP domains are read directly from - `UdpTrackerConfig::domain()`. - -#### `src/presentation/cli/views/commands/provision/view_data/provision_details.rs` - -- `From<&Environment<Provisioned>>` implementation: replace the `tls_domain_names()` - call with `all_domain_names()`. - -#### `src/presentation/cli/views/commands/provision/view_data/dns_reminder.rs` - -- Replace the `tls_domain_names()` call with `all_domain_names()`. - -### What Does Not Change - -- `tls_domain_names()` is kept as-is for the `show` command's HTTPS/TLS hint, which - should only list TLS domains (the hint is about configuring reverse proxy, not DNS). -- The UDP tracker URLs in `ServiceInfo.udp_trackers` are unchanged. -- The domain name in a UDP tracker config is optional; UDP trackers without a configured - domain produce no entry in `all_domain_names()`. - -## Implementation Plan - -### Phase 1: Add `all_domain_names()` to `ServiceInfo` - -- [ ] In `ServiceInfo` (`tracker.rs`): implement `all_domain_names() -> Vec<&str>` that - returns the union of TLS domain names and UDP tracker domain names (where - `udp.domain()` is `Some`) -- [ ] Keep `tls_domain_names()` unchanged - -### Phase 2: Update callers - -- [ ] `provision_details.rs`: replace `tls_domain_names()` call with `all_domain_names()` -- [ ] `dns_reminder.rs`: replace `tls_domain_names()` call with `all_domain_names()` - -### Phase 3: Tests - -- [ ] `tracker.rs`: add test `it_should_return_all_domain_names_including_udp` that - asserts UDP tracker domains appear in `all_domain_names()` when a UDP domain is set -- [ ] `tracker.rs`: add test `it_should_exclude_udp_trackers_without_domain_from_all_domain_names` - that asserts UDP trackers without a `domain` do not appear -- [ ] `provision_details.rs` or `text_view.rs`/`json_view.rs`: update or add test - covering UDP domains in the rendered provision output -- [ ] Run `cargo test` to verify all tests pass - -### Phase 4: Linting and pre-commit - -- [ ] Run linters: `cargo run --bin linter all` -- [ ] Run pre-commit: `./scripts/pre-commit.sh` - -## Acceptance Criteria - -> **Note for Contributors**: These criteria define what the PR reviewer will check. -> Use this as your pre-review checklist before submitting the PR to minimize -> back-and-forth iterations. - -**Quality Checks**: - -- [ ] Pre-commit checks pass: `./scripts/pre-commit.sh` - -**Task-Specific Criteria**: - -- [ ] `provision` output `domains` array includes UDP tracker domain names when the - environment config supplies a `domain` for UDP trackers -- [ ] `provision` output `domains` array does **not** include entries for UDP trackers - that have no `domain` configured (IP-only UDP trackers) -- [ ] The DNS setup reminder shown after `provision` also includes UDP tracker domains -- [ ] `tls_domain_names()` is unchanged — the HTTPS-specific TLS hint is not affected -- [ ] Unit tests for `all_domain_names()` pass for both the UDP-with-domain and - UDP-without-domain cases - -## Related Documentation - -- [docs/deployments/hetzner-demo-tracker/commands/provision/bugs.md](../deployments/hetzner-demo-tracker/commands/provision/bugs.md) — original bug report -- [src/application/command_handlers/show/info/tracker.rs](../../src/application/command_handlers/show/info/tracker.rs) — `ServiceInfo`, `tls_domain_names()` -- [src/presentation/cli/views/commands/provision/view_data/provision_details.rs](../../src/presentation/cli/views/commands/provision/view_data/provision_details.rs) — `ProvisionDetailsData` and its `From` impl -- [src/presentation/cli/views/commands/provision/view_data/dns_reminder.rs](../../src/presentation/cli/views/commands/provision/view_data/dns_reminder.rs) — DNS reminder view diff --git a/docs/issues/416-replace-local-linting-with-published-crate.md b/docs/issues/416-replace-local-linting-with-published-crate.md deleted file mode 100644 index e9cb5db7c..000000000 --- a/docs/issues/416-replace-local-linting-with-published-crate.md +++ /dev/null @@ -1,139 +0,0 @@ -# Replace Local `packages/linting` with Published `torrust-linting` Crate - -**Issue**: #416 -**Parent Epic**: None -**Related**: None - -## Overview - -The `packages/linting` workspace package has been extracted and published to -[crates.io as `torrust-linting`](https://crates.io/crates/torrust-linting) -(v0.1.0). This makes it an independently versioned and reusable library for any -Torrust project. - -This task replaces the local path dependency with the published crate and removes -the now-redundant workspace member. - -## Goals - -- [ ] Replace `torrust-linting = { path = "packages/linting" }` with `torrust-linting = "0.1.0"` in `Cargo.toml` -- [ ] Remove `"packages/linting"` from the workspace `members` list in `Cargo.toml` -- [ ] Delete the `packages/linting/` directory -- [ ] Update documentation that references `packages/linting/` -- [ ] Update `packages/README.md` to reflect that `torrust-linting` is now an external dependency -- [ ] Verify the build and all tests pass after the change - -## 🏗️ Architecture Requirements - -**DDD Layer**: N/A (build infrastructure / workspace configuration) -**Module Path**: N/A -**Pattern**: External crate dependency substitution - -### Architectural Constraints - -- [ ] No changes to `src/` code — the public API of `torrust-linting` on crates.io matches the local package - -### Anti-Patterns to Avoid - -- ❌ Keeping the local package in the workspace after migrating (dead code / confusion) -- ❌ Updating `src/bin/linter.rs` imports — the API is identical and no source changes are needed - -## Specifications - -### Cargo.toml Changes - -**Before**: - -```toml -[workspace] -members = [ - "packages/linting", - "packages/dependency-installer", - "packages/sdk", - "packages/deployer-types", -] - -[dependencies] -torrust-linting = { path = "packages/linting" } -``` - -**After**: - -```toml -[workspace] -members = [ - "packages/dependency-installer", - "packages/sdk", - "packages/deployer-types", -] - -[dependencies] -torrust-linting = "0.1.0" -``` - -### Directory Removal - -Delete `packages/linting/` in its entirety (the code is now maintained in the -upstream [torrust/torrust-linting](https://github.com/torrust/torrust-linting) -repository). - -### Documentation Updates - -Files that reference `packages/linting` and will need updating: - -| File | Change | -|------|--------| -| `packages/README.md` | Remove `packages/linting` entry; add note that `torrust-linting` is an external crate | -| `docs/codebase-architecture.md` | Update linting section to reference the external crate | -| `.github/skills/dev/git-workflow/run-linters/skill.md` | Update link to linting framework | -| `.github/skills/dev/git-workflow/run-linters/references/linters.md` | Update package location description | - -## Implementation Plan - -### Phase 1: Update Cargo workspace and dependency (< 30 min) - -- [ ] Task 1.1: Remove `"packages/linting"` from `[workspace] members` in `Cargo.toml` -- [ ] Task 1.2: Replace path dependency with `torrust-linting = "0.1.0"` in `[dependencies]` -- [ ] Task 1.3: Run `cargo build` and `cargo test` to confirm no compilation errors - -### Phase 2: Remove local package (< 15 min) - -- [ ] Task 2.1: Delete `packages/linting/` directory -- [ ] Task 2.2: Run `cargo build` and `cargo test` again to confirm clean build after deletion - -### Phase 3: Update documentation (< 30 min) - -- [ ] Task 3.1: Update `packages/README.md` — remove local package entry, document as external -- [ ] Task 3.2: Update `docs/codebase-architecture.md` — linting section -- [ ] Task 3.3: Update `.github/skills/` references to `packages/linting` - -## Acceptance Criteria - -> **Note for Contributors**: These criteria define what the PR reviewer will check. -> Use this as your pre-review checklist before submitting the PR to minimize -> back-and-forth iterations. - -**Quality Checks**: - -- [ ] Pre-commit checks pass: `./scripts/pre-commit.sh` - -**Task-Specific Criteria**: - -- [ ] `packages/linting/` directory no longer exists in the repository -- [ ] `Cargo.toml` workspace `members` does not include `"packages/linting"` -- [ ] `Cargo.toml` dependency is `torrust-linting = "0.1.0"` (crates.io) -- [ ] `cargo build` and `cargo test` pass with no errors -- [ ] No remaining references to `packages/linting` in source or documentation - -## Related Documentation - -- [crates.io: torrust-linting](https://crates.io/crates/torrust-linting) -- [GitHub: torrust/torrust-linting](https://github.com/torrust/torrust-linting) -- [packages/README.md](../../packages/README.md) -- [docs/codebase-architecture.md](../codebase-architecture.md) - -## Notes - -The published crate API is identical to the local package — `src/bin/linter.rs` -and all other callers require no changes. Only the Cargo configuration and -package directory are affected. diff --git a/docs/issues/429-deployer-cves.md b/docs/issues/429-deployer-cves.md new file mode 100644 index 000000000..ed769e34f --- /dev/null +++ b/docs/issues/429-deployer-cves.md @@ -0,0 +1,70 @@ +# Issue #429: Deployer Image CVEs after Remediation Pass 1 + +**GitHub**: <https://github.com/torrust/torrust-tracker-deployer/issues/429> +**Image**: `torrust/tracker-deployer:local` +**Dockerfile**: `docker/deployer/Dockerfile` + +--- + +## Context + +After PR #436 removed `gnupg` from the runtime layer: + +| Pass | HIGH | CRITICAL | +| ------------------ | ---- | -------- | +| Before remediation | 49 | 1 | +| After pass 1 | 44 | 1 | + +Remaining findings split into two areas: + +1. **Debian 13.4 (trixie) base packages** — HIGH, blocked on upstream patches +2. **OpenTofu binary** — 2 HIGH + 1 CRITICAL, blocked on OpenTofu release + +## Decision + +**Re-scan and check OpenTofu release, then decide**: + +- If a newer OpenTofu release clears the CRITICAL: update the pinned version, + rebuild, re-scan, update scan doc, close #429 +- If Debian packages are now patched: `docker build --no-cache` will pick them up; + re-scan, update scan doc, re-evaluate #429 +- If nothing has changed: post comment documenting current state and accepted risk; + leave open with revisit note + +## Steps + +- [x] Check current OpenTofu version pinned in the Dockerfile: + `grep -i opentofu docker/deployer/Dockerfile` +- [x] Check latest OpenTofu release: + <https://github.com/opentofu/opentofu/releases> +- [x] Rebuild and re-scan: + + ```bash + docker build --no-cache -t torrust/tracker-deployer:local docker/deployer/ + trivy image --severity HIGH,CRITICAL torrust/tracker-deployer:local + ``` + +- [x] Compare against the pass-1 baseline in + `docs/security/deployer/docker/scans/torrust-tracker-deployer.md` +- [x] For Debian base package CVEs, check fix availability: + <https://security-tracker.debian.org/tracker/> +- [x] Update `docs/security/deployer/docker/scans/torrust-tracker-deployer.md` with new + scan results +- [ ] **If CRITICAL is cleared**: update Dockerfile OpenTofu version; post results + comment; close #429 +- [ ] **If only Debian packages improved**: post results comment; re-evaluate open + status +- [x] **If no change**: post comment with accepted risk rationale for remaining + CVEs; label `accepted-risk`; leave open with revisit note + +## Outcome + +- Date: Apr 15, 2026 +- Current OpenTofu version in Dockerfile: installed via script (no pinned version) +- Latest OpenTofu release: v1.11.6 (2026-04-08) — installed in rebuilt image +- Findings after rebuild (HIGH / CRITICAL): 46 HIGH / 1 CRITICAL + - Debian OS: 42 HIGH, 0 CRITICAL + - `usr/bin/tofu` (v1.11.6): 4 HIGH, 1 CRITICAL +- Decision: **leave open** — CRITICAL CVE-2026-33186 (grpc-go gRPC auth bypass) remains in tofu binary; requires OpenTofu upstream to bump grpc-go to v1.79.3+ +- Comment/PR: PR #458, comment on #429 +- Revisit: when OpenTofu ships v1.11.7+ or v1.12.x with updated grpc-go dependency diff --git a/docs/issues/432-caddy-cves.md b/docs/issues/432-caddy-cves.md new file mode 100644 index 000000000..3ffc7c4f0 --- /dev/null +++ b/docs/issues/432-caddy-cves.md @@ -0,0 +1,75 @@ +# Issue #432: Caddy CVEs after upgrade to 2.10.2 + +**GitHub**: <https://github.com/torrust/torrust-tracker-deployer/issues/432> +**Image**: `caddy:2.10.2` +**Template**: `templates/docker-compose/docker-compose.yml.tera` + +--- + +## Context + +After PR #436 upgraded Caddy from `2.10` to `2.10.2`: + +| Version | HIGH | CRITICAL | +| -------- | ---- | -------- | +| `2.10` | 18 | 6 | +| `2.10.2` | 14 | 4 | + +4 CRITICAL remain in upstream Caddy binary dependencies. + +## Decision + +**Re-scan with latest Caddy tag, then decide**: + +- If a newer tag clears CRITICALs: upgrade, update scan doc, close #432 +- If not: post comment with scan results, document accepted risk, leave open with + revisit note + +## Steps + +- [x] Check the latest Caddy release: + <https://hub.docker.com/_/caddy> and <https://github.com/caddyserver/caddy/releases> +- [x] Run Trivy against the latest tag: + `trivy image --severity HIGH,CRITICAL caddy:LATEST_TAG` +- [x] Compare results against the 2.10.2 baseline in + `docs/security/production/scans/caddy.md` +- [x] **If CRITICALs are cleared (or HIGH count drops meaningfully)**: update + `templates/docker-compose/docker-compose.yml.tera` and the CI scan matrix; + update the scan doc; post results comment; close #432 +- [ ] **If CRITICALs remain**: post comment documenting which CVEs remain and why + they cannot be fixed (upstream binary); add revisit note to #432; leave open + +## Outcome + +- Date: 2026-04-15 +- Latest Caddy tag tested: `2.11.2` (released 2026-04-14) +- Decision: **upgrade to `caddy:2.11.2`** — HIGH count dropped meaningfully (14→10), CRITICAL halved (4→2) +- Action: updated `templates/docker-compose/docker-compose.yml.tera` and CI scan matrix +- Issue: **left open** — 2 CRITICAL CVEs remain in upstream binary dependencies +- PR: opened against `main` on branch `432-caddy-cves` + +### Scan details — `caddy:2.11.2` (Trivy v0.69.3, 2026-04-15) + +**Version comparison:** + +| Version | HIGH | CRITICAL | +| -------- | ---- | -------- | +| `2.10` | 18 | 6 | +| `2.10.2` | 14 | 4 | +| `2.11.2` | 10 | 2 | + +**Target breakdown:** + +| Target | HIGH | CRITICAL | +| -------------- | ---- | -------- | +| caddy (alpine) | 3 | 0 | +| usr/bin/caddy | 7 | 2 | + +**Remaining CRITICAL CVEs (upstream binary, cannot be fixed without Caddy release):** + +| CVE | Library | Fix | Notes | +| -------------- | ---------------------- | ------ | ---------------------------------------------------------- | +| CVE-2026-30836 | smallstep/certificates | 0.30.0 | Unauthenticated SCEP cert issuance | +| CVE-2026-33186 | google.golang.org/grpc | 1.79.3 | Authorization bypass via HTTP/2 path ⚠️ network-accessible | + +**Revisit**: when Caddy ships updated grpc-go (≥1.79.3) and smallstep/certificates (≥0.30.0). diff --git a/docs/issues/443-rand-0.8.5-rustsec.md b/docs/issues/443-rand-0.8.5-rustsec.md new file mode 100644 index 000000000..891fc3437 --- /dev/null +++ b/docs/issues/443-rand-0.8.5-rustsec.md @@ -0,0 +1,73 @@ +# Issue #443: RUSTSEC-2026-0097 — `rand 0.8.5` unsound (transitive) + +**GitHub**: <https://github.com/torrust/torrust-tracker-deployer/issues/443> +**Advisory**: <https://rustsec.org/advisories/RUSTSEC-2026-0097.html> +**Affected**: `rand >= 0.7, < 0.9.3` and `0.10.0` +**Reported version**: `0.8.5` + +--- + +## Context + +`rand 0.8.5` is a transitive dependency via `tera v1.20.1`. It cannot be +directly upgraded — only a new `tera` release that bumps its own rand dependency +will clear it. + +Current dependency paths (`cargo tree -i rand@0.8.5`): + +```text +rand v0.8.5 +└── tera v1.20.1 + └── torrust-tracker-deployer v0.1.0 + +rand v0.8.5 +└── phf_generator v0.11.3 + └── phf_codegen v0.11.3 + └── chrono-tz-build v0.3.0 + [build-dependencies] + └── chrono-tz v0.9.0 + └── tera v1.20.1 (*) +``` + +## Risk Assessment + +The unsoundness requires **all** of the following conditions simultaneously: + +1. The `log` and `thread_rng` features of `rand 0.8.5` are both enabled +2. A custom logger is registered +3. The custom logger calls `rand::rng()` inside its logging code +4. `ThreadRng` attempts to reseed (every 64 kB of random output) +5. Trace-level or warn-level (with getrandom failure) logging is active + +This application does **not** implement a custom logger that calls back into rand. +Only `tera` uses `rand 0.8.5` for template rendering — it does not trigger the +logging path. + +**Conclusion**: Low practical risk — the conditions for unsoundness are not met. + +## Decision + +**Post a comment with the risk assessment, then leave open** until `tera` releases +a version using `rand >= 0.9.3`. + +## Steps + +- [x] Run `cargo audit` to confirm RUSTSEC-2026-0097 is still reported for rand 0.8.5 +- [x] Run `cargo tree -i rand@0.8.5` to confirm `tera` is still the only consumer +- [x] Check whether `tera` has released a version with `rand >= 0.9.3`: + <https://crates.io/crates/tera> +- [x] **`tera` has not updated yet** — latest stable is `1.20.1` (released ~6 months + ago). A `2.0.0-alpha.2` pre-release exists (~1 month ago) but is not production + ready. + - [x] Post a comment on #443 with the risk assessment and cargo tree output + - Leave the issue open — revisit when `tera` releases a new stable version + +## Outcome + +- Date: 2026-04-14 +- tera latest stable version: `1.20.1` (no fix available yet) +- Result: **Cannot fix.** `rand 0.8.5` is pulled in solely by `tera 1.20.1`. No + stable `tera` release uses `rand >= 0.9.3`. Practical risk is low — the + unsoundness conditions are not met in this application (no custom logger calling + back into rand). Risk assessment posted as comment on #443; issue left open. +- Comment/PR: https://github.com/torrust/torrust-tracker-deployer/issues/443#issuecomment-4246102278 diff --git a/docs/issues/460-node-24-action-deprecation-warnings.md b/docs/issues/460-node-24-action-deprecation-warnings.md new file mode 100644 index 000000000..d474f6a7d --- /dev/null +++ b/docs/issues/460-node-24-action-deprecation-warnings.md @@ -0,0 +1,131 @@ +# Update GitHub Actions to Node.js 24 Compatible Versions + +**Issue**: #460 +**Parent Epic**: N/A +**Related**: N/A + +## Overview + +Several GitHub Actions workflows produce deprecation warnings because some actions +still run on Node.js 20. Starting **June 2nd, 2026**, GitHub will force all actions +to run with Node.js 24 by default, and Node.js 20 will be removed from runners on +**September 16th, 2026**. + +Each affected action needs to be reviewed: in some cases a newer version with +Node.js 24 support exists and can be adopted; in other cases no compatible release +exists yet and the issue must be tracked until one does. + +Reference: <https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/> + +## Goals + +- [ ] Identify which affected actions have Node.js 24-compatible releases available +- [ ] Update workflow files to use compatible versions where possible +- [ ] Track actions that have no compatible release yet, and re-check periodically +- [ ] Eliminate all Node.js 20 deprecation warnings from CI runs + +## Affected Actions by Workflow + +### `backup-container.yaml` — Backup Container + +| Action | Current Version | Node.js 24? | +| ---------------------------- | --------------- | ----------- | +| `docker/setup-buildx-action` | `@v3` | TBD | +| `docker/build-push-action` | `@v6` | TBD | +| `docker/login-action` | `@v3` | TBD | +| `docker/metadata-action` | `@v5` | TBD | + +### `container.yaml` — Container + +| Action | Current Version | Node.js 24? | +| ---------------------------- | --------------- | ----------- | +| `docker/setup-buildx-action` | `@v3` | TBD | +| `docker/build-push-action` | `@v6` | TBD | +| `docker/login-action` | `@v3` | TBD | +| `docker/metadata-action` | `@v5` | TBD | + +### `cargo-security-audit.yml` — Cargo Security Audit + +| Action | Current Version | Node.js 24? | +| --------------------- | --------------- | ----------- | +| `rustsec/audit-check` | `@v2.0.0` | TBD | + +### `docker-security-scan.yml` — Docker Security Scan + +| Action | Current Version | Node.js 24? | +| --------------------------- | --------------- | ----------- | +| `aquasecurity/trivy-action` | `@0.35.0` | TBD | + +> **Note**: The warning in this workflow shows `actions/cache@0400d5f...` running on +> Node.js 20. This is a **transitive dependency** used internally by +> `aquasecurity/trivy-action`. Updating Trivy to a newer release should resolve it. + +### `test-e2e-deployment.yml` — E2E Deployment Workflow Tests + +| Action | Current Version | Node.js 24? | +| ---------------------------- | --------------- | ----------- | +| `docker/setup-buildx-action` | `@v3` | TBD | + +### `dependabot-updates` — Dependabot (GitHub-managed) + +| Action | Current Version | Node.js 24? | +| -------------------------- | --------------- | ----------- | +| `github/dependabot-action` | `@main` | TBD | + +> **Note**: This workflow is **managed entirely by GitHub** and is not present in +> this repository. We cannot update it directly. The warning may resolve +> automatically when GitHub updates their internal Dependabot runner, or it may +> require a GitHub support request. + +## Implementation Plan + +### Phase 1: Research available updates + +- [ ] Check latest releases of `docker/setup-buildx-action`, `docker/build-push-action`, `docker/login-action`, `docker/metadata-action` for Node.js 24 support +- [ ] Check latest release of `rustsec/audit-check` for Node.js 24 support +- [ ] Check latest release of `aquasecurity/trivy-action` for Node.js 24 support (resolves transitive `actions/cache` warning) +- [ ] Investigate `github/dependabot-action` — determine if this is fully GitHub-managed and no action is needed from our side + +### Phase 2: Apply available updates + +- [ ] Update all docker action versions in `backup-container.yaml` where newer Node.js 24 compatible versions are available +- [ ] Update all docker action versions in `container.yaml` where newer Node.js 24 compatible versions are available +- [ ] Update `docker/setup-buildx-action` in `test-e2e-deployment.yml` +- [ ] Update `rustsec/audit-check` in `cargo-security-audit.yml` +- [ ] Update `aquasecurity/trivy-action` in `docker-security-scan.yml` + +### Phase 3: Handle actions with no available update + +- [ ] For any action without a Node.js 24-compatible release, open a follow-up tracking note or issue +- [ ] Document the status and re-check schedule + +## Acceptance Criteria + +> **Note for Contributors**: These criteria define what the PR reviewer will check. Use this as your pre-review checklist before submitting the PR to minimize back-and-forth iterations. + +**Quality Checks**: + +- [ ] Pre-commit checks pass: `./scripts/pre-commit.sh` + +**Task-Specific Criteria**: + +- [ ] No Node.js 20 deprecation warnings appear in any of the affected workflow runs +- [ ] All updated action versions are pinned correctly and tested +- [ ] Any action that cannot be updated is documented with a follow-up plan + +## Related Documentation + +- [GitHub blog: Deprecation of Node 20 on Actions Runners](https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/) +- Affected workflow runs: + - [backup-container.yaml run #24191868780](https://github.com/torrust/torrust-tracker-deployer/actions/runs/24191868780) + - [cargo-security-audit.yml run #24455465380](https://github.com/torrust/torrust-tracker-deployer/actions/runs/24455465380) + - [container.yaml run #24455465394](https://github.com/torrust/torrust-tracker-deployer/actions/runs/24455465394) + - [dependabot-updates run #24389583837](https://github.com/torrust/torrust-tracker-deployer/actions/runs/24389583837) + - [docker-security-scan.yml run #24445697392](https://github.com/torrust/torrust-tracker-deployer/actions/runs/24445697392) + - [test-e2e-deployment.yml run #24455481734](https://github.com/torrust/torrust-tracker-deployer/actions/runs/24455481734) + +## Notes + +- The `docker/*` actions appear in both `backup-container.yaml` and `container.yaml` with the same versions. They should be updated together. +- The `dependabot-updates` warning may resolve itself without any action on our part — GitHub is likely already working on updating the internal runner. +- Setting `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true` in the workflow environment is available as a temporary opt-in to test compatibility before the forced migration. diff --git a/docs/issues/462-release-v0-1-0-stable-end-to-end-execution.md b/docs/issues/462-release-v0-1-0-stable-end-to-end-execution.md new file mode 100644 index 000000000..e7240867b --- /dev/null +++ b/docs/issues/462-release-v0-1-0-stable-end-to-end-execution.md @@ -0,0 +1,148 @@ +# Release v0.1.0: Stable End-to-End Execution + +**Issue**: #462 +**Parent Epic**: N/A +**Related**: [#459](https://github.com/torrust/torrust-tracker-deployer/issues/459) (beta.2 release validation), +[#450](https://github.com/torrust/torrust-tracker-deployer/issues/450) (beta.1 release validation), +[#448](https://github.com/torrust/torrust-tracker-deployer/issues/448) (release process definition), +[#452](https://github.com/torrust/torrust-tracker-deployer/pull/452) (crate rename to tracker-deployer namespace) + +## Overview + +Execute the first stable release of the Torrust Tracker Deployer (`v0.1.0`), +validating the full release workflow from version bump through release publication. + +This release is the GA cut after beta validation. The objective is to execute the +canonical process cleanly, ensure all release artifacts are published and verifiable, +and capture any friction that still exists in the release workflow documentation, +skill instructions, or CI automation. + +## Goals + +- [ ] Version `0.1.0` is reflected in all four `Cargo.toml` files on `main` +- [ ] Signed tag `v0.1.0` and release branch `releases/v0.1.0` exist +- [ ] Docker image `torrust/tracker-deployer:0.1.0` is published to Docker Hub +- [ ] All four crates are published to crates.io at version `0.1.0` +- [ ] GitHub release `v0.1.0` is published (not draft) +- [ ] Any new friction points discovered are documented and filed as follow-ups + +## Specifications + +### Version Bump Scope + +Update `version` field to `0.1.0` in all four manifests: + +| File | Crate | +| ------------------------------------------ | ----------------------------------------------- | +| `Cargo.toml` (workspace root) | `torrust-tracker-deployer` | +| `packages/deployer-types/Cargo.toml` | `torrust-tracker-deployer-types` | +| `packages/dependency-installer/Cargo.toml` | `torrust-tracker-deployer-dependency-installer` | +| `packages/sdk/Cargo.toml` | `torrust-tracker-deployer-sdk` | + +Also update every internal path dependency `version` constraint to `0.1.0`. + +### Publish Order (crates.io dependency order) + +1. `torrust-tracker-deployer-types` +2. `torrust-tracker-deployer-dependency-installer` +3. `torrust-tracker-deployer` +4. `torrust-tracker-deployer-sdk` + +Each crate's dry-run step in CI runs only after its prerequisites are indexed on +crates.io - do not attempt to publish out of order. + +## Implementation Plan + +### Phase 1: Pre-Flight and Setup + +- [ ] Task 1.1: Verify local workspace is clean and on `torrust/main` up-to-date + with `origin/main` (`git status`, `git pull --ff-only`) +- [ ] Task 1.2: Confirm GitHub environments `dockerhub-torrust` and `crates-io` are + correctly configured for stable release publication +- [ ] Task 1.3: Confirm releaser has push access to `main`, tags, and release branches +- [ ] Task 1.4: Document any pre-flight issues found + +### Phase 2: Execute the Release + +- [ ] Task 2.1: Update `version` to `0.1.0` in all four `Cargo.toml` files + and in every internal path dependency constraint +- [ ] Task 2.2: Run `cargo build && cargo test` and verify they pass +- [ ] Task 2.3: Run `./scripts/pre-commit.sh` and verify all checks pass +- [ ] Task 2.4: Create and push the signed release commit to `main` + (`git commit -S -m "release: version v0.1.0"`) +- [ ] Task 2.5: Create and push annotated signed tag `v0.1.0` +- [ ] Task 2.6: Create and push release branch `releases/v0.1.0` +- [ ] Task 2.7: Monitor Container and Publish Crate workflows to completion + +### Phase 3: Artifact Verification + +- [ ] Task 3.1: Pull and inspect Docker image `torrust/tracker-deployer:0.1.0` +- [ ] Task 3.2: Verify all four crates at `0.1.0` are visible on crates.io +- [ ] Task 3.3: Verify docs.rs build for the published crate versions +- [ ] Task 3.4: Create GitHub release from tag `v0.1.0` + +### Phase 4: Process Review and Fixes + +- [ ] Task 4.1: Collect every friction point, error, or unexpected step encountered + during the release - no matter how small +- [ ] Task 4.2: For each issue found, fix the root cause immediately in the relevant + artifact: + - `docs/release-process.md` - if a step was wrong, missing, or misleading + - `.github/skills/dev/git-workflow/release-new-version/skill.md` - if the skill + diverged from reality + - `scripts/` - if a helper script failed or was absent + - CI workflow files (`.github/workflows/`) - if a workflow behaved unexpectedly + - Any other documentation, template, or configuration that contributed to the + friction +- [ ] Task 4.3: File follow-up issues for any non-trivial problems that cannot be + fixed inline (e.g., upstream blockers, larger refactors) +- [ ] Task 4.4: Re-run `./scripts/pre-commit.sh` after any fixes to confirm nothing + was broken +- [ ] Task 4.5: Confirm all finalization gates below are met + +## Acceptance Criteria + +**Quality Checks**: + +- [ ] Pre-commit checks pass: `./scripts/pre-commit.sh` + +**Release Execution**: + +- [ ] Version `0.1.0` is committed and present in all four `Cargo.toml` files on `main` +- [ ] Tag `v0.1.0` exists and is signed +- [ ] Branch `releases/v0.1.0` exists +- [ ] Container workflow completed successfully +- [ ] Publish Crate workflow completed successfully +- [ ] GitHub release `v0.1.0` is published (not draft) + +**Artifact Verification**: + +- [ ] Docker image `torrust/tracker-deployer:0.1.0` can be pulled and run +- [ ] All four crates at `0.1.0` are visible on crates.io: + - [ ] `torrust-tracker-deployer-types@0.1.0` + - [ ] `torrust-tracker-deployer-dependency-installer@0.1.0` + - [ ] `torrust-tracker-deployer@0.1.0` + - [ ] `torrust-tracker-deployer-sdk@0.1.0` +- [ ] docs.rs build page loads for the published versions + +**Process Quality**: + +- [ ] Every friction point or error encountered is documented (inline comment or + filed follow-up issue) +- [ ] Every fixable problem is fixed in the relevant artifact - documentation, + skill, script, or workflow - not just noted +- [ ] No step was silently skipped or improvised without documentation +- [ ] `docs/release-process.md` and the release skill accurately reflect how the + release was actually executed + +## Related Documentation + +- [Release Process](../release-process.md) +- [Release Skill](../../.github/skills/dev/git-workflow/release-new-version/skill.md) +- [beta.2 release issue #459](https://github.com/torrust/torrust-tracker-deployer/issues/459) + +## Notes + +This is a stable release (`0.1.0`), not a pre-release. Cargo users should receive +this version by default through normal semver resolution, and release notes should +highlight upgrade guidance from `0.1.0-beta.2` where relevant. diff --git a/docs/refactors/completed-refactorings.md b/docs/refactors/completed-refactorings.md index ee709f543..d3576d47a 100644 --- a/docs/refactors/completed-refactorings.md +++ b/docs/refactors/completed-refactorings.md @@ -4,7 +4,7 @@ | --------------------------------------------------- | ------------ | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Standardize JsonView Render API | Feb 28, 2026 | Consistent `render()` return type + `Render<T>` trait for all views | See git history at `docs/refactors/plans/standardize-json-view-render-api.md` - Introduced `ViewRenderError` + `Render<T>` trait; removed inherent `render()` from all 26 view structs (13 `JsonView` + 13 `TextView`); eliminated dead fallback code; propagated errors through all handler chains with new `OutputFormatting` variant on 11 command error types; removed asymmetry between text and JSON renderers (3 phases, final commit `221c998a`, PR #402, 750 additions / 1528 deletions) | | SDK DDD Layer Boundary Fixes | Feb 25, 2026 | Remove DDD violations from SDK public surface | See git history at `docs/refactors/plans/sdk-ddd-layer-boundary-fixes.md` - Introduced `PersistenceError`, `InvalidStateError`, `ReleaseWorkflowStep` application-layer wrappers; updated all 9 command handler error enums; added `RepositoryProvider` trait; moved infra wiring to bootstrap; deleted orphaned `src/presentation/sdk/`; renamed `RepositoryFactory` → `FileRepositoryFactory` (commits `6f3028f4`, `52628329`, PR #381) | -| Extract Shared Types Package | Feb 25, 2026 | Extract shared value objects and traits into packages/deployer-types/ | See git history at `docs/refactors/plans/extract-shared-types-package.md` - Created `packages/deployer-types/` (`torrust-deployer-types`) with all shared value objects and traits from `src/shared/` and `EnvironmentName` from `src/domain/`; root crate and SDK now depend on the types package, backward-compat re-exports maintained, PR #381 | +| Extract Shared Types Package | Feb 25, 2026 | Extract shared value objects and traits into packages/deployer-types/ | See git history at `docs/refactors/plans/extract-shared-types-package.md` - Created `packages/deployer-types/` (`torrust-tracker-deployer-types`) with all shared value objects and traits from `src/shared/` and `EnvironmentName` from `src/domain/`; root crate and SDK now depend on the types package, backward-compat re-exports maintained, PR #381 | | Extract SDK Workspace Package | Feb 25, 2026 | Create packages/sdk/ as independently consumable workspace package | See git history at `docs/refactors/plans/extract-sdk-workspace-package.md` - Created `packages/sdk/` (`torrust-tracker-deployer-sdk`) with SDK source, README, and examples moved from root `examples/sdk/`; backward compat deferred (cyclic dep resolved in Plan 3), PR #381 | | Presentation CLI/SDK Separation | Feb 25, 2026 | Separate CLI and SDK in presentation layer | See git history at `docs/refactors/plans/presentation-cli-sdk-separation.md` - Moved all CLI-specific modules (controllers, dispatch, input, views, error, errors, tests) under `src/presentation/cli/`, updated 160 files, SDK confirmed zero CLI imports (commit f02024f6, PR #381) | | E2E Test Isolation - Complete Log Directory Support | Feb 18, 2026 | Add log_dir to all E2E tests | See git history at `docs/refactors/plans/e2e-test-isolation-log-dir.md` - Added `.log_dir()` to 45 ProcessRunner calls across 6 E2E test files (validate, create, list, show, destroy, purge commands); all tests produce zero production `data/` pollution (6 phases, commits 1d576a5a→1c437d80, Issue [#365](https://github.com/torrust/torrust-tracker-deployer/issues/365)) | diff --git a/docs/release-process.md b/docs/release-process.md new file mode 100644 index 000000000..7731773fd --- /dev/null +++ b/docs/release-process.md @@ -0,0 +1,333 @@ +# Release Process + +This document defines the standard release process for the Torrust Tracker Deployer +repository. Following these steps ensures that releases are predictable, auditable, +and consistent across Git tags, Docker images, and published crates. + +## Overview + +Releasing consists of these mandatory steps, executed **in order**: + +1. Update version in all relevant `Cargo.toml` files +2. Commit the version bump (`release: version vX.Y.Z`) +3. Push the release commit to `main` +4. Create and push the annotated, signed release tag (`vX.Y.Z`) +5. Create and push the release branch (`releases/vX.Y.Z`) +6. Wait for GitHub Actions to publish release artifacts (Docker image, crate) +7. Create the GitHub release from the tag + +Do not skip or reorder steps. Each step is a prerequisite for the next. + +## Naming Conventions + +| Artifact | Convention | Example | +| ---------------- | ----------------------- | ----------------- | +| Git tag | `vX.Y.Z` | `v1.2.3` | +| Release branch | `releases/vX.Y.Z` | `releases/v1.2.3` | +| Docker image tag | `X.Y.Z` (no `v` prefix) | `1.2.3` | +| Crate version | `X.Y.Z` (no `v` prefix) | `1.2.3` | + +> **Important**: Docker release tags must use bare semver (`X.Y.Z`). Never publish +> release Docker tags with the `v` prefix (e.g., `v1.2.3`). + +## Files to Update for Each Release + +Update the `version` field in all four manifests: + +| File | Crate | +| ------------------------------------------ | ----------------------------------------------- | +| `Cargo.toml` (workspace root) | `torrust-tracker-deployer` | +| `packages/deployer-types/Cargo.toml` | `torrust-tracker-deployer-types` | +| `packages/dependency-installer/Cargo.toml` | `torrust-tracker-deployer-dependency-installer` | +| `packages/sdk/Cargo.toml` | `torrust-tracker-deployer-sdk` | + +All four must carry the same non-prefixed semver version string (e.g., `1.2.3`). + +Also ensure that every internal path dependency in each manifest declares an explicit +`version` constraint that matches the release version, for example: + +```toml +torrust-tracker-deployer-types = { path = "packages/deployer-types", version = "1.2.3" } +``` + +crates.io rejects packages with path-only dependencies (no `version` field) because +consumers resolving the crate from the registry cannot follow local paths. + +## Pre-Flight Checklist + +Run these checks before starting any release actions: + +**Git state:** + +- [ ] You are on the `main` branch with a clean working tree (`git status`) +- [ ] The branch is up to date with `origin/main` (`git pull --ff-only`) + +**GitHub Environments:** + +- [ ] GitHub Environment `dockerhub-torrust` exists and contains: + - `DOCKER_HUB_ACCESS_TOKEN` — secret + - `DOCKER_HUB_USERNAME` — variable (value: `torrust`) +- [ ] GitHub Environment `crates-io` exists and contains: + - `CARGO_REGISTRY_TOKEN` — secret with `publish-new` and `publish-update` scopes + for **all four** crate names: + - `torrust-tracker-deployer-types` + - `torrust-tracker-deployer-dependency-installer` + - `torrust-tracker-deployer` + - `torrust-tracker-deployer-sdk` + + > A token scoped to only one crate name will cause HTTP 403 Forbidden for all + > others. Create a single token covering all four names. + +**Permissions:** + +- [ ] You have push access to `main`, and can push tags and release branches +- [ ] You have access to the `dockerhub-torrust` and `crates-io` environments + +**Crate metadata** (verify for all four crates before first publish, or whenever +adding a new publishable crate): + +- [ ] Each `Cargo.toml` has `description`, `license`, `repository`, and `readme` +- [ ] Each internal path dependency also declares an explicit `version` constraint +- [ ] All four crate names follow the `torrust-tracker-deployer-*` namespace + (crate names are permanent on crates.io — audit before first publish) +- [ ] `Cargo.lock` is committed and up to date + +## Release Steps + +### Step 1 — Update Versions + +Edit the `version` field in all four `Cargo.toml` files. Also update the `version` +constraint on every internal path dependency in each manifest to match the new +release version: + +```bash +# Edit these four files: +# Cargo.toml +# packages/deployer-types/Cargo.toml +# packages/dependency-installer/Cargo.toml +# packages/sdk/Cargo.toml +# +# In each, change: version = "X.Y.Z-dev" (or current dev version) +# to: version = "X.Y.Z" +# +# Also update the version constraint on internal path deps, for example: +# torrust-tracker-deployer-types = { path = "...", version = "X.Y.Z" } +``` + +Verify the workspace compiles and tests pass after the version change: + +```bash +cargo build +cargo test +``` + +### Step 2 — Create the Release Commit + +Stage all four manifests **and `Cargo.lock`**, then create a signed commit: + +```bash +git add Cargo.toml \ + packages/deployer-types/Cargo.toml \ + packages/dependency-installer/Cargo.toml \ + packages/sdk/Cargo.toml \ + Cargo.lock +git commit -S -m "release: version vX.Y.Z" +``` + +The commit subject must follow the pattern `release: version vX.Y.Z` so releases +are easily identifiable in the git log. + +### Step 3 — Push to `main` + +```bash +git push origin main +``` + +Wait for the CI pipeline on `main` to pass before continuing. + +### Step 4 — Create and Push the Release Tag + +Create an annotated, signed tag from the release commit: + +```bash +git tag -s -a vX.Y.Z -m "Release vX.Y.Z" +git push origin vX.Y.Z +``` + +Verify the tag: + +```bash +git tag -v vX.Y.Z +``` + +### Step 5 — Create and Push the Release Branch + +Create the release branch from the same commit (already at `HEAD` of `main`): + +```bash +git checkout -b releases/vX.Y.Z +git push origin releases/vX.Y.Z +``` + +This push triggers the GitHub Actions workflows that publish the Docker image and +the crate. + +### Step 6 — Wait for CI Artifacts + +Monitor the following workflows in GitHub Actions: + +- **Container** workflow — publishes the Docker image tagged `X.Y.Z` to Docker Hub +- **Publish Crate** workflow — publishes all four crates to crates.io in dependency + order: + 1. `torrust-tracker-deployer-types` + 2. `torrust-tracker-deployer-dependency-installer` + 3. `torrust-tracker-deployer` + 4. `torrust-tracker-deployer-sdk` + + Each crate's dry-run step runs only after its prerequisites are available on + crates.io. Do not attempt to manually publish out of order. + +Both workflows must succeed before moving to step 7. See +[Finalization Gates](#finalization-gates) below. + +### Step 7 — Create the GitHub Release + +Once both workflows have passed: + +1. Go to **GitHub → Releases → Draft a new release** +2. Select tag `vX.Y.Z` +3. Write release notes (highlights, breaking changes, upgrade instructions) +4. Publish the release + +## Finalization Gates + +All of the following must be confirmed before marking the release as complete: + +- [ ] Release commit is on `main` and CI passed +- [ ] Tag `vX.Y.Z` is pushed and signed +- [ ] Branch `releases/vX.Y.Z` is pushed +- [ ] Container workflow completed successfully (Docker image `X.Y.Z` published) +- [ ] Publish Crate workflow completed successfully (crate `X.Y.Z` on crates.io) +- [ ] GitHub release created from tag `vX.Y.Z` + +## Docker Image Verification + +After the Container workflow completes: + +```bash +# Pull and inspect the published image +docker pull torrust/tracker-deployer:X.Y.Z +docker image inspect torrust/tracker-deployer:X.Y.Z + +# Confirm the version and tools +docker run --rm torrust/tracker-deployer:X.Y.Z --version || true +docker run --rm --entrypoint tofu torrust/tracker-deployer:X.Y.Z version +``` + +## Crate Verification + +After the Publish Crate workflow completes, verify all four crates are indexed +(crates.io indexing may take a few minutes after publish): + +```bash +for crate in \ + torrust-tracker-deployer-types \ + torrust-tracker-deployer-dependency-installer \ + torrust-tracker-deployer \ + torrust-tracker-deployer-sdk; do + status=$(curl -s -o /dev/null -w "%{http_code}" \ + "https://crates.io/api/v1/crates/$crate/X.Y.Z") + echo "$crate: HTTP $status" +done +``` + +All four should return `HTTP 200`. If any return 404, wait a few minutes and retry. + +**docs.rs** pages may take minutes to hours to become available after a first +publish, especially for a new crate. The Publish Crate workflow prints the URLs +as informational output. Verify them manually when convenient: + +- `https://docs.rs/torrust-tracker-deployer-types/X.Y.Z` +- `https://docs.rs/torrust-tracker-deployer-dependency-installer/X.Y.Z` +- `https://docs.rs/torrust-tracker-deployer/X.Y.Z` +- `https://docs.rs/torrust-tracker-deployer-sdk/X.Y.Z` + +A 404 on docs.rs is **not** a release failure. The crate is published; docs.rs +builds in its own queue. + +## Failure Handling and Recovery + +### Partial-Failure Action Matrix + +| Failure point | Action | +| --------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| Docker failed, crate not started | Fix the Docker workflow and re-run the Container workflow on the same release branch | +| Docker passed, crate failed before upload | Fix the issue and re-run the Publish Crate workflow on the same release branch | +| Crate published, later step (e.g., GitHub release) failed | Do not republish. Proceed with follow-up patch release if the crate artifact is broken | + +### Re-Run Rules + +**Docker publication** is safely re-runnable for the same release branch. Pushing the +same Docker tag twice with identical content is idempotent. + +**Crate publication** must detect previously published versions: + +- `cargo publish` will fail with a clear error if the version is already on crates.io +- Do not attempt to republish the same version; instead, cut a patch release + +**Tag and branch creation** must verify that refs do not already exist: + +```bash +# Check before creating the tag +git ls-remote --tags origin vX.Y.Z + +# Check before creating the release branch +git ls-remote --heads origin releases/vX.Y.Z +``` + +If a ref already exists, **do not force-push**. Investigate the previous state and +determine whether the release partially succeeded. + +### Crate Rollback and Yank Policy + +Yanking a published crate is a last resort, not a routine operation. + +Use `cargo yank` **only** for: + +- A critical security vulnerability in the published version +- A broken build that prevents dependents from compiling +- Corruption that makes the crate entirely unusable + +```bash +# Yank a specific version (prevents new Cargo.lock pins; existing users keep it) +cargo yank --version X.Y.Z torrust-tracker-deployer-sdk +``` + +After yanking, cut a patch release (`X.Y.Z+1`) with a fix and document the +remediation in its release notes. + +Never yank for minor issues. Prefer a follow-up patch release instead. + +### Tag and Branch Rollback + +If the release commit has not been pushed to `main` yet, you can reset locally: + +```bash +# Delete the local tag +git tag -d vX.Y.Z + +# Delete the local branch +git branch -d releases/vX.Y.Z +``` + +Once a tag or branch is pushed and CI has run, **do not delete** the remote ref +without coordinating with maintainers. Deleting a published release ref can break +CI re-runs and audit trails. + +## Related Documentation + +- [Branching conventions](contributing/branching.md) +- [Commit process](contributing/commit-process.md) +- [Docker workflow](../.github/workflows/container.yaml) +- [Crate publish workflow](../.github/workflows/publish-crate.yaml) +- [Roadmap](roadmap.md) diff --git a/docs/research/caddy-tls-proxy-evaluation/security-scan.md b/docs/research/caddy-tls-proxy-evaluation/security-scan.md index fb1521508..6a9ba7dfe 100644 --- a/docs/research/caddy-tls-proxy-evaluation/security-scan.md +++ b/docs/research/caddy-tls-proxy-evaluation/security-scan.md @@ -28,7 +28,6 @@ ### HIGH Severity 1. **CVE-2025-59530** - Crash in github.com/quic-go/quic-go - - **Component**: `github.com/quic-go/quic-go` - **Installed Version**: v0.54.0 - **Fixed Version**: 0.49.1, 0.54.1 @@ -36,7 +35,6 @@ - **Reference**: https://avd.aquasec.com/nvd/cve-2025-59530 2. **CVE-2025-58183** - Unbounded allocation in Go stdlib - - **Component**: `stdlib` - **Installed Version**: v1.25.0 - **Fixed Version**: 1.24.8, 1.25.2 @@ -55,13 +53,11 @@ ### Risk Assessment 1. **CVE-2025-44005 (CRITICAL)**: - - **Impact**: Authorization bypass in certificate creation - **Mitigation**: This affects the `smallstep/certificates` library, which is used by Caddy for certificate management - **Action Required**: Monitor for Caddy v2.11 release with updated dependencies 2. **CVE-2025-59530 (HIGH)**: - - **Impact**: QUIC protocol crash vulnerability - **Mitigation**: Affects HTTP/3 (QUIC) support; HTTP/2 and HTTP/1.1 not affected - **Action Required**: Monitor for Caddy release with patched QUIC library @@ -106,14 +102,12 @@ Caddy's vulnerability count is within normal range for Go-based proxies. When Caddy is officially integrated into the deployer (new issue), the following workflow updates will be required: 1. **Update `.github/workflows/docker-security-scan.yml`**: - - Add `caddy:2.10` (or latest version) to the third-party images matrix - This ensures automated security scanning in CI/CD pipeline 2. **Add to security scan documentation**: - - - Create `docs/security/docker/scans/caddy.md` with scan history - - Update summary table in `docs/security/docker/scans/README.md` + - Create `docs/security/production/scans/caddy.md` with scan history + - Update summary table in `docs/security/production/scans/README.md` 3. **Set up GitHub Security monitoring**: - SARIF results will automatically upload to GitHub Security tab diff --git a/docs/security/README.md b/docs/security/README.md new file mode 100644 index 000000000..a85d4f6e9 --- /dev/null +++ b/docs/security/README.md @@ -0,0 +1,111 @@ +# Security Overview + +This directory documents security considerations for the Torrust Tracker Deployer project, organized by priority level. + +## Priority Levels + +Security effort should be distributed according to exposure and risk. The highest-priority areas are those that directly affect end users in production. + +### Priority 1 — Production Environment (Critical) + +**Directory**: [`production/`](production/) + +The most critical security surface: the Docker images, OS packages, system dependencies, and server configuration that the deployer deploys to production. + +These are exposed to the internet and run continuously. Any vulnerability here directly affects tracker users. + +**Scope**: + +- Service container images: `caddy`, `prom/prometheus`, `grafana/grafana`, `mysql` +- Backup service container: `torrust/tracker-backup` +- OS base layers of the provisioned VM +- Server configuration (TLS, SSH access policies) + +**Scan history**: [`production/scans/`](production/scans/) + +--- + +### Priority 2 — User Workflow Security (Important) + +**Directory**: [`user-security/`](user-security/) + +How users interact with the deployer affects the security of their deployments. Mistakes here can expose secrets or production credentials. + +**Scope**: + +- Sharing secrets with AI coding agents during deployment +- SSH access controls and key management +- Safe handling of deployment credentials (`envs/*.json`) + +**Documents**: + +- [AI Agents and Secrets](user-security/ai-agents-and-secrets.md) — risks when using cloud-based AI agents during deployments +- [SSH Root Access on Hetzner](user-security/ssh-root-access-hetzner.md) — SSH key behavior and hardening guidance + +--- + +### Priority 3 — Deployer Tooling Security (Standard) + +**Directory**: [`deployer/`](deployer/) + +The deployer itself — its Rust binary, container images, and bundled tools (OpenTofu, Ansible). This is a **lower-risk surface** because: + +- Users run the deployer locally for minutes at a time +- It is not exposed to the internet during normal use +- It runs in a controlled local or CI environment + +This priority increases if the deployer is ever embedded in a long-running service (e.g., a web application that calls the deployer on demand). + +**Scope**: + +- The deployer container image: `torrust/tracker-deployer` (Rust binary + OpenTofu + Ansible) +- Rust dependency vulnerabilities (`cargo audit` / RustSec) +- Bundled tool vulnerabilities: OpenTofu, Ansible + +**Subdirectories**: + +- [`deployer/docker/`](deployer/docker/) — Docker image scans +- [`deployer/dependencies/`](deployer/dependencies/) — Rust dependency audits + +--- + +### Priority 4 — Testing Artifacts (Low) + +**Directory**: [`testing/`](testing/) + +Docker images and other artifacts used only in automated tests or local development. These never run in production and have a minimal attack surface. + +**Scope**: + +- `torrust/tracker-ssh-server` — SSH server used in E2E integration tests +- `torrust/tracker-provisioned-instance` — Ubuntu VM simulation used in E2E deployment workflow tests + +**Scan history**: [`testing/scans/`](testing/scans/) + +--- + +## Scan Tooling + +| Tool | Purpose | Run Command | +| ----------- | ------------------------- | ---------------------------------------------- | +| Trivy | Docker image CVE scanning | `trivy image --severity HIGH,CRITICAL <image>` | +| cargo-audit | Rust dependency audits | `cargo audit` | + +## Current Security Status + +### Production Images + +See [`production/scans/README.md`](production/scans/README.md) for the latest status of all production-deployed images. + +### Deployer Images + +See [`deployer/docker/scans/README.md`](deployer/docker/scans/README.md) for the latest status of deployer-internal images. + +### Rust Dependencies + +See [`deployer/dependencies/README.md`](deployer/dependencies/README.md) for the latest cargo-audit report. + +## Related Documentation + +- [Docker Image Scanning Guide](production/README.md) +- [Dependency Security Reports](deployer/dependencies/README.md) diff --git a/docs/security/deployer/README.md b/docs/security/deployer/README.md new file mode 100644 index 000000000..2cd35576d --- /dev/null +++ b/docs/security/deployer/README.md @@ -0,0 +1,30 @@ +# Deployer Tooling Security + +This directory covers security for the deployer's own tools and container images. +These are [Priority 3](../README.md) — a lower-risk surface because the deployer runs locally for minutes at a time and is not exposed to the internet. + +> **Note**: This priority increases if the deployer is ever embedded in a long-running service +> (e.g., a web application that provisions environments on demand). + +## Subdirectories + +### [`docker/`](docker/) + +Security scans for Docker images used by the deployer itself: + +- `torrust/tracker-deployer` — the deployer container (Rust binary + OpenTofu + Ansible) +- `torrust/tracker-backup` — backup helper container +- `torrust/tracker-ssh-server` — SSH server used in local testing + +### [`dependencies/`](dependencies/) + +Rust dependency security audits via `cargo audit`: + +- Tracks RustSec advisories for the deployer's Cargo.lock +- Records remediation actions and accepted risks + +## Relationship to Priority 1 + +Vulnerabilities in deployer tooling are less urgent than production image vulnerabilities +because the deployer is a short-lived local tool. However, CRITICAL CVEs in tools like +OpenTofu or Ansible should still be tracked and addressed when upstream fixes are available. diff --git a/docs/security/deployer/dependencies/README.md b/docs/security/deployer/dependencies/README.md new file mode 100644 index 000000000..c1accee93 --- /dev/null +++ b/docs/security/deployer/dependencies/README.md @@ -0,0 +1,21 @@ +# Dependency Security Reports + +This directory tracks Rust dependency security scans for the deployer workspace. + +## Current Status + +- Last scan: 2026-04-10 +- Tool: `cargo-audit` +- Status: no known RustSec vulnerabilities in `Cargo.lock` +- Latest report: [scans/2026-04-10-cargo-audit.md](scans/2026-04-10-cargo-audit.md) + +## Scanning Standard + +- Run command: `cargo audit` +- Record date, scanner output summary, and remediation actions. +- If findings remain and cannot be fixed quickly, open a follow-up GitHub issue and link it in the report. + +## Related Automation + +- Workflow: `.github/workflows/cargo-security-audit.yml` +- RustSec action: <https://github.com/rustsec/audit-check> diff --git a/docs/security/deployer/dependencies/scans/2026-04-10-cargo-audit.md b/docs/security/deployer/dependencies/scans/2026-04-10-cargo-audit.md new file mode 100644 index 000000000..f6f09c9e2 --- /dev/null +++ b/docs/security/deployer/dependencies/scans/2026-04-10-cargo-audit.md @@ -0,0 +1,72 @@ +<!-- cspell:ignore RUSTSEC webpki pemfile --> + +# Cargo Audit Security Scan - 2026-04-10 + +## Scan Metadata + +- Date: 2026-04-10 +- Tool: `cargo-audit` +- Workspace: `torrust-tracker-deployer` +- Command: `cargo audit` + +## Baseline (Before Remediation) + +Initial scan found 4 vulnerabilities and 1 warning: + +1. `RUSTSEC-2026-0066` - `astral-tokio-tar 0.5.6` +1. `RUSTSEC-2026-0007` - `bytes 1.11.0` +1. `RUSTSEC-2026-0049` - `rustls-webpki 0.103.8` +1. `RUSTSEC-2026-0009` - `time 0.3.44` +1. `RUSTSEC-2025-0134` - `rustls-pemfile 2.2.0` (unmaintained warning) + +Baseline output excerpt: + +```text +error: 4 vulnerabilities found! +warning: 1 allowed warning found +``` + +## Remediation Actions + +Applied updates: + +1. Upgraded `testcontainers` in workspace root from `0.26` to `0.27`. +1. Upgraded `testcontainers` in `packages/dependency-installer` dev-dependencies from `0.25` to `0.27`. +1. Refreshed lockfile with `cargo update`. + +These updates pulled patched transitive dependencies, including: + +- `bytes 1.11.1` +- `time 0.3.47` +- `rustls-webpki 0.103.10` + +## Verification (After Remediation) + +Command rerun: + +```bash +cargo audit +``` + +Result: + +- Exit code: `0` +- No vulnerabilities reported for current lockfile. + +Output excerpt: + +```text +Fetching advisory database from `https://github.com/RustSec/advisory-db.git` +Loaded 1042 security advisories +Scanning Cargo.lock for vulnerabilities (380 crate dependencies) +``` + +## Follow-up Issues + +No follow-up issue was required for this scan because all reported vulnerabilities were resolved through dependency updates. + +## Related + +- Main task: <https://github.com/torrust/torrust-tracker-deployer/issues/439> +- Workflow: `.github/workflows/cargo-security-audit.yml` +- Dependency report index: `docs/security/deployer/dependencies/README.md` diff --git a/docs/security/docker/README.md b/docs/security/deployer/docker/README.md similarity index 83% rename from docs/security/docker/README.md rename to docs/security/deployer/docker/README.md index e4ef218e0..b5a0b8a2b 100644 --- a/docs/security/docker/README.md +++ b/docs/security/deployer/docker/README.md @@ -1,15 +1,21 @@ -# Docker Image Security Scanning Guide +# Deployer Docker Image Security -This document explains how to perform security scans on Docker images used in the deployer. +This directory covers security scanning for Docker images used by the deployer tooling. +These are [Priority 3](../../README.md) images — they run locally for minutes during deployment +and are not exposed to the internet. + +For production image security, see [`../../production/`](../../production/). ## Purpose -Regular security scanning ensures that Docker images used in production deployments are free from known vulnerabilities. This documentation provides: +Regular security scanning ensures that deployer tool images are free from known vulnerabilities. This documentation provides: -- Instructions for running security scans +- Instructions for running security scans on deployer images - Configuration guidelines - Best practices for vulnerability management +See [`../../production/`](../../production/) for scanning guidance on production-deployed images. + ## Automated Scanning For ongoing security monitoring, see [Issue #250: Implement periodic security vulnerability scanning workflow](https://github.com/torrust/torrust-tracker-deployer/issues/250). @@ -121,13 +127,8 @@ trivy image --severity HIGH,CRITICAL prom/prometheus:v3.5.0 See the [scans/](scans/) directory for historical security scan results: - [Torrust Tracker Deployer](scans/torrust-tracker-deployer.md) -- [Torrust Tracker Backup](scans/tracker-backup.md) -- [Prometheus](scans/prometheus.md) -- [Grafana](scans/grafana.md) -- [MySQL](scans/mysql.md) ## References - [Trivy Documentation](https://aquasecurity.github.io/trivy/) - [Issue #250: Automated Security Scanning](https://github.com/torrust/torrust-tracker-deployer/issues/250) -- [Issue #253: Docker Image Updates](https://github.com/torrust/torrust-tracker-deployer/issues/253) diff --git a/docs/security/deployer/docker/scans/README.md b/docs/security/deployer/docker/scans/README.md new file mode 100644 index 000000000..b4efce9f8 --- /dev/null +++ b/docs/security/deployer/docker/scans/README.md @@ -0,0 +1,31 @@ +# Deployer Docker Image Scan Results + +Historical security scan results for Docker images used by the deployer itself. +These are [Priority 3](../../README.md) images — lower risk, short-lived, not internet-exposed. + +For production image scans, see [`../../../production/scans/`](../../../production/scans/). + +## Current Status Summary + +| Image | Version | HIGH | CRITICAL | Status | Last Scan | Details | +| -------------------------- | ------- | ---- | -------- | -------------------------------------- | ------------ | ----------------------------------- | +| `torrust/tracker-deployer` | trixie | 46 | 1 | ⚠️ CRITICAL blocked (OpenTofu grpc-go) | Apr 15, 2026 | [View](torrust-tracker-deployer.md) | + +## Scan Archives + +- [torrust-tracker-deployer.md](torrust-tracker-deployer.md) — Deployer (base: rust:trixie) + +For backup service scans (production container), see [`../../../production/scans/torrust-tracker-backup.md`](../../../production/scans/torrust-tracker-backup.md). +For SSH server and provisioned-instance scans (testing only), see [`../../../testing/scans/`](../../../testing/scans/). + +## Build and Scan + +```bash +# Build deployer image +docker build --target release --tag torrust/tracker-deployer:local --file docker/deployer/Dockerfile . + +# Scan +trivy image --severity HIGH,CRITICAL torrust/tracker-deployer:local +``` + +See [`../README.md`](../README.md) for detailed scanning instructions. diff --git a/docs/security/docker/scans/torrust-tracker-deployer.md b/docs/security/deployer/docker/scans/torrust-tracker-deployer.md similarity index 51% rename from docs/security/docker/scans/torrust-tracker-deployer.md rename to docs/security/deployer/docker/scans/torrust-tracker-deployer.md index 54f1900a2..dcc27b3da 100644 --- a/docs/security/docker/scans/torrust-tracker-deployer.md +++ b/docs/security/deployer/docker/scans/torrust-tracker-deployer.md @@ -4,9 +4,9 @@ Security scan history for the `torrust/tracker-deployer` Docker image. ## Current Status -| Version | HIGH | CRITICAL | Status | Last Scan | -| ------- | ---- | -------- | --------------------------- | ----------- | -| trixie | 1 | 0 | ✅ Improved (Trixie Update) | Feb 5, 2026 | +| Version | HIGH | CRITICAL | Status | Last Scan | +| ------- | ---- | -------- | -------------------------------------------------- | ------------ | +| trixie | 46 | 1 | ⚠️ CRITICAL blocked on OpenTofu upstream (grpc-go) | Apr 15, 2026 | ## Build & Scan Commands @@ -24,6 +24,169 @@ trivy image --severity HIGH,CRITICAL torrust/tracker-deployer:local ## Scan History +### April 15, 2026 - Remediation Pass 2 (Issue #429) + +**Image**: `torrust/tracker-deployer:local` +**OpenTofu version**: v1.11.6 (latest, released 2026-04-08) +**Trivy Version**: 0.69.3 +**Scan Mode**: `--scanners vuln --severity HIGH,CRITICAL` +**Base OS**: Debian 13.4 (trixie) +**Status**: ⚠️ **1 CRITICAL remains** (blocked on OpenTofu upstream) — 46 HIGH, 1 CRITICAL + +#### Summary + +Image rebuilt from scratch with `--no-cache`. OpenTofu v1.11.6 (latest) was installed. +CRITICAL in `usr/bin/tofu` (CVE-2026-33186, grpc-go) **remains unresolved** — needs +OpenTofu to upgrade `google.golang.org/grpc` to v1.79.3+. + +#### Target breakdown + +| Target | HIGH | CRITICAL | +| ---------------------------------------------- | ------ | -------- | +| `torrust/tracker-deployer:local` (debian 13.4) | 42 | 0 | +| `usr/bin/tofu` | 4 | 1 | +| **Total** | **46** | **1** | + +#### Comparison vs pass 1 (Apr 8) + +| Target | Apr 8 (HIGH / CRITICAL) | Apr 15 (HIGH / CRITICAL) | Delta | +| -------------- | ----------------------- | ------------------------ | ------------------------------------------ | +| Debian OS | 42 / 0 | 42 / 0 | no change (same Debian state) | +| `usr/bin/tofu` | 2 / 1 | 4 / 1 | +2 HIGH (Trivy DB update) | +| **Total** | **44 / 1** | **46 / 1** | **+2 HIGH (Trivy DB), CRITICAL unchanged** | + +#### `usr/bin/tofu` CVE details (OpenTofu v1.11.6) + +| CVE | Library | Severity | Status | Installed | Fixed | Title | +| -------------- | ------------------------------ | -------- | ------ | --------- | ------ | ----------------------------------------------- | +| CVE-2026-33186 | google.golang.org/grpc | CRITICAL | fixed | v1.76.0 | 1.79.3 | gRPC-Go: Authorization bypass via HTTP/2 path | +| CVE-2026-34986 | github.com/go-jose/go-jose/v4 | HIGH | fixed | v4.1.2 | 4.1.4 | JOSE: DoS via crafted JSON Web Encryption | +| CVE-2026-4660 | github.com/hashicorp/go-getter | HIGH | fixed | v1.8.2 | 1.8.6 | go-getter: Arbitrary file reads via crafted URL | +| CVE-2026-24051 | go.opentelemetry.io/otel/sdk | HIGH | fixed | v1.38.0 | 1.40.0 | OTel Go SDK: Arbitrary code execution via PATH | +| CVE-2026-39883 | go.opentelemetry.io/otel/sdk | HIGH | fixed | v1.38.0 | 1.43.0 | OTel Go SDK: BSD kenv PATH hijacking | + +All `usr/bin/tofu` CVEs have fixes available in their respective upstream libraries but +require OpenTofu to update its Go module dependencies and ship a new release. + +#### Notable Debian OS CVEs (selected new or notable HIGH, all `affected` / no fix in trixie) + +| CVE | Package | Title | +| -------------- | -------------- | ----------------------------------------------------------- | +| CVE-2025-13836 | python3.13 | cpython: Excessive read buffering DoS in http.client | +| CVE-2025-15366 | python3.13 | cpython: IMAP command injection (`will_not_fix`) | +| CVE-2025-15367 | python3.13 | cpython: POP3 command injection (`will_not_fix`) | +| CVE-2026-25210 | libexpat1 | libexpat: Integer overflow — data integrity issues | +| CVE-2026-29111 | libsystemd0 | systemd: Assert/freeze via spurious IPC (`<no-dsa>`) | +| CVE-2026-35385 | openssh-client | OpenSSH: Priv escalation via scp legacy protocol | +| CVE-2026-35414 | openssh-client | OpenSSH: Security bypass via authorized_keys principals | +| CVE-2026-35535 | sudo | Sudo: Privilege escalation via failed privilege drop | +| CVE-2025-69720 | ncurses | ncurses: Buffer overflow in `infocmp` CLI tool (`<no-dsa>`) | + +#### Decision + +**Leave issue #429 open — CRITICAL unresolved.** + +- CRITICAL CVE-2026-33186 (grpc-go, gRPC authorization bypass) remains in `usr/bin/tofu` v1.11.6 +- Fix requires OpenTofu to bump `google.golang.org/grpc` to v1.79.3+ and ship a new release +- Debian OS CVEs are all `affected`/`will_not_fix`/`<no-dsa>` with no trixie backports available + +**Revisit**: When OpenTofu releases v1.11.7+ or v1.12.x with updated `grpc-go` dependency. + +--- + +### April 8, 2026 - Remediation Pass 1 (Issue #428) + +**Image**: `torrust/tracker-deployer:local` +**Trivy Version**: 0.68.2 +**Scan Mode**: `--scanners vuln --severity HIGH,CRITICAL` +**Base OS**: Debian 13.4 (trixie) +**Status**: ⚠️ **Partial improvement** - 44 HIGH, 1 CRITICAL + +#### Summary + +After the first remediation pass in issue #428: + +- Runtime GnuPG footprint was reduced (install only for OpenTofu setup, then purge) +- Package upgrade was applied during image build +- Image remained functional in smoke test (`docker run --rm ... --help`) + +#### Comparison vs previous April scan + +| Target | Previous | Current | Delta | +| ------------------------------------- | ----------------------- | ----------------------- | ----------- | +| `torrust/tracker-deployer:local` (OS) | 49 HIGH | 42 HIGH | -7 HIGH | +| `usr/bin/tofu` | 2 HIGH, 1 CRITICAL | 2 HIGH, 1 CRITICAL | no change | +| **Total** | **51 HIGH, 1 CRITICAL** | **44 HIGH, 1 CRITICAL** | **-7 HIGH** | + +#### Remaining concerns + +- OpenTofu binary still reports 1 CRITICAL and 2 HIGH findings +- Debian base packages still contain unresolved HIGH findings +- Additional remediation/follow-up required + +### April 8, 2026 - Regression Alert - New CVEs Discovered + +**Image**: `torrust/tracker-deployer:local` +**Trivy Version**: 0.68.2 +**Base OS**: Debian 13.4 (trixie) +**Status**: ⚠️ **Significant regression** - 49 HIGH vulnerabilities (up from 1 in Feb 5 scan) + +#### Summary + +The April 8, 2026 scan reveals major vulnerabilities that were not detected in the February 5 scan. The Trivy vulnerability database appears to have been updated with new CVE entries, or Debian 13.4 contains additional security issues not present in earlier 13.x releases. + +**Alert**: Before deploying, manually verify whether these represent: + +1. New Debian 13.4 package vulnerabilities that need investigation +2. Updated Trivy database with previously unknown CVEs +3. False positives from secret scanning (test fixtures) + +#### Detailed Results + +**Debian Base Packages (49 HIGH)**: + +The majority of vulnerabilities are in Debian 13.4 base packages: + +| Package Category | Count | CVEs | Status | +| ---------------- | ----- | -------------------------------------------------------------------- | ------------------------------ | +| GnuPG packages | ~32 | CVE-2026-24882 | Affected - needs investigation | +| System libraries | ~8 | CVE-2025-69720, CVE-2026-27135, CVE-2025-13836, CVE-2025-15366, etc. | Affected | +| Test artifacts | ~9 | SSH keys, AWS credentials in test fixtures | Low risk - test only | + +**Binary Vulnerabilities (3 total: 2 HIGH, 1 CRITICAL)**: + +| Binary | CVEs | Severity | +| --------------------------------- | ------------------------ | ------------------ | +| `/usr/bin/tofu` (OpenTofu binary) | CVE-2026-34986 (go-jose) | 1 HIGH, 1 CRITICAL | + +#### Risk Assessment + +**High Priority Action Needed**: + +1. **GnuPG Regression** (CVE-2026-24882): Stack-based buffer overflow in tpm2daemon + - Status: Marked as "affected" with no fixed version in Debian repos + - Risk: Could allow arbitrary code execution + - Action: Contact Debian maintainers or consider alternative base image + +2. **Binary Dependencies**: OpenTofu go-jose library needs update + - Status: Fixed version available (go-jose v4.1.4) + - Action: Rebuild with updated OpenTofu + +3. **Test Artifacts**: Private keys and AWS credentials in test fixtures + - Status: Expected in test code + - Risk: Negligible (test-only, not deployed) + +#### Investigation Required + +This is a significant change from the February scan result (1 HIGH) to April (49 HIGH). Before updating deployment systems, determine: + +1. Check Debian 13.4 security advisories: https://security-tracker.debian.org/ +2. Compare Trivy database versions between Feb and Apr scans +3. Verify if base `debian:trixie` image has the same issues +4. Check if Dockerfile changes inadvertently added new packages + +**Pending**: Need manual investigation before marking as clear for deployment. + ### February 5, 2026 - UPDATE TO DEBIAN 13 (TRIXIE) **Image**: `torrust/tracker-deployer:local` diff --git a/docs/security/docker/scans/README.md b/docs/security/docker/scans/README.md deleted file mode 100644 index 20f8cf21f..000000000 --- a/docs/security/docker/scans/README.md +++ /dev/null @@ -1,97 +0,0 @@ -# Security Scan Results - -This directory contains historical security scan results for Docker images used in the deployer. - -## Current Status Summary - -| Image | Version | HIGH | CRITICAL | Status | Last Scan | Details | -| -------------------------------------- | ------- | ---- | -------- | -------------------- | ------------ | ----------------------------------------------- | -| `torrust/tracker-deployer` | trixie | 1 | 0 | ✅ Improved (Trixie) | Feb 5, 2026 | [View](torrust-tracker-deployer.md) | -| `torrust/tracker-backup` | trixie | 7 | 0 | ℹ️ Monitored | Feb 5, 2026 | [View](torrust-tracker-backup.md) | -| `torrust/tracker-ssh-server` | 3.23.3 | 1 | 0 | ✅ Secure (Alpine) | Feb 5, 2026 | [View](torrust-ssh-server.md) | -| `torrust/tracker-provisioned-instance` | 24.04 | 11 | 0 | ℹ️ Ubuntu LTS | Feb 5, 2026 | [View](torrust-tracker-provisioned-instance.md) | -| `caddy` | 2.10 | 3 | 1 | ⚠️ Monitored | Jan 13, 2026 | [View](caddy.md) | -| `prom/prometheus` | v3.5.0 | 0 | 0 | ✅ SECURE | Dec 29, 2025 | [View](prometheus.md) | -| `grafana/grafana` | 12.3.1 | 0 | 0 | ✅ SECURE | Dec 29, 2025 | [View](grafana.md) | -| `mysql` | 8.4 | 0 | 0 | ✅ SECURE | Dec 29, 2025 | [View](mysql.md) | - -**Overall Status**: ✅ **Major improvement** - Deployer updated to Debian 13 (trixie) reducing HIGH vulnerabilities from 25 to 1. SSH server and provisioned instance scans added. Backup image vulnerabilities documented with mitigation strategies. - -## Scan Archives - -Each file contains the complete scan history for a service: - -- [torrust-tracker-deployer.md](torrust-tracker-deployer.md) - Deployer (base: rust:trixie, **updated from bookworm**) -- [torrust-tracker-backup.md](torrust-tracker-backup.md) - Backup container (base: debian:trixie-slim, **updated**) -- [torrust-ssh-server.md](torrust-ssh-server.md) - SSH test server (base: alpine:3.23.3, **new**) -- [torrust-tracker-provisioned-instance.md](torrust-tracker-provisioned-instance.md) - Ubuntu VM simulation (base: ubuntu:24.04, **new**) -- [caddy.md](caddy.md) - Caddy TLS termination proxy -- [prometheus.md](prometheus.md) - Prometheus monitoring -- [grafana.md](grafana.md) - Grafana dashboards -- [mysql.md](mysql.md) - MySQL database - -## Build & Scan All Images - -To build and scan all Torrust Tracker Deployer images: - -```bash -# Build all images -docker build --target release --tag torrust/tracker-deployer:local --file docker/deployer/Dockerfile . -docker build --tag torrust/tracker-backup:local docker/backup/ -docker build --tag torrust/tracker-ssh-server:local docker/ssh-server/ -docker build --tag torrust/tracker-provisioned-instance:local docker/provisioned-instance/ - -# Run scans on all images -trivy image --severity HIGH,CRITICAL torrust/tracker-deployer:local -trivy image --severity HIGH,CRITICAL torrust/tracker-backup:local -trivy image --severity HIGH,CRITICAL torrust/tracker-ssh-server:local -trivy image --severity HIGH,CRITICAL torrust/tracker-provisioned-instance:local -``` - -## Scanning Standards - -All scans use: - -- **Tool**: Trivy (latest) -- **Severity Filter**: HIGH and CRITICAL only (MEDIUM and LOW omitted for brevity) -- **Update Frequency**: On every push (GitHub Actions), weekly schedules, and manual verification -- **Documentation**: Each scan includes context on image purpose, vulnerability analysis, and mitigation strategies - -## How to Add New Scans - -1. Build image: `docker build --tag <image-name>:local <dockerfile-path>` -2. Run Trivy scan: `trivy image --severity HIGH,CRITICAL <image-name>:local` -3. Create or update scan file in this directory -4. Update the summary table above -5. Commit with message: `docs: add security scan for <image-name> (<date>)` or `docs: [#<issue>] update security scans` - -See [../README.md](../README.md) for detailed scanning instructions and best practices. - -## Image Purpose & Risk Context - -Each image serves a different purpose with different security contexts: - -| Image | Purpose | Runtime | Network Exposure | Data Access | Risk Level | -| ------------------------ | ---------------------------------------- | ------------------- | ----------------- | ------------------ | ---------- | -| **Deployer** | CLI tool for infrastructure provisioning | User's machine / CI | None | SSH keys only | LOW | -| **Backup** | Database backup container | Controlled schedule | Internal only | Read access to DB | MEDIUM | -| **SSH Server** | E2E testing SSH connectivity | CI test environment | Test network only | Test data only | NEGLIGIBLE | -| **Provisioned Instance** | E2E deployment workflow testing | CI test environment | Test network only | Test data only | NEGLIGIBLE | -| **Caddy** | TLS termination and reverse proxy | Production optional | Public internet | Configuration only | MEDIUM | -| **Prometheus** | Metrics collection | Infrastructure | Internal network | Metrics only | LOW | -| **Grafana** | Metrics visualization | Infrastructure | Internal network | Read-only graphs | LOW | -| **MySQL** | Database storage | Infrastructure | Internal network | Application data | HIGH | - -## Security Updates Schedule - -- **Deployer image**: Rebuilt whenever Rust or Debian releases updates (typically monthly) -- **Backup image**: Rebuilt with base OS updates (tied to Debian release cycle) -- **SSH/Provisioned**: Rebuilt on every CI run (via GitHub Actions) -- **Monitoring images**: Scanned weekly, rebuilt when security advisories issued - -## References - -- [Trivy Documentation](https://aquasecurity.github.io/trivy/) -- [OWASP Docker Security](https://owasp.org/www-community/attacks/Docker_Escapes) -- [CIS Docker Benchmark](https://www.cisecurity.org/benchmark/docker) -- [GitHub Actions Docker Security](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions) diff --git a/docs/security/docker/scans/caddy.md b/docs/security/docker/scans/caddy.md deleted file mode 100644 index 4e66fbb72..000000000 --- a/docs/security/docker/scans/caddy.md +++ /dev/null @@ -1,68 +0,0 @@ -# Caddy Security Scan History - -**Image**: `caddy:2.10` -**Purpose**: TLS termination proxy for HTTPS support -**Documentation**: [Caddy TLS Proxy Evaluation](../../research/caddy-tls-proxy-evaluation/README.md) - -## Current Status - -| Version | HIGH | CRITICAL | Status | Scan Date | -| ------- | ---- | -------- | ------------ | ------------ | -| 2.10 | 3 | 1 | ⚠️ Monitored | Jan 13, 2026 | - -**Deployment Status**: ✅ Safe to deploy with monitoring - -## Vulnerability Summary - -The Caddy 2.10 image has: - -- **Alpine base image**: Clean (0 vulnerabilities) -- **Caddy binary (Go)**: 4 vulnerabilities in dependencies (not Caddy core) - -All vulnerabilities have fixed versions available upstream and are expected to be resolved in the next Caddy release. - -## Scan History - -### January 13, 2026 - caddy:2.10 - -**Scanner**: Trivy v0.68 - -| Target | Type | HIGH | CRITICAL | -| -------------------------- | -------- | ---- | -------- | -| caddy:2.10 (alpine 3.22.2) | alpine | 0 | 0 | -| usr/bin/caddy | gobinary | 3 | 1 | - -**Vulnerabilities Found**: - -| CVE | Severity | Component | Fixed Version | -| -------------- | -------- | --------------------------------- | --------------- | -| CVE-2025-44005 | CRITICAL | github.com/smallstep/certificates | 0.29.0 | -| CVE-2025-59530 | HIGH | github.com/quic-go/quic-go | 0.49.1, 0.54.1 | -| CVE-2025-58183 | HIGH | stdlib (archive/tar) | 1.24.8, 1.25.2 | -| CVE-2025-61729 | HIGH | stdlib (crypto/x509) | 1.24.11, 1.25.5 | - -**Risk Assessment**: - -1. **CVE-2025-44005**: Authorization bypass in certificate creation (smallstep library) -2. **CVE-2025-59530**: QUIC protocol crash (affects HTTP/3 only) -3. **CVE-2025-58183**: Unbounded allocation in tar parsing -4. **CVE-2025-61729**: Resource consumption in x509 certificate validation - -**Recommendation**: Deploy with monitoring. Update to patched version when Caddy v2.11 releases. - -## Related Documentation - -- [Full Security Analysis](../../../research/caddy-tls-proxy-evaluation/security-scan.md) -- [Caddy Evaluation Summary](../../../research/caddy-tls-proxy-evaluation/README.md) -- [HTTPS Implementation](../../../issues/272-add-https-support-with-caddy.md) - -## How to Rescan - -```bash -trivy image --severity HIGH,CRITICAL caddy:2.10 -``` - -## Security Advisories - -- **Caddy**: <https://github.com/caddyserver/caddy/security/advisories> -- **Alpine Linux**: <https://secdb.alpinelinux.org/> diff --git a/docs/security/docker/scans/grafana.md b/docs/security/docker/scans/grafana.md deleted file mode 100644 index cec6e5cbb..000000000 --- a/docs/security/docker/scans/grafana.md +++ /dev/null @@ -1,52 +0,0 @@ -# Grafana - Security Scans - -Security scan history for the `grafana/grafana` Docker image. - -## Current Status - -| Version | HIGH | CRITICAL | Status | Last Scan | Support EOL | -| ------- | ---- | -------- | --------- | ------------ | ------------ | -| 12.3.1 | 0 | 0 | ✅ SECURE | Dec 29, 2025 | Feb 24, 2026 | - -## Scan History - -### December 29, 2025 - -**Image**: `grafana/grafana:12.3.1` -**Trivy Version**: 0.68.2 -**Status**: ✅ SECURE - 0 HIGH/CRITICAL vulnerabilities - -#### Results - -```text -grafana/grafana:12.3.1 (alpine 3.23.0) -====================================== -Total: 0 (HIGH: 0, CRITICAL: 0) - -Scanned 17 targets (alpine, node-pkg, gobinary) -All targets clean - no HIGH or CRITICAL vulnerabilities detected -``` - -#### Notes - -- Alpine 3.23.0 warnings are cosmetic - Grafana image is recent and actively maintained -- Zero HIGH/CRITICAL vulnerabilities detected across all 17 targets -- Grafana team maintains official images with security patches - -#### Support Status - -- Release: November 19, 2025 -- Latest Major: 12.x series -- EOL: February 24, 2026 (2 months remaining as of Dec 2025) -- Note: Grafana follows bi-monthly release cycle - ---- - -### Previous Scans - -#### December 23, 2025 (Pre-Update Baseline) - -**Image**: `grafana/grafana:11.4.0` -**Status**: Preliminary scan - 0 HIGH/CRITICAL (informal assessment) - -**Note**: December 23 scan was a preliminary assessment before formal documentation was established. diff --git a/docs/security/docker/scans/mysql.md b/docs/security/docker/scans/mysql.md deleted file mode 100644 index cd1ad95a0..000000000 --- a/docs/security/docker/scans/mysql.md +++ /dev/null @@ -1,52 +0,0 @@ -# MySQL - Security Scans - -Security scan history for the `mysql` Docker image. - -## Current Status - -| Version | HIGH | CRITICAL | Status | Last Scan | Support EOL | -| ------- | ---- | -------- | --------- | ------------ | ------------ | -| 8.4 | 0 | 0 | ✅ SECURE | Dec 29, 2025 | Apr 30, 2032 | - -## Scan History - -### December 29, 2025 - -**Image**: `mysql:8.4` -**Trivy Version**: 0.68.2 -**Status**: ✅ SECURE - 0 HIGH/CRITICAL vulnerabilities - -#### Results - -```text -mysql:8.4 (oracle 9.7) -====================== -Total: 0 (HIGH: 0, CRITICAL: 0) - -MySQL server core: 0 vulnerabilities -``` - -#### Notes - -- MySQL 8.4 is an LTS release with extended support -- Oracle Linux 9.7 base has no HIGH/CRITICAL vulnerabilities -- MySQL server itself has 0 vulnerabilities -- LTS release designed for production stability - -#### Support Status - -- Release: April 10, 2024 -- Premier Support: Until April 30, 2029 (3+ years remaining) -- Extended Support: Until April 30, 2032 (6+ years remaining) -- LTS Release: Designed for production stability - ---- - -### Previous Scans - -#### December 23, 2025 (Pre-Update Baseline) - -**Image**: `mysql:8.0` -**Status**: Preliminary scan - 0 HIGH/CRITICAL (informal assessment) - -**Note**: December 23 scan was a preliminary assessment before formal documentation was established. diff --git a/docs/security/docker/scans/prometheus.md b/docs/security/docker/scans/prometheus.md deleted file mode 100644 index f3ab42db1..000000000 --- a/docs/security/docker/scans/prometheus.md +++ /dev/null @@ -1,48 +0,0 @@ -# Prometheus - Security Scans - -Security scan history for the `prom/prometheus` Docker image. - -## Current Status - -| Version | HIGH | CRITICAL | Status | Last Scan | Support EOL | -| ------- | ---- | -------- | --------- | ------------ | ------------ | -| v3.5.0 | 0 | 0 | ✅ SECURE | Dec 29, 2025 | Jul 31, 2026 | - -## Scan History - -### December 29, 2025 - -**Image**: `prom/prometheus:v3.5.0` -**Trivy Version**: 0.68.2 -**Status**: ✅ SECURE - 0 HIGH/CRITICAL vulnerabilities - -#### Results - -```text -bin/prometheus (gobinary) -Total: 0 (HIGH: 0, CRITICAL: 0) -``` - -#### Notes - -- Prometheus v3.5.0 LTS release -- Go stdlib vulnerabilities from earlier scans have been patched -- Minimal scratch-based image reduces attack surface -- LTS support until July 31, 2026 - -#### Support Status - -- Release: July 14, 2025 -- LTS Support: 1-year window -- EOL: July 31, 2026 (7 months remaining as of Dec 2025) - ---- - -### Previous Scans - -#### December 23, 2025 (Pre-Update Baseline) - -**Image**: `prom/prometheus:v3.0.1` -**Status**: Preliminary scan - 0 HIGH/CRITICAL (informal assessment) - -**Note**: December 23 scan was a preliminary assessment before formal documentation was established. diff --git a/docs/security/production/README.md b/docs/security/production/README.md new file mode 100644 index 000000000..921e82cee --- /dev/null +++ b/docs/security/production/README.md @@ -0,0 +1,59 @@ +# Production Image Security + +This directory covers security scanning for Docker images that the deployer **deploys to production**. +These are [Priority 1](../README.md) — the highest-risk surface because they run continuously and are exposed to the internet. + +## Images Covered + +| Image | Role | +| ------------------------ | ------------------------------------------------------------------- | +| `caddy` | TLS termination proxy — public-facing | +| `prom/prometheus` | Metrics collection | +| `grafana/grafana` | Metrics dashboards | +| `mysql` | Tracker database | +| `torrust/tracker-backup` | Backup service — runs on a schedule inside the deployed environment | + +## Scanning with Trivy + +```bash +# Scan a production image +trivy image --severity HIGH,CRITICAL caddy:2.11.2 + +# Scan and output JSON +trivy image --format json --output report.json caddy:2.11.2 + +# Scan all severities for a full report +trivy image caddy:2.11.2 +``` + +## When to Act on Findings + +**CRITICAL severity**: + +1. Check whether the upstream vendor has released a patched image +2. Update the image version in `templates/docker-compose/docker-compose.yml.tera` +3. Re-scan the updated image to confirm the fix +4. Update scan history in `scans/<image>.md` + +**HIGH severity**: + +1. Check Debian/Alpine security tracker for fix availability +2. If a fix exists, update the image as above +3. If no fix exists (`affected` / `will_not_fix` / `<no-dsa>`), document the accepted risk + +## Best Practices + +- Pin to specific versions, never `latest`, in production templates +- Prefer official vendor images (`prom`, `grafana`, `mysql`) +- Re-scan after every image version bump +- Monitor vendor security advisories + +## Scan History + +See [`scans/`](scans/) for per-image scan history and current status. + +## References + +- [Trivy Documentation](https://aquasecurity.github.io/trivy/) +- [Debian Security Tracker](https://security-tracker.debian.org/tracker/) +- [Issue #250: Automated Security Scanning](https://github.com/torrust/torrust-tracker-deployer/issues/250) diff --git a/docs/security/production/scans/README.md b/docs/security/production/scans/README.md new file mode 100644 index 000000000..c5064ab22 --- /dev/null +++ b/docs/security/production/scans/README.md @@ -0,0 +1,27 @@ +# Production Image Scan Results + +Historical security scan results for Docker images deployed to production by the deployer. + +## Current Status Summary + +| Image | Version | HIGH | CRITICAL | Status | Last Scan | Details | +| ------------------------ | ------- | ---- | -------- | ----------------------------------------- | ------------ | --------------------------------- | +| `caddy` | 2.11.2 | 10 | 2 | ⚠️ CRITICAL pending upstream | Apr 15, 2026 | [View](caddy.md) | +| `prom/prometheus` | v3.11.2 | 4 | 0 | ✅ Remediated | Apr 14, 2026 | [View](prometheus.md) | +| `grafana/grafana` | 12.4.2 | 4 | 0 | ⚠️ Accepted risk (OS `<no-dsa>`) | Apr 8, 2026 | [View](grafana.md) | +| `mysql` | 8.4 | 9 | 1 | ⚠️ Accepted risk (gosu/mysqlsh, not core) | Apr 15, 2026 | [View](mysql.md) | +| `torrust/tracker-backup` | trixie | 6 | 0 | ⚠️ Accepted risk (Debian `<no-dsa>`) | Apr 15, 2026 | [View](torrust-tracker-backup.md) | + +## Scanning Instructions + +See [`../README.md`](../README.md) for Trivy usage and remediation workflow. + +## Scan Archives + +Each file contains the complete scan history for a service: + +- [caddy.md](caddy.md) — Caddy TLS termination proxy +- [prometheus.md](prometheus.md) — Prometheus monitoring +- [grafana.md](grafana.md) — Grafana dashboards +- [mysql.md](mysql.md) — MySQL tracker database +- [torrust-tracker-backup.md](torrust-tracker-backup.md) — Backup service container diff --git a/docs/security/production/scans/caddy.md b/docs/security/production/scans/caddy.md new file mode 100644 index 000000000..27b33654d --- /dev/null +++ b/docs/security/production/scans/caddy.md @@ -0,0 +1,151 @@ +# Caddy Security Scan History + +**Image**: `caddy:2.11.2` +**Purpose**: TLS termination proxy for HTTPS support +**Documentation**: [Caddy TLS Proxy Evaluation](../../research/caddy-tls-proxy-evaluation/README.md) + +## Current Status + +| Version | HIGH | CRITICAL | Status | Scan Date | +| ------- | ---- | -------- | ------------------------------------ | ------------ | +| 2.11.2 | 10 | 2 | ⚠️ Partial improvement after upgrade | Apr 15, 2026 | + +**Deployment Status**: ⚠️ Requires follow-up — 2 CRITICAL CVEs remain in upstream Caddy binary dependencies (smallstep/certificates, grpc-go). Fixes require upstream Caddy releases. + +## Vulnerability Summary + +The Caddy 2.11.2 image has: + +- **Alpine base image**: 3 HIGH, 0 CRITICAL (libcrypto3/libssl3, zlib — fixed versions available) +- **Caddy binary (Go)**: 7 HIGH, 2 CRITICAL in dependencies (not Caddy core) + +The 2 CRITICAL CVEs are in upstream Caddy binary dependencies and require Caddy to update its vendored modules. + +## Scan History + +### April 15, 2026 - Remediation Pass 2 (Issue #432) + +**Scanner**: Trivy v0.69.3 +**Scan Mode**: `--scanners vuln --severity HIGH,CRITICAL` +**Image**: `caddy:2.11.2` +**Status**: ⚠️ **12 vulnerabilities** (10 HIGH, 2 CRITICAL) + +#### Summary + +Upgraded Caddy from `2.10.2` to `2.11.2` (latest as of 2026-04-14). Meaningful reduction in findings but 2 CRITICAL CVEs remain in upstream binary dependencies. + +Vulnerability comparison: + +| Version | HIGH | CRITICAL | +| -------- | ---- | -------- | +| `2.10` | 18 | 6 | +| `2.10.2` | 14 | 4 | +| `2.11.2` | 10 | 2 | + +Issue left open — CRITICALs not fully cleared. + +#### Target Breakdown (`2.11.2`) + +| Target | Type | HIGH | CRITICAL | +| -------------- | -------- | ---- | -------- | +| caddy (alpine) | alpine | 3 | 0 | +| usr/bin/caddy | gobinary | 7 | 2 | + +#### CVE Details + +**Alpine OS layer:** + +| CVE | Library | Severity | Fixed In | Notes | +| -------------- | ------------------- | -------- | -------- | -------------------------- | +| CVE-2026-28390 | libcrypto3, libssl3 | HIGH | 3.5.6-r0 | OpenSSL DoS via NULL deref | +| CVE-2026-22184 | zlib | HIGH | 1.3.2-r0 | Buffer overflow in untgz | + +**Caddy binary (Go):** + +| CVE | Library | Severity | Fixed In | Notes | +| -------------- | ---------------------- | -------- | ------------- | ------------------------------------------- | +| CVE-2026-34986 | go-jose/go-jose v3+v4 | HIGH | 3.0.5 / 4.1.4 | DoS via crafted JWE | +| CVE-2026-30836 | smallstep/certificates | CRITICAL | 0.30.0 | Unauthenticated SCEP cert issuance | +| CVE-2026-39883 | otel/sdk | HIGH | 1.43.0 | Local PATH hijack (no remote path) | +| CVE-2026-33186 | google.golang.org/grpc | CRITICAL | 1.79.3 | Authorization bypass via HTTP/2 path | +| CVE-2026-25679 | stdlib | HIGH | 1.26.1 | Incorrect IPv6 parsing in net/url | +| CVE-2026-27137 | stdlib | HIGH | 1.26.1 | Email constraint enforcement in crypto/x509 | +| CVE-2026-32280 | stdlib | HIGH | 1.26.2 | Excessive work during chain building | +| CVE-2026-32282 | stdlib | HIGH | 1.26.2 | Root.Chmod follows symlinks out of root | + +**Overall risk**: The 2 CRITICAL CVEs (CVE-2026-30836, CVE-2026-33186) are in upstream +Caddy binary dependencies and require a new Caddy release to fix. CVE-2026-33186 +(gRPC authorization bypass) has a network-accessible attack path. Revisit when +Caddy ships the updated grpc-go and smallstep dependencies. + +--- + +### April 8, 2026 - Remediation Pass 1 (Issue #428) + +**Scanner**: Trivy v0.68.2 +**Scan Mode**: `--scanners vuln --severity HIGH,CRITICAL` +**Image**: `caddy:2.10.2` +**Status**: ⚠️ **18 vulnerabilities** (14 HIGH, 4 CRITICAL) + +#### Summary + +Easy remediation applied by upgrading Caddy image tag from `2.10` to `2.10.2`. + +Vulnerability comparison: + +- Previous (`2.10`): 18 HIGH, 6 CRITICAL +- Current (`2.10.2`): 14 HIGH, 4 CRITICAL + +Improvement: -4 HIGH, -2 CRITICAL + +#### Target Breakdown (`2.10.2`) + +| Target | Type | HIGH | CRITICAL | +| ------------- | -------- | ---- | -------- | +| usr/bin/caddy | gobinary | 14 | 4 | + +Remaining issues are in upstream Caddy binary dependencies and require vendor/upstream updates. + +### January 13, 2026 - caddy:2.10 + +**Scanner**: Trivy v0.68 + +| Target | Type | HIGH | CRITICAL | +| -------------------------- | -------- | ---- | -------- | +| caddy:2.10 (alpine 3.22.2) | alpine | 0 | 0 | +| usr/bin/caddy | gobinary | 3 | 1 | + +**Vulnerabilities Found**: + +| CVE | Severity | Component | Fixed Version | +| -------------- | -------- | --------------------------------- | --------------- | +| CVE-2025-44005 | CRITICAL | github.com/smallstep/certificates | 0.29.0 | +| CVE-2025-59530 | HIGH | github.com/quic-go/quic-go | 0.49.1, 0.54.1 | +| CVE-2025-58183 | HIGH | stdlib (archive/tar) | 1.24.8, 1.25.2 | +| CVE-2025-61729 | HIGH | stdlib (crypto/x509) | 1.24.11, 1.25.5 | + +**Risk Assessment**: + +1. **CVE-2025-44005**: Authorization bypass in certificate creation (smallstep library) +2. **CVE-2025-59530**: QUIC protocol crash (affects HTTP/3 only) +3. **CVE-2025-58183**: Unbounded allocation in tar parsing +4. **CVE-2025-61729**: Resource consumption in x509 certificate validation + +**Recommendation**: Deploy with monitoring. Update to patched version when Caddy v2.11 releases. + +## Related Documentation + +- [Full Security Analysis](../../../research/caddy-tls-proxy-evaluation/security-scan.md) +- [Caddy Evaluation Summary](../../../research/caddy-tls-proxy-evaluation/README.md) +- [HTTPS Implementation](../../../issues/272-add-https-support-with-caddy.md) + +## How to Rescan + +```bash +trivy image --severity HIGH,CRITICAL caddy:2.11.2 +``` + +## Security Advisories + +- **Caddy**: <https://github.com/caddyserver/caddy/security/advisories> +- **Alpine Linux**: <https://secdb.alpinelinux.org/> diff --git a/docs/security/production/scans/grafana.md b/docs/security/production/scans/grafana.md new file mode 100644 index 000000000..8da2c76ed --- /dev/null +++ b/docs/security/production/scans/grafana.md @@ -0,0 +1,147 @@ +# Grafana - Security Scans + +Security scan history for the `grafana/grafana` Docker image. + +## Current Status + +| Version | HIGH | CRITICAL | Status | Last Scan | Support EOL | +| ------- | ---- | -------- | ------------------------------------- | ------------ | ----------- | +| 13.0.0 | 10 | 0 | ⚠️ Accepted risk (no remote exposure) | Apr 14, 2026 | Unknown | +| 12.4.2 | 13 | 0 | ✅ Replaced by 13.0.0 | Apr 14, 2026 | Unknown | + +## Scan History + +### April 14, 2026 - CVE-2026-34986 remediation (Issue #434) + +**Image**: `grafana/grafana:13.0.0` +**Trivy Version**: 0.68.2 +**Scan Mode**: `--scanners vuln --severity HIGH,CRITICAL` +**Status**: ⚠️ **10 HIGH, 0 CRITICAL** — CVE-2026-34986 (remote DoS) eliminated + +#### Summary + +Full re-scan revealed 13 HIGH in `grafana/grafana:12.4.2` including CVE-2026-34986, +an unauthenticated remote DoS via a crafted JWE bearer token (CVSS 7.5, +AV:N/AC:L/PR:N/UI:N). The fix (bumping `go-jose/v4` to `4.1.4`) was merged in +[grafana/grafana#121830](https://github.com/grafana/grafana/pull/121830) with label +`no-backport` — no 12.x patch will be issued. Upgraded to `13.0.0`. + +Vulnerability comparison: + +- `12.4.2`: 13 HIGH, 0 CRITICAL (CVE-2026-34986 present) +- `13.0.0`: 10 HIGH, 0 CRITICAL (CVE-2026-34986 **absent**) + +Improvement: -3 HIGH; remote DoS eliminated. + +Detail by target in 13.0.0: + +- Alpine 3.23.3 base: 3 HIGH (openssl + zlib — same as 12.4.2, blocked on Alpine rebuild) +- grafana binary: 2 HIGH (moby/moby CVE-2026-34040, otel/sdk CVE-2026-39883) +- grafana-cli binary: 0 HIGH ✅ +- grafana-server binary: 0 HIGH ✅ +- elasticsearch plugin (new bundled binary): 5 HIGH (otel + stdlib, all local-only) + +### April 14, 2026 - Full scan (Issue #434) + +**Image**: `grafana/grafana:12.4.2` +**Trivy Version**: 0.68.2 (updated DB) +**Scan Mode**: `--scanners vuln --severity HIGH,CRITICAL` +**Status**: ⚠️ **13 HIGH, 0 CRITICAL** — includes remote-exploitable CVE-2026-34986 + +#### Summary + +Re-scan with updated Trivy DB (April 14) revealed 13 HIGH in `12.4.2`, significantly +more than the 4 HIGH found in the April 8 scan due to new CVE entries added to the +vulnerability database. CVE-2026-34986 (`go-jose/v4`, CVSS 7.5) is the only +finding with a remote attack path. + +Breakdown: + +- Alpine 3.23.3 base: 3 HIGH (openssl + zlib) +- grafana binary: 6 HIGH (go-jose, moby, otel × 2, stdlib × 2) +- grafana-cli binary: 2 HIGH (moby + otel) +- grafana-server binary: 2 HIGH (moby + otel) + +### April 8, 2026 — Remediation Pass 1 (Issue #428) + +**Image**: `grafana/grafana:12.4.2` +**Trivy Version**: 0.68.2 +**Scan Mode**: `--scanners vuln --severity HIGH,CRITICAL` +**Status**: ⚠️ **4 vulnerabilities** (4 HIGH, 0 CRITICAL) + +#### Summary + +Easy remediation applied by upgrading Grafana from `12.3.1` to `12.4.2`. + +Vulnerability comparison: + +- Previous (`12.3.1`): 18 HIGH, 6 CRITICAL +- Current (`12.4.2`): 4 HIGH, 0 CRITICAL + +Improvement: -14 HIGH, -6 CRITICAL. All CRITICAL findings cleared. + +### April 8, 2026 — Prior scan pre-upgrade (12.3.1) + +**Image**: `grafana/grafana:12.3.1` +**Trivy Version**: 0.68.2 +**Status**: ⚠️ **24 vulnerabilities** (24 HIGH, 0 CRITICAL) — significant increase from Dec scan + +#### Summary + +Vulnerability count increased dramatically from 0 to 24 HIGH. Breakdown by target: + +- Alpine 3.23.0 base: 7 HIGH +- grafana binary: 9 HIGH +- grafana-cli binary: 4 HIGH +- grafana-server binary: 4 HIGH + +This sharp increase strongly suggests the Trivy vulnerability database was updated rather than Grafana becoming intrinsically more vulnerable. + +#### Changes Since December + +- December scan: 0 vulnerabilities +- April scan: 24 HIGH total +- Alpine warnings likely cosmetic (see Dec notes) + +**Recommended Action**: Verify findings against official Grafana security advisories: https://github.com/grafana/grafana/security/advisories + +### December 29, 2025 + +**Image**: `grafana/grafana:12.3.1` +**Trivy Version**: 0.68.2 +**Status**: ✅ SECURE - 0 HIGH/CRITICAL vulnerabilities + +#### Results + +```text +grafana/grafana:12.3.1 (alpine 3.23.0) +====================================== +Total: 0 (HIGH: 0, CRITICAL: 0) + +Scanned 17 targets (alpine, node-pkg, gobinary) +All targets clean - no HIGH or CRITICAL vulnerabilities detected +``` + +#### Notes + +- Alpine 3.23.0 warnings are cosmetic - Grafana image is recent and actively maintained +- Zero HIGH/CRITICAL vulnerabilities detected across all 17 targets +- Grafana team maintains official images with security patches + +#### Support Status + +- Release: November 19, 2025 +- Latest Major: 12.x series +- EOL: February 24, 2026 (2 months remaining as of Dec 2025) +- Note: Grafana follows bi-monthly release cycle + +--- + +### Previous Scans + +#### December 23, 2025 (Pre-Update Baseline) + +**Image**: `grafana/grafana:11.4.0` +**Status**: Preliminary scan - 0 HIGH/CRITICAL (informal assessment) + +**Note**: December 23 scan was a preliminary assessment before formal documentation was established. diff --git a/docs/security/production/scans/mysql.md b/docs/security/production/scans/mysql.md new file mode 100644 index 000000000..098e5e90b --- /dev/null +++ b/docs/security/production/scans/mysql.md @@ -0,0 +1,163 @@ +# MySQL - Security Scans + +Security scan history for the `mysql` Docker image. + +## Current Status + +| Version | HIGH | CRITICAL | Status | Last Scan | Support EOL | +| ------- | ---- | -------- | ----------------------- | ------------ | ------------ | +| 8.4 | 9 | 1 | ⚠️ Accepted risk (gosu) | Apr 15, 2026 | Apr 30, 2032 | + +## Scan History + +### April 15, 2026 - Remediation Pass 2 / Accepted Risk (Issue #435) + +**Image**: `mysql:8.4` (resolves to `8.4.8`) +**Trivy Version**: 0.69.3 +**Scan Mode**: `--scanners vuln --severity HIGH,CRITICAL` +**Status**: ⚠️ **10 vulnerabilities** (9 HIGH, 1 CRITICAL) + +#### Summary + +Floating tag still resolves to `8.4.8` (unchanged from Apr 8 baseline). Vulnerability count +increased from 7 HIGH + 1 CRITICAL to 9 HIGH + 1 CRITICAL due to Trivy DB updates only; +no new MySQL release shipped. + +A comparison scan of `mysql:9.6` (latest Innovation Release, shipped 2026-04-14) shows an +**identical CVE profile** — same `gosu v1.24.6` Go binary and same Python packages: + +| Version | HIGH | CRITICAL | Notes | +| ------- | ---- | -------- | ------------------------------------- | +| `8.4.8` | 9 | 1 | LTS, support EOL Apr 2032 | +| `9.6` | 9 | 1 | Innovation Release, shorter lifecycle | + +All CVEs are in helper components only: + +| Target | HIGH | CRITICAL | +| ------------------------- | ----- | -------- | +| `mysql:8.4` (oracle 9.7) | 0 | 0 | +| Python packages (mysqlsh) | 2 | 0 | +| `usr/local/bin/gosu` | 7 | 1 | +| **Total** | **9** | **1** | + +**CVE details — Python packages (`cryptography 45.0.7`, `pyOpenSSL 25.1.0`):** + +| CVE | Library | Severity | Status | Fixed Version | Title | +| -------------- | ------------ | -------- | ------ | ------------- | ---------------------------------------------- | +| CVE-2026-26007 | cryptography | HIGH | fixed | 46.0.5 | Subgroup attack due to missing SECT validation | +| CVE-2026-27459 | pyOpenSSL | HIGH | fixed | 26.0.0 | DTLS cookie callback buffer overflow | + +**CVE details — `gosu` (`stdlib v1.24.6`):** + +| CVE | Severity | Status | Fixed Version | Title | +| -------------- | -------- | ------ | --------------- | ------------------------------------------------------------- | +| CVE-2025-68121 | CRITICAL | fixed | 1.24.13, 1.25.7 | crypto/tls: Incorrect certificate validation (TLS resumption) | +| CVE-2025-58183 | HIGH | fixed | 1.24.8, 1.25.2 | archive/tar: Unbounded allocation in GNU sparse map | +| CVE-2025-61726 | HIGH | fixed | 1.24.12, 1.25.6 | net/url: Memory exhaustion in query parameter parsing | +| CVE-2025-61728 | HIGH | fixed | 1.24.12, 1.25.6 | archive/zip: Excessive CPU - building archive index | +| CVE-2025-61729 | HIGH | fixed | 1.24.11, 1.25.5 | crypto/x509: DoS via excessive resource consumption | +| CVE-2026-25679 | HIGH | fixed | 1.25.8, 1.26.1 | net/url: Incorrect parsing of IPv6 host literals | +| CVE-2026-32280 | HIGH | fixed | 1.25.9, 1.26.2 | chain building: unbounded work amount | +| CVE-2026-32282 | HIGH | fixed | 1.25.9, 1.26.2 | internal/syscall/unix: Root.Chmod can follow symlinks | + +#### Decision + +**Accepted risk — close issue #435.** + +- No viable upgrade path: `mysql:9.6` (latest) has an identical CVE profile +- All CVEs are in `gosu` (process privilege helper) and MySQL Shell Python packages — + **not MySQL Server itself** +- The CRITICAL (CVE-2025-68121, crypto/tls cert validation) is in `gosu`, not in any + MySQL network-facing code path +- `mysql:8.4` remains the correct choice: LTS with support until Apr 30, 2032 +- Fix requires MySQL upstream to release a new image with `gosu` rebuilt on Go ≥ 1.24.13 + +**Revisit**: When MySQL upstream ships `8.4.9` or later with updated `gosu`. + +--- + +### April 8, 2026 - Remediation Pass 1 (Issue #428) + +**Image**: `mysql:8.4` +**Trivy Version**: 0.68.2 +**Scan Mode**: `--scanners vuln --severity HIGH,CRITICAL` +**Status**: ⚠️ **8 vulnerabilities** (7 HIGH, 1 CRITICAL) + +#### Summary + +Findings are concentrated in helper components, not MySQL server core: + +- Python packages: 2 HIGH +- `gosu` Go stdlib dependencies: 5 HIGH, 1 CRITICAL + +Tag comparison for easy remediation was performed (`8.4.1`, `8.4.2`, `8.4.3`, `9.0`, `9.1`, `latest`). +No safer drop-in tag with lower overall risk profile was identified for immediate adoption in this pass. + +#### Decision + +- Keep `mysql:8.4` for now (validated runtime and LTS alignment) +- Track unresolved CVEs in follow-up issue for deeper investigation + +### April 8, 2026 + +**Image**: `mysql:8.4` +**Trivy Version**: 0.68.2 +**Status**: ⚠️ **8 vulnerabilities** (8 HIGH, 0 CRITICAL) - Increase from Dec scan + +#### Summary + +Vulnerability count increased from 0 to 8 HIGH. Breakdown: + +- Python libraries: 2 HIGH +- `/usr/local/bin/gosu`: 6 HIGH + +This increase suggests Trivy database update rather than actual MySQL regression. + +#### Changes Since December + +- December scan: 0 vulnerabilities +- April scan: 8 HIGH +- MySQL server binary itself appears unaffected + +**Recommended Action**: Most concerns are in helper binaries (gosu) and Python tools, not MySQL core. Verify with MySQL security advisories: https://www.mysql.com/support/security/ + +### December 29, 2025 + +**Image**: `mysql:8.4` +**Trivy Version**: 0.68.2 +**Status**: ✅ SECURE - 0 HIGH/CRITICAL vulnerabilities + +#### Results + +```text +mysql:8.4 (oracle 9.7) +====================== +Total: 0 (HIGH: 0, CRITICAL: 0) + +MySQL server core: 0 vulnerabilities +``` + +#### Notes + +- MySQL 8.4 is an LTS release with extended support +- Oracle Linux 9.7 base has no HIGH/CRITICAL vulnerabilities +- MySQL server itself has 0 vulnerabilities +- LTS release designed for production stability + +#### Support Status + +- Release: April 10, 2024 +- Premier Support: Until April 30, 2029 (3+ years remaining) +- Extended Support: Until April 30, 2032 (6+ years remaining) +- LTS Release: Designed for production stability + +--- + +### Previous Scans + +#### December 23, 2025 (Pre-Update Baseline) + +**Image**: `mysql:8.0` +**Status**: Preliminary scan - 0 HIGH/CRITICAL (informal assessment) + +**Note**: December 23 scan was a preliminary assessment before formal documentation was established. diff --git a/docs/security/production/scans/prometheus.md b/docs/security/production/scans/prometheus.md new file mode 100644 index 000000000..20aaa1744 --- /dev/null +++ b/docs/security/production/scans/prometheus.md @@ -0,0 +1,137 @@ +# Prometheus - Security Scans + +Security scan history for the `prom/prometheus` Docker image. + +## Current Status + +| Version | HIGH | CRITICAL | Status | Last Scan | Support EOL | +| ------- | ---- | -------- | ----------------------------- | ------------ | ----------- | +| v3.11.2 | 4 | 0 | ✅ No CRITICALs after upgrade | Apr 14, 2026 | TBD | + +## Scan History + +### April 14, 2026 - Remediation Pass 2 (Issue #433) + +**Image**: `prom/prometheus:v3.11.2` +**Trivy Version**: 0.68.2 +**Scan Mode**: `--scanners vuln --severity HIGH,CRITICAL` +**Status**: ✅ **4 vulnerabilities** (4 HIGH, 0 CRITICAL) + +#### Summary + +Upgraded Prometheus from `v3.5.1` to `v3.11.2` (latest as of 2026-04-13). All +CRITICAL vulnerabilities eliminated. Four HIGH findings remain in upstream +binary dependencies; all are local-only (no remote attack path). + +Vulnerability comparison: + +| Version | HIGH | CRITICAL | +| ------- | ---- | -------- | +| v3.5.0 | 16 | 4 | +| v3.5.1 | 6 | 2 | +| v3.11.2 | 4 | 0 | + +#### Target Breakdown (`v3.11.2`) + +| Target | HIGH | CRITICAL | +| ---------------- | ---- | -------- | +| `bin/prometheus` | 3 | 0 | +| `bin/promtool` | 1 | 0 | + +No OS layer — pure Go binaries, no Alpine/Debian base image. + +#### Remaining CVEs + +| CVE | Library | Installed | Fixed In | Severity | Notes | +| -------------- | ---------------- | --------- | -------- | -------- | ----------------------------------------- | +| CVE-2026-32285 | buger/jsonparser | v1.1.1 | 1.1.2 | HIGH | DoS via malformed JSON; internal use only | +| CVE-2026-34040 | moby/docker | v28.5.2 | 29.3.1 | HIGH | Auth bypass; Docker-client code path | +| CVE-2026-39883 | otel/sdk | v1.42.0 | 1.43.0 | HIGH | Local PATH hijack; no remote path | + +All remaining findings are in upstream Prometheus binary dependencies. No +remote attack path exists for any of the three CVE types, and fixes are +pending upstream Prometheus releases. + +--- + +### April 8, 2026 - Remediation Pass 1 (Issue #428) + +**Image**: `prom/prometheus:v3.5.1` +**Trivy Version**: 0.68.2 +**Scan Mode**: `--scanners vuln --severity HIGH,CRITICAL` +**Status**: ⚠️ **10 vulnerabilities** (6 HIGH, 4 CRITICAL) + +#### Summary + +Easy remediation applied by upgrading Prometheus from `v3.5.0` to `v3.5.1`. + +Vulnerability comparison: + +- Previous (`v3.5.0`): 16 HIGH, 4 CRITICAL +- Current (`v3.5.1`): 6 HIGH, 4 CRITICAL + +Improvement: -10 HIGH, 0 CRITICAL + +#### Target Breakdown (`v3.5.1`) + +| Target | Type | HIGH | CRITICAL | +| ---------------- | -------- | ---- | -------- | +| `bin/prometheus` | gobinary | 3 | 2 | +| `bin/promtool` | gobinary | 3 | 2 | + +Remaining vulnerabilities are in upstream Prometheus binary dependencies. + +### April 8, 2026 + +**Image**: `prom/prometheus:v3.5.0` +**Trivy Version**: 0.68.2 +**Status**: ⚠️ **20 vulnerabilities** (20 HIGH, 0 CRITICAL) - Significant increase from Dec scan + +#### Summary + +Vulnerability count increased dramatically from 0 to 20 HIGH. This represents a significant change, strongly suggesting the Trivy vulnerability database was updated with new CVE entries rather than Prometheus actually becoming more vulnerable. + +#### Changes Since December + +- December scan: 0 vulnerabilities +- April scan: 10 HIGH per binary (prometheus, promtool) = 20 total +- Most likely cause: Trivy database updated with newly-discovered Go stdlib CVEs + +**Recommended Action**: Verify that Prometheus binary and dependencies haven't actually been compromised. Check official Prometheus security advisories: https://github.com/prometheus/prometheus/security/advisories + +### December 29, 2025 + +**Image**: `prom/prometheus:v3.5.0` +**Trivy Version**: 0.68.2 +**Status**: ✅ SECURE - 0 HIGH/CRITICAL vulnerabilities + +#### Results + +```text +bin/prometheus (gobinary) +Total: 0 (HIGH: 0, CRITICAL: 0) +``` + +#### Notes + +- Prometheus v3.5.0 LTS release +- Go stdlib vulnerabilities from earlier scans have been patched +- Minimal scratch-based image reduces attack surface +- LTS support until July 31, 2026 + +#### Support Status + +- Release: July 14, 2025 +- LTS Support: 1-year window +- EOL: July 31, 2026 (7 months remaining as of Dec 2025) + +--- + +### Previous Scans + +#### December 23, 2025 (Pre-Update Baseline) + +**Image**: `prom/prometheus:v3.0.1` +**Status**: Preliminary scan - 0 HIGH/CRITICAL (informal assessment) + +**Note**: December 23 scan was a preliminary assessment before formal documentation was established. diff --git a/docs/security/docker/scans/torrust-tracker-backup.md b/docs/security/production/scans/torrust-tracker-backup.md similarity index 56% rename from docs/security/docker/scans/torrust-tracker-backup.md rename to docs/security/production/scans/torrust-tracker-backup.md index e6c2cbd30..44098841d 100644 --- a/docs/security/docker/scans/torrust-tracker-backup.md +++ b/docs/security/production/scans/torrust-tracker-backup.md @@ -4,9 +4,9 @@ Security scan history for the `torrust/tracker-backup` Docker image. ## Current Status -| Version | HIGH | CRITICAL | Status | Last Scan | -| ------- | ---- | -------- | -------------------- | ----------- | -| trixie | 7 | 0 | ℹ️ Base OS Monitored | Feb 5, 2026 | +| Version | HIGH | CRITICAL | Status | Last Scan | +| ------- | ---- | -------- | ------------------------------------ | ------------ | +| trixie | 6 | 0 | ⚠️ Accepted risk (Debian `<no-dsa>`) | Apr 15, 2026 | ## Build & Scan Commands @@ -24,6 +24,96 @@ trivy image --severity HIGH,CRITICAL torrust/tracker-backup:local ## Scan History +### April 15, 2026 - Remediation Pass 2 / Accepted Risk (Issue #431) + +**Image**: `torrust/tracker-backup:local` +**Trivy Version**: 0.69.3 +**Scan Mode**: `--scanners vuln --severity HIGH,CRITICAL` +**Base OS**: Debian 13.4 (trixie-slim) +**Status**: ⚠️ **No change** — 6 HIGH, 0 CRITICAL + +#### Summary + +Image rebuilt from scratch with `--no-cache`. All Debian packages updated to latest trixie +repository state. Vulnerability count unchanged: **6 HIGH, 0 CRITICAL**. + +| Target | HIGH | CRITICAL | +| -------------------------------------------- | ---- | -------- | +| `torrust/tracker-backup:local` (debian 13.4) | 6 | 0 | + +| CVE | Library | Severity | Status | Fixed Version | Title | +| -------------- | ------------------------------------------------- | -------- | -------- | ------------- | --------------------------------------------------------- | +| CVE-2025-69720 | libncurses6, libtinfo6, ncurses-base, ncurses-bin | HIGH | affected | — | ncurses: Buffer overflow in `infocmp` CLI tool | +| CVE-2026-29111 | libsystemd0, libudev1 | HIGH | affected | — | systemd: Assert/freeze via spurious unprivileged IPC call | + +#### Debian Security Tracker Status + +Both CVEs confirmed as `<no-dsa>` (minor issue) for trixie — Debian Security Team will not +issue a DSA for stable trixie: + +- **CVE-2025-69720**: Fixed only in `forky/sid` (`ncurses 6.6+20251231-1`). Affects the + `infocmp` CLI tool (`progs/infocmp.c`) — **not the ncurses library itself**. Our backup + container never invokes `infocmp`. +- **CVE-2026-29111**: Fixed only in `forky/sid` (`systemd 260.1-1`). Affects systemd when + running as PID 1 and receiving a spurious unprivileged IPC call. Our container runs a bash + script as entrypoint — **systemd is not PID 1**; `libsystemd0`/`libudev1` are installed as + transitive dependencies of other packages but the daemon is never started. + +#### Decision + +**Accepted risk — close issue #431.** + +- No fixes available in Debian trixie for either CVE +- Both CVEs are marked `<no-dsa>` minor issues by Debian Security Team +- Neither CVE is reachable in our container's runtime behaviour: + - `infocmp` is never called + - systemd is not running as PID 1 +- The backup container has a minimal footprint, runs non-root, and is not network-accessible + +**Revisit**: When Debian trixie backports fixes for `ncurses` or `systemd`. + +--- + +### April 8, 2026 - Remediation Pass 1 (Issue #428) + +**Image**: `torrust/tracker-backup:local` +**Trivy Version**: 0.68.2 +**Scan Mode**: `--scanners vuln --severity HIGH,CRITICAL` +**Base OS**: Debian 13.4 (trixie-slim) +**Status**: ℹ️ **No change after remediation attempt** - 6 HIGH, 0 CRITICAL + +#### Summary + +An easy remediation was applied by adding `apt-get upgrade -y` in the base layer. +The image rebuilt successfully and unit tests embedded in the Docker build still passed. + +Post-remediation scan result remained unchanged: + +- Before: 6 HIGH, 0 CRITICAL +- After: 6 HIGH, 0 CRITICAL + +This indicates the remaining findings are not resolved by package upgrades at current Debian 13.4 repository state. + +### April 8, 2026 + +**Image**: `torrust/tracker-backup:local` +**Trivy Version**: 0.68.2 +**Base OS**: Debian 13.4 (trixie-slim) +**Status**: ℹ️ **6 vulnerabilities** (6 HIGH, 0 CRITICAL) - Minor improvement from Feb scan + +#### Summary + +Vulnerability count reduced from 7 to 6 HIGH. All vulnerabilities remain in Debian 13.4 base packages. + +#### Changes Since February + +- Resolved 1 vulnerability (likely CVE-2026-24882 GnuPG fix partial coverage) +- GnuPG buffer overflow fix appears to have improved some package versions +- OpenSSL vulnerabilities may have been addressed +- MariaDB and glibc issues still present + +**Recommended Action**: Re-scan to identify which specific CVEs were resolved and which remain. + ### February 5, 2026 **Image**: `torrust/tracker-backup:local` diff --git a/docs/security/testing/README.md b/docs/security/testing/README.md new file mode 100644 index 000000000..6547c00b8 --- /dev/null +++ b/docs/security/testing/README.md @@ -0,0 +1,21 @@ +# Testing Artifacts Security + +This directory covers security for Docker images used only in automated tests or local development. +These are [Priority 4](../README.md) — the lowest-risk surface, as they never run in production. + +## Scope + +- `torrust/tracker-ssh-server` — SSH server used in E2E integration tests +- `torrust/tracker-provisioned-instance` — Ubuntu VM simulation used in E2E deployment workflow tests + +## Scan History + +See [`scans/`](scans/) for historical scan results. + +## When to Re-scan + +Scan testing artifacts when: + +- A test image uses a base image with known CRITICALs +- An artifact is promoted to be used in the deployer itself (moves to Priority 3) +- An artifact is deployed to production (moves to Priority 1) diff --git a/docs/security/testing/scans/README.md b/docs/security/testing/scans/README.md new file mode 100644 index 000000000..5526a5a7d --- /dev/null +++ b/docs/security/testing/scans/README.md @@ -0,0 +1,28 @@ +# Testing Image Scan Results + +Historical security scan results for Docker images used only in automated tests or local development. +These are [Priority 4](../../README.md) images — they never run in production. + +## Current Status Summary + +| Image | Version | HIGH | CRITICAL | Status | Last Scan | Details | +| -------------------------------------- | ------- | ---- | -------- | ------------------------- | ----------- | ----------------------------------------------- | +| `torrust/tracker-ssh-server` | 3.23.3 | 0 | 0 | ✅ Remediated (vuln scan) | Apr 8, 2026 | [View](torrust-ssh-server.md) | +| `torrust/tracker-provisioned-instance` | 24.04 | 0 | 0 | ✅ Remediated (vuln scan) | Apr 8, 2026 | [View](torrust-tracker-provisioned-instance.md) | + +## Scan Archives + +- [torrust-ssh-server.md](torrust-ssh-server.md) — SSH test server (base: alpine:3.23.3), used for E2E integration tests +- [torrust-tracker-provisioned-instance.md](torrust-tracker-provisioned-instance.md) — Ubuntu VM simulation (base: ubuntu:24.04), used for E2E deployment workflow tests + +## Build and Scan + +```bash +# Build testing images +docker build --tag torrust/tracker-ssh-server:local docker/ssh-server/ +docker build --tag torrust/tracker-provisioned-instance:local docker/provisioned-instance/ + +# Scan +trivy image --severity HIGH,CRITICAL torrust/tracker-ssh-server:local +trivy image --severity HIGH,CRITICAL torrust/tracker-provisioned-instance:local +``` diff --git a/docs/security/docker/scans/torrust-ssh-server.md b/docs/security/testing/scans/torrust-ssh-server.md similarity index 76% rename from docs/security/docker/scans/torrust-ssh-server.md rename to docs/security/testing/scans/torrust-ssh-server.md index a32df0cc1..203135bc7 100644 --- a/docs/security/docker/scans/torrust-ssh-server.md +++ b/docs/security/testing/scans/torrust-ssh-server.md @@ -4,9 +4,9 @@ Security scan history for the `torrust/tracker-ssh-server` Docker image used for ## Current Status -| Version | HIGH | CRITICAL | Status | Last Scan | -| ------- | ---- | -------- | ------------------ | ----------- | -| 3.23.3 | 1 | 0 | ✅ Current/Minimal | Feb 5, 2026 | +| Version | HIGH | CRITICAL | Status | Last Scan | +| ------- | ---- | -------- | ----------------------------------------- | ----------- | +| 3.23.3 | 0 | 0 | ✅ Vulnerabilities remediated (vuln scan) | Apr 8, 2026 | ## Build & Scan Commands @@ -24,6 +24,49 @@ trivy image --severity HIGH,CRITICAL torrust/tracker-ssh-server:local ## Scan History +### April 8, 2026 - Remediation Pass 1 (Issue #428) + +**Image**: `torrust/tracker-ssh-server:local` +**Trivy Version**: 0.68.2 +**Scan Mode**: `--scanners vuln --severity HIGH,CRITICAL` +**Base OS**: Alpine Linux 3.23.3 +**Status**: ✅ **0 vulnerabilities** (0 HIGH, 0 CRITICAL) + +#### Summary + +Remediation applied: + +- Added `apk upgrade --no-cache` in package install layer +- Fixed entrypoint script generation to ensure reliable container startup + +Verification results: + +- Vulnerability scan changed from 1 HIGH to 0 HIGH +- Secret scan still reports expected test private keys (non-production test artifacts) +- Container startup validated after entrypoint fix + +#### Delta from previous scan + +- Before (vuln): 1 HIGH, 0 CRITICAL +- After (vuln): 0 HIGH, 0 CRITICAL +- Improvement: -1 HIGH + +### April 8, 2026 + +**Image**: `torrust/tracker-ssh-server:local` +**Trivy Version**: 0.68.2 +**Base OS**: Alpine Linux 3.23.3 +**Purpose**: Integration testing SSH connectivity for E2E tests +**Status**: ✅ **1 vulnerability** (1 HIGH, 0 CRITICAL) - Unchanged from Feb 5, test artifact only + +#### Summary + +The April 8, 2026 scan confirms the same security posture as the previous scan: + +- Alpine base remains clean for HIGH/CRITICAL OS vulnerabilities +- The single finding is the expected private-key test artifact +- No new actionable vulnerabilities detected + ### February 5, 2026 **Image**: `torrust/tracker-ssh-server:local` diff --git a/docs/security/docker/scans/torrust-tracker-provisioned-instance.md b/docs/security/testing/scans/torrust-tracker-provisioned-instance.md similarity index 83% rename from docs/security/docker/scans/torrust-tracker-provisioned-instance.md rename to docs/security/testing/scans/torrust-tracker-provisioned-instance.md index 1c8708416..d6585fe36 100644 --- a/docs/security/docker/scans/torrust-tracker-provisioned-instance.md +++ b/docs/security/testing/scans/torrust-tracker-provisioned-instance.md @@ -4,9 +4,9 @@ Security scan history for the `torrust/tracker-provisioned-instance` Docker imag ## Current Status -| Version | HIGH | CRITICAL | Status | Last Scan | -| ------- | ---- | -------- | -------------------- | ----------- | -| 24.04 | 11 | 0 | ℹ️ Ubuntu LTS Stable | Feb 5, 2026 | +| Version | HIGH | CRITICAL | Status | Last Scan | +| ------- | ---- | -------- | ----------------------------------------- | ----------- | +| 24.04 | 0 | 0 | ✅ Vulnerabilities remediated (vuln scan) | Apr 8, 2026 | ## Build & Scan Commands @@ -24,6 +24,48 @@ trivy image --severity HIGH,CRITICAL torrust/tracker-provisioned-instance:local ## Scan History +### April 8, 2026 - Remediation Pass 1 (Issue #428) + +**Image**: `torrust/tracker-provisioned-instance:local` +**Trivy Version**: 0.68.2 +**Scan Mode**: `--scanners vuln --severity HIGH,CRITICAL` +**Base OS**: Ubuntu 24.04 LTS +**Status**: ✅ **0 vulnerabilities** (0 HIGH, 0 CRITICAL) + +#### Summary + +Applied remediation in Dockerfile: + +- Switched to `apt-get install --no-install-recommends` +- Added `apt-get upgrade -y` during package install + +Verification results: + +- Before: 12 HIGH, 0 CRITICAL +- After: 0 HIGH, 0 CRITICAL +- Improvement: -12 HIGH + +Container startup smoke test passed after rebuild. + +### April 8, 2026 + +**Image**: `torrust/tracker-provisioned-instance:local` +**Trivy Version**: 0.68.2 +**Base OS**: Ubuntu 24.04 LTS +**Status**: ⚠️ **12 vulnerabilities** (12 HIGH, 0 CRITICAL) - Minor increase from Feb (+1) + +#### Summary + +Vulnerability count increased from 11 to 12 HIGH. The single new vulnerability is likely from a Ubuntu 24.04 security update adding a newly-discovered CVE to the Trivy database. + +#### Changes Since February + +- Added 1 new HIGH vulnerability (likely from Ubuntu security advisory) +- All vulnerabilities remain in Ubuntu 24.04 LTS base packages +- Image remains suitable for E2E testing (ephemeral, isolated) + +**Assessment**: This is expected as Ubuntu continuously updates its security advisory database. + ### February 5, 2026 **Image**: `torrust/tracker-provisioned-instance:local` diff --git a/docs/security/ai-agents-and-secrets.md b/docs/security/user-security/ai-agents-and-secrets.md similarity index 100% rename from docs/security/ai-agents-and-secrets.md rename to docs/security/user-security/ai-agents-and-secrets.md diff --git a/docs/security/ssh-root-access-hetzner.md b/docs/security/user-security/ssh-root-access-hetzner.md similarity index 100% rename from docs/security/ssh-root-access-hetzner.md rename to docs/security/user-security/ssh-root-access-hetzner.md diff --git a/packages/README.md b/packages/README.md index 9f91a498a..ca2544ccf 100644 --- a/packages/README.md +++ b/packages/README.md @@ -88,8 +88,8 @@ All packages in this directory: ```rust // Add to your Cargo.toml [dependencies] -torrust-linting = "0.1.0" # external crate: https://crates.io/crates/torrust-linting -torrust-dependency-installer = { path = "packages/dependency-installer" } +torrust-tracker-deployer-types = { path = "packages/deployer-types" } +torrust-tracker-deployer-dependency-installer = { path = "packages/dependency-installer" } torrust-tracker-deployer-sdk = { path = "packages/sdk" } ``` diff --git a/packages/dependency-installer/Cargo.toml b/packages/dependency-installer/Cargo.toml index f56e87ce1..b33e2aeb1 100644 --- a/packages/dependency-installer/Cargo.toml +++ b/packages/dependency-installer/Cargo.toml @@ -1,12 +1,14 @@ [package] -name = "torrust-dependency-installer" +name = "torrust-tracker-deployer-dependency-installer" version = "0.1.0" edition = "2021" description = "Dependency detection and installation utilities for the Torrust Tracker Deployer project" license = "MIT" +repository = "https://github.com/torrust/torrust-tracker-deployer" +readme = "README.md" [lib] -name = "torrust_dependency_installer" +name = "torrust_tracker_deployer_dependency_installer" path = "src/lib.rs" [[bin]] @@ -22,7 +24,7 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", features = [ "env-filter" ] } [dev-dependencies] -testcontainers = "0.25" +testcontainers = "0.27" [[test]] name = "check_command_docker_integration" diff --git a/packages/dependency-installer/README.md b/packages/dependency-installer/README.md index 74dac7bda..2c8a24fc0 100644 --- a/packages/dependency-installer/README.md +++ b/packages/dependency-installer/README.md @@ -1,4 +1,4 @@ -# Torrust Dependency Installer Package +# Torrust Tracker Deployer Dependency Installer This package provides dependency detection and installation utilities for the Torrust Tracker Deployer project. @@ -125,51 +125,51 @@ This output is designed for human reading, **not for parsing by scripts**. ```bash # Check all dependencies (default log level shows INFO and above) $ dependency-installer check -2025-11-04T17:33:20.959847Z INFO torrust_dependency_installer::handlers::check: Checking all dependencies -2025-11-04T17:33:20.960126Z INFO torrust_dependency_installer::handlers::check: Dependency check result dependency="cargo-machete" status="installed" -2025-11-04T17:33:20.960131Z INFO torrust_dependency_installer::handlers::check: Dependency check result dependency="OpenTofu" status="not installed" -2025-11-04T17:33:20.960136Z INFO torrust_dependency_installer::handlers::check: Dependency check result dependency="Ansible" status="not installed" -2025-11-04T17:33:20.960139Z INFO torrust_dependency_installer::handlers::check: Dependency check result dependency="LXD" status="installed" -2025-11-04T17:33:20.960144Z INFO torrust_dependency_installer::handlers::check: Missing dependencies missing_count=2 total_count=4 +2025-11-04T17:33:20.959847Z INFO torrust_tracker_deployer_dependency_installer::handlers::check: Checking all dependencies +2025-11-04T17:33:20.960126Z INFO torrust_tracker_deployer_dependency_installer::handlers::check: Dependency check result dependency="cargo-machete" status="installed" +2025-11-04T17:33:20.960131Z INFO torrust_tracker_deployer_dependency_installer::handlers::check: Dependency check result dependency="OpenTofu" status="not installed" +2025-11-04T17:33:20.960136Z INFO torrust_tracker_deployer_dependency_installer::handlers::check: Dependency check result dependency="Ansible" status="not installed" +2025-11-04T17:33:20.960139Z INFO torrust_tracker_deployer_dependency_installer::handlers::check: Dependency check result dependency="LXD" status="installed" +2025-11-04T17:33:20.960144Z INFO torrust_tracker_deployer_dependency_installer::handlers::check: Missing dependencies missing_count=2 total_count=4 Error: Check command failed: Failed to check all dependencies: Missing 2 out of 4 required dependencies # Check specific dependency $ dependency-installer check --dependency opentofu -2025-11-04T17:33:20.959855Z INFO torrust_dependency_installer::handlers::check: Checking specific dependency dependency=opentofu -2025-11-04T17:33:20.960473Z INFO torrust_dependency_installer::detector::opentofu: OpenTofu is not installed dependency="opentofu" -2025-11-04T17:33:20.960482Z INFO torrust_dependency_installer::handlers::check: Dependency is not installed dependency="OpenTofu" status="not installed" +2025-11-04T17:33:20.959855Z INFO torrust_tracker_deployer_dependency_installer::handlers::check: Checking specific dependency dependency=opentofu +2025-11-04T17:33:20.960473Z INFO torrust_tracker_deployer_dependency_installer::detector::opentofu: OpenTofu is not installed dependency="opentofu" +2025-11-04T17:33:20.960482Z INFO torrust_tracker_deployer_dependency_installer::handlers::check: Dependency is not installed dependency="OpenTofu" status="not installed" Error: Check command failed: Failed to check specific dependency: opentofu: not installed # Install all dependencies $ dependency-installer install -2025-11-04T19:30:10.000000Z INFO torrust_dependency_installer::handlers::install: Installing all dependencies -2025-11-04T19:30:10.100000Z INFO torrust_dependency_installer::installer::cargo_machete: Installing cargo-machete dependency="cargo-machete" -2025-11-04T19:30:25.000000Z INFO torrust_dependency_installer::handlers::install: Dependency installation result dependency="cargo-machete" status="installed" -2025-11-04T19:30:25.100000Z INFO torrust_dependency_installer::installer::opentofu: Installing OpenTofu dependency="opentofu" -2025-11-04T19:30:40.000000Z INFO torrust_dependency_installer::handlers::install: Dependency installation result dependency="OpenTofu" status="installed" +2025-11-04T19:30:10.000000Z INFO torrust_tracker_deployer_dependency_installer::handlers::install: Installing all dependencies +2025-11-04T19:30:10.100000Z INFO torrust_tracker_deployer_dependency_installer::installer::cargo_machete: Installing cargo-machete dependency="cargo-machete" +2025-11-04T19:30:25.000000Z INFO torrust_tracker_deployer_dependency_installer::handlers::install: Dependency installation result dependency="cargo-machete" status="installed" +2025-11-04T19:30:25.100000Z INFO torrust_tracker_deployer_dependency_installer::installer::opentofu: Installing OpenTofu dependency="opentofu" +2025-11-04T19:30:40.000000Z INFO torrust_tracker_deployer_dependency_installer::handlers::install: Dependency installation result dependency="OpenTofu" status="installed" ... -2025-11-04T19:31:00.000000Z INFO torrust_dependency_installer::handlers::install: All dependencies installed successfully +2025-11-04T19:31:00.000000Z INFO torrust_tracker_deployer_dependency_installer::handlers::install: All dependencies installed successfully # Install specific dependency with verbose logging $ dependency-installer install --dependency opentofu --verbose -2025-11-04T19:30:10.000000Z INFO torrust_dependency_installer::handlers::install: Installing specific dependency dependency=opentofu -2025-11-04T19:30:10.100000Z INFO torrust_dependency_installer::installer::opentofu: Installing OpenTofu dependency="opentofu" -2025-11-04T19:30:10.200000Z DEBUG torrust_dependency_installer::installer::opentofu: Downloading OpenTofu installer script -2025-11-04T19:30:12.000000Z DEBUG torrust_dependency_installer::installer::opentofu: Making installer script executable -2025-11-04T19:30:12.100000Z DEBUG torrust_dependency_installer::installer::opentofu: Running OpenTofu installer with sudo -2025-11-04T19:30:25.000000Z DEBUG torrust_dependency_installer::installer::opentofu: Cleaning up installer script -2025-11-04T19:30:25.100000Z INFO torrust_dependency_installer::handlers::install: Dependency installation completed dependency="OpenTofu" status="installed" +2025-11-04T19:30:10.000000Z INFO torrust_tracker_deployer_dependency_installer::handlers::install: Installing specific dependency dependency=opentofu +2025-11-04T19:30:10.100000Z INFO torrust_tracker_deployer_dependency_installer::installer::opentofu: Installing OpenTofu dependency="opentofu" +2025-11-04T19:30:10.200000Z DEBUG torrust_tracker_deployer_dependency_installer::installer::opentofu: Downloading OpenTofu installer script +2025-11-04T19:30:12.000000Z DEBUG torrust_tracker_deployer_dependency_installer::installer::opentofu: Making installer script executable +2025-11-04T19:30:12.100000Z DEBUG torrust_tracker_deployer_dependency_installer::installer::opentofu: Running OpenTofu installer with sudo +2025-11-04T19:30:25.000000Z DEBUG torrust_tracker_deployer_dependency_installer::installer::opentofu: Cleaning up installer script +2025-11-04T19:30:25.100000Z INFO torrust_tracker_deployer_dependency_installer::handlers::install: Dependency installation completed dependency="OpenTofu" status="installed" # List all dependencies $ dependency-installer list -2025-11-04T17:33:20.960482Z INFO torrust_dependency_installer::handlers::list: Available dependency dependency="cargo-machete" status="installed" -2025-11-04T17:33:20.960494Z INFO torrust_dependency_installer::handlers::list: Available dependency dependency="OpenTofu" status="not installed" -2025-11-04T17:33:20.960962Z INFO torrust_dependency_installer::handlers::list: Available dependency dependency="Ansible" status="not installed" -2025-11-04T17:33:20.961521Z INFO torrust_dependency_installer::handlers::list: Available dependency dependency="LXD" status="installed" +2025-11-04T17:33:20.960482Z INFO torrust_tracker_deployer_dependency_installer::handlers::list: Available dependency dependency="cargo-machete" status="installed" +2025-11-04T17:33:20.960494Z INFO torrust_tracker_deployer_dependency_installer::handlers::list: Available dependency dependency="OpenTofu" status="not installed" +2025-11-04T17:33:20.960962Z INFO torrust_tracker_deployer_dependency_installer::handlers::list: Available dependency dependency="Ansible" status="not installed" +2025-11-04T17:33:20.961521Z INFO torrust_tracker_deployer_dependency_installer::handlers::list: Available dependency dependency="LXD" status="installed" # Enable verbose logging (includes DEBUG level) $ dependency-installer check --verbose -2025-11-04T17:33:20.959872Z DEBUG torrust_dependency_installer::detector::cargo_machete: Checking if cargo-machete is installed dependency="cargo-machete" +2025-11-04T17:33:20.959872Z DEBUG torrust_tracker_deployer_dependency_installer::detector::cargo_machete: Checking if cargo-machete is installed dependency="cargo-machete" ... ``` @@ -187,7 +187,7 @@ The CLI accepts the following dependency names: #### Checking Dependencies ```rust -use torrust_dependency_installer::DependencyManager; +use torrust_tracker_deployer_dependency_installer::DependencyManager; fn main() -> Result<(), Box<dyn std::error::Error>> { // Initialize tracing for structured logging @@ -214,7 +214,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { #### Installing Dependencies ```rust -use torrust_dependency_installer::{Dependency, DependencyManager}; +use torrust_tracker_deployer_dependency_installer::{Dependency, DependencyManager}; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { @@ -252,7 +252,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { #### Using Individual Detectors ```rust -use torrust_dependency_installer::{DependencyDetector, Dependency, DependencyManager}; +use torrust_tracker_deployer_dependency_installer::{DependencyDetector, Dependency, DependencyManager}; fn main() -> Result<(), Box<dyn std::error::Error>> { tracing_subscriber::fmt::init(); @@ -290,14 +290,14 @@ The Dockerfile serves as **explicit documentation** of what must be installed on ```bash # Run all tests (unit tests run normally, Docker tests use pre-built image) -cargo test -p torrust-dependency-installer +cargo test -p torrust-tracker-deployer-dependency-installer # Run only Docker-based integration tests -cargo test -p torrust-dependency-installer --test docker_install_command -cargo test -p torrust-dependency-installer --test docker_check_command +cargo test -p torrust-tracker-deployer-dependency-installer --test docker_install_command +cargo test -p torrust-tracker-deployer-dependency-installer --test docker_check_command # Run expensive tests (OpenTofu, Ansible, LXD) -cargo test -p torrust-dependency-installer -- --ignored +cargo test -p torrust-tracker-deployer-dependency-installer -- --ignored # Build the test Docker image docker build -f docker/ubuntu-24.04.Dockerfile -t dependency-installer-test:ubuntu-24.04 . @@ -311,15 +311,15 @@ Add to your `Cargo.toml`: ```toml [dependencies] -torrust-dependency-installer = { path = "path/to/torrust-dependency-installer" } +torrust-tracker-deployer-dependency-installer = { path = "path/to/torrust-tracker-deployer-dependency-installer" } ``` Or if using in a workspace: ```toml [workspace] -members = ["packages/torrust-dependency-installer"] +members = ["packages/torrust-tracker-deployer-dependency-installer"] [dependencies] -torrust-dependency-installer = { path = "packages/torrust-dependency-installer" } +torrust-tracker-deployer-dependency-installer = { path = "packages/torrust-tracker-deployer-dependency-installer" } ``` diff --git a/packages/dependency-installer/examples/check_dependencies.rs b/packages/dependency-installer/examples/check_dependencies.rs index 90196f771..0e8af1011 100644 --- a/packages/dependency-installer/examples/check_dependencies.rs +++ b/packages/dependency-installer/examples/check_dependencies.rs @@ -5,7 +5,7 @@ //! //! Run with: `cargo run --example check_dependencies` -use torrust_dependency_installer::{init_tracing, DependencyManager}; +use torrust_tracker_deployer_dependency_installer::{init_tracing, DependencyManager}; fn main() { // Initialize tracing for structured logging with INFO level diff --git a/packages/dependency-installer/src/bin/dependency-installer.rs b/packages/dependency-installer/src/bin/dependency-installer.rs index c8e8f3443..e1d73c5f1 100644 --- a/packages/dependency-installer/src/bin/dependency-installer.rs +++ b/packages/dependency-installer/src/bin/dependency-installer.rs @@ -41,7 +41,7 @@ use std::process; -use torrust_dependency_installer::app; +use torrust_tracker_deployer_dependency_installer::app; #[tokio::main] async fn main() { diff --git a/packages/dependency-installer/src/command.rs b/packages/dependency-installer/src/command.rs index 25be8cfe9..7344f926e 100644 --- a/packages/dependency-installer/src/command.rs +++ b/packages/dependency-installer/src/command.rs @@ -23,7 +23,7 @@ use thiserror::Error; /// # Examples /// /// ```rust -/// use torrust_dependency_installer::command::command_exists; +/// use torrust_tracker_deployer_dependency_installer::command::command_exists; /// /// // Check if 'cargo' is installed /// let exists = command_exists("cargo").unwrap(); @@ -53,7 +53,7 @@ pub fn command_exists(command: &str) -> Result<bool, CommandError> { /// # Examples /// /// ```rust,no_run -/// use torrust_dependency_installer::command::execute_command; +/// use torrust_tracker_deployer_dependency_installer::command::execute_command; /// /// // Get cargo version /// let version = execute_command("cargo", &["--version"]).unwrap(); diff --git a/packages/dependency-installer/src/verification.rs b/packages/dependency-installer/src/verification.rs index b81a99063..00f7d5808 100644 --- a/packages/dependency-installer/src/verification.rs +++ b/packages/dependency-installer/src/verification.rs @@ -28,7 +28,7 @@ use crate::{Dependency, DependencyManager, DetectionError}; /// # Example /// /// ```no_run -/// use torrust_dependency_installer::{Dependency, verify_dependencies}; +/// use torrust_tracker_deployer_dependency_installer::{Dependency, verify_dependencies}; /// /// // Verify all dependencies for a full workflow /// let deps = &[Dependency::OpenTofu, Dependency::Ansible, Dependency::Lxd]; diff --git a/packages/dependency-installer/tests/detector_tests.rs b/packages/dependency-installer/tests/detector_tests.rs index 9b8678137..377b9dff1 100644 --- a/packages/dependency-installer/tests/detector_tests.rs +++ b/packages/dependency-installer/tests/detector_tests.rs @@ -5,10 +5,10 @@ //! - `DependencyManager` functionality //! - Error handling -use torrust_dependency_installer::detector::{ +use torrust_tracker_deployer_dependency_installer::detector::{ AnsibleDetector, CargoMacheteDetector, DependencyDetector, LxdDetector, OpenTofuDetector, }; -use torrust_dependency_installer::{CheckResult, Dependency, DependencyManager}; +use torrust_tracker_deployer_dependency_installer::{CheckResult, Dependency, DependencyManager}; // ============================================================================= // DETECTOR TRAIT TESTS @@ -165,7 +165,7 @@ fn it_should_get_lxd_detector_from_manager() { #[test] fn it_should_create_check_result() { - use torrust_dependency_installer::Dependency; + use torrust_tracker_deployer_dependency_installer::Dependency; let result = CheckResult { dependency: Dependency::CargoMachete, installed: true, @@ -177,7 +177,7 @@ fn it_should_create_check_result() { #[test] fn it_should_clone_check_result() { - use torrust_dependency_installer::Dependency; + use torrust_tracker_deployer_dependency_installer::Dependency; let result = CheckResult { dependency: Dependency::OpenTofu, installed: false, @@ -194,7 +194,7 @@ fn it_should_clone_check_result() { #[test] fn it_should_detect_existing_command() { - use torrust_dependency_installer::command::command_exists; + use torrust_tracker_deployer_dependency_installer::command::command_exists; // Test with 'sh' which should always exist on Unix systems let result = command_exists("sh"); @@ -205,7 +205,7 @@ fn it_should_detect_existing_command() { #[test] fn it_should_detect_nonexistent_command() { - use torrust_dependency_installer::command::command_exists; + use torrust_tracker_deployer_dependency_installer::command::command_exists; // Test with a command that definitely doesn't exist let result = command_exists("this-command-definitely-does-not-exist-12345"); @@ -216,7 +216,7 @@ fn it_should_detect_nonexistent_command() { #[test] fn it_should_execute_command_successfully() { - use torrust_dependency_installer::command::execute_command; + use torrust_tracker_deployer_dependency_installer::command::execute_command; // Test with 'echo' which should always work let result = execute_command("echo", &["hello"]); @@ -226,7 +226,7 @@ fn it_should_execute_command_successfully() { #[test] fn it_should_fail_to_execute_nonexistent_command() { - use torrust_dependency_installer::command::execute_command; + use torrust_tracker_deployer_dependency_installer::command::execute_command; // Test with nonexistent command let result = execute_command("this-command-definitely-does-not-exist-12345", &["test"]); diff --git a/packages/dependency-installer/tests/install_command_docker_integration.rs b/packages/dependency-installer/tests/install_command_docker_integration.rs index 346282f76..375dd9c37 100644 --- a/packages/dependency-installer/tests/install_command_docker_integration.rs +++ b/packages/dependency-installer/tests/install_command_docker_integration.rs @@ -155,7 +155,7 @@ async fn it_should_install_opentofu_successfully() { /// /// **Known Issue**: This test is flaky due to Ansible installation reliability in containers. /// It's marked as `#[ignore]` to prevent CI failures. Run manually with: -/// `cargo test --package torrust-dependency-installer --test install_command_docker_integration it_should_install_ansible_successfully -- --ignored` +/// `cargo test --package torrust-tracker-deployer-dependency-installer --test install_command_docker_integration it_should_install_ansible_successfully -- --ignored` #[tokio::test] #[ignore = "Flaky test: Ansible installation is unreliable in containers"] async fn it_should_install_ansible_successfully() { diff --git a/packages/deployer-types/Cargo.toml b/packages/deployer-types/Cargo.toml index e51b95f36..b67cc2c9c 100644 --- a/packages/deployer-types/Cargo.toml +++ b/packages/deployer-types/Cargo.toml @@ -1,9 +1,11 @@ [package] -name = "torrust-deployer-types" +name = "torrust-tracker-deployer-types" version = "0.1.0" edition = "2021" description = "Shared value objects and traits for the Torrust Tracker Deployer" license = "MIT" +repository = "https://github.com/torrust/torrust-tracker-deployer" +readme = "README.md" [dependencies] chrono = { version = "0.4", features = [ "serde" ] } diff --git a/packages/deployer-types/README.md b/packages/deployer-types/README.md index 1df9f435e..f40b75d4d 100644 --- a/packages/deployer-types/README.md +++ b/packages/deployer-types/README.md @@ -31,17 +31,17 @@ and minimal external dependencies. ```toml [dependencies] -torrust-deployer-types = { path = "packages/deployer-types" } +torrust-tracker-deployer-types = { path = "packages/deployer-types" } ``` ```rust -use torrust_deployer_types::{EnvironmentName, DomainName, Clock}; +use torrust_tracker_deployer_types::{EnvironmentName, DomainName, Clock}; ``` ## Architecture ```text -torrust-deployer-types ← this package (no internal deps) +torrust-tracker-deployer-types ← this package (no internal deps) ↑ ↑ torrust-tracker-deployer torrust-tracker-deployer-sdk ``` diff --git a/packages/deployer-types/src/clock.rs b/packages/deployer-types/src/clock.rs index 7c6793dec..932c1d71c 100644 --- a/packages/deployer-types/src/clock.rs +++ b/packages/deployer-types/src/clock.rs @@ -20,7 +20,7 @@ //! ## In Production Code //! //! ```rust -//! use torrust_deployer_types::Clock; +//! use torrust_tracker_deployer_types::Clock; //! //! fn record_event(clock: &dyn Clock) { //! let timestamp = clock.now(); @@ -72,7 +72,7 @@ pub trait Clock: Send + Sync { /// # Example /// /// ```rust -/// use torrust_deployer_types::{Clock, SystemClock}; +/// use torrust_tracker_deployer_types::{Clock, SystemClock}; /// /// let clock = SystemClock; /// let now = clock.now(); diff --git a/packages/deployer-types/src/domain_name.rs b/packages/deployer-types/src/domain_name.rs index 6f894ea9c..36a3d81ee 100644 --- a/packages/deployer-types/src/domain_name.rs +++ b/packages/deployer-types/src/domain_name.rs @@ -37,7 +37,7 @@ //! # Examples //! //! ``` -//! use torrust_deployer_types::DomainName; +//! use torrust_tracker_deployer_types::DomainName; //! //! // Valid domain names //! let domain = DomainName::new("example.com").unwrap(); @@ -91,7 +91,7 @@ impl DomainName { /// # Examples /// /// ``` - /// use torrust_deployer_types::DomainName; + /// use torrust_tracker_deployer_types::DomainName; /// /// let domain = DomainName::new("tracker.torrust.org").unwrap(); /// assert_eq!(domain.as_str(), "tracker.torrust.org"); @@ -117,7 +117,7 @@ impl DomainName { /// # Examples /// /// ``` - /// use torrust_deployer_types::DomainName; + /// use torrust_tracker_deployer_types::DomainName; /// /// let domain = DomainName::new("api.tracker.torrust.org").unwrap(); /// assert_eq!(domain.tld(), "org"); @@ -135,7 +135,7 @@ impl DomainName { /// # Examples /// /// ``` - /// use torrust_deployer_types::DomainName; + /// use torrust_tracker_deployer_types::DomainName; /// /// let domain = DomainName::new("api.tracker.torrust.org").unwrap(); /// assert_eq!(domain.subdomains(), vec!["api", "tracker", "torrust"]); diff --git a/packages/deployer-types/src/email.rs b/packages/deployer-types/src/email.rs index 66afad6d9..584aff75c 100644 --- a/packages/deployer-types/src/email.rs +++ b/packages/deployer-types/src/email.rs @@ -6,7 +6,7 @@ //! # Usage //! //! ```rust -//! use torrust_deployer_types::Email; +//! use torrust_tracker_deployer_types::Email; //! //! // Valid email //! let email = Email::new("admin@example.com").unwrap(); @@ -47,7 +47,7 @@ use serde::{Deserialize, Serialize}; /// # Examples /// /// ```rust -/// use torrust_deployer_types::Email; +/// use torrust_tracker_deployer_types::Email; /// /// let email = Email::new("user@example.com").unwrap(); /// println!("Email: {}", email); @@ -76,7 +76,7 @@ impl Email { /// # Examples /// /// ```rust - /// use torrust_deployer_types::Email; + /// use torrust_tracker_deployer_types::Email; /// /// let valid = Email::new("admin@example.com"); /// assert!(valid.is_ok()); @@ -104,7 +104,7 @@ impl Email { /// # Examples /// /// ```rust - /// use torrust_deployer_types::Email; + /// use torrust_tracker_deployer_types::Email; /// /// let email = Email::new("user@example.com").unwrap(); /// assert_eq!(email.local_part(), "user"); @@ -119,7 +119,7 @@ impl Email { /// # Examples /// /// ```rust - /// use torrust_deployer_types::Email; + /// use torrust_tracker_deployer_types::Email; /// /// let email = Email::new("user@example.com").unwrap(); /// assert_eq!(email.domain_part(), "example.com"); diff --git a/packages/deployer-types/src/environment_name.rs b/packages/deployer-types/src/environment_name.rs index c660414e5..5d800133d 100644 --- a/packages/deployer-types/src/environment_name.rs +++ b/packages/deployer-types/src/environment_name.rs @@ -43,7 +43,7 @@ use thiserror::Error; /// # Examples /// /// ```rust -/// use torrust_deployer_types::EnvironmentName; +/// use torrust_tracker_deployer_types::EnvironmentName; /// /// // Valid environment names /// let dev = EnvironmentName::new("dev".to_string())?; @@ -75,7 +75,7 @@ impl EnvironmentName { /// # Examples /// /// ```rust - /// # use torrust_deployer_types::EnvironmentName; + /// # use torrust_tracker_deployer_types::EnvironmentName; /// // Valid names - accepts various string types /// assert!(EnvironmentName::new("dev").is_ok()); /// assert!(EnvironmentName::new("e2e-config".to_string()).is_ok()); @@ -103,7 +103,7 @@ impl EnvironmentName { /// # Examples /// /// ```rust - /// use torrust_deployer_types::EnvironmentName; + /// use torrust_tracker_deployer_types::EnvironmentName; /// /// let env_name = EnvironmentName::new("production".to_string())?; /// assert_eq!(env_name.as_str(), "production"); diff --git a/packages/deployer-types/src/error/kind.rs b/packages/deployer-types/src/error/kind.rs index 56e1632b9..b7489d0ad 100644 --- a/packages/deployer-types/src/error/kind.rs +++ b/packages/deployer-types/src/error/kind.rs @@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize}; /// # Examples /// /// ``` -/// use torrust_deployer_types::ErrorKind; +/// use torrust_tracker_deployer_types::ErrorKind; /// /// let kind = ErrorKind::TemplateRendering; /// assert_eq!(format!("{kind:?}"), "TemplateRendering"); diff --git a/packages/deployer-types/src/error/traceable.rs b/packages/deployer-types/src/error/traceable.rs index 65a04a955..4ec4f840c 100644 --- a/packages/deployer-types/src/error/traceable.rs +++ b/packages/deployer-types/src/error/traceable.rs @@ -16,8 +16,8 @@ use super::ErrorKind; /// # Example /// /// ```rust -/// use torrust_deployer_types::error::Traceable; -/// use torrust_deployer_types::ErrorKind; +/// use torrust_tracker_deployer_types::error::Traceable; +/// use torrust_tracker_deployer_types::ErrorKind; /// /// #[derive(Debug, thiserror::Error)] /// enum MyError { @@ -97,7 +97,7 @@ pub trait Traceable: std::error::Error { /// # Example /// /// ```rust - /// use torrust_deployer_types::{Traceable, ErrorKind}; + /// use torrust_tracker_deployer_types::{Traceable, ErrorKind}; /// /// fn handle_error<E: Traceable>(error: &E) { /// let kind = error.error_kind(); diff --git a/packages/deployer-types/src/secrets/api_token.rs b/packages/deployer-types/src/secrets/api_token.rs index f5e56910e..bd8fac1d7 100644 --- a/packages/deployer-types/src/secrets/api_token.rs +++ b/packages/deployer-types/src/secrets/api_token.rs @@ -31,7 +31,7 @@ impl ApiToken { /// /// # Examples /// ``` - /// use torrust_deployer_types::secrets::ApiToken; + /// use torrust_tracker_deployer_types::secrets::ApiToken; /// /// let from_string = ApiToken::new(String::from("token")); /// let from_str = ApiToken::new("token"); diff --git a/packages/deployer-types/src/secrets/password.rs b/packages/deployer-types/src/secrets/password.rs index 5f99107ed..2549ce015 100644 --- a/packages/deployer-types/src/secrets/password.rs +++ b/packages/deployer-types/src/secrets/password.rs @@ -31,7 +31,7 @@ impl Password { /// /// # Examples /// ``` - /// use torrust_deployer_types::secrets::Password; + /// use torrust_tracker_deployer_types::secrets::Password; /// /// let from_string = Password::new(String::from("password")); /// let from_str = Password::new("password"); diff --git a/packages/deployer-types/src/service_endpoint.rs b/packages/deployer-types/src/service_endpoint.rs index 99e0f13c5..04e44c29b 100644 --- a/packages/deployer-types/src/service_endpoint.rs +++ b/packages/deployer-types/src/service_endpoint.rs @@ -41,7 +41,7 @@ impl std::error::Error for InvalidServiceEndpointUrl {} /// /// ``` /// use std::net::SocketAddr; -/// use torrust_deployer_types::ServiceEndpoint; +/// use torrust_tracker_deployer_types::ServiceEndpoint; /// /// // HTTP endpoint /// let socket_addr: SocketAddr = "10.0.0.1:1212".parse().unwrap(); diff --git a/packages/deployer-types/src/username.rs b/packages/deployer-types/src/username.rs index 3a2c30985..b50d22999 100644 --- a/packages/deployer-types/src/username.rs +++ b/packages/deployer-types/src/username.rs @@ -76,7 +76,7 @@ impl Username { /// # Examples /// /// ```rust - /// use torrust_deployer_types::Username; + /// use torrust_tracker_deployer_types::Username; /// /// // Valid usernames - accepts both &str and String /// let user1 = Username::new("torrust")?; diff --git a/packages/sdk/Cargo.toml b/packages/sdk/Cargo.toml index fe0c42668..0b21ff64d 100644 --- a/packages/sdk/Cargo.toml +++ b/packages/sdk/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" edition = "2021" description = "Programmatic SDK for the Torrust Tracker Deployer" license = "MIT" +repository = "https://github.com/torrust/torrust-tracker-deployer" +readme = "README.md" [[example]] name = "sdk_basic_usage" @@ -26,8 +28,8 @@ name = "sdk_validate_config" path = "examples/validate_config.rs" [dependencies] -torrust-tracker-deployer = { path = "../.." } -torrust-deployer-types = { path = "../deployer-types" } +torrust-tracker-deployer = { path = "../..", version = "0.1.0" } +torrust-tracker-deployer-types = { path = "../deployer-types", version = "0.1.0" } thiserror = "2.0" [dev-dependencies] diff --git a/packages/sdk/README.md b/packages/sdk/README.md index 2bc9af3ec..0186b1bd9 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -13,7 +13,7 @@ Add to your `Cargo.toml`: ```toml [dependencies] -torrust-tracker-deployer-sdk = { git = "https://github.com/torrust/torrust-tracker-deployer" } +torrust-tracker-deployer-sdk = "0.1.0-beta.1" ``` Basic usage: @@ -46,27 +46,23 @@ cargo run --example sdk_validate_config -p torrust-tracker-deployer-sdk ```text torrust-tracker-deployer-sdk ← this package │ - ▼ -torrust-tracker-deployer (root crate) + ├──▶ torrust-tracker-deployer (root crate) + │ │ + │ ▼ + │ Application Layer (command_handlers/) + │ │ + │ ▼ + │ Domain Layer (environment/, template/, topology/, ...) │ - ▼ -Application Layer (command_handlers/) - │ - ▼ -Domain Layer (environment/, template/, topology/, ...) + └──▶ torrust-tracker-deployer-types (packages/deployer-types/) + (shared value objects and traits) ``` -> **Note**: This package currently depends on the root `torrust-tracker-deployer` -> crate for application-layer types. Plans 3 and 4 will progressively decouple -> this dependency by extracting shared types into a `packages/deployer-types/` -> package. - ## Status This package was extracted from `src/presentation/sdk/` in the root crate as part -of the SDK workspace package refactoring (Plan 2 of 4). See the -[refactoring plan](../../docs/refactors/plans/extract-sdk-workspace-package.md) -for details. +of the workspace package refactoring. The extraction is complete and the package is +published on [crates.io](https://crates.io/crates/torrust-tracker-deployer-sdk). ## License diff --git a/packages/sdk/src/lib.rs b/packages/sdk/src/lib.rs index c45dd73e4..752226425 100644 --- a/packages/sdk/src/lib.rs +++ b/packages/sdk/src/lib.rs @@ -40,7 +40,7 @@ pub use builder::{DeployerBuildError, DeployerBuilder}; pub use deployer::Deployer; // === Domain types (inputs only) === -pub use torrust_deployer_types::{EnvironmentName, EnvironmentNameError}; +pub use torrust_tracker_deployer_types::{EnvironmentName, EnvironmentNameError}; // === Configuration types (for create_environment) === pub use torrust_tracker_deployer_lib::application::command_handlers::create::config::{ diff --git a/project-words.txt b/project-words.txt index 9bf02f3d3..30fd91f6f 100644 --- a/project-words.txt +++ b/project-words.txt @@ -51,6 +51,7 @@ Firestore Freshping Frontegg Geeksfor +GHSA Gossman Grafana Grafonnet @@ -97,10 +98,12 @@ Pythonic QUIC RAII RUSTDOCFLAGS +RUSTSEC Regenerable Repomix Repositóri Rescan +Remediations Restic Runrestic Rustdoc @@ -161,12 +164,14 @@ bootcmd browsable btih btrfs +buger buildx cdmon celano certbot certonly chatbots +chacha chdir checkmark checkmarks @@ -195,6 +200,7 @@ crontabs cursorignore custompass customuser +cves cyberneering dcron dearmor @@ -215,6 +221,9 @@ doctests downcasted downcasting downloadedi +pinentry +signingkey +clearsign dpkg drwxr drwxrwxr @@ -234,6 +243,7 @@ ethernets executability exfiltration exitcode +exploitability filesd flatlined frontends @@ -272,6 +282,7 @@ josecelano journalctl jsonlint jsonls +jsonparser keepalive keygen keypair @@ -283,6 +294,7 @@ leecher leechers letsencrypt libc +libcrypto libldap libmariadb libpam @@ -426,7 +438,18 @@ rustup rwxrwx sandboxed sarif +sarifs scannability +SCEP +DTLS +mysqlsh +syscall +infocmp +libncurses +libtinfo +libsystemd +libudev +behaviour schemafile schemars scriptable @@ -506,6 +529,7 @@ unconfigured undertested unergonomic unittests +untgz unrepresentable DNAT UNCONN @@ -539,7 +563,12 @@ youruser zcat zeroize zoneinfo +worktree zstd +ciphertext +makeslice +rstrip +urlsafe CSPRNG USERINFO plainpassword @@ -549,3 +578,7 @@ userinfo ключ конфиг файл + +cpython +kenv +libexpat diff --git a/scripts/setup/README.md b/scripts/setup/README.md index e142aa1da..37a50d835 100644 --- a/scripts/setup/README.md +++ b/scripts/setup/README.md @@ -12,19 +12,19 @@ For dependency installation, use the `dependency-installer` binary: ```bash # Install all dependencies -cargo run -p torrust-dependency-installer --bin dependency-installer -- install +cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer -- install # Check which dependencies are installed -cargo run -p torrust-dependency-installer --bin dependency-installer -- check +cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer -- check # List all available dependencies -cargo run -p torrust-dependency-installer --bin dependency-installer -- list +cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer -- list # Install specific dependency -cargo run -p torrust-dependency-installer --bin dependency-installer -- install --dependency opentofu +cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer -- install --dependency opentofu # See all options -cargo run -p torrust-dependency-installer --bin dependency-installer -- --help +cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer -- --help ``` ### Benefits of the New Approach @@ -66,8 +66,8 @@ Example workflow step: ```yaml - name: Install dependencies run: | - cargo build -p torrust-dependency-installer --bin dependency-installer - cargo run -p torrust-dependency-installer --bin dependency-installer -- install + cargo build -p torrust-tracker-deployer-dependency-installer --bin dependency-installer + cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer -- install ``` ### Local Development @@ -84,9 +84,9 @@ For local development, the dependency installer automatically handles: If you encounter issues with the dependency installer: -1. **Check installation status**: `cargo run -p torrust-dependency-installer --bin dependency-installer -- check` +1. **Check installation status**: `cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer -- check` 2. **Enable debug logging**: Add `--verbose` flag or `--log-level debug` -3. **View available dependencies**: `cargo run -p torrust-dependency-installer --bin dependency-installer -- list` +3. **View available dependencies**: `cargo run -p torrust-tracker-deployer-dependency-installer --bin dependency-installer -- list` 4. **Check exit codes**: Exit code 0 = success, non-zero = failure For detailed troubleshooting, see the [dependency installer README](../../packages/dependency-installer/README.md). diff --git a/scripts/update-dependencies.sh b/scripts/update-dependencies.sh new file mode 100755 index 000000000..9e413d99d --- /dev/null +++ b/scripts/update-dependencies.sh @@ -0,0 +1,306 @@ +#!/bin/bash + +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: ./scripts/update-dependencies.sh --branch <branch-name> [options] + +Automates the dependency update workflow: +- sync the base branch from the base remote +- create a fresh working branch +- run cargo update +- run pre-commit checks +- create a signed commit with the full cargo update output +- optionally push the branch and create a pull request + +Options: + --branch <name> Working branch to create (required) + --base-branch <name> Base branch to update from (default: main) + --base-remote <name> Remote that owns the base branch (default: torrust, then origin, then first remote) + --push-remote <name> Remote used to push the branch + --repo <owner/repo> Repository slug for PR creation + --commit-title <title> Commit title and default PR title + --pr-title <title> Pull request title override + --delete-existing-branch Delete an existing local/remote branch with the same name + --skip-pre-commit Skip ./scripts/pre-commit.sh + --create-pr Create a PR after pushing the branch + --no-sign-commit Do not use git commit -S + --help Show this help message + +Examples: + ./scripts/update-dependencies.sh \ + --branch 445-update-dependencies \ + --push-remote josecelano \ + --create-pr + + ./scripts/update-dependencies.sh \ + --branch update-dependencies \ + --push-remote josecelano \ + --delete-existing-branch \ + --commit-title "chore: update dependencies" +EOF +} + +log() { + echo "[update-dependencies] $*" +} + +fail() { + echo "Error: $*" >&2 + exit 1 +} + +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +detect_default_remote() { + if git remote | grep -qx "torrust"; then + echo "torrust" + return + fi + + if git remote | grep -qx "origin"; then + echo "origin" + return + fi + + git remote | head -n 1 +} + +parse_github_slug_from_remote() { + local remote=$1 + local remote_url + local slug + + remote_url=$(git remote get-url "$remote") + slug=$(printf '%s' "$remote_url" | sed -E 's#^(git@github.com:|https://github.com/|ssh://git@github.com/)##; s#\.git$##') + + [[ "$slug" == */* ]] || fail "Could not parse GitHub repository slug from remote '$remote'" + + echo "$slug" +} + +parse_github_owner_from_remote() { + local remote=$1 + local slug + + slug=$(parse_github_slug_from_remote "$remote") + echo "${slug%%/*}" +} + +branch_exists_local() { + local branch=$1 + git show-ref --verify --quiet "refs/heads/$branch" +} + +branch_exists_remote() { + local remote=$1 + local branch=$2 + git ls-remote --exit-code --heads "$remote" "$branch" >/dev/null 2>&1 +} + +ensure_clean_worktree() { + git diff --quiet || fail "Working tree has unstaged changes" + git diff --cached --quiet || fail "Working tree has staged changes" +} + +cleanup_files() { + rm -f "$CARGO_UPDATE_OUTPUT_FILE" "$COMMIT_MESSAGE_FILE" "$PR_BODY_FILE" +} + +BRANCH_NAME="" +BASE_BRANCH="main" +BASE_REMOTE="" +PUSH_REMOTE="" +REPOSITORY_SLUG="" +COMMIT_TITLE="chore: update dependencies" +PR_TITLE="" +DELETE_EXISTING_BRANCH=false +RUN_PRE_COMMIT=true +CREATE_PR=false +SIGN_COMMIT=true + +while [[ $# -gt 0 ]]; do + case "$1" in + --branch) + BRANCH_NAME=${2:-} + shift 2 + ;; + --base-branch) + BASE_BRANCH=${2:-} + shift 2 + ;; + --base-remote) + BASE_REMOTE=${2:-} + shift 2 + ;; + --push-remote) + PUSH_REMOTE=${2:-} + shift 2 + ;; + --repo) + REPOSITORY_SLUG=${2:-} + shift 2 + ;; + --commit-title) + COMMIT_TITLE=${2:-} + shift 2 + ;; + --pr-title) + PR_TITLE=${2:-} + shift 2 + ;; + --delete-existing-branch) + DELETE_EXISTING_BRANCH=true + shift + ;; + --skip-pre-commit) + RUN_PRE_COMMIT=false + shift + ;; + --create-pr) + CREATE_PR=true + shift + ;; + --no-sign-commit) + SIGN_COMMIT=false + shift + ;; + --help) + usage + exit 0 + ;; + *) + fail "Unknown option: $1" + ;; + esac +done + +[[ -n "$BRANCH_NAME" ]] || fail "--branch is required" + +command_exists git || fail "git is required" +command_exists cargo || fail "cargo is required" + +BASE_REMOTE=${BASE_REMOTE:-$(detect_default_remote)} +[[ -n "$BASE_REMOTE" ]] || fail "Could not determine a base remote" + +if [[ -z "$REPOSITORY_SLUG" ]]; then + REPOSITORY_SLUG=$(parse_github_slug_from_remote "$BASE_REMOTE") +fi + +if [[ "$CREATE_PR" == true ]]; then + [[ -n "$PUSH_REMOTE" ]] || fail "--push-remote is required when --create-pr is used" + command_exists gh || fail "gh is required when --create-pr is used" +fi + +if [[ -n "$PUSH_REMOTE" ]]; then + git remote get-url "$PUSH_REMOTE" >/dev/null 2>&1 || fail "Remote '$PUSH_REMOTE' does not exist" +fi + +CARGO_UPDATE_OUTPUT_FILE=$(mktemp) +COMMIT_MESSAGE_FILE=$(mktemp) +PR_BODY_FILE=$(mktemp) +trap cleanup_files EXIT + +ensure_clean_worktree + +if branch_exists_local "$BRANCH_NAME"; then + if [[ "$DELETE_EXISTING_BRANCH" == true ]]; then + log "Deleting local branch '$BRANCH_NAME'" + current_branch=$(git branch --show-current) + if [[ "$current_branch" == "$BRANCH_NAME" ]]; then + git checkout "$BASE_BRANCH" + fi + git branch -D "$BRANCH_NAME" + else + fail "Local branch '$BRANCH_NAME' already exists. Use --delete-existing-branch to replace it." + fi +fi + +if [[ -n "$PUSH_REMOTE" ]] && branch_exists_remote "$PUSH_REMOTE" "$BRANCH_NAME"; then + if [[ "$DELETE_EXISTING_BRANCH" == true ]]; then + log "Deleting remote branch '$BRANCH_NAME' from '$PUSH_REMOTE'" + git push "$PUSH_REMOTE" --delete "$BRANCH_NAME" + else + fail "Remote branch '$BRANCH_NAME' already exists on '$PUSH_REMOTE'. Use --delete-existing-branch to replace it." + fi +fi + +log "Fetching '$BASE_REMOTE/$BASE_BRANCH'" +git fetch "$BASE_REMOTE" "$BASE_BRANCH" + +log "Checking out '$BASE_BRANCH'" +git checkout "$BASE_BRANCH" + +log "Fast-forwarding '$BASE_BRANCH' from '$BASE_REMOTE/$BASE_BRANCH'" +git merge --ff-only "$BASE_REMOTE/$BASE_BRANCH" + +log "Creating branch '$BRANCH_NAME'" +git checkout -b "$BRANCH_NAME" + +log "Running cargo update" +cargo update 2>&1 | tee "$CARGO_UPDATE_OUTPUT_FILE" + +if git diff --quiet; then + log "No dependency changes were produced by cargo update" + git checkout "$BASE_BRANCH" + git branch -D "$BRANCH_NAME" + exit 0 +fi + +if [[ "$RUN_PRE_COMMIT" == true ]]; then + log "Running pre-commit checks" + ./scripts/pre-commit.sh + PRE_COMMIT_SUMMARY="- run \`./scripts/pre-commit.sh\` successfully" +else + PRE_COMMIT_SUMMARY="- skip \`./scripts/pre-commit.sh\` by request" +fi + +{ + printf '%s\n\n' "$COMMIT_TITLE" + printf '%s\n' 'cargo update output:' + printf '%s\n' '```' + cat "$CARGO_UPDATE_OUTPUT_FILE" + printf '%s\n' '```' +} > "$COMMIT_MESSAGE_FILE" + +log "Creating commit" +git add -u +if [[ "$SIGN_COMMIT" == true ]]; then + git commit -S -F "$COMMIT_MESSAGE_FILE" +else + git commit -F "$COMMIT_MESSAGE_FILE" +fi + +if [[ -n "$PUSH_REMOTE" ]]; then + log "Pushing branch to '$PUSH_REMOTE'" + git push -u "$PUSH_REMOTE" "$BRANCH_NAME" +fi + +if [[ "$CREATE_PR" == true ]]; then + HEAD_OWNER=$(parse_github_owner_from_remote "$PUSH_REMOTE") + PR_TITLE=${PR_TITLE:-$COMMIT_TITLE} + + { + printf '%s\n' '## Summary' + printf '%s\n' "- run \`cargo update\`" + printf '%s\n' "- commit the resulting \`Cargo.lock\` changes" + printf '%s\n\n' "$PRE_COMMIT_SUMMARY" + printf '%s\n' '## cargo update output' + printf '%s\n' '```' + cat "$CARGO_UPDATE_OUTPUT_FILE" + printf '%s\n' '```' + } > "$PR_BODY_FILE" + + log "Creating pull request in '$REPOSITORY_SLUG'" + gh pr create \ + --repo "$REPOSITORY_SLUG" \ + --base "$BASE_BRANCH" \ + --head "$HEAD_OWNER:$BRANCH_NAME" \ + --title "$PR_TITLE" \ + --body-file "$PR_BODY_FILE" +fi + +log "Dependency update workflow completed" diff --git a/src/application/command_handlers/show/handler.rs b/src/application/command_handlers/show/handler.rs index 60c71d8cb..c6f48a988 100644 --- a/src/application/command_handlers/show/handler.rs +++ b/src/application/command_handlers/show/handler.rs @@ -28,9 +28,15 @@ use std::sync::Arc; use tracing::instrument; use super::errors::ShowCommandHandlerError; -use super::info::{EnvironmentInfo, GrafanaInfo, InfrastructureInfo, PrometheusInfo, ServiceInfo}; +use super::info::{ + DockerImagesInfo, EnvironmentInfo, GrafanaInfo, InfrastructureInfo, PrometheusInfo, ServiceInfo, +}; use crate::domain::environment::repository::EnvironmentRepository; use crate::domain::environment::state::AnyEnvironmentState; +use crate::domain::grafana::GrafanaConfig; +use crate::domain::mysql::MysqlServiceConfig; +use crate::domain::prometheus::PrometheusConfig; +use crate::domain::tracker::config::TrackerConfig; use crate::domain::EnvironmentName; /// Default SSH port when not specified @@ -121,7 +127,24 @@ impl ShowCommandHandler { let created_at = any_env.created_at(); let state_name = any_env.state_name().to_string(); - let mut info = EnvironmentInfo::new(name, state, provider, created_at, state_name); + let tracker_config = any_env.tracker_config(); + let docker_images = DockerImagesInfo::new( + TrackerConfig::docker_image().full_reference(), + if tracker_config.uses_mysql() { + Some(MysqlServiceConfig::docker_image().full_reference()) + } else { + None + }, + any_env + .prometheus_config() + .map(|_| PrometheusConfig::docker_image().full_reference()), + any_env + .grafana_config() + .map(|_| GrafanaConfig::docker_image().full_reference()), + ); + + let mut info = + EnvironmentInfo::new(name, state, provider, created_at, docker_images, state_name); // Add infrastructure info if instance IP is available if let Some(instance_ip) = any_env.instance_ip() { @@ -144,7 +167,6 @@ impl ShowCommandHandler { if Self::should_show_services(any_env.state_name()) { // Always compute from tracker config to show proper service information // including TLS domains, localhost hints, and HTTPS status - let tracker_config = any_env.tracker_config(); let grafana_config = any_env.grafana_config(); let services = ServiceInfo::from_tracker_config(tracker_config, instance_ip, grafana_config); diff --git a/src/application/command_handlers/show/info/docker_images.rs b/src/application/command_handlers/show/info/docker_images.rs new file mode 100644 index 000000000..0853618f5 --- /dev/null +++ b/src/application/command_handlers/show/info/docker_images.rs @@ -0,0 +1,38 @@ +use serde::Serialize; + +/// Docker image information for the deployment stack +/// +/// Contains the Docker image references for all services in the deployment. +/// Optional services (`MySQL`, Prometheus, Grafana) are `None` if not configured. +#[derive(Debug, Clone, Serialize)] +pub struct DockerImagesInfo { + /// Tracker Docker image reference (e.g. `torrust/tracker:develop`) + pub tracker: String, + + /// `MySQL` Docker image reference (e.g. `mysql:8.4`), present when `MySQL` is configured + pub mysql: Option<String>, + + /// Prometheus Docker image reference (e.g. `prom/prometheus:v3.11.2`), present when configured + pub prometheus: Option<String>, + + /// Grafana Docker image reference (e.g. `grafana/grafana:12.4.2`), present when configured + pub grafana: Option<String>, +} + +impl DockerImagesInfo { + /// Create a new `DockerImagesInfo` with the tracker image and optional service images + #[must_use] + pub fn new( + tracker: String, + mysql: Option<String>, + prometheus: Option<String>, + grafana: Option<String>, + ) -> Self { + Self { + tracker, + mysql, + prometheus, + grafana, + } + } +} diff --git a/src/application/command_handlers/show/info/mod.rs b/src/application/command_handlers/show/info/mod.rs index 96219ac22..d88a9be3a 100644 --- a/src/application/command_handlers/show/info/mod.rs +++ b/src/application/command_handlers/show/info/mod.rs @@ -7,10 +7,12 @@ //! # Module Structure //! //! Each service in the deployment stack has its own submodule: +//! - `docker_images`: Docker image references for all services //! - `tracker`: Tracker service information (UDP/HTTP trackers, API, health check) //! - `prometheus`: Prometheus metrics service information //! - `grafana`: Grafana visualization service information +mod docker_images; mod grafana; mod prometheus; mod tracker; @@ -20,6 +22,7 @@ use std::net::IpAddr; use chrono::{DateTime, Utc}; use serde::Serialize; +pub use self::docker_images::DockerImagesInfo; pub use self::grafana::GrafanaInfo; pub use self::prometheus::PrometheusInfo; pub use self::tracker::{LocalhostServiceInfo, ServiceInfo, TlsDomainInfo}; @@ -55,6 +58,9 @@ pub struct EnvironmentInfo { /// Grafana visualization service information, available for Released/Running states pub grafana: Option<GrafanaInfo>, + /// Docker image references for all services in the deployment stack + pub docker_images: DockerImagesInfo, + /// Internal state name (e.g., "created", "provisioned") for guidance generation pub state_name: String, } @@ -67,6 +73,7 @@ impl EnvironmentInfo { state: String, provider: String, created_at: DateTime<Utc>, + docker_images: DockerImagesInfo, state_name: String, ) -> Self { Self { @@ -78,6 +85,7 @@ impl EnvironmentInfo { services: None, prometheus: None, grafana: None, + docker_images, state_name, } } @@ -166,6 +174,10 @@ mod tests { use super::*; + fn test_docker_images() -> DockerImagesInfo { + DockerImagesInfo::new("torrust/tracker:develop".to_string(), None, None, None) + } + #[test] fn it_should_create_environment_info() { let created_at = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap(); @@ -174,6 +186,7 @@ mod tests { "Created".to_string(), "LXD".to_string(), created_at, + test_docker_images(), "Run 'provision' to create infrastructure.".to_string(), ); @@ -191,6 +204,7 @@ mod tests { "Provisioned".to_string(), "LXD".to_string(), created_at, + test_docker_images(), "Run 'configure' to set up the system.".to_string(), ) .with_infrastructure(InfrastructureInfo::new( diff --git a/src/application/command_handlers/show/mod.rs b/src/application/command_handlers/show/mod.rs index ae202d46c..983b4d98b 100644 --- a/src/application/command_handlers/show/mod.rs +++ b/src/application/command_handlers/show/mod.rs @@ -45,6 +45,7 @@ mod tests; // Re-export main types for convenience pub use errors::ShowCommandHandlerError; pub use handler::ShowCommandHandler; +pub use info::DockerImagesInfo; pub use info::EnvironmentInfo; pub use info::GrafanaInfo; pub use info::InfrastructureInfo; diff --git a/src/bin/e2e_complete_workflow_tests.rs b/src/bin/e2e_complete_workflow_tests.rs index e75517eb1..9c27f2845 100644 --- a/src/bin/e2e_complete_workflow_tests.rs +++ b/src/bin/e2e_complete_workflow_tests.rs @@ -48,7 +48,7 @@ use anyhow::Result; use clap::Parser; use std::time::Instant; -use torrust_dependency_installer::Dependency; +use torrust_tracker_deployer_dependency_installer::Dependency; use tracing::{error, info}; use torrust_tracker_deployer_lib::bootstrap::logging::{LogFormat, LogOutput, LoggingBuilder}; diff --git a/src/bin/e2e_deployment_workflow_tests.rs b/src/bin/e2e_deployment_workflow_tests.rs index 21d7ce072..85f26b3b5 100644 --- a/src/bin/e2e_deployment_workflow_tests.rs +++ b/src/bin/e2e_deployment_workflow_tests.rs @@ -57,7 +57,7 @@ use std::time::Instant; use anyhow::{Context, Result}; use clap::Parser; -use torrust_dependency_installer::Dependency; +use torrust_tracker_deployer_dependency_installer::Dependency; use tracing::{error, info}; use torrust_tracker_deployer_lib::adapters::ssh::SshCredentials; diff --git a/src/bin/e2e_infrastructure_lifecycle_tests.rs b/src/bin/e2e_infrastructure_lifecycle_tests.rs index d9ba3cc6f..3c3ffa459 100644 --- a/src/bin/e2e_infrastructure_lifecycle_tests.rs +++ b/src/bin/e2e_infrastructure_lifecycle_tests.rs @@ -41,7 +41,7 @@ use anyhow::Result; use clap::Parser; use std::time::Instant; -use torrust_dependency_installer::Dependency; +use torrust_tracker_deployer_dependency_installer::Dependency; use tracing::{error, info}; use torrust_tracker_deployer_lib::bootstrap::logging::{LogFormat, LogOutput, LoggingBuilder}; diff --git a/src/bootstrap/help.rs b/src/bootstrap/help.rs index 8d4d51587..7249a120b 100644 --- a/src/bootstrap/help.rs +++ b/src/bootstrap/help.rs @@ -44,7 +44,7 @@ pub fn display_getting_started() { println!("📋 Quick Start:"); println!(" 1. Check dependencies:"); println!( - " cargo run --package torrust-dependency-installer --bin dependency-installer check" + " cargo run --package torrust-tracker-deployer-dependency-installer --bin dependency-installer check" ); println!(); println!(" 2. Create and deploy an environment:"); diff --git a/src/bootstrap/sdk.rs b/src/bootstrap/sdk.rs index f0134b404..b97b1f4a3 100644 --- a/src/bootstrap/sdk.rs +++ b/src/bootstrap/sdk.rs @@ -14,7 +14,7 @@ use std::time::Duration; use crate::application::traits::RepositoryProvider; use crate::infrastructure::persistence::file_repository_factory::FileRepositoryFactory; use crate::shared::SystemClock; -use torrust_deployer_types::Clock; +use torrust_tracker_deployer_types::Clock; /// The default file-lock timeout used by the SDK when no custom value is provided. pub const DEFAULT_SDK_LOCK_TIMEOUT: Duration = Duration::from_secs(30); diff --git a/src/domain/environment/name.rs b/src/domain/environment/name.rs index 98be05007..0907a4f46 100644 --- a/src/domain/environment/name.rs +++ b/src/domain/environment/name.rs @@ -1,2 +1,2 @@ -// Backward compatibility re-export — types moved to torrust-deployer-types. -pub use torrust_deployer_types::environment_name::*; +// Backward compatibility re-export — types moved to torrust-tracker-deployer-types. +pub use torrust_tracker_deployer_types::environment_name::*; diff --git a/src/domain/grafana/config.rs b/src/domain/grafana/config.rs index 0043a36f8..760e96f3b 100644 --- a/src/domain/grafana/config.rs +++ b/src/domain/grafana/config.rs @@ -5,9 +5,16 @@ use serde::{Deserialize, Serialize}; use crate::domain::topology::{ EnabledServices, Network, NetworkDerivation, PortBinding, PortDerivation, Service, }; +use crate::shared::docker_image::DockerImage; use crate::shared::domain_name::DomainName; use crate::shared::secrets::Password; +/// Docker image repository for the Grafana container +pub const GRAFANA_DOCKER_IMAGE_REPOSITORY: &str = "grafana/grafana"; + +/// Docker image tag for the Grafana container +pub const GRAFANA_DOCKER_IMAGE_TAG: &str = "13.0.0"; + /// Grafana metrics visualization configuration /// /// Configures Grafana service for displaying tracker metrics. @@ -106,6 +113,23 @@ impl GrafanaConfig { pub fn use_tls_proxy(&self) -> bool { self.use_tls_proxy } + + /// Returns the Docker image used for the Grafana service. + /// + /// This is a pinned constant — not user-configurable. + /// + /// # Examples + /// + /// ```rust + /// use torrust_tracker_deployer_lib::domain::grafana::GrafanaConfig; + /// + /// let image = GrafanaConfig::docker_image(); + /// assert_eq!(image.full_reference(), "grafana/grafana:13.0.0"); + /// ``` + #[must_use] + pub fn docker_image() -> DockerImage { + DockerImage::new(GRAFANA_DOCKER_IMAGE_REPOSITORY, GRAFANA_DOCKER_IMAGE_TAG) + } } impl Default for GrafanaConfig { diff --git a/src/domain/mysql/config.rs b/src/domain/mysql/config.rs index a9e5ebece..a24e0097c 100644 --- a/src/domain/mysql/config.rs +++ b/src/domain/mysql/config.rs @@ -28,6 +28,13 @@ use serde::{Deserialize, Serialize}; use crate::domain::topology::{ EnabledServices, Network, NetworkDerivation, PortBinding, PortDerivation, }; +use crate::shared::docker_image::DockerImage; + +/// Docker image repository for the `MySQL` container +pub const MYSQL_DOCKER_IMAGE_REPOSITORY: &str = "mysql"; + +/// Docker image tag for the `MySQL` container +pub const MYSQL_DOCKER_IMAGE_TAG: &str = "8.4"; /// `MySQL` database service configuration for Docker Compose topology /// @@ -69,6 +76,23 @@ impl MysqlServiceConfig { pub const fn new() -> Self { Self {} } + + /// Returns the Docker image used for the `MySQL` service. + /// + /// This is a pinned constant — not user-configurable. + /// + /// # Examples + /// + /// ```rust + /// use torrust_tracker_deployer_lib::domain::mysql::MysqlServiceConfig; + /// + /// let image = MysqlServiceConfig::docker_image(); + /// assert_eq!(image.full_reference(), "mysql:8.4"); + /// ``` + #[must_use] + pub fn docker_image() -> DockerImage { + DockerImage::new(MYSQL_DOCKER_IMAGE_REPOSITORY, MYSQL_DOCKER_IMAGE_TAG) + } } impl PortDerivation for MysqlServiceConfig { diff --git a/src/domain/prometheus/config.rs b/src/domain/prometheus/config.rs index ff6ce35c2..6b7d19320 100644 --- a/src/domain/prometheus/config.rs +++ b/src/domain/prometheus/config.rs @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; use crate::domain::topology::{ EnabledServices, Network, NetworkDerivation, PortBinding, PortDerivation, Service, }; +use crate::shared::docker_image::DockerImage; /// Default scrape interval in seconds /// @@ -16,6 +17,12 @@ use crate::domain::topology::{ /// monitoring frequency with resource usage. const DEFAULT_SCRAPE_INTERVAL_SECS: u32 = 15; +/// Docker image repository for the Prometheus container +pub const PROMETHEUS_DOCKER_IMAGE_REPOSITORY: &str = "prom/prometheus"; + +/// Docker image tag for the Prometheus container +pub const PROMETHEUS_DOCKER_IMAGE_TAG: &str = "v3.11.2"; + /// Prometheus metrics collection configuration /// /// Configures how Prometheus scrapes metrics from the tracker. @@ -77,6 +84,26 @@ impl PrometheusConfig { pub fn scrape_interval_in_secs(&self) -> u32 { self.scrape_interval_in_secs.get() } + + /// Returns the Docker image used for the Prometheus service. + /// + /// This is a pinned constant — not user-configurable. + /// + /// # Examples + /// + /// ```rust + /// use torrust_tracker_deployer_lib::domain::prometheus::PrometheusConfig; + /// + /// let image = PrometheusConfig::docker_image(); + /// assert_eq!(image.full_reference(), "prom/prometheus:v3.11.2"); + /// ``` + #[must_use] + pub fn docker_image() -> DockerImage { + DockerImage::new( + PROMETHEUS_DOCKER_IMAGE_REPOSITORY, + PROMETHEUS_DOCKER_IMAGE_TAG, + ) + } } impl Default for PrometheusConfig { diff --git a/src/domain/tracker/config/core/database/mod.rs b/src/domain/tracker/config/core/database/mod.rs index 4b6d14b44..3c336fe01 100644 --- a/src/domain/tracker/config/core/database/mod.rs +++ b/src/domain/tracker/config/core/database/mod.rs @@ -13,6 +13,9 @@ use serde::{Deserialize, Serialize}; +use crate::domain::mysql::MysqlServiceConfig; +use crate::shared::docker_image::DockerImage; + mod mysql; mod sqlite; @@ -93,6 +96,33 @@ impl DatabaseConfig { Self::Mysql(config) => config.database_name(), } } + + /// Returns the Docker image for the database service container, if applicable. + /// + /// - `Mysql` variant returns the `MySQL` docker image + /// - `Sqlite` returns `None` — `SQLite` runs in-process; no container is needed + /// + /// # Examples + /// + /// ```rust + /// use torrust_tracker_deployer_lib::domain::tracker::{DatabaseConfig, SqliteConfig, MysqlConfig}; + /// use torrust_tracker_deployer_lib::shared::Password; + /// + /// let sqlite = DatabaseConfig::Sqlite(SqliteConfig::new("tracker.db").unwrap()); + /// assert!(sqlite.docker_image().is_none()); + /// + /// let mysql = DatabaseConfig::Mysql( + /// MysqlConfig::new("localhost", 3306, "tracker", "user", "pass".to_string().into(), "root_pass".to_string().into()).unwrap() + /// ); + /// assert_eq!(mysql.docker_image().unwrap().full_reference(), "mysql:8.4"); + /// ``` + #[must_use] + pub fn docker_image(&self) -> Option<DockerImage> { + match self { + Self::Mysql(_) => Some(MysqlServiceConfig::docker_image()), + Self::Sqlite(_) => None, + } + } } #[cfg(test)] diff --git a/src/domain/tracker/config/mod.rs b/src/domain/tracker/config/mod.rs index be1efcd83..405d478fc 100644 --- a/src/domain/tracker/config/mod.rs +++ b/src/domain/tracker/config/mod.rs @@ -13,8 +13,15 @@ use super::{BindingAddress, Protocol}; use crate::domain::topology::{ EnabledServices, Network, NetworkDerivation, PortBinding, PortDerivation, Service, }; +use crate::shared::docker_image::DockerImage; use crate::shared::DomainName; +/// Docker image repository for the Torrust Tracker container +pub const TRACKER_DOCKER_IMAGE_REPOSITORY: &str = "torrust/tracker"; + +/// Docker image tag for the Torrust Tracker container +pub const TRACKER_DOCKER_IMAGE_TAG: &str = "develop"; + mod core; mod health_check_api; mod http; @@ -330,6 +337,23 @@ impl TrackerConfig { matches!(self.core.database(), DatabaseConfig::Mysql(_)) } + /// Returns the Docker image used for the tracker service. + /// + /// This is a pinned constant — not user-configurable. + /// + /// # Examples + /// + /// ```rust + /// use torrust_tracker_deployer_lib::domain::tracker::TrackerConfig; + /// + /// let image = TrackerConfig::docker_image(); + /// assert_eq!(image.full_reference(), "torrust/tracker:develop"); + /// ``` + #[must_use] + pub fn docker_image() -> DockerImage { + DockerImage::new(TRACKER_DOCKER_IMAGE_REPOSITORY, TRACKER_DOCKER_IMAGE_TAG) + } + /// Checks for socket address conflicts /// /// Validates that no two services using the same protocol attempt to bind diff --git a/src/infrastructure/templating/docker_compose/template/renderer/docker_compose.rs b/src/infrastructure/templating/docker_compose/template/renderer/docker_compose.rs index 970ec758e..f24c79d0e 100644 --- a/src/infrastructure/templating/docker_compose/template/renderer/docker_compose.rs +++ b/src/infrastructure/templating/docker_compose/template/renderer/docker_compose.rs @@ -407,8 +407,8 @@ mod tests { "Rendered output should contain prometheus service" ); assert!( - rendered_content.contains("image: prom/prometheus:v3.5.0"), - "Should use Prometheus v3.5.0 image" + rendered_content.contains("image: prom/prometheus:v3.11.2"), + "Should use Prometheus v3.11.2 image" ); assert!( rendered_content.contains("container_name: prometheus"), @@ -466,7 +466,7 @@ mod tests { // Verify Prometheus service is NOT present assert!( - !rendered_content.contains("image: prom/prometheus:v3.5.0"), + !rendered_content.contains("image: prom/prometheus:v3.11.2"), "Should not contain Prometheus service when config absent" ); assert!( diff --git a/src/infrastructure/templating/docker_compose/template/wrappers/docker_compose/context/grafana.rs b/src/infrastructure/templating/docker_compose/template/wrappers/docker_compose/context/grafana.rs index 62eca609b..819f1c654 100644 --- a/src/infrastructure/templating/docker_compose/template/wrappers/docker_compose/context/grafana.rs +++ b/src/infrastructure/templating/docker_compose/template/wrappers/docker_compose/context/grafana.rs @@ -18,6 +18,9 @@ use super::service_topology::ServiceTopology; /// Uses `ServiceTopology` to share the common topology structure with other services. #[derive(Serialize, Debug, Clone)] pub struct GrafanaServiceContext { + /// Docker image reference (e.g. `grafana/grafana:13.0.0`) + pub image: String, + /// Service topology (ports and networks) /// /// Flattened for template compatibility - serializes ports/networks at top level. @@ -60,6 +63,7 @@ impl GrafanaServiceContext { format!("{scheme}://{}", domain.as_str()) }); Self { + image: GrafanaConfig::docker_image().full_reference(), topology: ServiceTopology::new(ports, networks), server_root_url, } diff --git a/src/infrastructure/templating/docker_compose/template/wrappers/docker_compose/context/mysql.rs b/src/infrastructure/templating/docker_compose/template/wrappers/docker_compose/context/mysql.rs index 3dd6b80ea..f090d7425 100644 --- a/src/infrastructure/templating/docker_compose/template/wrappers/docker_compose/context/mysql.rs +++ b/src/infrastructure/templating/docker_compose/template/wrappers/docker_compose/context/mysql.rs @@ -46,6 +46,9 @@ use super::service_topology::ServiceTopology; /// ``` #[derive(Debug, Clone, Serialize, PartialEq)] pub struct MysqlServiceContext { + /// Docker image reference (e.g. `mysql:8.4`) + pub image: String, + /// Service topology (ports and networks) /// /// Flattened for template compatibility - serializes ports/networks at top level. @@ -73,6 +76,7 @@ impl MysqlServiceContext { let networks = config.derive_networks(enabled_services); Self { + image: DomainMysqlConfig::docker_image().full_reference(), topology: ServiceTopology::new(ports, networks), } } diff --git a/src/infrastructure/templating/docker_compose/template/wrappers/docker_compose/context/prometheus.rs b/src/infrastructure/templating/docker_compose/template/wrappers/docker_compose/context/prometheus.rs index 84a8d5377..01a88477d 100644 --- a/src/infrastructure/templating/docker_compose/template/wrappers/docker_compose/context/prometheus.rs +++ b/src/infrastructure/templating/docker_compose/template/wrappers/docker_compose/context/prometheus.rs @@ -18,6 +18,9 @@ use super::service_topology::ServiceTopology; /// Uses `ServiceTopology` to share the common topology structure with other services. #[derive(Serialize, Debug, Clone)] pub struct PrometheusServiceContext { + /// Docker image reference (e.g. `prom/prometheus:v3.11.2`) + pub image: String, + /// Service topology (ports and networks) /// /// Flattened for template compatibility - serializes ports/networks at top level. @@ -47,6 +50,7 @@ impl PrometheusServiceContext { .map(PortDefinition::from) .collect(); Self { + image: PrometheusConfig::docker_image().full_reference(), topology: ServiceTopology::new(ports, networks), } } diff --git a/src/infrastructure/templating/docker_compose/template/wrappers/docker_compose/context/tracker.rs b/src/infrastructure/templating/docker_compose/template/wrappers/docker_compose/context/tracker.rs index c638c3e11..c6f6c4833 100644 --- a/src/infrastructure/templating/docker_compose/template/wrappers/docker_compose/context/tracker.rs +++ b/src/infrastructure/templating/docker_compose/template/wrappers/docker_compose/context/tracker.rs @@ -18,6 +18,9 @@ use super::service_topology::ServiceTopology; /// Uses `ServiceTopology` to share the common topology structure with other services. #[derive(Serialize, Debug, Clone)] pub struct TrackerServiceContext { + /// Docker image reference (e.g. `torrust/tracker:develop`) + pub image: String, + /// Service topology (ports and networks) /// /// Flattened for template compatibility - serializes ports/networks at top level. @@ -45,6 +48,7 @@ impl TrackerServiceContext { .collect(); Self { + image: TrackerConfig::docker_image().full_reference(), topology: ServiceTopology::new(ports, networks), } } diff --git a/src/presentation/cli/views/commands/show/view_data/mod.rs b/src/presentation/cli/views/commands/show/view_data/mod.rs index 53ca21b5d..b1492d6d0 100644 --- a/src/presentation/cli/views/commands/show/view_data/mod.rs +++ b/src/presentation/cli/views/commands/show/view_data/mod.rs @@ -1,6 +1,6 @@ pub mod show_details; pub use show_details::{ - EnvironmentInfo, GrafanaInfo, InfrastructureInfo, LocalhostServiceInfo, PrometheusInfo, - ServiceInfo, TlsDomainInfo, + DockerImagesInfo, EnvironmentInfo, GrafanaInfo, InfrastructureInfo, LocalhostServiceInfo, + PrometheusInfo, ServiceInfo, TlsDomainInfo, }; diff --git a/src/presentation/cli/views/commands/show/view_data/show_details.rs b/src/presentation/cli/views/commands/show/view_data/show_details.rs index a30be7bf7..800f274a4 100644 --- a/src/presentation/cli/views/commands/show/view_data/show_details.rs +++ b/src/presentation/cli/views/commands/show/view_data/show_details.rs @@ -4,6 +4,7 @@ //! The presentation layer references this module rather than importing directly //! from the application layer. +pub use crate::application::command_handlers::show::info::DockerImagesInfo; pub use crate::application::command_handlers::show::info::EnvironmentInfo; pub use crate::application::command_handlers::show::info::GrafanaInfo; pub use crate::application::command_handlers::show::info::InfrastructureInfo; diff --git a/src/presentation/cli/views/commands/show/views/json_view.rs b/src/presentation/cli/views/commands/show/views/json_view.rs index 1fb582818..6eef2ffec 100644 --- a/src/presentation/cli/views/commands/show/views/json_view.rs +++ b/src/presentation/cli/views/commands/show/views/json_view.rs @@ -23,16 +23,18 @@ use crate::presentation::cli::views::{Render, ViewRenderError}; /// /// ```rust /// # use torrust_tracker_deployer_lib::presentation::cli::views::Render; -/// use torrust_tracker_deployer_lib::application::command_handlers::show::info::EnvironmentInfo; +/// use torrust_tracker_deployer_lib::application::command_handlers::show::info::{DockerImagesInfo, EnvironmentInfo}; /// use torrust_tracker_deployer_lib::presentation::cli::views::commands::show::JsonView; /// use chrono::{TimeZone, Utc}; /// /// let created_at = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap(); +/// let docker_images = DockerImagesInfo::new("torrust/tracker:develop".to_string(), None, None, None); /// let info = EnvironmentInfo::new( /// "my-env".to_string(), /// "Created".to_string(), /// "LXD".to_string(), /// created_at, +/// docker_images, /// "created".to_string(), /// ); /// @@ -57,9 +59,14 @@ mod tests { use chrono::{TimeZone, Utc}; use super::*; + use crate::presentation::cli::views::commands::show::view_data::DockerImagesInfo; use crate::presentation::cli::views::commands::show::view_data::InfrastructureInfo; use crate::presentation::cli::views::Render; + fn test_docker_images() -> DockerImagesInfo { + DockerImagesInfo::new("torrust/tracker:develop".to_string(), None, None, None) + } + #[test] fn it_should_render_created_state_as_json() { let created_at = Utc.with_ymd_and_hms(2026, 2, 16, 10, 0, 0).unwrap(); @@ -68,6 +75,7 @@ mod tests { "Created".to_string(), "LXD".to_string(), created_at, + test_docker_images(), "created".to_string(), ); @@ -102,6 +110,7 @@ mod tests { "Provisioned".to_string(), "LXD".to_string(), created_at, + test_docker_images(), "provisioned".to_string(), ) .with_infrastructure(InfrastructureInfo::new( @@ -134,6 +143,7 @@ mod tests { "Created".to_string(), "LXD".to_string(), created_at, + test_docker_images(), "created".to_string(), ); diff --git a/src/presentation/cli/views/commands/show/views/text_view.rs b/src/presentation/cli/views/commands/show/views/text_view.rs index a617a94db..eeebedf51 100644 --- a/src/presentation/cli/views/commands/show/views/text_view.rs +++ b/src/presentation/cli/views/commands/show/views/text_view.rs @@ -23,7 +23,9 @@ use super::next_step::NextStepGuidanceView; use super::prometheus::PrometheusView; use super::tracker_services::TrackerServicesView; -use crate::presentation::cli::views::commands::show::view_data::EnvironmentInfo; +use crate::presentation::cli::views::commands::show::view_data::{ + DockerImagesInfo, EnvironmentInfo, +}; use crate::presentation::cli::views::{Render, ViewRenderError}; /// View for rendering environment information @@ -43,16 +45,18 @@ use crate::presentation::cli::views::{Render, ViewRenderError}; /// /// ```rust /// # use torrust_tracker_deployer_lib::presentation::cli::views::Render; -/// use torrust_tracker_deployer_lib::application::command_handlers::show::info::EnvironmentInfo; +/// use torrust_tracker_deployer_lib::application::command_handlers::show::info::{DockerImagesInfo, EnvironmentInfo}; /// use torrust_tracker_deployer_lib::presentation::cli::views::commands::show::TextView; /// use chrono::{TimeZone, Utc}; /// /// let created_at = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap(); +/// let docker_images = DockerImagesInfo::new("torrust/tracker:develop".to_string(), None, None, None); /// let info = EnvironmentInfo::new( /// "my-env".to_string(), /// "Created".to_string(), /// "LXD".to_string(), /// created_at, +/// docker_images, /// "created".to_string(), /// ); /// @@ -94,6 +98,9 @@ impl Render<EnvironmentInfo> for TextView { lines.extend(GrafanaView::render(grafana)); } + // Docker images (always present) + lines.extend(Self::render_docker_images(&info.docker_images)); + // HTTPS hint with /etc/hosts (if TLS is configured) if let Some(ref services) = info.services { let instance_ip = info.infrastructure.as_ref().map(|i| i.instance_ip); @@ -107,6 +114,25 @@ impl Render<EnvironmentInfo> for TextView { } } +impl TextView { + fn render_docker_images(docker_images: &DockerImagesInfo) -> Vec<String> { + let mut lines = Vec::new(); + lines.push(String::new()); + lines.push("Docker Images:".to_string()); + lines.push(format!(" Tracker: {}", docker_images.tracker)); + if let Some(ref mysql) = docker_images.mysql { + lines.push(format!(" MySQL: {mysql}")); + } + if let Some(ref prometheus) = docker_images.prometheus { + lines.push(format!(" Prometheus: {prometheus}")); + } + if let Some(ref grafana) = docker_images.grafana { + lines.push(format!(" Grafana: {grafana}")); + } + lines + } +} + #[cfg(test)] mod tests { use std::net::{IpAddr, Ipv4Addr}; @@ -115,7 +141,7 @@ mod tests { use super::*; use crate::presentation::cli::views::commands::show::view_data::{ - InfrastructureInfo, ServiceInfo, TlsDomainInfo, + DockerImagesInfo, InfrastructureInfo, ServiceInfo, TlsDomainInfo, }; /// Helper to create a fixed test timestamp @@ -123,6 +149,10 @@ mod tests { Utc.with_ymd_and_hms(2025, 1, 7, 12, 30, 45).unwrap() } + fn test_docker_images() -> DockerImagesInfo { + DockerImagesInfo::new("torrust/tracker:develop".to_string(), None, None, None) + } + #[test] fn it_should_render_basic_environment_info() { let info = EnvironmentInfo::new( @@ -130,6 +160,7 @@ mod tests { "Created".to_string(), "LXD".to_string(), test_timestamp(), + test_docker_images(), "created".to_string(), ); @@ -149,6 +180,7 @@ mod tests { "Provisioned".to_string(), "LXD".to_string(), test_timestamp(), + test_docker_images(), "provisioned".to_string(), ) .with_infrastructure(InfrastructureInfo::new( @@ -176,6 +208,7 @@ mod tests { "Running".to_string(), "LXD".to_string(), test_timestamp(), + test_docker_images(), "running".to_string(), ) .with_services(ServiceInfo::new( @@ -212,6 +245,7 @@ mod tests { "Running".to_string(), "LXD".to_string(), test_timestamp(), + test_docker_images(), "running".to_string(), ) .with_infrastructure(InfrastructureInfo::new( @@ -253,6 +287,7 @@ mod tests { "Running".to_string(), "LXD".to_string(), test_timestamp(), + test_docker_images(), "running".to_string(), ) .with_infrastructure(InfrastructureInfo::new( @@ -318,6 +353,7 @@ mod tests { "Provisioned".to_string(), "LXD".to_string(), test_timestamp(), + test_docker_images(), "provisioned".to_string(), ) .with_infrastructure(InfrastructureInfo::new( diff --git a/src/shared/clock.rs b/src/shared/clock.rs index e97a06724..2057af090 100644 --- a/src/shared/clock.rs +++ b/src/shared/clock.rs @@ -1,2 +1,2 @@ -// Backward compatibility re-export — types moved to torrust-deployer-types. -pub use torrust_deployer_types::clock::*; +// Backward compatibility re-export — types moved to torrust-tracker-deployer-types. +pub use torrust_tracker_deployer_types::clock::*; diff --git a/src/shared/docker_image.rs b/src/shared/docker_image.rs new file mode 100644 index 000000000..37825313c --- /dev/null +++ b/src/shared/docker_image.rs @@ -0,0 +1,145 @@ +//! Docker image reference value object +//! +//! This module provides a strongly-typed Docker image reference that combines +//! a repository name with a tag. It is used to represent Docker images in +//! service configurations and templates. +//! +//! # Design Decision +//! +//! Docker image versions are **not user-configurable** — they are pinned as +//! constants in the code to ensure compatibility between the deployer and the +//! images it uses. Exposing them through domain configs (rather than hardcoding +//! in templates) gives us: +//! +//! - A **single source of truth** for each image version +//! - The ability to **inspect images via the `show` command** +//! - Automatic propagation of version changes to both templates and CI scanning +//! +//! # Examples +//! +//! ```rust +//! use torrust_tracker_deployer_lib::shared::docker_image::DockerImage; +//! +//! let image = DockerImage::new("torrust/tracker", "develop"); +//! assert_eq!(image.full_reference(), "torrust/tracker:develop"); +//! assert_eq!(image.repository(), "torrust/tracker"); +//! assert_eq!(image.tag(), "develop"); +//! ``` + +use std::fmt; + +use serde::{Deserialize, Serialize}; + +/// Docker image reference with repository and tag +/// +/// Represents an image reference of the form `repository:tag`, +/// e.g. `torrust/tracker:develop` or `mysql:8.4`. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct DockerImage { + repository: String, + tag: String, +} + +impl DockerImage { + /// Creates a new Docker image reference + /// + /// # Examples + /// + /// ```rust + /// use torrust_tracker_deployer_lib::shared::docker_image::DockerImage; + /// + /// let image = DockerImage::new("torrust/tracker", "develop"); + /// assert_eq!(image.repository(), "torrust/tracker"); + /// assert_eq!(image.tag(), "develop"); + /// ``` + #[must_use] + pub fn new(repository: impl Into<String>, tag: impl Into<String>) -> Self { + Self { + repository: repository.into(), + tag: tag.into(), + } + } + + /// Returns the repository name (e.g. `"torrust/tracker"`) + #[must_use] + pub fn repository(&self) -> &str { + &self.repository + } + + /// Returns the image tag (e.g. `"develop"` or `"8.4"`) + #[must_use] + pub fn tag(&self) -> &str { + &self.tag + } + + /// Returns the full image reference as `repository:tag` + /// + /// # Examples + /// + /// ```rust + /// use torrust_tracker_deployer_lib::shared::docker_image::DockerImage; + /// + /// let image = DockerImage::new("mysql", "8.4"); + /// assert_eq!(image.full_reference(), "mysql:8.4"); + /// ``` + #[must_use] + pub fn full_reference(&self) -> String { + format!("{}:{}", self.repository, self.tag) + } +} + +impl fmt::Display for DockerImage { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}", self.repository, self.tag) + } +} + +impl From<(&str, &str)> for DockerImage { + fn from((repository, tag): (&str, &str)) -> Self { + Self::new(repository, tag) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_should_create_docker_image_with_repository_and_tag() { + let image = DockerImage::new("torrust/tracker", "develop"); + + assert_eq!(image.repository(), "torrust/tracker"); + assert_eq!(image.tag(), "develop"); + } + + #[test] + fn it_should_return_full_reference_as_repository_colon_tag() { + let image = DockerImage::new("torrust/tracker", "develop"); + + assert_eq!(image.full_reference(), "torrust/tracker:develop"); + } + + #[test] + fn it_should_display_as_full_reference() { + let image = DockerImage::new("mysql", "8.4"); + + assert_eq!(format!("{image}"), "mysql:8.4"); + } + + #[test] + fn it_should_create_from_str_tuple() { + let image = DockerImage::from(("prom/prometheus", "v3.5.1")); + + assert_eq!(image.full_reference(), "prom/prometheus:v3.5.1"); + } + + #[test] + fn it_should_implement_equality() { + let a = DockerImage::new("grafana/grafana", "12.4.2"); + let b = DockerImage::new("grafana/grafana", "12.4.2"); + let c = DockerImage::new("grafana/grafana", "11.4.0"); + + assert_eq!(a, b); + assert_ne!(a, c); + } +} diff --git a/src/shared/domain_name.rs b/src/shared/domain_name.rs index 5a975b979..38de2751d 100644 --- a/src/shared/domain_name.rs +++ b/src/shared/domain_name.rs @@ -1,2 +1,2 @@ -// Backward compatibility re-export — types moved to torrust-deployer-types. -pub use torrust_deployer_types::domain_name::*; +// Backward compatibility re-export — types moved to torrust-tracker-deployer-types. +pub use torrust_tracker_deployer_types::domain_name::*; diff --git a/src/shared/email.rs b/src/shared/email.rs index 7c2cef2e2..4d6c049a6 100644 --- a/src/shared/email.rs +++ b/src/shared/email.rs @@ -1,2 +1,2 @@ -// Backward compatibility re-export — types moved to torrust-deployer-types. -pub use torrust_deployer_types::email::*; +// Backward compatibility re-export — types moved to torrust-tracker-deployer-types. +pub use torrust_tracker_deployer_types::email::*; diff --git a/src/shared/error/kind.rs b/src/shared/error/kind.rs index 7b6a2018d..78aef4b36 100644 --- a/src/shared/error/kind.rs +++ b/src/shared/error/kind.rs @@ -1,2 +1,2 @@ -// Backward compatibility re-export — types moved to torrust-deployer-types. -pub use torrust_deployer_types::error::kind::*; +// Backward compatibility re-export — types moved to torrust-tracker-deployer-types. +pub use torrust_tracker_deployer_types::error::kind::*; diff --git a/src/shared/error/traceable.rs b/src/shared/error/traceable.rs index a3c9a3d90..d773622ca 100644 --- a/src/shared/error/traceable.rs +++ b/src/shared/error/traceable.rs @@ -1,2 +1,2 @@ -// Backward compatibility re-export — types moved to torrust-deployer-types. -pub use torrust_deployer_types::error::traceable::*; +// Backward compatibility re-export — types moved to torrust-tracker-deployer-types. +pub use torrust_tracker_deployer_types::error::traceable::*; diff --git a/src/shared/mod.rs b/src/shared/mod.rs index 337115faa..2e0855576 100644 --- a/src/shared/mod.rs +++ b/src/shared/mod.rs @@ -6,6 +6,7 @@ pub mod clock; pub mod command; +pub mod docker_image; pub mod domain_name; pub mod email; pub mod error; diff --git a/src/shared/secrets/api_token.rs b/src/shared/secrets/api_token.rs index 0575d6720..f6495328e 100644 --- a/src/shared/secrets/api_token.rs +++ b/src/shared/secrets/api_token.rs @@ -1,2 +1,2 @@ -// Backward compatibility re-export — types moved to torrust-deployer-types. -pub use torrust_deployer_types::secrets::{ApiToken, PlainApiToken}; +// Backward compatibility re-export — types moved to torrust-tracker-deployer-types. +pub use torrust_tracker_deployer_types::secrets::{ApiToken, PlainApiToken}; diff --git a/src/shared/secrets/password.rs b/src/shared/secrets/password.rs index 2cbded774..2f42fb2ca 100644 --- a/src/shared/secrets/password.rs +++ b/src/shared/secrets/password.rs @@ -1,2 +1,2 @@ -// Backward compatibility re-export — types moved to torrust-deployer-types. -pub use torrust_deployer_types::secrets::{Password, PlainPassword}; +// Backward compatibility re-export — types moved to torrust-tracker-deployer-types. +pub use torrust_tracker_deployer_types::secrets::{Password, PlainPassword}; diff --git a/src/shared/service_endpoint.rs b/src/shared/service_endpoint.rs index 11626ec49..b0d4bad71 100644 --- a/src/shared/service_endpoint.rs +++ b/src/shared/service_endpoint.rs @@ -1,2 +1,2 @@ -// Backward compatibility re-export — types moved to torrust-deployer-types. -pub use torrust_deployer_types::service_endpoint::*; +// Backward compatibility re-export — types moved to torrust-tracker-deployer-types. +pub use torrust_tracker_deployer_types::service_endpoint::*; diff --git a/src/shared/username.rs b/src/shared/username.rs index 4585b012c..5e0cca7a2 100644 --- a/src/shared/username.rs +++ b/src/shared/username.rs @@ -1,2 +1,2 @@ -// Backward compatibility re-export — types moved to torrust-deployer-types. -pub use torrust_deployer_types::username::*; +// Backward compatibility re-export — types moved to torrust-tracker-deployer-types. +pub use torrust_tracker_deployer_types::username::*; diff --git a/src/testing/e2e/mod.rs b/src/testing/e2e/mod.rs index 8fbc82974..9654331df 100644 --- a/src/testing/e2e/mod.rs +++ b/src/testing/e2e/mod.rs @@ -20,7 +20,7 @@ //! //! ## Dependency Verification //! -//! E2E test binaries use the `torrust-dependency-installer` package to verify +//! E2E test binaries use the `torrust-tracker-deployer-dependency-installer` package to verify //! required system dependencies are installed before running tests. pub mod container; diff --git a/src/testing/e2e/tasks/black_box/mod.rs b/src/testing/e2e/tasks/black_box/mod.rs index 41c1ab1da..56af6200d 100644 --- a/src/testing/e2e/tasks/black_box/mod.rs +++ b/src/testing/e2e/tasks/black_box/mod.rs @@ -23,7 +23,7 @@ //! E2eTestRunner, generate_environment_config, run_preflight_cleanup, //! verify_required_dependencies, //! }; -//! use torrust_dependency_installer::Dependency; +//! use torrust_tracker_deployer_dependency_installer::Dependency; //! //! // Setup tasks (before creating the test runner) //! verify_required_dependencies(&[Dependency::OpenTofu, Dependency::Ansible])?; diff --git a/src/testing/e2e/tasks/black_box/verify_dependencies.rs b/src/testing/e2e/tasks/black_box/verify_dependencies.rs index b3bc31890..1dbdeb9ea 100644 --- a/src/testing/e2e/tasks/black_box/verify_dependencies.rs +++ b/src/testing/e2e/tasks/black_box/verify_dependencies.rs @@ -7,7 +7,7 @@ //! //! ```rust,ignore //! use torrust_tracker_deployer_lib::testing::e2e::tasks::black_box::verify_required_dependencies; -//! use torrust_dependency_installer::Dependency; +//! use torrust_tracker_deployer_dependency_installer::Dependency; //! //! // Verify dependencies for provision tests (only Ansible needed) //! verify_required_dependencies(&[Dependency::Ansible])?; @@ -21,7 +21,7 @@ //! ``` use anyhow::Result; -use torrust_dependency_installer::{verify_dependencies, Dependency}; +use torrust_tracker_deployer_dependency_installer::{verify_dependencies, Dependency}; use tracing::error; /// Verify that all required dependencies are installed for E2E tests. @@ -42,7 +42,7 @@ use tracing::error; /// /// ```rust,ignore /// use torrust_tracker_deployer_lib::testing::e2e::black_box::tasks::verify_required_dependencies; -/// use torrust_dependency_installer::Dependency; +/// use torrust_tracker_deployer_dependency_installer::Dependency; /// /// // For provision-only tests /// verify_required_dependencies(&[Dependency::Ansible])?; diff --git a/templates/docker-compose/docker-compose.yml.tera b/templates/docker-compose/docker-compose.yml.tera index 526ef42c5..fa9a45309 100644 --- a/templates/docker-compose/docker-compose.yml.tera +++ b/templates/docker-compose/docker-compose.yml.tera @@ -52,7 +52,7 @@ services: # Placed first as it's the entry point for HTTPS traffic caddy: <<: *defaults - image: caddy:2.10 + image: caddy:2.11.2 container_name: caddy # NOTE: No UFW firewall rule needed for these ports! # Docker-published ports bypass iptables/UFW rules entirely. @@ -90,7 +90,7 @@ services: # Tracking issue: https://github.com/torrust/torrust-tracker-deployer/issues/TBD # Rationale: The develop tag is mutable and introduces deployment non-reproducibility. # Pinning to a stable release ensures predictable deployments and easier rollback. - image: torrust/tracker:develop + image: {{ tracker.image }} container_name: tracker {%- if mysql %} depends_on: @@ -126,7 +126,7 @@ services: prometheus: <<: *defaults - image: prom/prometheus:v3.5.0 + image: {{ prometheus.image }} container_name: prometheus {%- if prometheus.networks | length > 0 %} networks: @@ -158,7 +158,7 @@ services: grafana: <<: *defaults - image: grafana/grafana:12.3.1 + image: {{ grafana.image }} container_name: grafana {%- if grafana.networks | length > 0 %} networks: @@ -196,7 +196,7 @@ services: mysql: <<: *defaults - image: mysql:8.4 + image: {{ mysql.image }} container_name: mysql environment: - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} diff --git a/templates/tofu/hetzner/main.tf b/templates/tofu/hetzner/main.tf index b78486960..2f88f9cbb 100644 --- a/templates/tofu/hetzner/main.tf +++ b/templates/tofu/hetzner/main.tf @@ -107,7 +107,7 @@ variable "server_labels" { # Root SSH provides a recovery/debugging path. # # SECURITY NOTE: For production deployments, consider disabling root SSH access -# after verifying deployment succeeded. See docs/security/ssh-root-access-hetzner.md +# after verifying deployment succeeded. See docs/security/user-security/ssh-root-access-hetzner.md # # This key will appear in Hetzner Console → Security → SSH Keys. resource "hcloud_ssh_key" "torrust" { diff --git a/tests/e2e/create_command.rs b/tests/e2e/create_command.rs index aba536511..3d54ef091 100644 --- a/tests/e2e/create_command.rs +++ b/tests/e2e/create_command.rs @@ -22,7 +22,7 @@ use super::super::support::{process_runner, EnvironmentStateAssertions, TempWorkspace}; use anyhow::Result; -use torrust_dependency_installer::{verify_dependencies, Dependency}; +use torrust_tracker_deployer_dependency_installer::{verify_dependencies, Dependency}; use torrust_tracker_deployer_lib::testing::e2e::tasks::black_box::create_test_environment_config; /// Verify that all required dependencies are installed for create command E2E tests. diff --git a/tests/e2e/destroy_command.rs b/tests/e2e/destroy_command.rs index 1c434344d..1846898b8 100644 --- a/tests/e2e/destroy_command.rs +++ b/tests/e2e/destroy_command.rs @@ -20,7 +20,7 @@ use super::super::support::{process_runner, EnvironmentStateAssertions, TempWorkspace}; use anyhow::Result; -use torrust_dependency_installer::{verify_dependencies, Dependency}; +use torrust_tracker_deployer_dependency_installer::{verify_dependencies, Dependency}; use torrust_tracker_deployer_lib::testing::e2e::tasks::black_box::create_test_environment_config; /// Verify that all required dependencies are installed for destroy command E2E tests. diff --git a/tests/e2e/exists_command.rs b/tests/e2e/exists_command.rs index 8345b3931..9acbf823e 100644 --- a/tests/e2e/exists_command.rs +++ b/tests/e2e/exists_command.rs @@ -20,7 +20,7 @@ use super::super::support::{process_runner, EnvironmentStateAssertions, TempWorkspace}; use anyhow::Result; -use torrust_dependency_installer::{verify_dependencies, Dependency}; +use torrust_tracker_deployer_dependency_installer::{verify_dependencies, Dependency}; use torrust_tracker_deployer_lib::testing::e2e::tasks::black_box::create_test_environment_config; /// Verify that all required dependencies are installed for exists command E2E tests. diff --git a/tests/e2e/list_command.rs b/tests/e2e/list_command.rs index a7ed5d303..980129b9f 100644 --- a/tests/e2e/list_command.rs +++ b/tests/e2e/list_command.rs @@ -20,7 +20,7 @@ use super::super::support::{process_runner, EnvironmentStateAssertions, TempWorkspace}; use anyhow::Result; -use torrust_dependency_installer::{verify_dependencies, Dependency}; +use torrust_tracker_deployer_dependency_installer::{verify_dependencies, Dependency}; use torrust_tracker_deployer_lib::testing::e2e::tasks::black_box::create_test_environment_config; /// Verify that all required dependencies are installed for list command E2E tests. diff --git a/tests/e2e/purge_command.rs b/tests/e2e/purge_command.rs index 7db565998..853b40a1f 100644 --- a/tests/e2e/purge_command.rs +++ b/tests/e2e/purge_command.rs @@ -28,7 +28,7 @@ use super::super::support::{process_runner, EnvironmentStateAssertions, TempWorkspace}; use anyhow::Result; -use torrust_dependency_installer::{verify_dependencies, Dependency}; +use torrust_tracker_deployer_dependency_installer::{verify_dependencies, Dependency}; use torrust_tracker_deployer_lib::testing::e2e::tasks::black_box::create_test_environment_config; /// Verify that all required dependencies are installed for purge command E2E tests. diff --git a/tests/e2e/render_command.rs b/tests/e2e/render_command.rs index 48c5505a7..825329fb2 100644 --- a/tests/e2e/render_command.rs +++ b/tests/e2e/render_command.rs @@ -32,7 +32,7 @@ use super::super::support::{process_runner, EnvironmentStateAssertions, TempWorkspace}; use anyhow::Result; -use torrust_dependency_installer::{verify_dependencies, Dependency}; +use torrust_tracker_deployer_dependency_installer::{verify_dependencies, Dependency}; use torrust_tracker_deployer_lib::testing::e2e::tasks::black_box::create_test_environment_config; /// Verify that all required dependencies are installed for render command E2E tests. diff --git a/tests/e2e/show_command.rs b/tests/e2e/show_command.rs index 46e07f64b..0646c69f7 100644 --- a/tests/e2e/show_command.rs +++ b/tests/e2e/show_command.rs @@ -19,7 +19,7 @@ use super::super::support::{process_runner, EnvironmentStateAssertions, TempWorkspace}; use anyhow::Result; -use torrust_dependency_installer::{verify_dependencies, Dependency}; +use torrust_tracker_deployer_dependency_installer::{verify_dependencies, Dependency}; use torrust_tracker_deployer_lib::testing::e2e::tasks::black_box::create_test_environment_config; /// Verify that all required dependencies are installed for show command E2E tests. diff --git a/tests/e2e/validate_command.rs b/tests/e2e/validate_command.rs index 00c1bb307..4398c7b7c 100644 --- a/tests/e2e/validate_command.rs +++ b/tests/e2e/validate_command.rs @@ -23,7 +23,7 @@ use super::super::support::{process_runner, TempWorkspace}; use anyhow::Result; use std::fs; -use torrust_dependency_installer::{verify_dependencies, Dependency}; +use torrust_tracker_deployer_dependency_installer::{verify_dependencies, Dependency}; use torrust_tracker_deployer_lib::testing::e2e::tasks::black_box::create_test_environment_config; /// Verify that all required dependencies are installed for validate command E2E tests. diff --git a/tests/ssh_client/mod.rs b/tests/ssh_client/mod.rs index 4a7ccd99f..31b763ea3 100644 --- a/tests/ssh_client/mod.rs +++ b/tests/ssh_client/mod.rs @@ -18,8 +18,9 @@ use torrust_tracker_deployer_lib::adapters::ssh::{ }; use torrust_tracker_deployer_lib::shared::Username; use torrust_tracker_deployer_lib::testing::integration::ssh_server::{ - MockSshServerContainer, RealSshServerContainer, + print_docker_debug_info, MockSshServerContainer, RealSshServerContainer, }; +use torrust_tracker_deployer_lib::testing::network::PortChecker; /// SSH test constants following testing conventions /// @@ -104,9 +105,16 @@ impl SshTestBuilder { /// Build the SSH client with configured parameters pub fn build_client(self) -> SshClient { + let private_key_path = self.private_key_path.unwrap(); + let public_key_path = self.public_key_path.unwrap(); + + // CI runners may check out fixture keys with permissive file modes. + // OpenSSH rejects private keys that are readable by group/others. + normalize_private_key_permissions(&private_key_path); + let ssh_credentials = SshCredentials::new( - self.private_key_path.unwrap(), - self.public_key_path.unwrap(), + private_key_path, + public_key_path, Username::new(self.username.unwrap()).unwrap(), ); @@ -125,6 +133,30 @@ impl Default for SshTestBuilder { } } +#[cfg(unix)] +fn normalize_private_key_permissions(private_key_path: &std::path::Path) { + use std::fs; + use std::os::unix::fs::PermissionsExt; + + if let Ok(metadata) = fs::metadata(private_key_path) { + let perms = metadata.permissions(); + let mode = perms.mode(); + + // SSH requires private keys to be mode 0600 (owner read/write only). + // GitHub runners checkout files with permissive permissions (e.g., 0644), + // causing OpenSSH to silently reject the key. Normalize to 0600. + if mode & 0o077 != 0 { + let restricted = fs::Permissions::from_mode(0o600); + drop(fs::set_permissions(private_key_path, restricted)); + } + } +} + +#[cfg(not(unix))] +fn normalize_private_key_permissions(_private_key_path: &std::path::Path) { + // No-op on non-Unix platforms. +} + // ============================================================================= // SSH CONNECTIVITY HELPERS // ============================================================================= @@ -183,9 +215,25 @@ pub async fn assert_connectivity_succeeds_eventually(client: &SshClient, max_sec // Use the built-in wait_for_connectivity method let result = test_client.wait_for_connectivity().await; - assert!( - result.is_ok(), - "Expected connectivity to succeed eventually within {max_seconds}s, but got error: {:?}", - result.err() - ); + if let Err(error) = result { + let socket_addr = test_client.ssh_config().socket_addr; + let tcp_probe_result = PortChecker::new().is_port_open(socket_addr); + let one_shot_ssh_result = test_client.test_connectivity(); + let one_shot_execute_result = test_client.execute("echo 'SSH connected'"); + + eprintln!( + "\n=== SSH Connectivity Failure Diagnostics ===\n\ + target: {socket_addr}\n\ + retry_window_secs: {max_seconds}\n\ + raw_tcp_port_open: {tcp_probe_result:?}\n\ + one_shot_ssh_connectivity: {one_shot_ssh_result:?}\n\ + one_shot_ssh_execute: {one_shot_execute_result:?}\n" + ); + + print_docker_debug_info(socket_addr.port()); + + panic!( + "Expected connectivity to succeed eventually within {max_seconds}s, but got error: {error:?}" + ); + } }