Рефакторинг под прод: nsenter-first транспорт, drift-aware reconcile, hardening daemon/lock/reboot/swap, расширенный status и build-скрипт
This commit is contained in:
+113
-12
@@ -1,31 +1,80 @@
|
||||
use std::{thread, time::Duration};
|
||||
use std::{
|
||||
fs,
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crate::{config::Config, firewall, reboot, traffic};
|
||||
use crate::{
|
||||
config::Config,
|
||||
docker,
|
||||
firewall,
|
||||
iface,
|
||||
netns::build_transport,
|
||||
reboot,
|
||||
traffic,
|
||||
};
|
||||
use tracing::{error, info};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct LastActionState {
|
||||
last_ok: Option<String>,
|
||||
last_error: Option<String>,
|
||||
}
|
||||
|
||||
const LAST_STATE_FILE: &str = "/run/awg2-harness/last_action.state";
|
||||
|
||||
pub fn run(cfg: Config) -> anyhow::Result<()> {
|
||||
println!("starting daemon");
|
||||
info!("starting daemon");
|
||||
let state = Arc::new(Mutex::new(LastActionState::default()));
|
||||
|
||||
let _ = traffic::reconcile_traffic(&cfg);
|
||||
let _ = firewall::apply_firewall(&cfg);
|
||||
if let Err(e) = traffic::reconcile_traffic(&cfg) {
|
||||
set_err(&state, format!("startup traffic reconcile: {e}"));
|
||||
} else {
|
||||
set_ok(&state, "startup traffic reconcile ok");
|
||||
}
|
||||
if let Err(e) = firewall::apply_firewall(&cfg) {
|
||||
set_err(&state, format!("startup firewall reconcile: {e}"));
|
||||
} else {
|
||||
set_ok(&state, "startup firewall reconcile ok");
|
||||
}
|
||||
let _ = reboot::maybe_schedule_reboot(&cfg);
|
||||
let reboot_cfg = cfg.clone();
|
||||
thread::spawn(move || {
|
||||
if let Err(e) = reboot::reboot_scheduler_loop(reboot_cfg) {
|
||||
error!(error = %e, "reboot scheduler loop error");
|
||||
}
|
||||
});
|
||||
|
||||
let events_cfg = cfg.clone();
|
||||
let events_state = state.clone();
|
||||
thread::spawn(move || {
|
||||
let _ = docker_events_loop(events_cfg);
|
||||
loop {
|
||||
if let Err(e) = docker_events_loop(&events_cfg, &events_state) {
|
||||
set_err(&events_state, format!("docker events loop crashed: {e}"));
|
||||
thread::sleep(Duration::from_secs(10));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
loop {
|
||||
if let Err(e) = traffic::reconcile_traffic(&cfg) {
|
||||
eprintln!("traffic reconcile error: {e}");
|
||||
error!(error = %e, "traffic reconcile error");
|
||||
set_err(&state, format!("periodic traffic reconcile: {e}"));
|
||||
} else {
|
||||
set_ok(&state, "periodic traffic reconcile ok");
|
||||
}
|
||||
if let Err(e) = firewall::apply_firewall(&cfg) {
|
||||
eprintln!("firewall reconcile error: {e}");
|
||||
error!(error = %e, "firewall reconcile error");
|
||||
set_err(&state, format!("periodic firewall reconcile: {e}"));
|
||||
} else {
|
||||
set_ok(&state, "periodic firewall reconcile ok");
|
||||
}
|
||||
thread::sleep(Duration::from_secs(cfg.traffic.reconcile_interval_secs.max(5)));
|
||||
}
|
||||
}
|
||||
|
||||
fn docker_events_loop(cfg: Config) -> anyhow::Result<()> {
|
||||
fn docker_events_loop(cfg: &Config, state: &Arc<Mutex<LastActionState>>) -> anyhow::Result<()> {
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
@@ -42,11 +91,63 @@ fn docker_events_loop(cfg: Config) -> anyhow::Result<()> {
|
||||
|
||||
for line in reader.lines() {
|
||||
let name = line?.trim().to_string();
|
||||
if cfg.amnezia.container_names.iter().any(|n| n == &name) {
|
||||
let _ = traffic::reconcile_traffic(&cfg);
|
||||
let _ = firewall::apply_firewall(&cfg);
|
||||
if cfg.amnezia.container_names.iter().any(|n| n == &name)
|
||||
&& wait_for_interface(cfg, &name, cfg.traffic.container_ready_timeout_secs)
|
||||
{
|
||||
if let Err(e) = traffic::reconcile_traffic(cfg) {
|
||||
set_err(state, format!("event traffic reconcile: {e}"));
|
||||
} else {
|
||||
set_ok(state, "event traffic reconcile ok");
|
||||
}
|
||||
|
||||
if let Err(e) = firewall::apply_firewall(cfg) {
|
||||
set_err(state, format!("event firewall reconcile: {e}"));
|
||||
} else {
|
||||
set_ok(state, "event firewall reconcile ok");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_for_interface(cfg: &Config, container_name: &str, timeout_secs: u64) -> bool {
|
||||
let start = Instant::now();
|
||||
while start.elapsed() < Duration::from_secs(timeout_secs.max(1)) {
|
||||
if let Ok(target) = docker::discover_target(cfg) {
|
||||
if target.container_name == container_name {
|
||||
let transport = build_transport(target.container_pid, &target.container_name);
|
||||
if iface::detect_interface_with_transport(cfg, transport.as_ref()).is_ok() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn set_ok(state: &Arc<Mutex<LastActionState>>, msg: &str) {
|
||||
if let Ok(mut s) = state.lock() {
|
||||
s.last_ok = Some(msg.to_string());
|
||||
persist_state(&s);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_err(state: &Arc<Mutex<LastActionState>>, msg: String) {
|
||||
if let Ok(mut s) = state.lock() {
|
||||
s.last_error = Some(msg);
|
||||
persist_state(&s);
|
||||
}
|
||||
}
|
||||
|
||||
fn persist_state(state: &LastActionState) {
|
||||
let _ = fs::create_dir_all("/run/awg2-harness");
|
||||
let content = format!(
|
||||
"last_ok={}\nlast_error={}\n",
|
||||
state.last_ok.clone().unwrap_or_default(),
|
||||
state.last_error.clone().unwrap_or_default()
|
||||
);
|
||||
let _ = fs::write(LAST_STATE_FILE, content);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user