style: cargo fmt the workspace (release-gate cargo-fmt was red)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-14 15:38:32 -04:00
co-authored by Claude Fable 5
parent 412251ac9a
commit db4de0ac96
45 changed files with 641 additions and 337 deletions
+5 -1
View File
@@ -32,7 +32,11 @@ pub async fn scan_subnet(
}
}
info!("{} hosts with TCP/22 open in /{}", candidates.len(), prefix_len);
info!(
"{} hosts with TCP/22 open in /{}",
candidates.len(),
prefix_len
);
let mut routers = Vec::new();
for ip in candidates {
+13 -3
View File
@@ -48,10 +48,17 @@ impl Router {
return Ok(PkgManager::Opkg);
}
if add_out.contains("no such package") || add_out.contains("unable to select") {
info!("[{}] opkg not in apk repos — staying in apk-native mode", self.host);
info!(
"[{}] opkg not in apk repos — staying in apk-native mode",
self.host
);
return Ok(PkgManager::ApkNative);
}
anyhow::bail!("apk add opkg failed (exit {}): {}", add_code, add_out.trim());
anyhow::bail!(
"apk add opkg failed (exit {}): {}",
add_code,
add_out.trim()
);
}
anyhow::bail!(
@@ -70,7 +77,10 @@ impl Router {
/// Install a package, skipping if already installed.
pub fn opkg_install(&self, package: &str) -> Result<()> {
// Check if already installed to avoid unnecessary network traffic.
let (_, code) = self.run(&format!("/usr/bin/opkg list-installed | grep -q '^{} '", package))?;
let (_, code) = self.run(&format!(
"/usr/bin/opkg list-installed | grep -q '^{} '",
package
))?;
if code == 0 {
info!("[{}] {} already installed", self.host, package);
return Ok(());
+11 -6
View File
@@ -16,8 +16,7 @@ impl Router {
/// Connect to an OpenWrt router via SSH using a private key.
pub fn connect(host: &str, port: u16, user: &str, key_path: &Path) -> Result<Self> {
let addr = format!("{}:{}", host, port);
let tcp = TcpStream::connect(&addr)
.with_context(|| format!("TCP connect to {}", addr))?;
let tcp = TcpStream::connect(&addr).with_context(|| format!("TCP connect to {}", addr))?;
let mut session = Session::new().context("create SSH session")?;
session.set_tcp_stream(tcp);
@@ -36,8 +35,7 @@ impl Router {
/// Connect using a password (fallback for routers not yet provisioned with a key).
pub fn connect_password(host: &str, port: u16, user: &str, password: &str) -> Result<Self> {
let addr = format!("{}:{}", host, port);
let tcp = TcpStream::connect(&addr)
.with_context(|| format!("TCP connect to {}", addr))?;
let tcp = TcpStream::connect(&addr).with_context(|| format!("TCP connect to {}", addr))?;
let mut session = Session::new().context("create SSH session")?;
session.set_tcp_stream(tcp);
@@ -58,7 +56,9 @@ impl Router {
debug!("ssh [{}] $ {}", self.host, cmd);
let mut channel = self.session.channel_session().context("open channel")?;
channel.exec(cmd).with_context(|| format!("exec: {}", cmd))?;
channel
.exec(cmd)
.with_context(|| format!("exec: {}", cmd))?;
let mut stdout = String::new();
channel.read_to_string(&mut stdout).context("read stdout")?;
@@ -72,7 +72,12 @@ impl Router {
pub fn run_ok(&self, cmd: &str) -> Result<String> {
let (out, code) = self.run(cmd)?;
if code != 0 {
anyhow::bail!("command `{}` exited with code {}: {}", cmd, code, out.trim());
anyhow::bail!(
"command `{}` exited with code {}: {}",
cmd,
code,
out.trim()
);
}
Ok(out)
}
+46 -27
View File
@@ -46,8 +46,14 @@ pub fn install_tollgate(router: &Router) -> Result<()> {
)
})?;
info!("[{}] Downloading TollGate for {} from GitHub releases", router.host, arch);
router.run_ok(&format!("wget --no-check-certificate -O /tmp/tollgate.ipk '{}' 2>&1", url))?;
info!(
"[{}] Downloading TollGate for {} from GitHub releases",
router.host, arch
);
router.run_ok(&format!(
"wget --no-check-certificate -O /tmp/tollgate.ipk '{}' 2>&1",
url
))?;
install_ipk(router, "/tmp/tollgate.ipk")
}
@@ -56,7 +62,10 @@ pub fn install_tollgate(router: &Router) -> Result<()> {
/// Downloads the .ipk from GitHub releases and extracts it manually using
/// BusyBox `ar` and `tar` (both present on all OpenWrt images).
pub fn install_tollgate_apk_native(router: &Router) -> Result<()> {
info!("[{}] Installing {} (apk-native mode)", router.host, TOLLGATE_PACKAGE);
info!(
"[{}] Installing {} (apk-native mode)",
router.host, TOLLGATE_PACKAGE
);
// Already installed? The service binary is /usr/bin/tollgate-wrt (per its
// init.d script) — TOLLGATE_PACKAGE is only the opkg/apk package name,
@@ -80,14 +89,14 @@ pub fn install_tollgate_apk_native(router: &Router) -> Result<()> {
&& a=\"${DISTRIB_ARCH:-${OPENWRT_ARCH:-}}\" \
&& [ -n \"$a\" ] && echo \"$a\" \
|| /usr/bin/apk --print-arch 2>/dev/null \
|| uname -m"
|| uname -m",
)?;
// Normalise: uname -m returns bare "mipsel"/"mips"; map to 24kc variant
// which is the standard for home-router MIPS builds.
let arch = match arch_raw.trim() {
"mipsel" => "mipsel_24kc",
"mips" => "mips_24kc",
other => other,
"mips" => "mips_24kc",
other => other,
};
info!("[{}] detected arch: {:?}", router.host, arch);
if arch.is_empty() {
@@ -102,11 +111,15 @@ pub fn install_tollgate_apk_native(router: &Router) -> Result<()> {
)
})?;
info!("[{}] Downloading TollGate for {} from GitHub releases", router.host, arch);
info!(
"[{}] Downloading TollGate for {} from GitHub releases",
router.host, arch
);
// --no-check-certificate: fresh OpenWrt 25.x images ship without a CA bundle;
// GitHub serves releases over HTTPS so wget would otherwise reject the cert.
let (dl_out, dl_code) = router.run(&format!(
"wget --no-check-certificate -O /tmp/tollgate.ipk '{}' 2>&1", url
"wget --no-check-certificate -O /tmp/tollgate.ipk '{}' 2>&1",
url
))?;
if dl_code != 0 {
anyhow::bail!("TollGate download failed: {}", dl_out.trim());
@@ -149,9 +162,8 @@ fn install_ipk(router: &Router, ipk_path: &str) -> Result<()> {
let (_, ar_found) = router.run("command -v ar >/dev/null 2>&1")?;
if ar_found != 0 {
info!("[{}] ar not found, installing binutils", router.host);
let (pkg_out, pkg_code) = router.run(
"apk add binutils 2>&1 || opkg install binutils 2>&1"
)?;
let (pkg_out, pkg_code) =
router.run("apk add binutils 2>&1 || opkg install binutils 2>&1")?;
if pkg_code != 0 {
anyhow::bail!(
"TollGate installation failed: ar not available and binutils install failed: {}",
@@ -161,9 +173,8 @@ fn install_ipk(router: &Router, ipk_path: &str) -> Result<()> {
}
// Try standard opkg ar format first (ar archive → data.tar.gz inside).
let (ar_out, ar_code) = router.run(&format!(
"cd /tmp/_tg_install && ar x {} 2>&1", ipk_path
))?;
let (ar_out, ar_code) =
router.run(&format!("cd /tmp/_tg_install && ar x {} 2>&1", ipk_path))?;
if ar_code != 0 {
// Fallback: some builds produce the .ipk as a gzip tarball rather than
@@ -173,17 +184,21 @@ fn install_ipk(router: &Router, ipk_path: &str) -> Result<()> {
// flat tarball of the real package files with no ipk structure at
// all. Extract to the scratch dir and check which shape it is before
// deciding how to install it.
info!("[{}] ar failed ({}), trying tar -xzf", router.host, ar_out.trim());
info!(
"[{}] ar failed ({}), trying tar -xzf",
router.host,
ar_out.trim()
);
// List contents first — validates format without writing anything.
let (list_out, list_code) = router.run(&format!(
"tar -tzf {} 2>&1 | head -30", ipk_path
))?;
let (list_out, list_code) =
router.run(&format!("tar -tzf {} 2>&1 | head -30", ipk_path))?;
if list_code != 0 {
anyhow::bail!(
"TollGate installation failed: file is not an ar archive or gzip tar.\n\
ar: {}\ntar -t: {}",
ar_out.trim(), list_out.trim()
ar_out.trim(),
list_out.trim()
);
}
info!("[{}] ipk contents:\n{}", router.host, list_out.trim());
@@ -206,14 +221,17 @@ fn install_ipk(router: &Router, ipk_path: &str) -> Result<()> {
}
let (cp_out, cp_code) = router.run("cp -a /tmp/_tg_install/. / 2>&1")?;
if cp_code != 0 {
anyhow::bail!("TollGate installation failed: file copy failed: {}", cp_out.trim());
anyhow::bail!(
"TollGate installation failed: file copy failed: {}",
cp_out.trim()
);
}
// No package-manager postinst ran for these files either — see
// the uci-defaults note below.
router.run_ok(
"for f in /etc/uci-defaults/*; do \
[ -f \"$f\" ] && ( cd \"$(dirname \"$f\")\" && . \"$f\" ) && rm -f \"$f\"; \
done; uci commit 2>/dev/null; true"
done; uci commit 2>/dev/null; true",
)?;
router.run_ok(&format!("rm -rf /tmp/_tg_install {}", ipk_path))?;
return Ok(());
@@ -223,18 +241,19 @@ fn install_ipk(router: &Router, ipk_path: &str) -> Result<()> {
// Unpack data.tar.gz (the real payload) from either the `ar`-extracted or
// gzip-tar-extracted scratch dir, then run control.tar.gz's postinst.
let (tar_out, tar_code) = router.run(
"tar -xzf /tmp/_tg_install/data.tar.gz -C / 2>&1"
)?;
let (tar_out, tar_code) = router.run("tar -xzf /tmp/_tg_install/data.tar.gz -C / 2>&1")?;
if tar_code != 0 {
anyhow::bail!("TollGate installation failed: data extract failed: {}", tar_out.trim());
anyhow::bail!(
"TollGate installation failed: data extract failed: {}",
tar_out.trim()
);
}
// Run postinst if present (optional — failures are non-fatal).
router.run_ok(
"if tar -xzf /tmp/_tg_install/control.tar.gz -C /tmp/_tg_install 2>/dev/null; then \
chmod +x /tmp/_tg_install/postinst 2>/dev/null; \
/tmp/_tg_install/postinst configure 2>/dev/null || true; \
fi"
fi",
)?;
// `default_postinst` (what most packages' postinst calls, including
// this one) only runs pending /etc/uci-defaults/* scripts for packages
@@ -246,7 +265,7 @@ fn install_ipk(router: &Router, ipk_path: &str) -> Result<()> {
router.run_ok(
"for f in /etc/uci-defaults/*; do \
[ -f \"$f\" ] && ( cd \"$(dirname \"$f\")\" && . \"$f\" ) && rm -f \"$f\"; \
done; uci commit 2>/dev/null; true"
done; uci commit 2>/dev/null; true",
)?;
router.run_ok(&format!("rm -rf /tmp/_tg_install {}", ipk_path))?;
+2 -4
View File
@@ -84,9 +84,7 @@ pub async fn provision(router: &Router, config: &TollGateConfig) -> Result<()> {
fn restart_services(router: &Router, enabled: bool) -> Result<()> {
if enabled {
router.run_ok("/etc/init.d/tollgate-wrt enable")?;
router.run_ok(
"/etc/init.d/tollgate-wrt restart || /etc/init.d/tollgate-wrt start"
)?;
router.run_ok("/etc/init.d/tollgate-wrt restart || /etc/init.d/tollgate-wrt start")?;
} else {
router.run_ok("/etc/init.d/tollgate-wrt stop || true")?;
router.run_ok("/etc/init.d/tollgate-wrt disable || true")?;
@@ -115,7 +113,7 @@ fn restart_services(router: &Router, enabled: bool) -> Result<()> {
echo \"br-tollgate has no kernel-level IPv4 address after cycle $i, retrying\"; \
done; \
ip -4 addr show br-tollgate 2>/dev/null | grep -q 'inet ' || \
{ echo 'br-tollgate never got a kernel-level IPv4 address after 5 cycles'; exit 1; }"
{ echo 'br-tollgate never got a kernel-level IPv4 address after 5 cycles'; exit 1; }",
)?;
Ok(())
}
+5 -2
View File
@@ -63,7 +63,7 @@ pub fn install_captive_portal_symlink(router: &Router) -> Result<()> {
fi; \
rm -rf /etc/nodogsplash/htdocs; \
ln -sf /etc/tollgate/tollgate-captive-portal-site /etc/nodogsplash/htdocs; \
fi"
fi",
)?;
Ok(())
}
@@ -91,7 +91,10 @@ pub fn configure(router: &Router, cfg: &TollGateConfig) -> Result<()> {
router.uci_set("nodogsplash.main", "nodogsplash")?;
router.uci_set("nodogsplash.main.enabled", "1")?;
router.uci_set("nodogsplash.main.gatewayinterface", "br-tollgate")?;
router.uci_set("nodogsplash.main.gatewayname", &format!("{} Portal", cfg.ssid))?;
router.uci_set(
"nodogsplash.main.gatewayname",
&format!("{} Portal", cfg.ssid),
)?;
router.uci_set("nodogsplash.main.gatewaydomainname", "TollGate.lan")?;
router.uci_set("nodogsplash.main.gatewayport", "2050")?;
+7 -8
View File
@@ -27,7 +27,10 @@ pub fn provision_ssid(router: &Router, cfg: &TollGateConfig) -> Result<()> {
("wireless.tollgate.ieee80211r", "0"),
// Stop broadcasting entirely when disabled, rather than leaving an
// open SSID up that leads nowhere once the backend is stopped.
("wireless.tollgate.disabled", if cfg.enabled { "0" } else { "1" }),
(
"wireless.tollgate.disabled",
if cfg.enabled { "0" } else { "1" },
),
],
)?;
@@ -117,13 +120,9 @@ fn provision_firewall(router: &Router) -> Result<()> {
/// Return the first available wireless radio device name (e.g. "radio0").
fn detect_radio(router: &Router) -> Result<String> {
let out = router.run_ok("uci show wireless | grep -o 'wireless\\.radio[0-9]*\\.type' | head -1")?;
let out =
router.run_ok("uci show wireless | grep -o 'wireless\\.radio[0-9]*\\.type' | head -1")?;
// Extract "radioN" from "wireless.radioN.type"
let radio = out
.trim()
.split('.')
.nth(1)
.unwrap_or("radio0")
.to_string();
let radio = out.trim().split('.').nth(1).unwrap_or("radio0").to_string();
Ok(radio)
}
+41 -14
View File
@@ -1,14 +1,14 @@
use crate::Router;
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 dhcp_start: u32, // first address in DHCP pool (default 100 → .100)
pub dhcp_limit: u32, // pool size (default 150 → .100.249)
pub masq: bool, // enable NAT on WAN zone (almost always true)
pub encryption: String, // psk2 | psk | sae | none
pub dhcp_start: u32, // first address in DHCP pool (default 100 → .100)
pub dhcp_limit: u32, // pool size (default 150 → .100.249)
pub masq: bool, // enable NAT on WAN zone (almost always true)
}
pub fn configure_wisp(router: &Router, config: &WispConfig) -> Result<()> {
@@ -54,11 +54,21 @@ pub fn configure_wisp(router: &Router, config: &WispConfig) -> Result<()> {
// "wifi reload" is not enough on some drivers — it keeps stale state.
let (down_out, down_code) = router.run("wifi down 2>&1")?;
if down_code != 0 {
info!("[{}] wifi down failed ({}): {}", router.host, down_code, down_out.trim());
info!(
"[{}] wifi down failed ({}): {}",
router.host,
down_code,
down_out.trim()
);
}
let (up_out, up_code) = router.run("wifi up 2>&1")?;
if up_code != 0 {
info!("[{}] wifi up failed ({}): {} — falling back to network restart", router.host, up_code, up_out.trim());
info!(
"[{}] wifi up failed ({}): {} — falling back to network restart",
router.host,
up_code,
up_out.trim()
);
router.run_ok("/etc/init.d/network restart 2>&1")?;
}
@@ -72,7 +82,9 @@ pub fn get_wan_status(router: &Router) -> serde_json::Value {
.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();
let encryption = router
.uci_get("wireless.wwan.encryption")
.unwrap_or_default();
let radio0_disabled = router
.uci_get("wireless.radio0.disabled")
.map(|v| v == "1")
@@ -85,7 +97,10 @@ pub fn get_wan_status(router: &Router) -> serde_json::Value {
// Interface operstate (up / down / absent)
let sta_state = if !sta_iface.is_empty() {
router
.run_ok(&format!("cat /sys/class/net/{}/operstate 2>/dev/null", sta_iface))
.run_ok(&format!(
"cat /sys/class/net/{}/operstate 2>/dev/null",
sta_iface
))
.unwrap_or_else(|_| "unknown".into())
.trim()
.to_string()
@@ -108,10 +123,18 @@ pub fn get_wan_status(router: &Router) -> serde_json::Value {
.to_string();
// LAN info for the DHCP setup display
let lan_ip = router.uci_get("network.lan.ipaddr").unwrap_or_else(|_| "192.168.1.1".into());
let lan_netmask = router.uci_get("network.lan.netmask").unwrap_or_else(|_| "255.255.255.0".into());
let dhcp_start = router.uci_get("dhcp.lan.start").unwrap_or_else(|_| "100".into());
let dhcp_limit = router.uci_get("dhcp.lan.limit").unwrap_or_else(|_| "150".into());
let lan_ip = router
.uci_get("network.lan.ipaddr")
.unwrap_or_else(|_| "192.168.1.1".into());
let lan_netmask = router
.uci_get("network.lan.netmask")
.unwrap_or_else(|_| "255.255.255.0".into());
let dhcp_start = router
.uci_get("dhcp.lan.start")
.unwrap_or_else(|_| "100".into());
let dhcp_limit = router
.uci_get("dhcp.lan.limit")
.unwrap_or_else(|_| "150".into());
// Masquerade: check WAN zone
let masq = {
@@ -126,7 +149,11 @@ pub fn get_wan_status(router: &Router) -> serde_json::Value {
info!("[{}] WAN status: configured={} ssid={:?} assoc={:?} sta_iface={:?} sta_state={:?} ip={:?} lan={} masq={}",
router.host, configured, ssid, assoc_ssid, sta_iface, sta_state, ip, lan_ip, masq);
if !wifi_log.is_empty() {
info!("[{}] wifi_log: {}", router.host, wifi_log.replace('\n', " | "));
info!(
"[{}] wifi_log: {}",
router.host,
wifi_log.replace('\n', " | ")
);
}
serde_json::json!({
+30 -7
View File
@@ -1,5 +1,5 @@
use anyhow::Result;
use crate::Router;
use anyhow::Result;
pub struct ScannedNetwork {
pub ssid: String,
@@ -42,7 +42,12 @@ fn scan_via_mtk_site_survey(router: &Router, iface: &str) -> Result<Vec<ScannedN
fn parse_mtk_site_survey(output: &str) -> Result<Vec<ScannedNetwork>> {
let mut networks = Vec::new();
for line in output.lines() {
if !line.trim_start().as_bytes().first().is_some_and(u8::is_ascii_digit) {
if !line
.trim_start()
.as_bytes()
.first()
.is_some_and(u8::is_ascii_digit)
{
continue; // skip header/summary lines; data rows start with an index
}
let ssid = line.get(8..41).unwrap_or("").trim().to_string();
@@ -51,8 +56,14 @@ fn parse_mtk_site_survey(output: &str) -> Result<Vec<ScannedNetwork>> {
}
let bssid = line.get(41..61).unwrap_or("").trim().to_string();
let security = line.get(61..84).unwrap_or("");
let channel: u8 = line.get(4..8).and_then(|s| s.trim().parse().ok()).unwrap_or(0);
let signal: i32 = line.get(84..92).and_then(|s| s.trim().parse().ok()).unwrap_or(-100);
let channel: u8 = line
.get(4..8)
.and_then(|s| s.trim().parse().ok())
.unwrap_or(0);
let signal: i32 = line
.get(84..92)
.and_then(|s| s.trim().parse().ok())
.unwrap_or(-100);
networks.push(ScannedNetwork {
ssid,
bssid,
@@ -96,7 +107,11 @@ fn find_wireless_iface(router: &Router) -> Result<(String, bool)> {
// Create a temporary managed interface directly on the PHY. This bypasses
// netifd entirely so it works even when there are no wifi-iface sections in
// UCI (common on a freshly-flashed device).
tracing::info!("[{}] Creating temporary scan interface on {}", router.host, phy);
tracing::info!(
"[{}] Creating temporary scan interface on {}",
router.host,
phy
);
// Remove any stale scan0 from a previous attempt, then add fresh
let _ = router.run("iw dev scan0 del 2>/dev/null");
router.run_ok(&format!(
@@ -119,7 +134,12 @@ fn parse_iwinfo_scan(output: &str) -> Result<Vec<ScannedNetwork>> {
networks.push(n);
}
}
let bssid = line.split("Address:").nth(1).unwrap_or("").trim().to_string();
let bssid = line
.split("Address:")
.nth(1)
.unwrap_or("")
.trim()
.to_string();
current = Some(ScannedNetwork {
ssid: String::new(),
bssid,
@@ -132,7 +152,10 @@ fn parse_iwinfo_scan(output: &str) -> Result<Vec<ScannedNetwork>> {
n.ssid = rest.trim().trim_matches('"').to_string();
} else if line.contains("Channel:") && !line.starts_with("Encryption") {
if let Some(ch_part) = line.split("Channel:").nth(1) {
n.channel = ch_part.trim().split_whitespace().next()
n.channel = ch_part
.trim()
.split_whitespace()
.next()
.and_then(|s| s.parse().ok())
.unwrap_or(0);
}