docs: require hardlink-safe route mount topology

This commit is contained in:
2026-07-26 04:26:23 +00:00
parent 90925fd321
commit a6da224f28
5 changed files with 43 additions and 9 deletions
+29 -4
View File
@@ -13,10 +13,35 @@ initial x1/x2/lithium topology.
`archive_control_token`, `qb_password`, and `syncthing_api_key`, each a
regular non-empty file with mode `0600`.
The Syncthing mounts intentionally reproduce each instance's `/var/syncthing`
layout, including nested data binds. This lets route discovery and route
provisioning use one safe API-to-local path mapping without altering an
existing Syncthing configuration.
## Hardlink-safe bind-mount topology
For an archive source, the qB content path and every Syncthing route used for
staging must resolve through the **same container mount**. Matching host
filesystem device IDs alone is insufficient: two separate Docker bind mounts
have different mount IDs and `link(2)` may return `EXDEV` across them. The
client deliberately treats that case as copy-only and performs a full payload
free-space check.
When a Syncthing route is physically nested below the qB root, mount the qB
root once and map the exact Syncthing API folder through it:
```yaml
volumes:
- /srv/downloads:/data/qb
- /srv/syncthing-config:/data/sync
```
```toml
[syncthing]
api_root = "/var/syncthing"
local_root = "/data/sync"
local_path_overrides = { "/var/syncthing/DownloadsSync-X2" = "/data/qb/Sync" }
```
Do **not** additionally mount `/srv/downloads/Sync` at a path beneath
`/data/sync`. The override is the authoritative mapping for that folder and
keeps qB source files and staging destinations in one mount namespace. Use
the folder ID returned by Syncthing's configuration as the override key.
Before starting a stack, validate it with:
+4
View File
@@ -39,4 +39,8 @@ endpoint = "http://127.0.0.1:8384"
api_key_file = "/run/secrets/syncthing_api_key"
api_root = "/var/syncthing"
local_root = "/data/sync"
# `DownloadsSync-X2` is ~/Downloads/Sync on the host, nested below the qB
# root. Resolve it through /data/qb rather than a second nested bind mount so
# source staging can hardlink it.
local_path_overrides = { "/var/syncthing/DownloadsSync-X2" = "/data/qb/Sync" }
advertised_addresses = ["dynamic"]
-1
View File
@@ -16,4 +16,3 @@ services:
- ./backups:/var/backups/archive-control
- /home/ubuntu/Downloads:/data/qb
- /home/ubuntu/compose/syncthing/st_home:/data/sync
- /home/ubuntu/Downloads/Sync:/data/sync/Downloads/Sync
+5 -1
View File
@@ -128,7 +128,11 @@ advertised_addresses = ["dynamic"]
When an existing Syncthing folder is physically nested in the qB data root,
use a `local_path_overrides` entry to map that exact Syncthing API path through
the same client bind mount. This enables hardlinks without creating two Docker
mount boundaries for the same host files.
mount boundaries for the same host files. Do not add a second bind mount for
the nested folder: Linux treats it as a distinct mount even when it has the
same `st_dev`, and the client correctly falls back to copy-only capacity
accounting. See the production deployment README for the required compose and
override pattern.
The remaining node examples omit optional `[connection]`, `[jobs]`, and
`[backup]` tables and therefore use these same defaults; deployments may
+5 -3
View File
@@ -89,9 +89,11 @@ left unchanged.
Syncthing may serialize a folder path relative to its home as `~/...`. The
client normalizes that notation beneath the configured API-visible sync root
before applying the API-to-local root mapping. Deployments must mirror
Syncthing's nested bind mounts into the client so the normalized API path and
the client filesystem path refer to the same bytes.
before applying the API-to-local root mapping. When that folder is nested
below qB's content root, an exact `local_path_overrides` entry must map it
through the qB bind mount. Do not mirror it as a second nested client bind
mount: it denotes the same host bytes but a distinct mount namespace boundary,
which prevents hardlink staging.
Provisioning uses idempotent device and folder configuration updates. Each
client receives its peer device ID and optional advertised addresses