2026-06-29 15:11:48 +00:00
|
|
|
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)?;
|
|
|
|
|
|
2026-06-29 16:52:06 +00:00
|
|
|
// Ensure the radio is enabled (disabled=1 by default on fresh flash)
|
|
|
|
|
router.uci_set("wireless.radio0.disabled", "0")?;
|
|
|
|
|
|
2026-06-29 15:11:48 +00:00
|
|
|
// 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
|
2026-06-29 16:52:06 +00:00
|
|
|
let (out, code) = router.run("wifi reload 2>&1")?;
|
2026-06-29 15:11:48 +00:00
|
|
|
if code != 0 {
|
2026-06-29 16:52:06 +00:00
|
|
|
info!("[{}] wifi reload failed ({}): {} — falling back to network restart", router.host, code, out.trim());
|
2026-06-29 15:11:48 +00:00
|
|
|
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<String> {
|
|
|
|
|
// 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(())
|
|
|
|
|
}
|