container-doctor.sh's fix_tor_permissions() exact-matches mode '700', but Tor sets its hidden-service dirs to 2700 (setgid). Every 5-minute doctor run 'fixes' 2700->700 and restarts tor@default; Tor resets it and the cycle repeats, so Tor never builds a usable consensus/HSDir cache. Result on a live node: 'No more HSDir available to query', onion peers unresolvable, and with the FIPS direct path also timing out, mesh sends failed entirely. Planned as 01-20 in wave 1 so it ships in the same release. FIPS direct connect_fail is a separate concern, handed to FED-03's transport review. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
8.3 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 01-federation-mesh-hardening | 20 | execute | 1 |
|
false |
|
|
Diagnosed 2026-07-31 on a live node:
- The FIPS direct transport (Yggdrasil-style
fd..IPv6) times out withconnect_failfor peers other than the currently-connected tree peers, so every send falls back to Tor. - The Tor fallback then fails with
No more HSDir available to query— Tor cannot resolve any peer.onionaddress.
Root cause of (2): the container doctor restarts Tor every ~5 minutes,
forever. fix_tor_permissions() in scripts/container-doctor.sh treats any
mode other than the literal string 700 as broken:
perms=$(stat -c '%a' "$dir")
if [ "$perms" != "700" ]; then
chmod 700 "$dir"; fixed=true
fi
...
if $fixed; then systemctl restart tor@default; fi
Tor sets its own HiddenServiceDir to 2700 (setgid). So every run the doctor
sees 2700 != 700, "fixes" it, and restarts Tor; Tor comes back up and sets 2700
again; the timer fires 5 minutes later and the cycle repeats. Observed restarts
on the node: 13:07:56 → 13:13:14 → 13:18:39 → 13:23:57, each within a second of
an archipelago-doctor.timer firing. Tor never survives long enough to build a
usable consensus/HSDir cache, so onion lookups fail and the mesh's only remaining
transport dies with it.
2700 is not a defect — the setgid bit is harmless here and group/other access
is still fully denied, which is the property that actually matters.
Scope note: this plan fixes the restart loop only. The FIPS direct-transport
connect_fail (problem 1) is a separate concern and belongs with FED-03's
structured review of the transport/dial layer — record it there, do not attempt
both here.
<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>
Task 1: Stop the doctor from fighting Tor over the setgid bit Contained to one shell function; revert is a single-file change. scripts/container-doctor.sh - `scripts/container-doctor.sh` — `fix_tor_permissions()` (~lines 139-164) and how other `fix_*` functions signal "changed" vs "no drift" - `image-recipe/configs/archipelago-doctor.timer` — `OnUnitActiveSec=5min`, `RandomizedDelaySec=60`: this is the loop's clock - `core/archipelago/src/bootstrap.rs:24-31` — the script is embedded via `include_str!` and written to `/home/archipelago/archy/scripts/container-doctor.sh` on every boot, so nodes pick the fix up through the normal binary release Correct the permission predicate so it tests the property that matters — group and other have no access — instead of exact-matching one octal string. Compare the low three digits of `stat -c '%a'` (which omits leading zeros, so handle both `700` and `2700` forms), and treat the directory as correct when those are `700`. Only a genuinely permissive mode (any group or other bit set, e.g. `750`, `707`, `2755`) is a real defect worth fixing.Keep correcting real defects, and keep restarting Tor when a real fix is
applied — but add a **restart backoff** so a restart storm is impossible even
if some future condition makes the fix fire repeatedly: record the last
restart time (e.g. a timestamp file under `/var/lib/archipelago/`) and skip
the restart if one happened within the last 30 minutes, logging that it was
skipped. The current defect is being fixed at the predicate, but the backoff
is what makes the class of failure non-recurring.
Log clearly in both directions — when a directory is accepted as already
correct (at debug level, so a healthy node stays quiet) and when a real fix
is applied. The original bug was invisible precisely because "Fixed
permissions on ... (2700 -> 700)" looked like the doctor working correctly.
<success_criteria>
- Tor is no longer restarted every ~5 minutes by the doctor
- Tor keeps its consensus/HSDir cache, so
.onionpeers resolve and the mesh Tor fallback works again - Genuinely insecure hidden-service directory permissions are still corrected
- A restart backoff makes this class of failure non-recurring </success_criteria>