diff --git a/core/archipelago/src/api/rpc/federation/handlers.rs b/core/archipelago/src/api/rpc/federation/handlers.rs index 75666db1..39fac73f 100644 --- a/core/archipelago/src/api/rpc/federation/handlers.rs +++ b/core/archipelago/src/api/rpc/federation/handlers.rs @@ -64,8 +64,9 @@ impl RpcHandler { .and_then(|p| p.get("trust_level")) .and_then(|v| v.as_str()) .map(|s| { - TrustLevel::parse(s) - .ok_or_else(|| anyhow::anyhow!("Invalid trust_level: {s} (expected trusted|observer)")) + TrustLevel::parse(s).ok_or_else(|| { + anyhow::anyhow!("Invalid trust_level: {s} (expected trusted|observer)") + }) }) .transpose()? .unwrap_or(TrustLevel::Trusted); diff --git a/core/archipelago/src/api/rpc/lnd/payments.rs b/core/archipelago/src/api/rpc/lnd/payments.rs index e810e10f..94e12090 100644 --- a/core/archipelago/src/api/rpc/lnd/payments.rs +++ b/core/archipelago/src/api/rpc/lnd/payments.rs @@ -292,9 +292,7 @@ impl RpcHandler { let r_hash_hex = inv .get("r_hash") .and_then(|v| v.as_str()) - .and_then(|b64| { - base64::engine::general_purpose::STANDARD.decode(b64).ok() - }) + .and_then(|b64| base64::engine::general_purpose::STANDARD.decode(b64).ok()) .map(hex::encode) .unwrap_or_default(); transactions.push(serde_json::json!({ diff --git a/core/archipelago/src/api/rpc/lnd/seed_backup.rs b/core/archipelago/src/api/rpc/lnd/seed_backup.rs index b424ccf7..3509dbcf 100644 --- a/core/archipelago/src/api/rpc/lnd/seed_backup.rs +++ b/core/archipelago/src/api/rpc/lnd/seed_backup.rs @@ -56,9 +56,8 @@ impl RpcHandler { let words = crate::seed::load_lnd_aezeed_encrypted(&self.config.data_dir, &node_secret).await; node_secret.zeroize(); - let words = words.map_err(|_| { - anyhow::anyhow!("Could not decrypt the saved Lightning seed backup") - })?; + let words = words + .map_err(|_| anyhow::anyhow!("Could not decrypt the saved Lightning seed backup"))?; let word_count = words.len(); Ok(serde_json::json!({ "words": words, "word_count": word_count })) diff --git a/core/archipelago/src/api/rpc/mesh/typed_messages.rs b/core/archipelago/src/api/rpc/mesh/typed_messages.rs index e7677ad5..28cc66f6 100644 --- a/core/archipelago/src/api/rpc/mesh/typed_messages.rs +++ b/core/archipelago/src/api/rpc/mesh/typed_messages.rs @@ -478,7 +478,8 @@ impl RpcHandler { bytes, }; let payload = message_types::encode_payload(&content)?; - let envelope = TypedEnvelope::new(MeshMessageType::ContentInline, payload).with_seq(seq); + let envelope = + TypedEnvelope::new(MeshMessageType::ContentInline, payload).with_seq(seq); let wire = envelope.to_wire()?; if use_resource_transfer { svc.send_content_resource( @@ -583,8 +584,7 @@ impl RpcHandler { .map(|d| nodes.iter().any(|n| &n.did == d)) .unwrap_or(false); - let est_seconds = - (size.saturating_add(lora_bytes_per_sec - 1) / lora_bytes_per_sec).max(1); + let est_seconds = (size.saturating_add(lora_bytes_per_sec - 1) / lora_bytes_per_sec).max(1); let is_reticulum = device_type == crate::mesh::types::DeviceType::Reticulum; let (tier, reason) = if size <= MESH_AUTO_MAX { @@ -596,7 +596,10 @@ impl RpcHandler { ("auto-mesh", "No Tor path — sending inline over mesh") } } else if is_reticulum && size <= RETICULUM_RESOURCE_MAX { - ("resource-mesh", "Sending directly over LoRa via a Reticulum resource transfer") + ( + "resource-mesh", + "Sending directly over LoRa via a Reticulum resource transfer", + ) } else if size <= TOR_LARGE_WARN { if has_tor { ("tor-only", "Too large for mesh — Tor only") diff --git a/core/archipelago/src/api/rpc/middleware.rs b/core/archipelago/src/api/rpc/middleware.rs index 11a4516f..51de3108 100644 --- a/core/archipelago/src/api/rpc/middleware.rs +++ b/core/archipelago/src/api/rpc/middleware.rs @@ -214,8 +214,9 @@ pub(super) fn extract_client_ip(parts: &hyper::http::request::Parts) -> IpAddr { Some(ip) => ip, // No socket info recorded (shouldn't happen in the server path); // fall back to the pre-extension behavior. - None => forwarded_client_ip(&parts.headers) - .unwrap_or(IpAddr::V4(std::net::Ipv4Addr::LOCALHOST)), + None => { + forwarded_client_ip(&parts.headers).unwrap_or(IpAddr::V4(std::net::Ipv4Addr::LOCALHOST)) + } } } @@ -234,10 +235,7 @@ mod client_ip_tests { use super::*; use std::net::SocketAddr; - fn parts_with( - peer: Option<&str>, - real_ip: Option<&str>, - ) -> hyper::http::request::Parts { + fn parts_with(peer: Option<&str>, real_ip: Option<&str>) -> hyper::http::request::Parts { let mut builder = hyper::Request::builder().uri("/rpc/v1"); if let Some(ip) = real_ip { builder = builder.header("x-real-ip", ip); diff --git a/core/archipelago/src/api/rpc/mod.rs b/core/archipelago/src/api/rpc/mod.rs index d0328b82..3829072c 100644 --- a/core/archipelago/src/api/rpc/mod.rs +++ b/core/archipelago/src/api/rpc/mod.rs @@ -54,11 +54,11 @@ use hyper::{Request, Response, StatusCode}; use std::sync::Arc; use tracing::{debug, error}; +pub use middleware::PeerAddr; use middleware::{ derive_csrf_token, extract_client_ip, extract_cookie, sanitize_error_message, CACHEABLE_METHODS, UNAUTHENTICATED_METHODS, }; -pub use middleware::PeerAddr; use response::{cookie_header, json_response, ResponseCache, RpcError, RpcRequest, RpcResponse}; /// Default dev password when no user is set up (matches mock-backend). diff --git a/core/archipelago/src/api/rpc/openwrt.rs b/core/archipelago/src/api/rpc/openwrt.rs index 664335de..0c30a0ca 100644 --- a/core/archipelago/src/api/rpc/openwrt.rs +++ b/core/archipelago/src/api/rpc/openwrt.rs @@ -1,13 +1,12 @@ use super::RpcHandler; +use crate::network::router as net_router; use anyhow::Result; use archipelago_openwrt::{ detect, router::Router, tollgate::{self, TollGateConfig}, - wan, - wifi_scan, + wan, wifi_scan, }; -use crate::network::router as net_router; /// Default port for the local Cashu mint (nutshell / cashu-mint app). const LOCAL_MINT_PORT: u16 = 3338; @@ -23,7 +22,9 @@ impl RpcHandler { ) -> Result { let p = params.unwrap_or_default(); let subnet: [u8; 4] = parse_ipv4( - p.get("subnet").and_then(|v| v.as_str()).unwrap_or("192.168.1.0"), + p.get("subnet") + .and_then(|v| v.as_str()) + .unwrap_or("192.168.1.0"), )?; let prefix = p.get("prefix").and_then(|v| v.as_u64()).unwrap_or(24) as u8; let ssh_user = p @@ -59,8 +60,18 @@ impl RpcHandler { .get("host") .and_then(|v| v.as_str()) .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"))?; + .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") @@ -92,11 +103,14 @@ impl RpcHandler { None, Some(&ssh_user), Some(&ssh_password), - ).await; + ) + .await; } // System info - let release = router.run_ok("cat /etc/openwrt_release").unwrap_or_default(); + let release = router + .run_ok("cat /etc/openwrt_release") + .unwrap_or_default(); let hostname = router .uci_get("system.@system[0].hostname") .unwrap_or_else(|_| "unknown".into()); @@ -170,8 +184,18 @@ impl RpcHandler { .get("host") .and_then(|v| v.as_str()) .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"))?; + .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()) @@ -200,10 +224,7 @@ impl RpcHandler { .get("step_size_ms") .and_then(|v| v.as_u64()) .unwrap_or(60_000), - min_steps: p - .get("min_steps") - .and_then(|v| v.as_u64()) - .unwrap_or(1) as u32, + min_steps: p.get("min_steps").and_then(|v| v.as_u64()).unwrap_or(1) as u32, enabled: p.get("enabled").and_then(|v| v.as_bool()).unwrap_or(true), }; @@ -229,13 +250,34 @@ impl RpcHandler { 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()).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()).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()).map(|s| s.to_string()) - .or_else(|| saved.password.clone()).unwrap_or_default(); + let host = p + .get("host") + .and_then(|v| v.as_str()) + .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()) + .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()) + .map(|s| s.to_string()) + .or_else(|| saved.password.clone()) + .unwrap_or_default(); let router = Router::connect_password(&host, 22, &ssh_user, &ssh_password)?; router.verify_openwrt()?; @@ -243,13 +285,15 @@ impl RpcHandler { let networks = wifi_scan::scan_networks(&router)?; let result: Vec = networks .iter() - .map(|n| serde_json::json!({ - "ssid": n.ssid, - "bssid": n.bssid, - "signal": n.signal, - "channel": n.channel, - "encryption": n.encryption, - })) + .map(|n| { + serde_json::json!({ + "ssid": n.ssid, + "bssid": n.bssid, + "signal": n.signal, + "channel": n.channel, + "encryption": n.encryption, + }) + }) .collect(); Ok(serde_json::json!({ "networks": result })) @@ -265,18 +309,50 @@ impl RpcHandler { 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()).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()).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()).map(|s| s.to_string()) - .or_else(|| saved.password.clone()).unwrap_or_default(); + let host = p + .get("host") + .and_then(|v| v.as_str()) + .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()) + .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()) + .map(|s| s.to_string()) + .or_else(|| saved.password.clone()) + .unwrap_or_default(); - let ssid = p.get("ssid").and_then(|v| v.as_str()) - .ok_or_else(|| anyhow::anyhow!("Missing required field: ssid"))?.to_string(); - let password = p.get("password").and_then(|v| v.as_str()).unwrap_or("").to_string(); - let encryption = p.get("encryption").and_then(|v| v.as_str()).unwrap_or("psk2").to_string(); + let ssid = p + .get("ssid") + .and_then(|v| v.as_str()) + .ok_or_else(|| anyhow::anyhow!("Missing required field: ssid"))? + .to_string(); + let password = p + .get("password") + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(); + let encryption = p + .get("encryption") + .and_then(|v| v.as_str()) + .unwrap_or("psk2") + .to_string(); let dhcp_start = p.get("dhcp_start").and_then(|v| v.as_u64()).unwrap_or(100) as u32; let dhcp_limit = p.get("dhcp_limit").and_then(|v| v.as_u64()).unwrap_or(150) as u32; let masq = p.get("masq").and_then(|v| v.as_bool()).unwrap_or(true); @@ -284,7 +360,14 @@ impl RpcHandler { let router = Router::connect_password(&host, 22, &ssh_user, &ssh_password)?; router.verify_openwrt()?; - let config = wan::WispConfig { ssid: ssid.clone(), password, encryption, dhcp_start, dhcp_limit, masq }; + let config = wan::WispConfig { + ssid: ssid.clone(), + password, + encryption, + dhcp_start, + dhcp_limit, + masq, + }; wan::configure_wisp(&router, &config)?; Ok(serde_json::json!({ "ok": true, "host": host, "ssid": ssid })) @@ -325,14 +408,16 @@ fn parse_wifi_interfaces(raw: &str) -> Vec { let mut ifaces: Vec = sections .into_iter() .filter(|(_, f)| f.get("mode").map(|m| m == "ap").unwrap_or(false)) - .map(|(name, f)| serde_json::json!({ - "section": name, - "ssid": f.get("ssid").cloned().unwrap_or_default(), - "device": f.get("device").cloned().unwrap_or_default(), - "encryption": f.get("encryption").cloned().unwrap_or_else(|| "none".into()), - "network": f.get("network").cloned().unwrap_or_default(), - "disabled": f.get("disabled").map(|v| v == "1").unwrap_or(false), - })) + .map(|(name, f)| { + serde_json::json!({ + "section": name, + "ssid": f.get("ssid").cloned().unwrap_or_default(), + "device": f.get("device").cloned().unwrap_or_default(), + "encryption": f.get("encryption").cloned().unwrap_or_else(|| "none".into()), + "network": f.get("network").cloned().unwrap_or_default(), + "disabled": f.get("disabled").map(|v| v == "1").unwrap_or(false), + }) + }) .collect(); ifaces.sort_by_key(|v| v["section"].as_str().unwrap_or("").to_string()); diff --git a/core/archipelago/src/api/rpc/package/dependencies.rs b/core/archipelago/src/api/rpc/package/dependencies.rs index 5d4a2575..d6c660d9 100644 --- a/core/archipelago/src/api/rpc/package/dependencies.rs +++ b/core/archipelago/src/api/rpc/package/dependencies.rs @@ -378,9 +378,11 @@ where // Fail fast if any missing dependency has no installed container // under any name variant — waiting cannot satisfy it. - let some_dep_not_installed = missing - .iter() - .any(|dep| !dep.containers.iter().any(|c| existing.iter().any(|e| e == c))); + let some_dep_not_installed = missing.iter().any(|dep| { + !dep.containers + .iter() + .any(|c| existing.iter().any(|e| e == c)) + }); if some_dep_not_installed { let msg = match check_install_deps(package_id, &running) { Err(e) => e.to_string(), @@ -879,8 +881,10 @@ mod tests { } /// Collects "Waiting for X to start…" labels emitted during the wait. - fn label_sink() -> (Arc>>, impl FnMut(String) -> std::future::Ready<()>) - { + fn label_sink() -> ( + Arc>>, + impl FnMut(String) -> std::future::Ready<()>, + ) { let labels = Arc::new(Mutex::new(Vec::new())); let sink = { let labels = Arc::clone(&labels); @@ -930,7 +934,8 @@ mod tests { // so async_lifecycle removes the optimistic Installing entry. assert!(err.downcast_ref::().is_some()); assert!( - err.to_string().contains("LND requires a running Bitcoin node"), + err.to_string() + .contains("LND requires a running Bitcoin node"), "unexpected message: {err}" ); } diff --git a/core/archipelago/src/api/rpc/package/runtime.rs b/core/archipelago/src/api/rpc/package/runtime.rs index 84640c70..42f99577 100644 --- a/core/archipelago/src/api/rpc/package/runtime.rs +++ b/core/archipelago/src/api/rpc/package/runtime.rs @@ -63,7 +63,9 @@ impl RpcHandler { let to_start = if self.orchestrator.is_some() && uses_single_orchestrator_app(package_id) { vec![orchestrator_app_id(package_id).to_string()] - } else if let Some(members) = orchestrator_stack_members(self.orchestrator.is_some(), package_id) { + } else if let Some(members) = + orchestrator_stack_members(self.orchestrator.is_some(), package_id) + { members } else { ordered_containers_for_start(package_id).await? @@ -170,11 +172,12 @@ impl RpcHandler { // fallback to a raw `podman stop` that races systemd over the unit // (immich, gate 2026-07-09). let to_stop_ids = if !single_orchestrator_app { - orchestrator_stack_members(self.orchestrator.is_some(), package_id) - .map(|mut members| { + orchestrator_stack_members(self.orchestrator.is_some(), package_id).map( + |mut members| { members.reverse(); members - }) + }, + ) } else { None }; @@ -280,7 +283,9 @@ impl RpcHandler { let companion_app_id = package_id_owned.clone(); let to_restart = if single_orchestrator_app { vec![orchestrator_app_id(package_id).to_string()] - } else if let Some(members) = orchestrator_stack_members(self.orchestrator.is_some(), package_id) { + } else if let Some(members) = + orchestrator_stack_members(self.orchestrator.is_some(), package_id) + { // Restart stacks via member APP ids: restarting by live container // name podman-stops the quadlet container (systemd --rm removes // it) and the start half then finds no such container — a 5-min @@ -2160,7 +2165,9 @@ mod tests { assert!(is_missing_container_error( "Error: no such object: \"mempool\"" )); - assert!(is_missing_container_error("Error: no such container mempool")); + assert!(is_missing_container_error( + "Error: no such container mempool" + )); assert!(is_missing_container_error( "Error: no container with name or id \"x\" found" )); diff --git a/core/archipelago/src/api/rpc/system/handlers.rs b/core/archipelago/src/api/rpc/system/handlers.rs index 5ddfa4a4..cb2969d1 100644 --- a/core/archipelago/src/api/rpc/system/handlers.rs +++ b/core/archipelago/src/api/rpc/system/handlers.rs @@ -100,7 +100,8 @@ impl RpcHandler { } let location_file = self.config.data_dir.join("server-location.json"); - let payload = serde_json::json!({ "lat": lat, "lon": lon, "share_location": share_location }); + let payload = + serde_json::json!({ "lat": lat, "lon": lon, "share_location": share_location }); tokio::fs::write(&location_file, serde_json::to_vec(&payload)?) .await .context("Failed to write server location")?; @@ -130,7 +131,9 @@ impl RpcHandler { /// resolves to on the LAN (avahi-daemon advertises `.local`). /// Lets Settings show users where to reach this node over HTTPS for /// features (mic/camera access) that require a secure context. - pub(in crate::api::rpc) async fn handle_system_get_hostname(&self) -> Result { + pub(in crate::api::rpc) async fn handle_system_get_hostname( + &self, + ) -> Result { let hostname = tokio::fs::read_to_string("/etc/hostname") .await .map(|s| s.trim().to_string()) @@ -401,7 +404,8 @@ async fn set_system_hostname(hostname: &str) -> Result<()> { /// top once a node has been renamed away from the install-time default. async fn regenerate_tls_cert(hostname: &str) -> Result<()> { let subj = format!("/C=XX/ST=Bitcoin/L=Node/O=Archipelago/CN={hostname}"); - let san = format!("subjectAltName=DNS:{hostname},DNS:{hostname}.local,DNS:localhost,IP:127.0.0.1"); + let san = + format!("subjectAltName=DNS:{hostname},DNS:{hostname}.local,DNS:localhost,IP:127.0.0.1"); let output = tokio::process::Command::new("/usr/bin/sudo") .args([ "-n", diff --git a/core/archipelago/src/app_ops.rs b/core/archipelago/src/app_ops.rs index b76c17f5..a9fc7acf 100644 --- a/core/archipelago/src/app_ops.rs +++ b/core/archipelago/src/app_ops.rs @@ -53,9 +53,7 @@ pub fn stack_member_app_ids(package_id: &str) -> &'static [&'static str] { // The legacy umbrella id maps to the split stack (the orchestrator's // umbrella alias handles this too; listing it here keeps the RPC // layer's fan-out explicit). - "mempool" | "mempool-web" => { - &["archy-mempool-db", "mempool-api", "archy-mempool-web"] - } + "mempool" | "mempool-web" => &["archy-mempool-db", "mempool-api", "archy-mempool-web"], _ => &[], } } diff --git a/core/archipelago/src/bootstrap.rs b/core/archipelago/src/bootstrap.rs index f28873ac..87183f50 100644 --- a/core/archipelago/src/bootstrap.rs +++ b/core/archipelago/src/bootstrap.rs @@ -131,7 +131,9 @@ pub async fn ensure_doctor_installed() { Err(e) => warn!("Bitcoin RPC repair failed (non-fatal): {:#}", e), } match run_apps_dir_repair().await { - Ok(true) => info!("Populated /opt/archipelago/apps from installer copy at /etc/archipelago/apps"), + Ok(true) => { + info!("Populated /opt/archipelago/apps from installer copy at /etc/archipelago/apps") + } Ok(false) => debug!("/opt/archipelago/apps already populated (or no installer copy)"), Err(e) => warn!("Apps dir repair failed (non-fatal): {:#}", e), } diff --git a/core/archipelago/src/container/app_catalog.rs b/core/archipelago/src/container/app_catalog.rs index f18b3d48..fccb23ba 100644 --- a/core/archipelago/src/container/app_catalog.rs +++ b/core/archipelago/src/container/app_catalog.rs @@ -400,10 +400,7 @@ pub async fn refresh_catalog(data_dir: &Path) -> anyhow::Result Err(last_err.unwrap_or_else(|| anyhow::anyhow!("no catalog mirrors reachable"))) } -async fn fetch_one( - client: &reqwest::Client, - url: &str, -) -> anyhow::Result<(AppCatalog, String)> { +async fn fetch_one(client: &reqwest::Client, url: &str) -> anyhow::Result<(AppCatalog, String)> { let resp = client.get(url).send().await?; if !resp.status().is_success() { anyhow::bail!("HTTP {}", resp.status()); @@ -510,7 +507,10 @@ mod tests { // on the apps HashMap's nondeterministic key order (seen live on .228). let dir = tempfile::tempdir().unwrap(); let body = r#"{"schema":1,"apps":{"demo":{"version":"1.0.0"}}}"#; - assert!(write_cache(dir.path(), body).unwrap(), "first write is a change"); + assert!( + write_cache(dir.path(), body).unwrap(), + "first write is a change" + ); assert!( !write_cache(dir.path(), body).unwrap(), "identical rewrite is not a change" diff --git a/core/archipelago/src/container/image_policy.rs b/core/archipelago/src/container/image_policy.rs index ea433d43..c6d7eab6 100644 --- a/core/archipelago/src/container/image_policy.rs +++ b/core/archipelago/src/container/image_policy.rs @@ -7,12 +7,8 @@ /// Registries images may be pulled from with an explicit host part. /// (git.tx1138.com was removed 2026-07-10: the host is retired and must /// never be pulled through again.) -pub const TRUSTED_REGISTRIES: &[&str] = &[ - "docker.io", - "ghcr.io", - "localhost", - "146.59.87.168:3000", -]; +pub const TRUSTED_REGISTRIES: &[&str] = + &["docker.io", "ghcr.io", "localhost", "146.59.87.168:3000"]; /// Validate a container image reference. /// diff --git a/core/archipelago/src/container/lnd.rs b/core/archipelago/src/container/lnd.rs index fb14a912..b6379a0c 100644 --- a/core/archipelago/src/container/lnd.rs +++ b/core/archipelago/src/container/lnd.rs @@ -727,14 +727,12 @@ pub async fn ensure_btcpay_lnd_connection_secret(secrets_dir: &std::path::Path) Ok(s) => s, Err(_) => return Ok(()), // LND not installed/provisioned yet }; - let thumbprint = - cert_sha256_thumbprint(&pem).context("computing LND tls.cert thumbprint")?; + let thumbprint = cert_sha256_thumbprint(&pem).context("computing LND tls.cert thumbprint")?; let target = secrets_dir.join(BTCPAY_LND_CONNECTION_SECRET); // Fast path (no sudo): existing secret already pins the current cert. if let Ok(existing) = fs::read_to_string(&target).await { - if !existing.trim().is_empty() - && existing.contains(&format!("certthumbprint={thumbprint}")) + if !existing.trim().is_empty() && existing.contains(&format!("certthumbprint={thumbprint}")) { return Ok(()); } @@ -782,7 +780,9 @@ mod tests { conf_path: tmp.path().join("lnd/lnd.conf"), }; - let out = ensure_config(&paths, "secret", "bitcoin-knots").await.unwrap(); + let out = ensure_config(&paths, "secret", "bitcoin-knots") + .await + .unwrap(); assert_eq!(out, EnsureOutcome::Written); let conf = fs::read_to_string(&paths.conf_path).await.unwrap(); assert!(conf.contains("bitcoin.active=true")); @@ -801,11 +801,15 @@ mod tests { }; assert_eq!( - ensure_config(&paths, "first", "bitcoin-knots").await.unwrap(), + ensure_config(&paths, "first", "bitcoin-knots") + .await + .unwrap(), EnsureOutcome::Written ); assert_eq!( - ensure_config(&paths, "second", "bitcoin-knots").await.unwrap(), + ensure_config(&paths, "second", "bitcoin-knots") + .await + .unwrap(), EnsureOutcome::Written ); let conf = fs::read_to_string(&paths.conf_path).await.unwrap(); @@ -854,7 +858,9 @@ mod tests { .unwrap(); assert_eq!( - ensure_config(&paths, "repaired", "bitcoin-knots").await.unwrap(), + ensure_config(&paths, "repaired", "bitcoin-knots") + .await + .unwrap(), EnsureOutcome::Written ); let conf = fs::read_to_string(&paths.conf_path).await.unwrap(); diff --git a/core/archipelago/src/container/prod_orchestrator.rs b/core/archipelago/src/container/prod_orchestrator.rs index e9494028..6cb14b55 100644 --- a/core/archipelago/src/container/prod_orchestrator.rs +++ b/core/archipelago/src/container/prod_orchestrator.rs @@ -1468,9 +1468,7 @@ impl ProdContainerOrchestrator { ); } } - for (port, a, b) in - host_port_collisions(state.manifests.values().map(|lm| &lm.manifest)) - { + for (port, a, b) in host_port_collisions(state.manifests.values().map(|lm| &lm.manifest)) { tracing::error!( port, app_a = %a, @@ -1666,7 +1664,10 @@ impl ProdContainerOrchestrator { // and left the unit down for minutes (.228 mempool frontend, gate // 2026-07-09). Skip this cycle; the worker owns the outcome. if crate::app_ops::lifecycle_op_in_flight(&app_id) { - report.record(&app_id, ReconcileAction::Left("lifecycle-op-in-flight".into())); + report.record( + &app_id, + ReconcileAction::Left("lifecycle-op-in-flight".into()), + ); crate::crash_recovery::pending_boot_start_done(&app_id); crate::crash_recovery::pending_boot_start_done(&container_name); continue; @@ -1773,10 +1774,9 @@ impl ProdContainerOrchestrator { { let state = self.state.read().await; - for (app, dep) in degraded_running_apps( - &report, - state.manifests.values().map(|lm| &lm.manifest), - ) { + for (app, dep) in + degraded_running_apps(&report, state.manifests.values().map(|lm| &lm.manifest)) + { tracing::error!( app_id = %app, dependency = %dep, @@ -3184,8 +3184,7 @@ impl ProdContainerOrchestrator { // `optional` secret_env — btcpay must still start when LND is // absent or the derivation fails, so log-and-continue. if let Err(e) = - crate::container::lnd::ensure_btcpay_lnd_connection_secret(&self.secrets_dir) - .await + crate::container::lnd::ensure_btcpay_lnd_connection_secret(&self.secrets_dir).await { tracing::warn!(error = %e, "btcpay-lnd-connection secret not generated; btcpay will run without the internal LND node"); } @@ -3260,20 +3259,17 @@ impl ProdContainerOrchestrator { manifest.app.container.secret_env_refs = Vec::new(); manifest.app.container.secret_env_hash = None; } else { - let hash = - archipelago_container::manifest::secret_env_content_hash(&secret_bearing); + let hash = archipelago_container::manifest::secret_env_content_hash(&secret_bearing); let app_id = manifest.app.id.clone(); manifest.app.container.secret_env_refs = secret_bearing .into_iter() - .map(|(key, value)| archipelago_container::manifest::SecretEnvRef { - secret_name: format!( - "archy-env-{}-{}", - app_id, - key.to_ascii_lowercase() - ), - env_key: key, - value, - }) + .map( + |(key, value)| archipelago_container::manifest::SecretEnvRef { + secret_name: format!("archy-env-{}-{}", app_id, key.to_ascii_lowercase()), + env_key: key, + value, + }, + ) .collect(); manifest.app.container.secret_env_hash = Some(hash.clone()); @@ -3281,12 +3277,7 @@ impl ProdContainerOrchestrator { // the steady-state reconcile free: podman is only consulted when // the resolved content actually changed (or on first touch after // boot). Mock runtimes no-op via the trait default. - let cached = self - .env_secret_cache - .lock() - .await - .get(&app_id) - .cloned(); + let cached = self.env_secret_cache.lock().await.get(&app_id).cloned(); if cached.as_deref() != Some(hash.as_str()) { self.runtime .ensure_env_secrets(&manifest.app.container.secret_env_refs) @@ -3926,7 +3917,10 @@ impl ContainerOrchestrator for ProdContainerOrchestrator { async fn start(&self, app_id: &str) -> Result<()> { if let Some(members) = self.mempool_umbrella_members(app_id).await { - tracing::info!(app_id, "starting legacy umbrella id via split-stack members"); + tracing::info!( + app_id, + "starting legacy umbrella id via split-stack members" + ); for (i, member) in members.iter().enumerate() { if i > 0 { tokio::time::sleep(std::time::Duration::from_secs(2)).await; @@ -3969,7 +3963,10 @@ impl ContainerOrchestrator for ProdContainerOrchestrator { async fn stop(&self, app_id: &str) -> Result<()> { if let Some(members) = self.mempool_umbrella_members(app_id).await { - tracing::info!(app_id, "stopping legacy umbrella id via split-stack members"); + tracing::info!( + app_id, + "stopping legacy umbrella id via split-stack members" + ); for member in members.iter().rev() { Box::pin(self.stop(member)) .await @@ -4036,7 +4033,10 @@ impl ContainerOrchestrator for ProdContainerOrchestrator { async fn restart(&self, app_id: &str) -> Result<()> { if let Some(members) = self.mempool_umbrella_members(app_id).await { - tracing::info!(app_id, "restarting legacy umbrella id via split-stack members"); + tracing::info!( + app_id, + "restarting legacy umbrella id via split-stack members" + ); for (i, member) in members.iter().enumerate() { if i > 0 { tokio::time::sleep(std::time::Duration::from_secs(2)).await; @@ -4239,9 +4239,8 @@ fn command_argv_drifted( // therefore Config.Cmd) has them as spaces. Normalize both sides so the // same script never reads as drift over line breaks alone (bitcoin-knots // and fedimint-gateway on .228, 2026-07-08). - let norm = |v: &[String]| -> Vec { - v.iter().map(|s| s.replace(['\r', '\n'], " ")).collect() - }; + let norm = + |v: &[String]| -> Vec { v.iter().map(|s| s.replace(['\r', '\n'], " ")).collect() }; let current_cmd = norm(current_cmd); let expected_args = norm(expected_args); let Some(expected_entry) = expected_entry else { @@ -5508,14 +5507,20 @@ app: ("bitcoin-knots", ReconcileAction::Installed), ("lnd", ReconcileAction::NoOp), ]); - assert_eq!(cascade_pairs_for_report(&r, &none), vec![("bitcoin-knots", "lnd")]); + assert_eq!( + cascade_pairs_for_report(&r, &none), + vec![("bitcoin-knots", "lnd")] + ); // Backend merely started from stopped also moves the IP → cascade. let r = report(vec![ ("bitcoin-core", ReconcileAction::Started), ("lnd", ReconcileAction::NoOp), ]); - assert_eq!(cascade_pairs_for_report(&r, &none), vec![("bitcoin-core", "lnd")]); + assert_eq!( + cascade_pairs_for_report(&r, &none), + vec![("bitcoin-core", "lnd")] + ); // Backend untouched → no cascade. let r = report(vec![ @@ -6016,7 +6021,9 @@ app: let calls = rt.calls(); for name in ["archy-mempool-db", "mempool-api", "archy-mempool-web"] { assert!( - calls.iter().any(|c| c == &format!("start_container:{name}")), + calls + .iter() + .any(|c| c == &format!("start_container:{name}")), "{name} not started: {calls:?}" ); } diff --git a/core/archipelago/src/container/quadlet.rs b/core/archipelago/src/container/quadlet.rs index 7c8bdd12..3c091102 100644 --- a/core/archipelago/src/container/quadlet.rs +++ b/core/archipelago/src/container/quadlet.rs @@ -1258,7 +1258,10 @@ app: assert!(u.read_only_root); assert!(u.no_new_privileges); assert_eq!(u.cap_add, vec!["NET_BIND_SERVICE"]); - assert_eq!(u.ports, vec![(8332, 8332, "tcp".to_string(), String::new())]); + assert_eq!( + u.ports, + vec![(8332, 8332, "tcp".to_string(), String::new())] + ); assert_eq!(u.environment, vec!["BITCOIN_NETWORK=mainnet"]); assert_eq!(u.bind_mounts.len(), 1); assert_eq!( diff --git a/core/archipelago/src/content_server.rs b/core/archipelago/src/content_server.rs index c9bae8f2..07f81568 100644 --- a/core/archipelago/src/content_server.rs +++ b/core/archipelago/src/content_server.rs @@ -607,14 +607,9 @@ mod prune_missing_content_tests { availability: Availability::AllPeers, added_at: "2026-01-01T00:00:00Z".to_string(), }; - save_catalog( - data_dir, - &ContentCatalog { - items: vec![item], - }, - ) - .await - .unwrap(); + save_catalog(data_dir, &ContentCatalog { items: vec![item] }) + .await + .unwrap(); // File was never written to disk under content/files/ or filebrowser/. let result = serve_content(data_dir, "missing-item", None, None, None, None) diff --git a/core/archipelago/src/crash_recovery.rs b/core/archipelago/src/crash_recovery.rs index 9831ab78..959f3997 100644 --- a/core/archipelago/src/crash_recovery.rs +++ b/core/archipelago/src/crash_recovery.rs @@ -60,8 +60,9 @@ pub fn is_recovery_complete() -> bool { // the outcome — a container that truly failed goes back to showing its // real state on the next scan. -static PENDING_BOOT_STARTS: std::sync::LazyLock>> = - std::sync::LazyLock::new(|| std::sync::RwLock::new(std::collections::HashSet::new())); +static PENDING_BOOT_STARTS: std::sync::LazyLock< + std::sync::RwLock>, +> = std::sync::LazyLock::new(|| std::sync::RwLock::new(std::collections::HashSet::new())); /// Register container/app names an active recovery or reconcile pass /// intends to start. @@ -182,7 +183,10 @@ pub async fn load_user_uninstalled(data_dir: &Path) -> std::collections::HashSet } /// Save the set of user-uninstalled app/container names to disk. -pub async fn save_user_uninstalled(data_dir: &Path, uninstalled: &std::collections::HashSet) { +pub async fn save_user_uninstalled( + data_dir: &Path, + uninstalled: &std::collections::HashSet, +) { let path = data_dir.join(USER_UNINSTALLED_FILE); if let Ok(json) = serde_json::to_string_pretty(uninstalled) { let _ = fs::write(&path, json).await; @@ -244,10 +248,7 @@ pub async fn check_for_crash(data_dir: &Path) -> Result() { - if pid != std::process::id() - && is_process_running(pid) - && process_is_archipelago(pid) - { + if pid != std::process::id() && is_process_running(pid) && process_is_archipelago(pid) { warn!( "Previous process (PID {}) is still running — not a crash, skipping recovery", pid diff --git a/core/archipelago/src/federation/invites.rs b/core/archipelago/src/federation/invites.rs index 4c1071ec..d05a13dd 100644 --- a/core/archipelago/src/federation/invites.rs +++ b/core/archipelago/src/federation/invites.rs @@ -360,8 +360,8 @@ mod tests { None, TrustLevel::Trusted, ) - .await - .unwrap(); + .await + .unwrap(); assert!(code.starts_with("fed1:")); let parsed = parse_invite(&code).unwrap(); @@ -384,8 +384,8 @@ mod tests { Some(fips), TrustLevel::Trusted, ) - .await - .unwrap(); + .await + .unwrap(); let parsed = parse_invite(&code).unwrap(); assert_eq!(parsed.fips_npub.as_deref(), Some(fips)); } diff --git a/core/archipelago/src/federation/sync.rs b/core/archipelago/src/federation/sync.rs index 7fdc3944..278eb8b4 100644 --- a/core/archipelago/src/federation/sync.rs +++ b/core/archipelago/src/federation/sync.rs @@ -403,7 +403,21 @@ mod tests { last_transport_at: None, }, ]; - let state = build_local_state(vec![], 0.0, 0, 0, 0, 0, 0, true, None, None, None, &peers, None); + let state = build_local_state( + vec![], + 0.0, + 0, + 0, + 0, + 0, + 0, + true, + None, + None, + None, + &peers, + None, + ); assert_eq!(state.federated_peers.len(), 1); assert_eq!(state.federated_peers[0].did, "did:key:zTrusted"); assert_eq!( diff --git a/core/archipelago/src/health_monitor.rs b/core/archipelago/src/health_monitor.rs index 0f1489c7..613db8e4 100644 --- a/core/archipelago/src/health_monitor.rs +++ b/core/archipelago/src/health_monitor.rs @@ -1486,7 +1486,10 @@ mod tests { #[tokio::test] async fn lifecycle_guard_covers_name_app_id_and_legacy_underscores() { - assert!(!lifecycle_op_covers_container("hm-guard-app", "hm-guard-app")); + assert!(!lifecycle_op_covers_container( + "hm-guard-app", + "hm-guard-app" + )); // Held package lock covers the container directly and via stack // membership; the '_'→'-' probe covers legacy underscore names. diff --git a/core/archipelago/src/main.rs b/core/archipelago/src/main.rs index c33d36c1..a105df47 100644 --- a/core/archipelago/src/main.rs +++ b/core/archipelago/src/main.rs @@ -107,8 +107,7 @@ async fn main() -> Result<()> { // RUST_LOG=archipelago=debug) to get debug logs back when debugging. tracing_subscriber::fmt() .with_env_filter( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| "info".into()), + tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()), ) .init(); diff --git a/core/archipelago/src/mesh/listener/node_cmd.rs b/core/archipelago/src/mesh/listener/node_cmd.rs index 0a87ca90..c066ab3b 100644 --- a/core/archipelago/src/mesh/listener/node_cmd.rs +++ b/core/archipelago/src/mesh/listener/node_cmd.rs @@ -162,7 +162,10 @@ async fn status_line() -> String { } else { format!("electrum {:.0}%", e.progress_pct) }; - format!("Archipelago OS v{}: {btc}, {elec}.", env!("CARGO_PKG_VERSION")) + format!( + "Archipelago OS v{}: {btc}, {elec}.", + env!("CARGO_PKG_VERSION") + ) } #[cfg(test)] diff --git a/core/archipelago/src/mesh/listener/session.rs b/core/archipelago/src/mesh/listener/session.rs index 04f89bb1..d49fd86a 100644 --- a/core/archipelago/src/mesh/listener/session.rs +++ b/core/archipelago/src/mesh/listener/session.rs @@ -141,7 +141,9 @@ impl MeshRadioDevice { anyhow::bail!("Native image send is Reticulum-only") } Self::Reticulum(device) => { - device.send_native_image(dest_pubkey_prefix, mime, bytes, caption).await + device + .send_native_image(dest_pubkey_prefix, mime, bytes, caption) + .await } } } @@ -297,7 +299,9 @@ async fn auto_detect_and_open( info!(path = %path, "Found Reticulum (RNode) device via auto-detect"); return Ok((path.clone(), MeshRadioDevice::Reticulum(dev), info)); } - Err(e) => debug!(path = %path, error = %e, "Reticulum daemon failed to initialize"), + Err(e) => { + debug!(path = %path, error = %e, "Reticulum daemon failed to initialize") + } }, Err(e) => debug!(path = %path, error = %e, "Not a Reticulum RNode"), } @@ -323,7 +327,9 @@ async fn auto_detect_and_open( } Err(e) => debug!(path = %path, error = %e, "Not a Meshtastic device"), }, - Err(e) => debug!(path = %path, error = %e, "Could not open serial port for Meshtastic"), + Err(e) => { + debug!(path = %path, error = %e, "Could not open serial port for Meshtastic") + } } } } @@ -399,7 +405,9 @@ async fn open_preferred_path( { Ok(mut dev) => match dev.initialize().await { Ok(info) => return Ok((MeshRadioDevice::Reticulum(dev), info)), - Err(e) => debug!(path = %path, error = %e, "Preferred path is not a working Reticulum RNode"), + Err(e) => { + debug!(path = %path, error = %e, "Preferred path is not a working Reticulum RNode") + } }, Err(e) => debug!(path = %path, error = %e, "Could not open preferred path as Reticulum"), } @@ -431,26 +439,22 @@ async fn open_reticulum_tcp( our_x25519_pubkey_hex: &str, ) -> Result<(String, MeshRadioDevice, DeviceInfo)> { let mut dev = match cfg { - ReticulumTcpConfig::Server { bind } => { - ReticulumLink::open_tcp_server( - bind, - data_dir, - Some(our_ed_pubkey_hex), - Some(our_x25519_pubkey_hex), - ) - .await - .context("Could not open Reticulum TCP server interface")? - } - ReticulumTcpConfig::Client { connect } => { - ReticulumLink::open_tcp_client( - connect, - data_dir, - Some(our_ed_pubkey_hex), - Some(our_x25519_pubkey_hex), - ) - .await - .context("Could not open Reticulum TCP client interface")? - } + ReticulumTcpConfig::Server { bind } => ReticulumLink::open_tcp_server( + bind, + data_dir, + Some(our_ed_pubkey_hex), + Some(our_x25519_pubkey_hex), + ) + .await + .context("Could not open Reticulum TCP server interface")?, + ReticulumTcpConfig::Client { connect } => ReticulumLink::open_tcp_client( + connect, + data_dir, + Some(our_ed_pubkey_hex), + Some(our_x25519_pubkey_hex), + ) + .await + .context("Could not open Reticulum TCP client interface")?, }; let info = dev .initialize() @@ -790,7 +794,9 @@ fn meshtastic_contact_id(public_key_hex: &str) -> Option { fn reticulum_contact_id(public_key_hex: &str) -> Option { let bytes = hex::decode(public_key_hex).ok()?; let hash: [u8; 16] = bytes.try_into().ok()?; - Some(super::super::reticulum::reticulum_contact_id_from_hash(&hash)) + Some(super::super::reticulum::reticulum_contact_id_from_hash( + &hash, + )) } /// Drain any queued messages from the device. @@ -873,12 +879,23 @@ pub(super) async fn run_mesh_session( "Preferred path {} probe failed: {} — trying auto-detect", path, e ); - auto_detect_and_open(data_dir, our_ed_pubkey_hex, our_x25519_pubkey_hex, device_kind) - .await? + auto_detect_and_open( + data_dir, + our_ed_pubkey_hex, + our_x25519_pubkey_hex, + device_kind, + ) + .await? } } } else { - auto_detect_and_open(data_dir, our_ed_pubkey_hex, our_x25519_pubkey_hex, device_kind).await? + auto_detect_and_open( + data_dir, + our_ed_pubkey_hex, + our_x25519_pubkey_hex, + device_kind, + ) + .await? }; // Update status diff --git a/core/archipelago/src/mesh/meshtastic.rs b/core/archipelago/src/mesh/meshtastic.rs index c57bf3af..0163c460 100644 --- a/core/archipelago/src/mesh/meshtastic.rs +++ b/core/archipelago/src/mesh/meshtastic.rs @@ -414,7 +414,11 @@ impl MeshtasticDevice { // tx_power defaults to max, which is what we want for a stock mesh. let mut lora = Vec::new(); encode_varint_field_into(LORA_USE_PRESET_FIELD, 1, &mut lora); - encode_varint_field_into(LORA_MODEM_PRESET_FIELD, LORA_MODEM_PRESET_LONG_FAST, &mut lora); + encode_varint_field_into( + LORA_MODEM_PRESET_FIELD, + LORA_MODEM_PRESET_LONG_FAST, + &mut lora, + ); encode_varint_field_into(LORA_REGION_FIELD, region_code as u64, &mut lora); encode_varint_field_into(LORA_HOP_LIMIT_FIELD, 3, &mut lora); encode_varint_field_into(LORA_TX_ENABLED_FIELD, 1, &mut lora); @@ -772,7 +776,9 @@ impl MeshtasticDevice { if let Err(e) = self.send_to_radio(&encode_want_config()).await { warn!("Failed to re-request config after radio reboot: {}", e); } else { - info!("Re-requested Meshtastic config after reboot — packet stream resubscribed"); + info!( + "Re-requested Meshtastic config after reboot — packet stream resubscribed" + ); } } if let Some(inbound) = inbound { @@ -890,7 +896,10 @@ impl MeshtasticDevice { if let Some((region, modem_preset)) = parse_config_lora_region(value) { self.current_region = Some(region); self.current_modem_preset = Some(modem_preset); - debug!(region, modem_preset, "Meshtastic LoRa region/preset from device config"); + debug!( + region, + modem_preset, "Meshtastic LoRa region/preset from device config" + ); } None } @@ -1066,7 +1075,10 @@ fn packet_to_inbound_frame( if packet.portnum == POSITION_APP { if let Some((lat, lon)) = parse_position_lat_lon(&packet.payload) { - debug!(from = format!("!{:08x}", from), lat, lon, "Meshtastic position update"); + debug!( + from = format!("!{:08x}", from), + lat, lon, "Meshtastic position update" + ); contact.lat = Some(lat); contact.lon = Some(lon); } @@ -1811,10 +1823,10 @@ mod tests { encode_fixed32_field(1, 0x1111_2222, &mut packet); // from encode_len_field(4, &decoded, &mut packet); // decoded encode_fixed32_field(8, (-7.5f32).to_bits(), &mut packet); // rx_snr - // int32 rx_rssi=-92 dBm, protobuf varint-encodes a negative int32 as - // the 10-byte two's-complement-extended varint; truncating back to - // i32 after decode recovers the original value (see parse_mesh_packet's - // `v as i32` cast — confirmed by this roundtrip, not just asserted). + // int32 rx_rssi=-92 dBm, protobuf varint-encodes a negative int32 as + // the 10-byte two's-complement-extended varint; truncating back to + // i32 after decode recovers the original value (see parse_mesh_packet's + // `v as i32` cast — confirmed by this roundtrip, not just asserted). encode_varint_field_into(12, (-92i32) as u32 as u64, &mut packet); let parsed = parse_mesh_packet(&packet).expect("packet should parse"); @@ -1901,7 +1913,10 @@ mod tests { // must still update the contact's signal/position bookkeeping. let frame = packet_to_inbound_frame(&packet, Some(0x1111_1111), &mut contacts, &mut peer_pubkeys); - assert!(frame.is_none(), "POSITION_APP must not surface as a chat frame"); + assert!( + frame.is_none(), + "POSITION_APP must not surface as a chat frame" + ); let contact = contacts.get(&from).expect("contact should be tracked"); assert_eq!(contact.snr, Some(-6.0)); assert_eq!(contact.rssi, Some(-80)); @@ -1930,7 +1945,10 @@ mod tests { // A `to == BROADCAST_NUM` text is a channel broadcast (3ccc on public // LongFast), so it routes to the channel thread, carrying its sender. assert_eq!(frame.code, protocol::RESP_MESHTASTIC_CHANNEL_TEXT); - assert_eq!(frame.data[0], 0, "no channel field set => primary/public (0)"); + assert_eq!( + frame.data[0], 0, + "no channel field set => primary/public (0)" + ); assert_eq!(&frame.data[1..7], &[0xcc, 0x3c, 0x00, 0x00, 0x6d, 0x65]); assert_eq!(&frame.data[7..], b"hello from 3ccc"); assert!(contacts.contains_key(&from)); @@ -1954,9 +1972,8 @@ mod tests { encode_len_field(4, &decoded, &mut packet); encode_fixed32_field(7, 12_345, &mut packet); - let frame = - packet_to_inbound_frame(&packet, Some(me), &mut contacts, &mut peer_pubkeys) - .expect("directed DM must surface"); + let frame = packet_to_inbound_frame(&packet, Some(me), &mut contacts, &mut peer_pubkeys) + .expect("directed DM must surface"); assert_eq!(frame.code, protocol::RESP_CONTACT_MSG_V3); let (sender_prefix, payload, _snr) = protocol::parse_contact_msg_v3_raw(&frame.data).unwrap(); diff --git a/core/archipelago/src/mesh/message_types.rs b/core/archipelago/src/mesh/message_types.rs index 43cee9ec..f8bf3063 100644 --- a/core/archipelago/src/mesh/message_types.rs +++ b/core/archipelago/src/mesh/message_types.rs @@ -213,7 +213,11 @@ pub struct TypedEnvelope { /// Unix timestamp (seconds since epoch). pub ts: u32, /// Optional Ed25519 signature of (t || v || ts_bytes) — for signed messages. - #[serde(default, skip_serializing_if = "Option::is_none", with = "compact_bytes_opt")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "compact_bytes_opt" + )] pub sig: Option>, /// Message sequence number (per-sender, monotonically increasing). #[serde(default)] @@ -564,7 +568,11 @@ pub struct ContentRefPayload { pub mime: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub filename: Option, - #[serde(default, skip_serializing_if = "Option::is_none", with = "base64_opt_bytes")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "base64_opt_bytes" + )] pub thumb_bytes: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub caption: Option, @@ -860,8 +868,8 @@ mod tests { use rand::rngs::OsRng; let key = SigningKey::generate(&mut OsRng); - let envelope = TypedEnvelope::new(MeshMessageType::Alert, b"test".to_vec()) - .signed_with_seq(42, &key); + let envelope = + TypedEnvelope::new(MeshMessageType::Alert, b"test".to_vec()).signed_with_seq(42, &key); assert!(envelope.verify_signature(&key.verifying_key()).unwrap()); // v2 binds seq: replaying the signed envelope under a different @@ -880,8 +888,8 @@ mod tests { // preimage, no seq) and allocate seq afterwards; verify must still // accept that. let key = SigningKey::generate(&mut OsRng); - let envelope = TypedEnvelope::new_signed(MeshMessageType::Alert, b"test".to_vec(), &key) - .with_seq(7); + let envelope = + TypedEnvelope::new_signed(MeshMessageType::Alert, b"test".to_vec(), &key).with_seq(7); assert!(envelope.verify_signature(&key.verifying_key()).unwrap()); } diff --git a/core/archipelago/src/mesh/mod.rs b/core/archipelago/src/mesh/mod.rs index d0ee096a..73339bf3 100644 --- a/core/archipelago/src/mesh/mod.rs +++ b/core/archipelago/src/mesh/mod.rs @@ -2327,7 +2327,10 @@ mod tests { // Force the dev fallback even if a packaged binary happens to exist // on this machine's PATH convention — this test wants exactly the // freshly-built venv daemon under test. - std::env::set_var("ARCHY_RETICULUM_DAEMON_BIN", "/nonexistent-force-dev-fallback"); + std::env::set_var( + "ARCHY_RETICULUM_DAEMON_BIN", + "/nonexistent-force-dev-fallback", + ); let data_dir = root.path().join("node"); std::fs::create_dir_all(data_dir.join("identity")).unwrap(); diff --git a/core/archipelago/src/mesh/reticulum.rs b/core/archipelago/src/mesh/reticulum.rs index 62944144..b278eb76 100644 --- a/core/archipelago/src/mesh/reticulum.rs +++ b/core/archipelago/src/mesh/reticulum.rs @@ -270,7 +270,9 @@ impl ReticulumLink { our_ed_pubkey_hex: Option<&str>, our_x25519_pubkey_hex: Option<&str>, ) -> Result { - probe_rnode(path).await.context("RNode KISS detect failed")?; + probe_rnode(path) + .await + .context("RNode KISS detect failed")?; Self::spawn( ReticulumInterface::Serial(path), data_dir, @@ -342,8 +344,9 @@ impl ReticulumLink { #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; - let _ = tokio::fs::set_permissions(&runtime_dir, std::fs::Permissions::from_mode(0o700)) - .await; + let _ = + tokio::fs::set_permissions(&runtime_dir, std::fs::Permissions::from_mode(0o700)) + .await; } let label = iface.label(); let iface_key = label.replace(['/', ' ', ':', ','], "_"); @@ -553,7 +556,11 @@ impl ReticulumLink { Ok(()) } - pub async fn send_text_msg(&mut self, dest_pubkey_prefix: &[u8; 6], payload: &[u8]) -> Result<()> { + pub async fn send_text_msg( + &mut self, + dest_pubkey_prefix: &[u8; 6], + payload: &[u8], + ) -> Result<()> { let dest_hash = self .prefix_to_hash .get(dest_pubkey_prefix) @@ -738,11 +745,9 @@ impl ReticulumLink { async fn drain_events(&mut self) { loop { let mut line = String::new(); - let read = tokio::time::timeout( - Duration::from_millis(20), - self.reader.read_line(&mut line), - ) - .await; + let read = + tokio::time::timeout(Duration::from_millis(20), self.reader.read_line(&mut line)) + .await; let n = match read { Ok(Ok(n)) => n, _ => break, // timeout (no data) or read error — stop draining @@ -840,7 +845,8 @@ impl ReticulumLink { // to survive a restart — give it a placeholder name (the real // one, if any, arrives via a later "announce" and overwrites // this) so its routing entry alone doesn't get lost. - if let std::collections::hash_map::Entry::Vacant(e) = self.peers.entry(source_hash) { + if let std::collections::hash_map::Entry::Vacant(e) = self.peers.entry(source_hash) + { e.insert(ReticulumPeer { dest_hash: source_hash, display_name: format!("Reticulum {}", hex::encode(&source_hash[..4])), @@ -858,13 +864,22 @@ impl ReticulumLink { // UI (dispatch.rs's existing ContentInline handling, zero new // frontend code) instead of the plain text bytes below. use base64::{engine::general_purpose::STANDARD as B64, Engine as _}; - let caption = ev.get("content").and_then(Value::as_str).filter(|s| !s.trim().is_empty()); + let caption = ev + .get("content") + .and_then(Value::as_str) + .filter(|s| !s.trim().is_empty()); if let (Some(fmt), Some(b64)) = ( ev.get("image_format").and_then(Value::as_str), ev.get("image_b64").and_then(Value::as_str), ) { if let Ok(bytes) = B64.decode(b64) { - match build_content_inline_frame(&prefix, image_format_to_mime(fmt), None, caption, bytes) { + match build_content_inline_frame( + &prefix, + image_format_to_mime(fmt), + None, + caption, + bytes, + ) { Ok(frame) => { self.inbound.push_back(frame); return; @@ -905,7 +920,8 @@ impl ReticulumLink { }, None => content_str.as_bytes().to_vec(), }; - self.inbound.push_back(build_synthetic_frame(&prefix, &content)); + self.inbound + .push_back(build_synthetic_frame(&prefix, &content)); } Some("resource_recv") => { let Some(source_hex) = ev.get("source_hash").and_then(Value::as_str) else { @@ -916,7 +932,8 @@ impl ReticulumLink { }; let prefix: [u8; 6] = source_hash[..6].try_into().unwrap(); self.prefix_to_hash.insert(prefix, source_hash); - if let std::collections::hash_map::Entry::Vacant(e) = self.peers.entry(source_hash) { + if let std::collections::hash_map::Entry::Vacant(e) = self.peers.entry(source_hash) + { e.insert(ReticulumPeer { dest_hash: source_hash, display_name: format!("Reticulum {}", hex::encode(&source_hash[..4])), @@ -939,7 +956,8 @@ impl ReticulumLink { // Resources are already a binary-safe whole-blob transfer), // so this is the same payload shape `decode.rs` already // accepts for a single-frame (non-chunked) typed envelope. - self.inbound.push_back(build_synthetic_frame(&prefix, &data)); + self.inbound + .push_back(build_synthetic_frame(&prefix, &data)); } Some("resource_progress") => { debug!( @@ -1138,7 +1156,14 @@ mod tests { #[test] fn detect_resp_found_in_kiss_stream() { - let stream = [0x10, 0x20, KISS_FEND, KISS_CMD_DETECT, KISS_DETECT_RESP, 0x99]; + let stream = [ + 0x10, + 0x20, + KISS_FEND, + KISS_CMD_DETECT, + KISS_DETECT_RESP, + 0x99, + ]; assert!(contains_detect_resp(&stream)); } @@ -1154,7 +1179,9 @@ mod tests { async fn probe_rnode_detects_real_hardware() { let port = std::env::var("ARCHY_RNODE_TEST_PORT") .expect("set ARCHY_RNODE_TEST_PORT to the RNode's serial path"); - probe_rnode(&port).await.expect("KISS detect probe failed against real hardware"); + probe_rnode(&port) + .await + .expect("KISS detect probe failed against real hardware"); } #[test] @@ -1181,7 +1208,8 @@ mod tests { arch_pubkey_hex: Some("abcdef".to_string()), }]; - let dir = std::env::temp_dir().join(format!("archy-reticulum-peers-test-{}", std::process::id())); + let dir = + std::env::temp_dir().join(format!("archy-reticulum-peers-test-{}", std::process::id())); std::fs::create_dir_all(&dir).unwrap(); let path = dir.join("peers.json"); std::fs::write(&path, serde_json::to_vec(&persisted).unwrap()).unwrap(); @@ -1207,7 +1235,10 @@ mod tests { h }; let id = reticulum_contact_id_from_hash(&hash_high_bit); - assert!(id < 0x8000_0000, "must not collide with federation-synthetic space"); + assert!( + id < 0x8000_0000, + "must not collide with federation-synthetic space" + ); assert_ne!(id, 0); let zero_hash = [0u8; 16]; diff --git a/core/archipelago/src/seed.rs b/core/archipelago/src/seed.rs index b7a8f301..a5793255 100644 --- a/core/archipelago/src/seed.rs +++ b/core/archipelago/src/seed.rs @@ -614,7 +614,9 @@ mod tests { #[tokio::test] async fn test_lnd_aezeed_empty_rejected() { let dir = tempfile::tempdir().unwrap(); - assert!(save_lnd_aezeed_encrypted(dir.path(), &[], "x").await.is_err()); + assert!(save_lnd_aezeed_encrypted(dir.path(), &[], "x") + .await + .is_err()); } #[tokio::test] diff --git a/core/archipelago/src/server.rs b/core/archipelago/src/server.rs index 991c4683..35ca03f5 100644 --- a/core/archipelago/src/server.rs +++ b/core/archipelago/src/server.rs @@ -89,8 +89,10 @@ impl Server { if let Ok(loc) = serde_json::from_slice::(&bytes) { data.server_info.lat = loc.get("lat").and_then(|v| v.as_f64()); data.server_info.lon = loc.get("lon").and_then(|v| v.as_f64()); - data.server_info.share_location = - loc.get("share_location").and_then(|v| v.as_bool()).unwrap_or(false); + data.server_info.share_location = loc + .get("share_location") + .and_then(|v| v.as_bool()) + .unwrap_or(false); } } data.server_info.tor_address = docker_packages::read_tor_address("archipelago").await; @@ -1265,8 +1267,9 @@ fn merge_preserving_transitional( /// scan is the owner: once podman reports a settled state and the id is no /// longer queued for a boot start, the fresh state wins immediately instead /// of being preserved for the transitional-stuck timeout. -static SCANNER_RESTARTING: std::sync::LazyLock>> = - std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashSet::new())); +static SCANNER_RESTARTING: std::sync::LazyLock< + std::sync::Mutex>, +> = std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashSet::new())); fn take_scanner_restarting(id: &str) -> bool { SCANNER_RESTARTING diff --git a/core/archipelago/src/tollgate_sweep.rs b/core/archipelago/src/tollgate_sweep.rs index ed4aa878..b86a7be9 100644 --- a/core/archipelago/src/tollgate_sweep.rs +++ b/core/archipelago/src/tollgate_sweep.rs @@ -67,7 +67,10 @@ pub async fn sweep_once(data_dir: &Path) -> Result { match ecash::receive_token(data_dir, token).await { Ok(amount) => { received_total += amount; - info!(amount_sats = amount, "swept TollGate ecash into local wallet"); + info!( + amount_sats = amount, + "swept TollGate ecash into local wallet" + ); } Err(e) => { // The token is still in this log line if this happens — not diff --git a/core/archipelago/src/update.rs b/core/archipelago/src/update.rs index b1c9f9e6..956860d7 100644 --- a/core/archipelago/src/update.rs +++ b/core/archipelago/src/update.rs @@ -355,8 +355,7 @@ fn parse_and_verify_manifest(raw: serde_json::Value) -> Result<(UpdateManifest, crate::trust::SignatureStatus::Verified { anchored, .. } => anchored, crate::trust::SignatureStatus::Unsigned => false, }; - let manifest: UpdateManifest = - serde_json::from_value(raw).context("parse update manifest")?; + let manifest: UpdateManifest = serde_json::from_value(raw).context("parse update manifest")?; Ok((manifest, signed)) } @@ -625,8 +624,8 @@ pub async fn verify_pending_update(data_dir: &Path) { // .116 during the v1.7.40 rollout recovery. tokio::time::sleep(std::time::Duration::from_secs(15)).await; - let deadline = std::time::Instant::now() - + std::time::Duration::from_secs(PENDING_VERIFY_WINDOW_SECS); + let deadline = + std::time::Instant::now() + std::time::Duration::from_secs(PENDING_VERIFY_WINDOW_SECS); while std::time::Instant::now() < deadline { attempt += 1; @@ -2087,7 +2086,10 @@ mod tests { key } - fn sign_value(key: &ed25519_dalek::SigningKey, mut doc: serde_json::Value) -> serde_json::Value { + fn sign_value( + key: &ed25519_dalek::SigningKey, + mut doc: serde_json::Value, + ) -> serde_json::Value { let (sig, did) = crate::trust::signed_doc::sign_detached(key, &doc).unwrap(); let obj = doc.as_object_mut().unwrap(); obj.insert("signed_by".into(), serde_json::json!(did)); diff --git a/core/archipelago/src/wallet/cashu.rs b/core/archipelago/src/wallet/cashu.rs index 5a4fc8ed..936e0155 100644 --- a/core/archipelago/src/wallet/cashu.rs +++ b/core/archipelago/src/wallet/cashu.rs @@ -171,19 +171,18 @@ impl CashuToken { let v4: TokenV4 = ciborium::from_reader(decoded.as_slice()).context("Invalid CBOR in cashuB token")?; - let proofs = v4 - .t - .into_iter() - .flat_map(|entry| { - let keyset_id = hex::encode(&entry.i); - entry.p.into_iter().map(move |p| Proof { - amount: p.a, - id: keyset_id.clone(), - secret: p.s, - c: hex::encode(&p.c), + let proofs = + v4.t.into_iter() + .flat_map(|entry| { + let keyset_id = hex::encode(&entry.i); + entry.p.into_iter().map(move |p| Proof { + amount: p.a, + id: keyset_id.clone(), + secret: p.s, + c: hex::encode(&p.c), + }) }) - }) - .collect(); + .collect(); let token = CashuToken { token: vec![TokenEntry { mint: v4.m, proofs }], @@ -407,10 +406,8 @@ mod tests { use ciborium::value::Value; let keyset_id = vec![0x00u8, 0x9a, 0x1f, 0x29, 0x32, 0x53, 0xe4, 0x1e]; - let sig = hex::decode( - "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d94ec4da0e7f6c2b4e24", - ) - .unwrap(); + let sig = hex::decode("02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d94ec4da0e7f6c2b4e24") + .unwrap(); let proof = Value::Map(vec![ (Value::from("a"), Value::from(8u64)), diff --git a/core/container/src/image_verify.rs b/core/container/src/image_verify.rs index 64b4e1f9..7d561630 100644 --- a/core/container/src/image_verify.rs +++ b/core/container/src/image_verify.rs @@ -58,7 +58,13 @@ pub async fn verify_declared_signature( sig_ref: &str, allow_insecure_registry: bool, ) -> Result<()> { - verify_with_key_path(image, sig_ref, &pinned_pubkey_path(), allow_insecure_registry).await + verify_with_key_path( + image, + sig_ref, + &pinned_pubkey_path(), + allow_insecure_registry, + ) + .await } async fn verify_with_key_path( @@ -192,7 +198,9 @@ mod tests { ) .await .unwrap_err(); - assert!(err.to_string().contains("pinned cosign public key is missing")); + assert!(err + .to_string() + .contains("pinned cosign public key is missing")); } #[tokio::test] diff --git a/core/container/src/runtime.rs b/core/container/src/runtime.rs index 72c147e9..c1e662da 100644 --- a/core/container/src/runtime.rs +++ b/core/container/src/runtime.rs @@ -158,9 +158,7 @@ impl ContainerRuntime for PodmanRuntime { .podman_cli(&["secret", "inspect", &r.secret_name, "--format", &fmt]) .await { - if out.status.success() - && String::from_utf8_lossy(&out.stdout).trim() == hash - { + if out.status.success() && String::from_utf8_lossy(&out.stdout).trim() == hash { continue; } } diff --git a/core/openwrt/src/detect.rs b/core/openwrt/src/detect.rs index cd67bcf7..b5e8d372 100644 --- a/core/openwrt/src/detect.rs +++ b/core/openwrt/src/detect.rs @@ -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 { diff --git a/core/openwrt/src/opkg.rs b/core/openwrt/src/opkg.rs index 296715ba..d62f36cf 100644 --- a/core/openwrt/src/opkg.rs +++ b/core/openwrt/src/opkg.rs @@ -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(()); diff --git a/core/openwrt/src/router.rs b/core/openwrt/src/router.rs index 97b1a84c..d0234178 100644 --- a/core/openwrt/src/router.rs +++ b/core/openwrt/src/router.rs @@ -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 { 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 { 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 { 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) } diff --git a/core/openwrt/src/tollgate/install.rs b/core/openwrt/src/tollgate/install.rs index e2634c6e..ff26a09e 100644 --- a/core/openwrt/src/tollgate/install.rs +++ b/core/openwrt/src/tollgate/install.rs @@ -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))?; diff --git a/core/openwrt/src/tollgate/mod.rs b/core/openwrt/src/tollgate/mod.rs index d92b1c55..b87073f7 100644 --- a/core/openwrt/src/tollgate/mod.rs +++ b/core/openwrt/src/tollgate/mod.rs @@ -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(()) } diff --git a/core/openwrt/src/tollgate/nodogsplash.rs b/core/openwrt/src/tollgate/nodogsplash.rs index 82bafed4..9bc59126 100644 --- a/core/openwrt/src/tollgate/nodogsplash.rs +++ b/core/openwrt/src/tollgate/nodogsplash.rs @@ -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")?; diff --git a/core/openwrt/src/tollgate/wifi.rs b/core/openwrt/src/tollgate/wifi.rs index 5d3428bd..2bdf786e 100644 --- a/core/openwrt/src/tollgate/wifi.rs +++ b/core/openwrt/src/tollgate/wifi.rs @@ -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 { - 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) } diff --git a/core/openwrt/src/wan.rs b/core/openwrt/src/wan.rs index b7dde420..11603170 100644 --- a/core/openwrt/src/wan.rs +++ b/core/openwrt/src/wan.rs @@ -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!({ diff --git a/core/openwrt/src/wifi_scan.rs b/core/openwrt/src/wifi_scan.rs index ac21918e..d16f0170 100644 --- a/core/openwrt/src/wifi_scan.rs +++ b/core/openwrt/src/wifi_scan.rs @@ -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 Result> { 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> { } 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> { 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> { 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); }