Документация и runtime: полный production-fix по fix6 без legacy
This commit is contained in:
@@ -130,13 +130,18 @@ impl Config {
|
||||
fn check_host_tools(&self) -> anyhow::Result<()> {
|
||||
let mut required = vec!["tc", "ip", "wg", "docker", "modprobe"];
|
||||
if !crate::netns::nsenter_available() {
|
||||
eprintln!("nsenter is not available, will use docker exec fallback");
|
||||
} else {
|
||||
required.push("nsenter");
|
||||
anyhow::bail!("required host tool not found in PATH: nsenter");
|
||||
}
|
||||
required.push("nsenter");
|
||||
if self.anti_torrent.enabled {
|
||||
required.push("nft");
|
||||
}
|
||||
if self.reboot.enabled {
|
||||
required.push("systemctl");
|
||||
}
|
||||
if self.swap.enabled {
|
||||
required.extend(["fallocate", "chmod", "mkswap", "swapon", "sysctl"]);
|
||||
}
|
||||
|
||||
for tool in required {
|
||||
if !tool_exists(tool) {
|
||||
@@ -199,6 +204,12 @@ fn tool_exists(tool: &str) -> bool {
|
||||
"modprobe" => &[&["--version"]],
|
||||
"nsenter" => &[&["--help"]],
|
||||
"nft" => &[&["--version"]],
|
||||
"systemctl" => &[&["--version"], &["--help"]],
|
||||
"fallocate" => &[&["--help"]],
|
||||
"chmod" => &[&["--help"]],
|
||||
"mkswap" => &[&["--help"]],
|
||||
"swapon" => &[&["--help"]],
|
||||
"sysctl" => &[&["--version"], &["--help"]],
|
||||
_ => &[&["--version"], &["--help"], &["-V"]],
|
||||
};
|
||||
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
use chrono::{Duration, NaiveDateTime, NaiveTime, Timelike, Utc};
|
||||
use rand::Rng;
|
||||
use std::{process::Command, thread, time::Duration as StdDuration};
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
pub fn maybe_schedule_reboot(cfg: &Config) -> anyhow::Result<()> {
|
||||
if !cfg.reboot.enabled {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let planned = next_reboot_time(&cfg.reboot.window_start_utc, &cfg.reboot.window_end_utc)?;
|
||||
println!("reboot planned at UTC {}", planned);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reboot_scheduler_loop(cfg: Config) -> anyhow::Result<()> {
|
||||
if !cfg.reboot.enabled {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
loop {
|
||||
let planned = next_reboot_time(&cfg.reboot.window_start_utc, &cfg.reboot.window_end_utc)?;
|
||||
println!("reboot scheduler: next reboot at UTC {}", planned);
|
||||
|
||||
loop {
|
||||
let now = Utc::now().naive_utc();
|
||||
if now >= planned {
|
||||
break;
|
||||
}
|
||||
thread::sleep(StdDuration::from_secs(30));
|
||||
}
|
||||
|
||||
let out = Command::new("systemctl")
|
||||
.args(["--no-block", "reboot"])
|
||||
.output()?;
|
||||
if out.status.success() {
|
||||
println!("reboot request accepted by systemctl");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"reboot request failed: {}",
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
thread::sleep(StdDuration::from_secs(60));
|
||||
}
|
||||
}
|
||||
|
||||
fn next_reboot_time(start_hhmm: &str, end_hhmm: &str) -> anyhow::Result<NaiveDateTime> {
|
||||
let start = parse_hhmm(start_hhmm)?;
|
||||
let end = parse_hhmm(end_hhmm)?;
|
||||
|
||||
let now = Utc::now().naive_utc();
|
||||
let today = now.date();
|
||||
let mut base_day = today;
|
||||
|
||||
let now_t = NaiveTime::from_hms_opt(now.hour(), now.minute(), now.second())
|
||||
.ok_or_else(|| anyhow::anyhow!("bad current time"))?;
|
||||
if now_t >= start {
|
||||
base_day = today
|
||||
.succ_opt()
|
||||
.ok_or_else(|| anyhow::anyhow!("date overflow"))?;
|
||||
}
|
||||
|
||||
let span = if end > start {
|
||||
end.signed_duration_since(start)
|
||||
} else {
|
||||
Duration::hours(24) - start.signed_duration_since(end)
|
||||
};
|
||||
|
||||
let secs = rand::thread_rng().gen_range(0..span.num_seconds().max(1));
|
||||
let base_dt = base_day
|
||||
.and_hms_opt(start.hour(), start.minute(), 0)
|
||||
.ok_or_else(|| anyhow::anyhow!("invalid base datetime"))?;
|
||||
Ok(base_dt + Duration::seconds(secs))
|
||||
}
|
||||
fn parse_hhmm(v: &str) -> anyhow::Result<NaiveTime> {
|
||||
let mut s = v.split(':');
|
||||
let h = s
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("bad time"))?
|
||||
.parse()?;
|
||||
let m = s
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("bad time"))?
|
||||
.parse()?;
|
||||
if h > 23 || m > 59 {
|
||||
anyhow::bail!("bad time")
|
||||
}
|
||||
NaiveTime::from_hms_opt(h, m, 0).ok_or_else(|| anyhow::anyhow!("bad time"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_time_rejects_invalid() {
|
||||
assert!(parse_hhmm("24:00").is_err());
|
||||
assert!(parse_hhmm("10:60").is_err());
|
||||
assert!(parse_hhmm("ab:cd").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cross_midnight_window_has_expected_span() {
|
||||
let start = parse_hhmm("23:00").expect("start");
|
||||
let end = parse_hhmm("02:00").expect("end");
|
||||
let span = if end > start {
|
||||
end.signed_duration_since(start)
|
||||
} else {
|
||||
Duration::hours(24) - start.signed_duration_since(end)
|
||||
};
|
||||
assert_eq!(span.num_hours(), 3);
|
||||
}
|
||||
}
|
||||
@@ -183,6 +183,13 @@ pub fn print_status(cfg: &Config) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
println!("Swap:");
|
||||
println!(" enabled: {}", cfg.swap.enabled);
|
||||
println!(" path: {}", cfg.swap.path);
|
||||
println!(" size_mb: {}", cfg.swap.size_mb);
|
||||
println!(" priority: {}", cfg.swap.priority);
|
||||
println!(" swappiness: {}", cfg.swap.swappiness);
|
||||
|
||||
let ct_count = Command::new("sysctl")
|
||||
.args(["-n", "net.netfilter.nf_conntrack_count"])
|
||||
.output()
|
||||
@@ -220,7 +227,7 @@ pub fn print_status(cfg: &Config) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
print_reboot_section();
|
||||
print_reboot_section(cfg);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -301,12 +308,15 @@ fn print_degraded_sections(cfg: &Config) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
print_reboot_section();
|
||||
print_reboot_section(cfg);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_reboot_section() {
|
||||
fn print_reboot_section(cfg: &Config) {
|
||||
println!("Reboot scheduler:");
|
||||
println!(" enabled: {}", cfg.reboot.enabled);
|
||||
println!(" window_start_utc: {}", cfg.reboot.window_start_utc);
|
||||
println!(" window_end_utc: {}", cfg.reboot.window_end_utc);
|
||||
let svc = Command::new("systemctl")
|
||||
.args(["is-active", "awg2-reboot-scheduler"])
|
||||
.output();
|
||||
|
||||
+33
-12
@@ -180,7 +180,7 @@ pub fn peer_status_table(cfg: &Config) -> anyhow::Result<Option<Vec<PeerTrafficS
|
||||
let actual_map_ingress = inspect_ingress.map;
|
||||
let egress_rates = parse_class_rate_ceil_map(&classes_egress_full, "1:");
|
||||
let ingress_rates = parse_class_rate_ceil_map(&classes_ingress_full, "2:");
|
||||
let expected_map = expected_peer_class_map(&peers, cfg.traffic.class_start_id);
|
||||
let expected_map = expected_peer_class_map(&peers, cfg.traffic.class_start_id)?;
|
||||
let expected_limit = normalize_tc_rate(&cfg.traffic.peer_limit);
|
||||
|
||||
let mut out = Vec::with_capacity(peers.len());
|
||||
@@ -384,7 +384,7 @@ fn inspect_and_diff(
|
||||
});
|
||||
}
|
||||
|
||||
let expected_map = expected_peer_class_map(peers, class_start_id);
|
||||
let expected_map = expected_peer_class_map(peers, class_start_id)?;
|
||||
if actual_map_egress != expected_map {
|
||||
return Ok(TrafficDrift {
|
||||
has_drift: true,
|
||||
@@ -657,12 +657,20 @@ fn extract_flowid_class_id(line: &str) -> Option<u16> {
|
||||
class_tail.parse::<u16>().ok()
|
||||
}
|
||||
|
||||
fn expected_peer_class_map(peers: &[wg::Peer], class_start_id: u16) -> BTreeMap<String, u16> {
|
||||
fn expected_peer_class_map(
|
||||
peers: &[wg::Peer],
|
||||
class_start_id: u16,
|
||||
) -> anyhow::Result<BTreeMap<String, u16>> {
|
||||
let mut map = BTreeMap::new();
|
||||
for (idx, p) in peers.iter().enumerate() {
|
||||
map.insert(p.ipv4.to_string(), class_start_id + idx as u16);
|
||||
let idx_u16 = u16::try_from(idx)
|
||||
.map_err(|_| anyhow::anyhow!("too many peers for class id allocation"))?;
|
||||
let class_id = class_start_id
|
||||
.checked_add(idx_u16)
|
||||
.ok_or_else(|| anyhow::anyhow!("class id overflow: too many peers"))?;
|
||||
map.insert(p.ipv4.to_string(), class_id);
|
||||
}
|
||||
map
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
fn extract_u32_match_hex(line: &str) -> Option<String> {
|
||||
@@ -773,7 +781,13 @@ fn apply_full_topology(
|
||||
)?;
|
||||
|
||||
for (idx, p) in peers.iter().enumerate() {
|
||||
let class_id = cfg.traffic.class_start_id + idx as u16;
|
||||
let idx_u16 = u16::try_from(idx)
|
||||
.map_err(|_| anyhow::anyhow!("too many peers for class id allocation"))?;
|
||||
let class_id = cfg
|
||||
.traffic
|
||||
.class_start_id
|
||||
.checked_add(idx_u16)
|
||||
.ok_or_else(|| anyhow::anyhow!("class id overflow: too many peers"))?;
|
||||
let ip = p.ipv4.to_string();
|
||||
let class_egress = format!("1:{class_id}");
|
||||
let class_ingress = format!("2:{class_id}");
|
||||
@@ -912,8 +926,12 @@ fn run_allow_exists(transport: &dyn NsTransport, args: &[&str]) -> anyhow::Resul
|
||||
match transport.run(args) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
let msg = e.to_string();
|
||||
if msg.contains("File exists") || msg.contains("EEXIST") {
|
||||
let msg = e.to_string().to_ascii_lowercase();
|
||||
if msg.contains("file exists")
|
||||
|| msg.contains("eexist")
|
||||
|| msg.contains("object already exists")
|
||||
|| msg.contains("exclusive flag on")
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
Err(e)
|
||||
@@ -925,11 +943,14 @@ fn run_allow_notfound(transport: &dyn NsTransport, args: &[&str]) -> anyhow::Res
|
||||
match transport.run(args) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
let msg = e.to_string();
|
||||
if msg.contains("No such file")
|
||||
|| msg.contains("No such")
|
||||
let msg = e.to_string().to_ascii_lowercase();
|
||||
if msg.contains("no such file")
|
||||
|| msg.contains("no such")
|
||||
|| msg.contains("not found")
|
||||
|| msg.contains("Cannot find")
|
||||
|| msg.contains("cannot find")
|
||||
|| msg.contains("cannot find device")
|
||||
|| msg.contains("no such device")
|
||||
|| msg.contains("does not exist")
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user