fix: resolve per-torrent qb save paths

This commit is contained in:
2026-08-14 11:56:06 +00:00
parent ae7175719b
commit a1bf3e4315
17 changed files with 453 additions and 55 deletions
+10
View File
@@ -120,6 +120,8 @@ username = "${QB_USER}"
password_file = "/run/secrets/qb_password"
api_root = "/downloads"
local_root = "/data/qb"
# Optional only when a nested qB path uses a different client-visible mount.
# local_path_overrides = { "/downloads/fast" = "/data/qb-fast" }
[syncthing]
endpoint = "http://syncthing:8384"
@@ -139,6 +141,14 @@ accounting. The override key is the normalized Syncthing folder path beneath
`api_root`, not its folder ID. See the production deployment README for the
required compose and override pattern.
qBittorrent content is resolved differently: every existing torrent keeps its
qB-reported `save_path`. The client maps that path beneath `qbittorrent.api_root`
to its own mount before a source, existing-target, permission, or eviction
operation. Thus `/downloads/Downloading` naturally maps below `/data/qb`;
there is no migration or per-resource configuration. Add a qB
`local_path_overrides` entry only when that nested API prefix is a separate
client mount.
The remaining node examples omit optional `[connection]`, `[jobs]`, and
`[backup]` tables and therefore use these same defaults; deployments may
override them per node.
+14 -1
View File
@@ -86,6 +86,9 @@ python3 scripts/preflight-deployment.py ... \
- 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.
- Every explicit `qbittorrent.local_path_overrides` entry maps the same host
path in the qBittorrent and client containers. This protects nested qB save
paths that use a dedicated bind mount.
- 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.
@@ -96,7 +99,8 @@ python3 scripts/preflight-deployment.py ... \
- 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.
sparse-file support, and filesystem capabilities for both primary roots and
every configured qBittorrent local-path override.
- qBittorrent authentication/version compatibility and Syncthing
authentication/device identity are healthy from the client container.
@@ -106,6 +110,15 @@ 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.
qBittorrent resources may use any `save_path` below `qbittorrent.api_root`.
At job preflight the client maps that qB API path to its local mount and uses
it as the resource root; data is never moved to fit Archive Control. A resource
outside the configured qB API root, or whose resolved directory is unavailable
or not a real directory in the client container, fails that job before staging
or eviction. Use `qbittorrent.local_path_overrides` only for a nested qB API
prefix backed by a distinct client mount; this preflight verifies the Docker
bind topology for each such override.
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
+7 -1
View File
@@ -14,6 +14,13 @@ client:
a regular file or an explicitly created directory;
5. verifies every operation remains beneath the local configured root.
For qBittorrent content, the root is resolved per resource: qB's authoritative
API-visible `save_path` is mapped beneath `qbittorrent.api_root` (or a more
specific configured local override) into the client namespace. This permits
existing nested save paths without moving data, but does not permit paths
outside the configured boundary. The resolved path remains local client state
and is never sent to the control daemon.
Sockets, devices, FIFOs, symlinks, and other special entries fail preflight.
Permission or ownership mismatch is fail-fast. Archive Control never changes
source ownership or mode to make a job pass.
@@ -150,4 +157,3 @@ Offline tooling provides list, verify, and restore. Restore requires stopped
daemon access, verifies the chosen backup, preserves the suspect database under
a timestamped name, installs the replacement atomically, and runs integrity and
schema checks before normal startup. Backups contain no configured secrets.