use anyhow::Result; use tracing::info; use crate::Router; pub struct WispConfig { pub ssid: String, pub password: String, pub encryption: String, // psk2 | psk | sae | none } pub fn configure_wisp(router: &Router, config: &WispConfig) -> Result<()> { info!("[{}] Configuring WISP → ssid={}", router.host, config.ssid); let radio = detect_radio(router)?; // Ensure the radio is enabled (disabled=1 by default on fresh flash) router.uci_set("wireless.radio0.disabled", "0")?; // Create/update named sta wifi-iface "wwan" (idempotent: uci set creates if absent) router.uci_set("wireless.wwan", "wifi-iface")?; router.uci_set("wireless.wwan.device", &radio)?; router.uci_set("wireless.wwan.mode", "sta")?; router.uci_set("wireless.wwan.ssid", &config.ssid)?; router.uci_set("wireless.wwan.network", "wwan")?; router.uci_set("wireless.wwan.encryption", &config.encryption)?; if config.encryption != "none" && !config.password.is_empty() { router.uci_set("wireless.wwan.key", &config.password)?; } router.uci_commit(Some("wireless"))?; // Create/update wwan network interface (DHCP) router.uci_set("network.wwan", "interface")?; router.uci_set("network.wwan.proto", "dhcp")?; router.uci_commit(Some("network"))?; // Add wwan to the WAN firewall zone (walk zones by name) ensure_wwan_in_wan_zone(router)?; // Apply wireless changes; fall back to full network restart if wifi reload fails let (out, code) = router.run("wifi reload 2>&1")?; if code != 0 { info!("[{}] wifi reload failed ({}): {} — falling back to network restart", router.host, code, out.trim()); router.run_ok("/etc/init.d/network restart 2>&1")?; } Ok(()) } pub fn get_wan_status(router: &Router) -> serde_json::Value { let configured = router .uci_get("network.wwan.proto") .map(|v| v == "dhcp") .unwrap_or(false); let ssid = router.uci_get("wireless.wwan.ssid").unwrap_or_default(); let encryption = router.uci_get("wireless.wwan.encryption").unwrap_or_default(); // Source IP for reaching 8.8.8.8 — empty if no default route yet let ip = router .run_ok("ip -4 route get 8.8.8.8 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i==\"src\"){print $(i+1); exit}}'") .unwrap_or_default() .trim() .to_string(); serde_json::json!({ "configured": configured, "ssid": ssid, "encryption": encryption, "ip": ip, "internet": !ip.is_empty(), }) } fn detect_radio(router: &Router) -> Result { // radio0 is universal; verify it exists let out = router.uci_get("wireless.radio0").unwrap_or_default(); if !out.is_empty() { return Ok("radio0".to_string()); } anyhow::bail!("No wireless radio (radio0) found in UCI config") } fn ensure_wwan_in_wan_zone(router: &Router) -> Result<()> { // Walk zones 0-9, find the one named "wan", add wwan to its network list let script = "for i in $(seq 0 9); do \ name=$(uci get firewall.@zone[$i].name 2>/dev/null) || break; \ if [ \"$name\" = \"wan\" ]; then \ uci add_list firewall.@zone[$i].network=wwan 2>/dev/null; \ uci commit firewall; \ break; \ fi; \ done; echo ok"; router.run_ok(script)?; Ok(()) }