diff --git a/core/archipelago/src/api/rpc/openwrt.rs b/core/archipelago/src/api/rpc/openwrt.rs index 2f74bd2d..6a9d47f8 100644 --- a/core/archipelago/src/api/rpc/openwrt.rs +++ b/core/archipelago/src/api/rpc/openwrt.rs @@ -136,23 +136,27 @@ impl RpcHandler { &self, params: Option, ) -> Result { - let p = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?; + let saved = net_router::load_router_config(&self.config.data_dir).await?; + let p = params.unwrap_or_default(); let host = p .get("host") .and_then(|v| v.as_str()) - .ok_or_else(|| anyhow::anyhow!("Missing host"))? - .to_string(); + .map(|s| s.to_string()) + .or_else(|| if saved.configured { Some(saved.address.clone()) } else { None }) + .ok_or_else(|| anyhow::anyhow!("No router configured — provide host or call router.configure first"))?; let ssh_user = p .get("ssh_user") .and_then(|v| v.as_str()) - .unwrap_or("root") - .to_string(); + .map(|s| s.to_string()) + .or_else(|| saved.username.clone()) + .unwrap_or_else(|| "root".to_string()); let ssh_password = p .get("ssh_password") .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(); + .map(|s| s.to_string()) + .or_else(|| saved.password.clone()) + .unwrap_or_default(); let default_mint_url = format!("http://{}:{}", self.config.host_ip, LOCAL_MINT_PORT); let mint_url = p diff --git a/core/openwrt/src/tollgate/install.rs b/core/openwrt/src/tollgate/install.rs index d559d832..4b1beb81 100644 --- a/core/openwrt/src/tollgate/install.rs +++ b/core/openwrt/src/tollgate/install.rs @@ -6,11 +6,50 @@ use crate::Router; /// The OpenWrt package name for the TollGate reference implementation. const TOLLGATE_PACKAGE: &str = "tollgate-module-basic-go"; -/// Install tollgate-module-basic-go via opkg. +/// Direct-download fallback URLs by opkg architecture string. +/// Used when the package is not in any configured feed. +/// Source: https://github.com/OpenTollGate/tollgate-module-basic-go/releases/tag/v0.2.0 +fn ipk_url(arch: &str) -> Option<&'static str> { + match arch { + "mips_24kc" => Some("https://github.com/OpenTollGate/tollgate-module-basic-go/releases/download/v0.2.0/mips_24kc.ipk"), + "mipsel_24kc" => Some("https://github.com/OpenTollGate/tollgate-module-basic-go/releases/download/v0.2.0/mipsel_24kc.ipk"), + "aarch64_cortex-a53" => Some("https://github.com/OpenTollGate/tollgate-module-basic-go/releases/download/v0.2.0/aarch64_cortex-a53.ipk"), + "aarch64_cortex-a72" => Some("https://github.com/OpenTollGate/tollgate-module-basic-go/releases/download/v0.2.0/aarch64_cortex-a72.ipk"), + "arm_cortex-a7" => Some("https://github.com/OpenTollGate/tollgate-module-basic-go/releases/download/v0.2.0/arm_cortex-a7.ipk"), + _ => None, + } +} + +/// Install tollgate-module-basic-go. /// +/// Tries opkg first (works if a custom feed is configured). Falls back to +/// downloading the .ipk directly from GitHub releases if opkg can't find it. /// Caller is responsible for running `opkg_update` first. pub fn install_tollgate(router: &Router) -> Result<()> { info!("[{}] Installing {}", router.host, TOLLGATE_PACKAGE); - router.opkg_install(TOLLGATE_PACKAGE)?; + + // Fast path: standard opkg install (or already installed). + if router.opkg_install(TOLLGATE_PACKAGE).is_ok() { + return Ok(()); + } + + // Package not in any feed — download the .ipk directly. + let arch = router + .run_ok("opkg print-architecture | grep -v 'all\\|noarch' | tail -1 | awk '{print $2}'")?; + let arch = arch.trim(); + + let url = ipk_url(arch).ok_or_else(|| { + anyhow::anyhow!( + "No pre-built TollGate package for architecture '{}'. \ + Add a custom opkg feed or build from source.", + arch + ) + })?; + + info!("[{}] Downloading TollGate for {} from GitHub releases", router.host, arch); + router.run_ok(&format!("wget -O /tmp/tollgate.ipk '{}'", url))?; + router.run_ok("opkg install --force-depends /tmp/tollgate.ipk")?; + router.run_ok("rm -f /tmp/tollgate.ipk")?; + Ok(()) } diff --git a/neode-ui/src/views/server/OpenWrtGateway.vue b/neode-ui/src/views/server/OpenWrtGateway.vue index 6e0481d5..e78c33c3 100644 --- a/neode-ui/src/views/server/OpenWrtGateway.vue +++ b/neode-ui/src/views/server/OpenWrtGateway.vue @@ -48,6 +48,13 @@ const sshPassword = ref('') const showConnectForm = ref(false) const connecting = ref(false) +// Credentials used for the last successful connection (reused for provisioning) +const connectedParams = ref | null>(null) + +const provisioning = ref(false) +const provisionError = ref('') +const provisionSuccess = ref(false) + async function load(params?: Record) { loading.value = true error.value = '' @@ -58,6 +65,7 @@ async function load(params?: Record) { timeout: 30000, }) showConnectForm.value = false + if (params) connectedParams.value = params } catch (e) { const msg = e instanceof Error ? e.message : String(e) if (msg.includes('No router configured')) { @@ -81,6 +89,28 @@ async function connect() { } } +async function provisionTollgate() { + provisioning.value = true + provisionError.value = '' + provisionSuccess.value = false + try { + const params: Record = { + // Use explicitly connected creds if available, otherwise fall back to + // host from the loaded status (backend will use saved router_config). + host: connectedParams.value?.host ?? status.value?.host, + ssh_user: connectedParams.value?.ssh_user ?? sshUser.value, + ssh_password: connectedParams.value?.ssh_password ?? sshPassword.value, + } + await rpcClient.call({ method: 'openwrt.provision-tollgate', params, timeout: 180000 }) + provisionSuccess.value = true + await load(connectedParams.value ?? undefined) + } catch (e) { + provisionError.value = e instanceof Error ? e.message : String(e) + } finally { + provisioning.value = false + } +} + function formatUptime(secs: number): string { const d = Math.floor(secs / 86400) const h = Math.floor((secs % 86400) / 3600) @@ -229,9 +259,23 @@ onMounted(() => load())

TollGate

-
- - Not installed +
+
+ + Not installed +
+ +

{{ provisionError }}

+

TollGate provisioned successfully.