use std::{fs, process::Command}; use crate::config::Config; pub fn ensure_swap(cfg: &Config) -> anyhow::Result<()> { if !cfg.swap.enabled { return Ok(()); } let path = &cfg.swap.path; let expected_size = cfg.swap.size_mb * 1024 * 1024; if std::path::Path::new(path).exists() { let actual_size = fs::metadata(path)?.len(); if actual_size != expected_size { anyhow::bail!( "existing swapfile has wrong size: {} bytes, expected {} bytes; manual intervention required", actual_size, expected_size ); } } else { run( "fallocate", &["-l", &format!("{}M", cfg.swap.size_mb), path], )?; run("chmod", &["600", path])?; run("mkswap", &[path])?; } let swapon = Command::new("swapon").args(["--show"]).output()?; let active = String::from_utf8_lossy(&swapon.stdout); if !active.contains(path) { run("swapon", &["-p", &cfg.swap.priority.to_string(), path])?; } ensure_fstab(path, cfg.swap.priority)?; run( "sysctl", &["-w", &format!("vm.swappiness={}", cfg.swap.swappiness)], )?; ensure_swappiness_persist(cfg.swap.swappiness)?; Ok(()) } fn ensure_fstab(path: &str, priority: i32) -> anyhow::Result<()> { let file = "/etc/fstab"; let content = fs::read_to_string(file).unwrap_or_default(); if content .lines() .any(|l| l.contains(path) && l.contains("swap")) { return Ok(()); } let line = format!("{path} none swap sw,pri={priority} 0 0\n"); fs::write(file, format!("{}{}", content, line))?; Ok(()) } fn run(program: &str, args: &[&str]) -> anyhow::Result<()> { let out = Command::new(program).args(args).output()?; if !out.status.success() { anyhow::bail!( "{} {:?} failed: {}", program, args, String::from_utf8_lossy(&out.stderr) ); } Ok(()) } fn ensure_swappiness_persist(swappiness: u8) -> anyhow::Result<()> { let conf_path = "/etc/sysctl.d/99-awg2-harness.conf"; let expected = format!("vm.swappiness = {}\n", swappiness); let current = fs::read_to_string(conf_path).unwrap_or_default(); if current != expected { fs::write(conf_path, expected)?; } Ok(()) }