From 300210c17d726f9b9a92ec2acdfcf0be05c72879 Mon Sep 17 00:00:00 2001 From: Cabbagec Date: Mon, 3 Aug 2026 10:22:38 +0000 Subject: [PATCH] Document deployment preflight workflow --- README.md | 7 +++ docs/README.md | 5 +- docs/deployment-preflight.md | 106 +++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 docs/deployment-preflight.md diff --git a/README.md b/README.md index 0a71ca3..d2dac29 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,13 @@ four eager-mesh routes, transfer/eviction/unarchive workflows, and the adversarial matrix without live infrastructure or Telegram. See `e2e/README.md`. +## Deployment Preflight Tests + +Check deployment configuration immediately before bringing a new node online. +The reusable read-only preflight validates the deployed image's filesystem and +API view against its Docker bind mounts. See details in +[docs/deployment-preflight.md](docs/deployment-preflight.md). + The complete cross-project design, protocol, workflow, safety, deployment, Telegram UX, and testing documentation is published under [`docs/`](docs/). diff --git a/docs/README.md b/docs/README.md index c703cfc..9859357 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,8 +16,9 @@ credentials and host-specific details. 6. [Storage safety and recovery](storage-safety.md) 7. [qBittorrent and Syncthing integration](service-apis.md) 8. [Configuration, deployment, and usage](deployment-and-usage.md) -9. [Telegram UX](telegram-ux.md) -10. [Testing strategy](testing.md) +9. [Deployment preflight tests](deployment-preflight.md) +10. [Telegram UX](telegram-ux.md) +11. [Testing strategy](testing.md) The independent protobuf source of truth lives in the `cabbage/archive-control-proto` repository. Generated bindings in this diff --git a/docs/deployment-preflight.md b/docs/deployment-preflight.md new file mode 100644 index 0000000..ff0f8ca --- /dev/null +++ b/docs/deployment-preflight.md @@ -0,0 +1,106 @@ +# Deployment preflight tests + +Run the deployment preflight on every new node after its qBittorrent and +Syncthing stacks are running, but before starting the Archive Control daemon +for normal operation or creating any routes/jobs. It is read-only: it does not +contact the control daemon, alter Syncthing/qBittorrent state, or print secret +contents. + +The script is [`scripts/preflight-deployment.py`](../scripts/preflight-deployment.py). +It deliberately takes paths and container names as arguments rather than +assuming hostnames, compose project names, mount paths, credentials, or roles. +It can therefore be used unchanged on cache and archive nodes. + +## Requirements + +- Run it on the Docker host being checked. +- Use Python 3.11 or newer (the script uses the standard-library `tomllib`). +- Have Docker CLI access to the daemon. +- Have the checkout containing the script available on that host, or copy only + this script into the node's compose directory. +- Bring up qBittorrent and Syncthing first. Do not start the normal Archive + Control daemon yet. + +The disposable client container below uses the exact `archive-client` service +image, mounts, environment, and network specified by that node's compose file. +Consequently its qB/Syncthing API probes validate the same runtime environment +the daemon will use, not the host shell's network namespace. + +## New-node procedure + +The example uses generic names. Replace paths, compose project directories, +service names, and real container names for the node being deployed. + +```sh +# 1. Bring up only the local dependencies. +cd /srv/compose/syncthing-node && docker compose up -d +cd /srv/compose/qbittorrent-node && docker compose up -d + +# 2. Create a temporary client from the exact production image/config, without +# running the daemon or registering it with control. +cd /srv/compose/ArchiveControl-archive +docker compose run -d --no-deps --name archive-control-preflight \ + --entrypoint sleep archive-client infinity + +# 3. Discover the real dependency container names if needed. +docker ps --format '{{.Names}}' + +# 4. Run the read-only checks. +python3 /path/to/archive-clients/scripts/preflight-deployment.py \ + --client-config /srv/compose/ArchiveControl-archive/client.toml \ + --client-container archive-control-preflight \ + --syncthing-container syncthing-node-syncthing-1 \ + --qbittorrent-container qbittorrent-node-qbittorrent-1 + +# 5. Remove the temporary container, then start the real daemon only after a +# successful result. +docker rm -f archive-control-preflight +docker compose up -d archive-client +``` + +If the real client configuration is mounted somewhere other than the standard +`/etc/archive-control/client.toml`, pass that in-container location explicitly: + +```sh +python3 scripts/preflight-deployment.py ... \ + --container-config /custom/path/client.toml +``` + +## Required arguments + +| Argument | Meaning | +| --- | --- | +| `--client-config` | Host path to the exact `client.toml` that will be mounted into the daemon. | +| `--client-container` | Running disposable or already-running client container to inspect and probe. | +| `--syncthing-container` | Running Syncthing container for this node. | +| `--qbittorrent-container` | Running qBittorrent container for this node. | +| `--container-config` | Optional `client.toml` path inside the client container; defaults to `/etc/archive-control/client.toml`. | + +## What it checks + +- Each configured qBittorrent and Syncthing API root resolves through Docker + mounts to the same host path as the matching client local root. +- The client uses one bind mount for its qBittorrent and Syncthing roots, so + hard-link staging remains possible rather than silently falling back to a + space-consuming copy. +- The token, qB password, and Syncthing API-key files are non-empty regular + files with no group/world permissions. +- The client image can read its configuration and reports usable permissions, + sparse-file support, and filesystem capabilities for both roots. +- qBittorrent authentication/version compatibility and Syncthing + authentication/device identity are healthy from the client container. + +In particular, `syncthing.api_root` must refer to the Syncthing **shared-data +mount**, not its configuration volume. For example, if Syncthing mounts its +config at `/var/syncthing` and data at `/var/syncthing/Sync`, use +`api_root = "/var/syncthing/Sync"` when `local_root` maps that data directory. +This prevents a route folder being created in the config volume while the +client writes nonce files into the data volume. + +## Failure handling + +Treat a nonzero exit status as a deployment blocker. Correct the compose +mounts, `client.toml`, secret permissions, service credentials, or service +image/version that the message identifies, then rerun the same command. Do not +work around a mapping failure by creating route folders manually: the route +provisioner relies on those API/local path mappings being exact.