diff --git a/scripts/container-doctor.sh b/scripts/container-doctor.sh index e2b41359..fa9d774a 100755 --- a/scripts/container-doctor.sh +++ b/scripts/container-doctor.sh @@ -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