diff --git a/deploy/production/README.md b/deploy/production/README.md index e981ed0..1c4cb19 100644 --- a/deploy/production/README.md +++ b/deploy/production/README.md @@ -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: diff --git a/deploy/production/x2/client.toml b/deploy/production/x2/client.toml index 0de0369..00f5e9e 100644 --- a/deploy/production/x2/client.toml +++ b/deploy/production/x2/client.toml @@ -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"] diff --git a/deploy/production/x2/compose.yaml b/deploy/production/x2/compose.yaml index fe05173..c0458ac 100644 --- a/deploy/production/x2/compose.yaml +++ b/deploy/production/x2/compose.yaml @@ -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 diff --git a/docs/deployment-and-usage.md b/docs/deployment-and-usage.md index 5abc136..24e2608 100644 --- a/docs/deployment-and-usage.md +++ b/docs/deployment-and-usage.md @@ -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 diff --git a/docs/service-apis.md b/docs/service-apis.md index c488305..44469d7 100644 --- a/docs/service-apis.md +++ b/docs/service-apis.md @@ -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