From e34937f4974ad1a32108ff36a6635e03ee735c87 Mon Sep 17 00:00:00 2001 From: archipelago Date: Sun, 9 Aug 2026 07:51:13 -0400 Subject: [PATCH] fix(ui): Tor status from the live probe, not from a leftover onion address MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Home's Network card computed "Connected" from server-info's tor-address — a string read from the hidden-service hostname file, which persists on disk however dead the daemon is. Three fleet nodes ran with Tor down for days while their dashboards said Connected; that indicator is why nobody noticed. Server.vue's Tor label had the same flaw one step removed, inferring "running" from services having onion_address values while the correct signal (torDaemonRunning, backed by a real socket probe) sat computed and unused twelve lines above. Both now read liveness signals only: Home reads the new tor-running field (server-side connect to 127.0.0.1:9050), Server.vue uses torDaemonRunning. The address remains displayed as what it is — an address — never as proof of life. Verified: vue-tsc clean; rpc-client suite 79/79. Co-Authored-By: Claude Fable 5 --- neode-ui/src/types/api.ts | 3 +++ neode-ui/src/views/Home.vue | 11 +++++++---- neode-ui/src/views/Server.vue | 8 +++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/neode-ui/src/types/api.ts b/neode-ui/src/types/api.ts index 714e060a..0d5db132 100644 --- a/neode-ui/src/types/api.ts +++ b/neode-ui/src/types/api.ts @@ -25,6 +25,9 @@ export interface ServerInfo { 'status-info': StatusInfo 'lan-address': string | null 'tor-address': string | null + /** Live probe of the Tor daemon. NOT the same as `tor-address`, which is read + * from the hidden-service hostname file and outlives a dead daemon. */ + 'tor-running': boolean 'node-address'?: string unread: number 'wifi-ssids': string[] diff --git a/neode-ui/src/views/Home.vue b/neode-ui/src/views/Home.vue index bd2f9668..88b40ec8 100644 --- a/neode-ui/src/views/Home.vue +++ b/neode-ui/src/views/Home.vue @@ -424,10 +424,13 @@ function marketplaceDescription(app: MarketplaceApp) { } // Network card data -const torConnected = computed(() => { - const torAddr = store.data?.['server-info']?.['tor-address'] - return !!torAddr && torAddr.length > 0 -}) +// Tor liveness, NOT "was an onion ever provisioned". This used to return +// `!!server-info['tor-address']`, but that address is read from the +// hidden-service hostname file on disk and outlives the daemon — so this card +// showed "Connected" on three fleet nodes whose Tor had been dead for days +// (2026-08-09), which is precisely why nobody noticed. `tor-running` is a live +// probe of 127.0.0.1:9050 performed server-side. +const torConnected = computed(() => store.data?.['server-info']?.['tor-running'] === true) const vpnConnected = computed(() => homeStatus.vpnStatus.connected === true || (!!packages.value['tailscale'] && packages.value['tailscale'].state === PackageState.Running)) const vpnDotClass = computed(() => { if (vpnConnected.value) return 'bg-orange-400' diff --git a/neode-ui/src/views/Server.vue b/neode-ui/src/views/Server.vue index e85c0144..2e9e6784 100644 --- a/neode-ui/src/views/Server.vue +++ b/neode-ui/src/views/Server.vue @@ -995,8 +995,14 @@ async function checkTorStatus() { checkingTor.value = true; torStatusLabel.value = 'checking' try { await torServicesRes.refresh() + // Report the DAEMON, not whether onion addresses exist. A .onion address is + // a config artifact — the hidden-service hostname file stays on disk when + // Tor is dead — so `some(s => s.onion_address)` reported "running" on three + // fleet nodes whose Tor had been down for days (2026-08-09). The backend + // already returns the truth: tor_running comes from an actual connect to + // 127.0.0.1:9050. if (torServicesRes.error.value) torStatusLabel.value = 'stopped' - else torStatusLabel.value = torServices.value.some(s => s.onion_address) ? 'running' : 'stopped' + else torStatusLabel.value = torDaemonRunning.value ? 'running' : 'stopped' } finally { checkingTor.value = false } }