Archipelago — open-source initial import
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
---
|
||||
phase: 01-federation-mesh-hardening
|
||||
plan: 20
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- scripts/container-doctor.sh
|
||||
autonomous: false
|
||||
requirements: [FED-09]
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "The container doctor no longer restarts Tor on every run — a node left alone shows Tor uptime growing past the doctor's 5-minute timer interval"
|
||||
- "A hidden-service directory at mode 2700 (Tor's own setting) is recognised as correct and triggers no chmod and no restart"
|
||||
- "A genuinely insecure hidden-service directory (group- or other-readable, e.g. 750 or 707) is still corrected"
|
||||
- "Even when a real permission fix IS applied, Tor cannot be restarted more than once per backoff window, so no future defect can reproduce a restart storm"
|
||||
- "Tor retains its consensus/HSDir cache long enough to resolve .onion addresses, so the mesh Tor fallback works"
|
||||
prohibitions:
|
||||
- "MUST NOT loosen hidden-service directory permissions — group and other access must remain denied"
|
||||
- "MUST NOT disable the doctor's other fixes or the timer itself"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Mesh sends fail entirely on affected nodes because both transports are down, and
|
||||
the second failure is self-inflicted.
|
||||
|
||||
Diagnosed 2026-07-31 on a live node:
|
||||
1. The FIPS direct transport (Yggdrasil-style `fd..` IPv6) times out with
|
||||
`connect_fail` for peers other than the currently-connected tree peers, so
|
||||
every send falls back to Tor.
|
||||
2. The Tor fallback then fails with `No more HSDir available to query` — Tor
|
||||
cannot resolve any peer `.onion` address.
|
||||
|
||||
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:
|
||||
|
||||
```sh
|
||||
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.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
||||
@$HOME/.claude/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Stop the doctor from fighting Tor over the setgid bit</name>
|
||||
<reversibility rating="reversible">Contained to one shell function; revert is a single-file change.</reversibility>
|
||||
<files>scripts/container-doctor.sh</files>
|
||||
<read_first>
|
||||
- `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
|
||||
</read_first>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>bash -n scripts/container-doctor.sh && sudo bash -c 'set -e; d=$(mktemp -d); mkdir -p "$d/hidden_service_test"; chmod 2700 "$d/hidden_service_test"; stat -c "%a" "$d/hidden_service_test"' </automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `bash -n scripts/container-doctor.sh` passes
|
||||
- A directory at mode `2700` is accepted: no chmod, no restart, `fixed` stays false
|
||||
- A directory at mode `750` or `707` is still corrected to deny group/other
|
||||
- A second real fix within the backoff window logs a skip instead of restarting Tor
|
||||
- The doctor's other fixes and the timer are untouched
|
||||
</acceptance_criteria>
|
||||
<done>The doctor recognises Tor's own 2700 as correct, so it stops restarting Tor every five minutes, and a backoff prevents any future restart storm.</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<name>Task 2: Confirm Tor stays up and mesh sends recover</name>
|
||||
<what-built>
|
||||
The doctor no longer mistakes Tor's setgid `2700` hidden-service directory for
|
||||
a permission defect, so it stops chmod-ing it and restarting Tor every ~5
|
||||
minutes. A restart backoff makes a restart storm impossible even if some other
|
||||
condition triggers the fix repeatedly.
|
||||
</what-built>
|
||||
<constraint priority="highest">
|
||||
This ships to affected nodes via the **normal OTA release only**. Never deploy
|
||||
directly to a user's device (`archy-x250-mad2` or any node that is not ours).
|
||||
</constraint>
|
||||
<how-to-verify>
|
||||
On an affected node, after the release lands:
|
||||
1. `systemctl status tor@default` — note the uptime. Wait 15 minutes (three
|
||||
doctor intervals) and check again: uptime should keep growing, with no
|
||||
restart. Before the fix it reset roughly every 5 minutes.
|
||||
2. `journalctl -u archipelago-doctor -n 50` — no recurring
|
||||
"Fixed permissions on ... hidden_service_* (2700 -> 700)" lines.
|
||||
3. Once Tor has been up ~20-30 minutes, confirm onion resolution works: a
|
||||
mesh send to a peer reachable only via Tor should succeed, and the logs
|
||||
should no longer show `No more HSDir available to query`.
|
||||
4. Confirm the doctor still does its job: temporarily `chmod 750` a
|
||||
hidden-service directory, wait for the next doctor run, and check it is
|
||||
corrected back to deny group/other access.
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved", or describe what you saw — which step, what happened instead.</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `bash -n` passes; the 2700 case is accepted and the 750/707 cases are still fixed
|
||||
- On an affected node, Tor uptime exceeds the doctor's interval and onion resolution recovers
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Tor is no longer restarted every ~5 minutes by the doctor
|
||||
- Tor keeps its consensus/HSDir cache, so `.onion` peers 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>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/01-federation-mesh-hardening/01-20-SUMMARY.md`. It MUST record the corrected predicate, the backoff mechanism and window, and a note handing the FIPS direct-transport `connect_fail` (problem 1 of the original diagnosis) to FED-03's transport/dial-layer review.
|
||||
</output>
|
||||
Reference in New Issue
Block a user