fix(01-20): stop doctor from fighting Tor over the setgid bit

fix_tor_permissions() exact-matched stat's output against the literal
string "700", but stat -c '%a' omits leading zeros so Tor's own
setgid HiddenServiceDir mode (2700) never matched. Every ~5-minute
doctor run "fixed" it back to 700, restarted Tor, and Tor immediately
set 2700 again — so Tor never survived long enough to build a usable
consensus/HSDir cache, breaking the mesh's Tor fallback entirely.

- Compare only the last 3 mode digits (owner/group/other), which is
  the property that actually matters, so 700 and 2700 both pass while
  750/707/2755 etc. are still corrected.
- Add a 30-minute restart backoff (timestamp file under
  /var/lib/archipelago/) so no future condition can reproduce a
  restart storm even if the fix fires repeatedly.
- Log clearly in both directions: a debug-level no-op line when a
  directory is already correct, and an explicit "was NOT fully
  denied" line when a real fix is applied, plus a line when a restart
  is skipped by the backoff.

Verified statically against temp directories (2700 accepted with 0
restarts; 750/707/2755 corrected with exactly 1 restart; a second
real fix inside the backoff window logs a skip instead of
restarting). No live Tor/doctor/systemd unit was touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-31 09:45:55 -04:00
co-authored by Claude Opus 5
parent 56cc94237b
commit 4435f95ef3
+42 -5
View File
@@ -10,7 +10,8 @@
# 1. Stale podman ps/stats processes (>10 = pileup)
# 2. Orphaned conmon/crun processes holding ports
# 3. System tor conflicting with container tor
# 4. Tor hidden service directory permissions (must be 700)
# 4. Tor hidden service directory permissions (group/other must have no
# access; Tor's own setgid 2700 is fine, restart is backed off)
# 5. SearXNG read-only root / cap-drop ALL
# 6. Bitcoin Knots prune+txindex conflict
# 7. Containers stuck with exit code 127 (binary not found)
@@ -137,6 +138,26 @@ fix_system_tor_conflict() {
}
# ── Fix 4: Tor hidden service permissions ────────────────────
# `stat -c '%a'` omits leading zeros, so a directory Tor manages itself
# reads "2700" (setgid bit set) rather than "700" -- but the setgid bit
# doesn't grant group/other any access. Comparing the full string against
# the literal "700" therefore flagged Tor's own correct setting as broken
# on every run, chmod'd it right back to what it already effectively was,
# and restarted Tor -- forever, every ~5 minutes (the doctor timer
# interval), so Tor never kept its consensus/HSDir cache long enough to
# resolve any .onion peer. Only the last 3 digits of the mode are the
# owner/group/other permission bits; anything before that is
# setuid/setgid/sticky, which is irrelevant to "is group/other access
# denied". Compare only those 3 digits so 700 and 2700 both pass, while a
# genuinely permissive mode (750, 707, 2755, ...) is still corrected.
#
# Restart backoff: even a real, legitimate fix must not restart Tor more
# than once per TOR_RESTART_BACKOFF_SECONDS, so no future defect in this
# (or any other) predicate can reproduce a restart storm. The timestamp
# file persists across doctor runs (and doctor is re-invoked from a fresh
# shell every run, so this can't just be a shell variable).
TOR_RESTART_BACKOFF_SECONDS=1800 # 30 minutes
TOR_RESTART_STATE_FILE="/var/lib/archipelago/doctor-tor-last-restart"
fix_tor_permissions() {
local fixed=false
local tor_dirs=("/var/lib/archipelago/tor" "/var/lib/tor")
@@ -148,17 +169,33 @@ fix_tor_permissions() {
while IFS= read -r dir; do
local perms
perms=$(stat -c '%a' "$dir" 2>/dev/null)
if [ "$perms" != "700" ]; then
local mode_bits="${perms: -3}"
if [ "$mode_bits" != "700" ]; then
chmod 700 "$dir"
log "Fixed permissions on $dir ($perms -> 700)"
log "Fixed permissions on $dir ($perms -> 700; group/other access was NOT fully denied)"
fixed=true
elif [ -n "$DOCTOR_DEBUG" ]; then
log "DEBUG: $dir already denies group/other access (mode $perms) — no change"
fi
done < <(find "$base" -maxdepth 1 -name "hidden_service_*" -type d 2>/dev/null)
done
# If we fixed permissions, restart system Tor to pick up the changes
# If we fixed a real permission defect, restart system Tor to pick up
# the change -- but never more than once per backoff window.
if $fixed; then
systemctl restart tor@default 2>/dev/null || true
local now last_restart elapsed
now=$(date +%s)
last_restart=$(cat "$TOR_RESTART_STATE_FILE" 2>/dev/null || echo 0)
case "$last_restart" in *[!0-9]*|"") last_restart=0 ;; esac
elapsed=$((now - last_restart))
if [ "$elapsed" -ge "$TOR_RESTART_BACKOFF_SECONDS" ]; then
systemctl restart tor@default 2>/dev/null || true
mkdir -p "$(dirname "$TOR_RESTART_STATE_FILE")" 2>/dev/null
echo "$now" > "$TOR_RESTART_STATE_FILE" 2>/dev/null
log "Restarted Tor to apply hidden-service permission fix"
else
log "Skipped Tor restart: last restart was ${elapsed}s ago (backoff window ${TOR_RESTART_BACKOFF_SECONDS}s) — permissions were still corrected"
fi
return 0
fi
return 1