use crate::command; use serde::Serialize; use serde_json::Value; use std::time::Duration; #[derive(Debug, Serialize)] pub struct FirewallStatus { pub backend: String, pub management_enabled: bool, pub managed_table: String, pub ruleset: Value, pub warning: Option, } pub async fn status(timeout: Duration) -> FirewallStatus { if command::run("nft", &["--version"], timeout).await.is_ok() { return match command::run("nft", &["--json", "list", "ruleset"], timeout).await { Ok(output) => FirewallStatus { backend: "nftables".into(), management_enabled: false, managed_table: "inet vps_control".into(), ruleset: serde_json::from_str(&output.stdout).unwrap_or(Value::Null), warning: None, }, Err(error) => FirewallStatus { backend: "nftables".into(), management_enabled: false, managed_table: "inet vps_control".into(), ruleset: Value::Null, warning: Some(format!("rules unavailable: {error}")), }, }; } for (program, backend) in [ ("iptables-nft", "iptables-nft"), ("iptables", "iptables-legacy"), ] { if let Ok(version) = command::run(program, &["--version"], timeout).await { let detected = if version.stdout.contains("nf_tables") { "iptables-nft" } else { backend }; let rules = command::run(program, &["-S"], timeout) .await .ok() .map(|v| v.stdout); return FirewallStatus { backend: detected.into(), management_enabled: false, managed_table: "inet vps_control".into(), ruleset: rules.map(Value::String).unwrap_or(Value::Null), warning: Some( "legacy-compatible firewall view is read-only and has limited structure".into(), ), }; } } FirewallStatus { backend: "unavailable".into(), management_enabled: false, managed_table: "inet vps_control".into(), ruleset: Value::Null, warning: Some("no supported firewall userspace utility found".into()), } }