Files
archy/scripts/tor-helper.sh
Dorian fc1120338d fix: Tor management system, bug fixes, federation name sync
Major changes:
- Full Tor hidden service management via systemd path unit pattern
  (tor-helper.sh + archipelago-tor-helper.path/service) — respects
  NoNewPrivileges=yes, no sudo needed from backend
- Container doctor: prefer system Tor over container, remove archy-tor
- Deploy script: fix torrc generation (read correct services.json path),
  web apps map port 80→local port, enable both tor and tor@default
- Federation: server rename pushes name to peers via background sync
- Server name: fix root-owned file, optimistic store update
- Mesh: local echo for sent messages, sendingArch loading state
- Web5: Message button → Mesh redirect, node name lookup in messages
- PeerFiles: show DID not onion in header
- Connected Nodes: flex-1 instead of fixed max-h
- Toast notifications route to Mesh
- Deploy script: fix single-quote syntax in SSH block

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 02:59:29 +00:00

118 lines
3.7 KiB
Bash
Executable File

#!/bin/bash
# tor-helper.sh — Privileged Tor operations for the Archipelago backend.
# Runs as root via systemd (archipelago-tor-helper.service), triggered by
# a path unit watching /var/lib/archipelago/tor-config/tor-action.
#
# The backend writes a JSON action file, the path unit triggers this script.
# This avoids calling sudo from within a NoNewPrivileges=yes service.
set -euo pipefail
ACTION_FILE="/var/lib/archipelago/tor-config/tor-action"
TORRC_STAGED="/var/lib/archipelago/tor-config/torrc.staged"
RESULT_FILE="/var/lib/archipelago/tor-config/tor-result"
HOSTNAMES_DIR="/var/lib/archipelago/tor-hostnames"
log() { echo "[tor-helper] $*"; }
write_result() {
echo "$1" > "$RESULT_FILE"
chown archipelago:archipelago "$RESULT_FILE" 2>/dev/null || true
}
sync_hostnames() {
mkdir -p "$HOSTNAMES_DIR"
# Clear stale copies first
rm -f "$HOSTNAMES_DIR"/* 2>/dev/null || true
# Prefer /var/lib/tor (system Tor, authoritative) over /var/lib/archipelago/tor
# Only copy from secondary if not already found in primary
for base in /var/lib/tor /var/lib/archipelago/tor; do
for dir in "$base"/hidden_service_*; do
[ -d "$dir" ] || continue
svc=$(basename "$dir" | sed 's/^hidden_service_//')
echo "$svc" | grep -q '_old_' && continue
# Skip if already synced from a higher-priority location
[ -f "${HOSTNAMES_DIR}/${svc}" ] && continue
if [ -f "$dir/hostname" ]; then
cp "$dir/hostname" "${HOSTNAMES_DIR}/${svc}"
log "Synced hostname: $svc ($base)"
fi
done
done
chown -R archipelago:archipelago "$HOSTNAMES_DIR" 2>/dev/null || true
}
# ─── Main ─────────────────────────────────────────────────────────
if [ ! -f "$ACTION_FILE" ]; then
log "No action file found"
exit 0
fi
ACTION=$(cat "$ACTION_FILE")
rm -f "$ACTION_FILE"
ACTION_TYPE=$(echo "$ACTION" | python3 -c "import sys,json; print(json.load(sys.stdin).get('action',''))" 2>/dev/null || echo "")
case "$ACTION_TYPE" in
write-torrc-and-restart)
if [ ! -f "$TORRC_STAGED" ]; then
log "ERROR: No staged torrc at $TORRC_STAGED"
write_result '{"ok":false,"error":"No staged torrc"}'
exit 1
fi
cp "$TORRC_STAGED" /etc/tor/torrc
chown debian-tor:debian-tor /etc/tor/torrc 2>/dev/null || true
log "torrc updated from staged file"
systemctl restart tor
log "Tor restarted"
# Wait for SOCKS port
for i in $(seq 1 30); do
if timeout 1 bash -c 'echo > /dev/tcp/127.0.0.1/9050' 2>/dev/null; then
break
fi
sleep 1
done
sync_hostnames
write_result '{"ok":true}'
;;
restart)
systemctl restart tor
log "Tor restarted"
sleep 3
sync_hostnames
write_result '{"ok":true}'
;;
delete-service)
NAME=$(echo "$ACTION" | python3 -c "import sys,json; print(json.load(sys.stdin).get('name',''))" 2>/dev/null || echo "")
if [ -z "$NAME" ]; then
write_result '{"ok":false,"error":"Missing service name"}'
exit 1
fi
if ! echo "$NAME" | grep -qE '^[a-zA-Z0-9_-]+$'; then
write_result '{"ok":false,"error":"Invalid service name"}'
exit 1
fi
rm -rf "/var/lib/tor/hidden_service_${NAME}" 2>/dev/null || true
rm -rf "/var/lib/archipelago/tor/hidden_service_${NAME}" 2>/dev/null || true
rm -f "${HOSTNAMES_DIR}/${NAME}" 2>/dev/null || true
log "Deleted hidden service: $NAME"
write_result '{"ok":true}'
;;
sync-hostnames)
sync_hostnames
write_result '{"ok":true}'
;;
*)
log "Unknown action: $ACTION_TYPE"
write_result '{"ok":false,"error":"Unknown action"}'
exit 1
;;
esac