From c45f0c8fb8df7d651aa1f03dd5a5a3a8949ed6ad Mon Sep 17 00:00:00 2001 From: Dorian Date: Fri, 13 Mar 2026 01:52:50 +0000 Subject: [PATCH] feat: federate 3 servers with Tor, fix inter-node auth (FED-DEPLOY-02) - Add tor-hostnames fallback for reading onion addresses when system Tor owns hidden_service directories (permissions 700) - Exempt federation.peer-joined, federation.get-state, and federation.peer-address-changed from auth/CSRF (inter-node RPC) - Set up system Tor with AppArmor overrides on archipelago-2 and 3 - All 3 servers federated and syncing successfully Co-Authored-By: Claude Opus 4.6 --- core/archipelago/src/api/rpc/mod.rs | 4 ++++ .../archipelago/src/container/docker_packages.rs | 16 ++++++++++++++++ loop/plan.md | 2 +- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/core/archipelago/src/api/rpc/mod.rs b/core/archipelago/src/api/rpc/mod.rs index e1d96ce6..5f2dd831 100644 --- a/core/archipelago/src/api/rpc/mod.rs +++ b/core/archipelago/src/api/rpc/mod.rs @@ -73,6 +73,10 @@ const UNAUTHENTICATED_METHODS: &[&str] = &[ "auth.login.backup", "auth.isOnboardingComplete", "health", + // Inter-node RPC: called by federated peers over Tor, no session cookies + "federation.peer-joined", + "federation.peer-address-changed", + "federation.get-state", ]; pub struct RpcHandler { diff --git a/core/archipelago/src/container/docker_packages.rs b/core/archipelago/src/container/docker_packages.rs index 2d7c8d44..9fb80519 100644 --- a/core/archipelago/src/container/docker_packages.rs +++ b/core/archipelago/src/container/docker_packages.rs @@ -540,6 +540,22 @@ fn is_real_onion_address(s: &str) -> bool { pub fn read_tor_address(app_id: &str) -> Option { let service = tor_service_name(app_id)?; let base = std::env::var("TOR_DATA_DIR").unwrap_or_else(|_| "/var/lib/archipelago/tor".to_string()); + + // Try readable hostname copy first (when system Tor owns hidden_service dirs) + let hostnames_path = std::path::Path::new(&base) + .parent() + .unwrap_or(std::path::Path::new("/var/lib/archipelago")) + .join("tor-hostnames") + .join(service); + if let Some(addr) = std::fs::read_to_string(&hostnames_path) + .ok() + .map(|s| s.trim().to_string()) + .filter(|s| s.ends_with(".onion") && !s.is_empty()) + { + return Some(addr); + } + + // Fall back to hidden_service directory let path = std::path::Path::new(&base) .join(format!("hidden_service_{}", service)) .join("hostname"); diff --git a/loop/plan.md b/loop/plan.md index 4275281f..16e1c505 100644 --- a/loop/plan.md +++ b/loop/plan.md @@ -504,7 +504,7 @@ - [x] **FED-DEPLOY-01** — Deployed to 3/4 servers (192.168.1.198 offline). Primary (192.168.1.228): full deploy via deploy script. Archipelago-2: full deploy via deploy script. Archipelago-3: SCP binary from archipelago-2, frontend tarball extracted, service restarted. All 3 servers return health OK (200), frontend loads. -- [ ] **FED-DEPLOY-02** — Federate all servers. On primary (192.168.1.228), call `federation.invite` to generate 3 invite codes (one per peer server). On each secondary server, call `federation.join` with the invite code. Verify bilateral trust is established: `federation.list-nodes` on primary shows all 3 peers as "trusted", each peer shows primary as "trusted". Trigger `federation.sync-state` and verify state snapshots contain real data (CPU, memory, disk, app list) from each peer. **Acceptance**: `federation.list-nodes` on any server lists all 4 nodes with recent `last_seen` timestamps and valid state snapshots. +- [x] **FED-DEPLOY-02** — Federated 3 servers (192.168.1.198 offline). Fixed: Tor hostname reading (tor-hostnames dir for system Tor), AppArmor profiles, inter-node RPC auth exemption (federation.peer-joined/get-state/peer-address-changed). Primary has 2 peers (archipelago-2 and archipelago-3), each peer has primary as trusted. Sync works: archipelago-2 has 24 apps, archipelago-3 has 10 apps. - [ ] **FED-DEPLOY-03** — Validate Nostr discovery across all nodes. On each server, call `node.nostr-publish` to publish identity to relays. Wait 30 seconds for relay propagation. On each server, call `node.nostr-discover` — verify it finds all other 3 nodes (DID, onion address, version). If discovery fails: check relay connectivity (are relays reachable from server?), check Tor proxy routing, check NIP-33 event format. Fix any issues. **Acceptance**: Every server can discover every other server via Nostr relays. Run discovery 3 times from each to confirm reliability.