5.9 KiB
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 does not contact the control daemon, alter Syncthing/qBittorrent state, or print secret contents. It does create and remove two unique, zero-byte probe files while proving that the live client mount topology permits hard-link staging; no resource data or configuration is modified.
The script is 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.8 or newer. The script reads the mounted configuration through the client image, so it does not depend on the host Python TOML library.
- 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.
# 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 deployment checks. They include a disposable hard-link probe.
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:
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
- The future automatic
routes/<route-id>path resolves through Docker mounts to the same host path as its client-side mapping. - That future route path and qBittorrent content root use one client bind mount, so hard-link staging remains possible rather than silently falling back to a space-consuming copy.
- A real
link(2)operation between a unique zero-byte file in the qB root and one in the future automatic-route root. The probe verifies that both names refer to the same inode and removes them unconditionally. - Every existing
archive-control:*Syncthing folder beneathroutes/still has its local directory. This catches a route-root bind-mount migration that would otherwise hide an already configured folder and later fail a job withNo such file or directory. - 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, if syncthing.api_root is the Syncthing configuration root,
bind its routes subdirectory from a dedicated directory beneath qB's data
root and add a local_path_overrides mapping for that exact API path. This
prevents automatic route folders being created on a small configuration
filesystem while the client expects to hardlink from the qB data mount.
When converting an existing node, stop its client and Syncthing containers,
move each existing routes/<route-id> directory from the old Syncthing config
tree into the new qB-backed route-root directory, then recreate Syncthing and
run this preflight. Do not discard existing route directories: they can contain
route handshake state or an in-progress transfer namespace.
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.