From 2da8e2c69877a42f73c82bb7f5ef7b1d230015b1 Mon Sep 17 00:00:00 2001 From: ssmithx Date: Mon, 27 Jul 2026 18:14:54 -0400 Subject: [PATCH] feat(openwrt): add "Forget router" to actually clear saved config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Switch router" only reset local Vue state, leaving router_config.json on disk — a reload silently reconnected with the old saved SSH password. Add openwrt.forget RPC + forget_router_config() to delete the saved config, and a "Forget router" button in OpenWrtGateway.vue. Co-Authored-By: Claude Sonnet 5 --- core/archipelago/src/api/rpc/dispatcher.rs | 1 + core/archipelago/src/api/rpc/openwrt.rs | 7 ++++ core/archipelago/src/network/router.rs | 11 ++++++ neode-ui/src/views/server/OpenWrtGateway.vue | 35 ++++++++++++++++++++ 4 files changed, 54 insertions(+) diff --git a/core/archipelago/src/api/rpc/dispatcher.rs b/core/archipelago/src/api/rpc/dispatcher.rs index b8a974d7..521ea1b0 100644 --- a/core/archipelago/src/api/rpc/dispatcher.rs +++ b/core/archipelago/src/api/rpc/dispatcher.rs @@ -244,6 +244,7 @@ impl RpcHandler { // OpenWrt / TollGate "openwrt.scan" => self.handle_openwrt_scan(params).await, "openwrt.get-status" => self.handle_openwrt_get_status(params).await, + "openwrt.forget" => self.handle_openwrt_forget().await, "openwrt.provision-tollgate" => self.handle_openwrt_provision_tollgate(params).await, "openwrt.scan-wifi" => self.handle_openwrt_scan_wifi(params).await, "openwrt.configure-wan" => self.handle_openwrt_configure_wan(params).await, diff --git a/core/archipelago/src/api/rpc/openwrt.rs b/core/archipelago/src/api/rpc/openwrt.rs index 0c30a0ca..1a87b2cd 100644 --- a/core/archipelago/src/api/rpc/openwrt.rs +++ b/core/archipelago/src/api/rpc/openwrt.rs @@ -165,6 +165,13 @@ impl RpcHandler { })) } + /// Forget the saved router config — deletes `router_config.json` so the + /// app requires a fresh login next time. Takes no params. + pub(super) async fn handle_openwrt_forget(&self) -> Result { + net_router::forget_router_config(&self.config.data_dir).await?; + Ok(serde_json::json!({ "ok": true })) + } + /// Provision TollGate on an OpenWrt router and create the "archipelago" SSID. /// /// Params: `{ "host": "192.168.1.1", "ssh_user": "root", "ssh_password": "", diff --git a/core/archipelago/src/network/router.rs b/core/archipelago/src/network/router.rs index a8620842..839a137e 100644 --- a/core/archipelago/src/network/router.rs +++ b/core/archipelago/src/network/router.rs @@ -366,6 +366,17 @@ pub async fn save_router_config(data_dir: &Path, config: &RouterConfig) -> Resul .context("Writing router config") } +/// Remove the saved router config (credentials + address) so the app forgets +/// this router entirely and the next call requires a fresh login. +pub async fn forget_router_config(data_dir: &Path) -> Result<()> { + let path = data_dir.join(ROUTER_CONFIG_FILE); + match fs::remove_file(&path).await { + Ok(()) => Ok(()), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()), + Err(e) => Err(e).context("Removing router config"), + } +} + /// Validate that an IP string is a private/LAN address (not public, not localhost). fn is_valid_private_ip(ip_str: &str) -> bool { let ip: std::net::IpAddr = match ip_str.parse() { diff --git a/neode-ui/src/views/server/OpenWrtGateway.vue b/neode-ui/src/views/server/OpenWrtGateway.vue index 1b091056..c245c136 100644 --- a/neode-ui/src/views/server/OpenWrtGateway.vue +++ b/neode-ui/src/views/server/OpenWrtGateway.vue @@ -203,6 +203,33 @@ function disconnectRouter() { showConnectForm.value = true } +const forgetting = ref(false) +const forgetError = ref('') + +// Unlike disconnectRouter() (which only resets local state, leaving the saved +// router_config.json in place so onMounted reconnects on next visit), this +// deletes the saved credentials server-side so the router is truly forgotten. +async function forgetRouter() { + if (!confirm('Forget this router? You will need to log in again next time.')) return + forgetting.value = true + forgetError.value = '' + try { + await rpcClient.call({ method: 'openwrt.forget', timeout: 10000 }) + status.value = null + connectedParams.value = null + host.value = '' + sshUser.value = 'root' + sshPassword.value = '' + detectError.value = '' + detectedCandidates.value = [] + showConnectForm.value = true + } catch (e) { + forgetError.value = e instanceof Error ? e.message : String(e) + } finally { + forgetting.value = false + } +} + async function provisionTollgate() { provisioning.value = true provisionError.value = '' @@ -499,7 +526,15 @@ onMounted(() => load()) + +

{{ forgetError }}