feat(openwrt): add TollGate provision button and direct-download fallback
- OpenWrtGateway.vue: add "Install TollGate" button when not installed; tracks connected credentials for reuse in the provision call - install.rs: fall back to wget download from GitHub releases when the package is not in any opkg feed (mips_24kc and other arches supported) - openwrt.rs: provision-tollgate now falls back to saved router_config for credentials, matching the behaviour of get-status Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6c534715ec
commit
f054766a58
@ -136,23 +136,27 @@ impl RpcHandler {
|
||||
&self,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<serde_json::Value> {
|
||||
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
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
@ -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<Record<string, string> | null>(null)
|
||||
|
||||
const provisioning = ref(false)
|
||||
const provisionError = ref('')
|
||||
const provisionSuccess = ref(false)
|
||||
|
||||
async function load(params?: Record<string, string>) {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
@ -58,6 +65,7 @@ async function load(params?: Record<string, string>) {
|
||||
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<string, unknown> = {
|
||||
// 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())
|
||||
<div class="rounded-xl border border-white/10 bg-white/5 p-5 mb-4">
|
||||
<h2 class="text-sm font-semibold text-white/80 mb-4">TollGate</h2>
|
||||
|
||||
<div v-if="!status.tollgate.installed" class="flex items-center gap-3">
|
||||
<span class="w-2 h-2 rounded-full bg-white/20 inline-block"></span>
|
||||
<span class="text-sm text-white/50">Not installed</span>
|
||||
<div v-if="!status.tollgate.installed">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<span class="w-2 h-2 rounded-full bg-white/20 inline-block"></span>
|
||||
<span class="text-sm text-white/50">Not installed</span>
|
||||
</div>
|
||||
<button
|
||||
:disabled="provisioning"
|
||||
class="w-full py-2 rounded-lg text-sm font-medium transition-colors"
|
||||
:class="provisioning
|
||||
? 'bg-white/5 text-white/30 cursor-not-allowed'
|
||||
: 'bg-blue-600 hover:bg-blue-500 text-white'"
|
||||
@click="provisionTollgate"
|
||||
>
|
||||
{{ provisioning ? 'Installing TollGate… (may take a few minutes)' : 'Install TollGate' }}
|
||||
</button>
|
||||
<p v-if="provisionError" class="mt-2 text-xs text-red-400">{{ provisionError }}</p>
|
||||
<p v-if="provisionSuccess && !provisioning" class="mt-2 text-xs text-green-400">TollGate provisioned successfully.</p>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user