fix7: продакшен-реализация полного reconcile swap и интеграций

- Полностью переписан swap-модуль в режиме exclusive ownership\n- Добавлены verify/checks, статус фактического swap-state и drift\n- Интегрирован ensure-swap в daemon и systemd ExecStartPre\n- Усилен lock API (таймауты) и применение в traffic/firewall/swap\n- Усилен parser firewall (JSON-first + устойчивый text fallback)\n- Обновлены config validation, runtime tools и default priority\n- Синхронизированы docs и build/package hygiene\n- Удалены member Cargo.lock в пользу корневого lockfile
This commit is contained in:
2026-05-11 17:30:15 +05:00
parent 819cc9c308
commit 7a4e9764b1
17 changed files with 842 additions and 957 deletions
+63 -3
View File
@@ -95,12 +95,22 @@ impl Config {
anyhow::bail!("traffic.reconcile_interval_secs must be > 0");
}
if self.swap.enabled {
if self.swap.size_mb == 0 {
anyhow::bail!("swap.size_mb must be > 0 when swap.enabled=true");
if self.swap.size_mb < 64 {
anyhow::bail!("swap.size_mb must be >= 64 when swap.enabled=true");
}
if self.swap.size_mb > 65536 {
anyhow::bail!("swap.size_mb must be <= 65536 when swap.enabled=true");
}
if !self.swap.path.starts_with('/') {
anyhow::bail!("swap.path must be absolute");
}
let dangerous = ["/", "/etc", "/etc/fstab", "/dev", "/proc", "/sys", "/run"];
if dangerous.iter().any(|p| *p == self.swap.path) {
anyhow::bail!("swap.path is dangerous and forbidden: {}", self.swap.path);
}
if self.swap.priority < -1 || self.swap.priority > 32767 {
anyhow::bail!("swap.priority must be between -1 and 32767");
}
}
if self.anti_torrent.enabled && self.anti_torrent.max_connections_per_peer == 0 {
anyhow::bail!("anti_torrent.max_connections_per_peer must be > 0");
@@ -140,7 +150,15 @@ impl Config {
required.push("systemctl");
}
if self.swap.enabled {
required.extend(["fallocate", "chmod", "mkswap", "swapon", "sysctl"]);
required.extend([
"fallocate",
"chmod",
"mkswap",
"swapon",
"swapoff",
"sysctl",
"dd",
]);
}
for tool in required {
@@ -209,7 +227,9 @@ fn tool_exists(tool: &str) -> bool {
"chmod" => &[&["--help"]],
"mkswap" => &[&["--help"]],
"swapon" => &[&["--help"]],
"swapoff" => &[&["--help"]],
"sysctl" => &[&["--version"], &["--help"]],
"dd" => &[&["--help"]],
_ => &[&["--version"], &["--help"], &["-V"]],
};
@@ -280,4 +300,44 @@ mod tests {
assert_eq!(parse_tc_rate_bps("1000mbit").expect("bps"), 1_000_000_000);
assert!(parse_tc_rate_bps("foo").is_err());
}
#[test]
fn swap_rejects_dangerous_path() {
let cfg = Config {
amnezia: AmneziaConfig {
container_names: vec!["x".into()],
interface_candidates: vec!["awg0".into()],
interface_patterns: vec!["awg*".into()],
peer_ipv4_cidr: "10.8.1.0/24".into(),
},
traffic: TrafficConfig {
enabled: true,
peer_limit: "70mbit".into(),
root_rate: "1000mbit".into(),
reconcile_interval_secs: 15,
container_ready_timeout_secs: 30,
class_start_id: 10,
ifb_name: "ifb0".into(),
},
anti_torrent: AntiTorrentConfig {
enabled: false,
max_connections_per_peer: 100,
peer_ipv4_cidr: "10.8.1.0/24".into(),
apply_in_amnezia_netns: false,
},
reboot: RebootConfig {
enabled: false,
window_start_utc: "01:00".into(),
window_end_utc: "02:00".into(),
},
swap: SwapConfig {
enabled: true,
path: "/etc/fstab".into(),
size_mb: 2048,
priority: 1,
swappiness: 1,
},
};
assert!(cfg.validate_config().is_err());
}
}