//! Generate systemd unit files for Warpgate services. use std::fmt::Write as _; use std::fs; use std::path::Path; use std::process::Command; use anyhow::{Context, Result}; use crate::config::Config; /// Target directory for systemd unit files. pub const SYSTEMD_DIR: &str = "/etc/systemd/system"; /// Single unified service unit name. pub const RUN_SERVICE: &str = "warpgate.service"; /// Generate the single `warpgate.service` unit that runs `warpgate run`. /// /// This replaces the old multi-unit approach. The `warpgate run` supervisor /// manages rclone mount, SMB, NFS, and WebDAV internally. pub fn generate_run_unit(_config: &Config) -> Result { let mut unit = String::new(); writeln!(unit, "# Generated by Warpgate — do not edit manually.")?; writeln!(unit, "[Unit]")?; writeln!(unit, "Description=Warpgate NAS cache proxy")?; writeln!(unit, "After=network-online.target")?; writeln!(unit, "Wants=network-online.target")?; writeln!(unit)?; writeln!(unit, "[Service]")?; writeln!(unit, "Type=simple")?; writeln!(unit, "ExecStart=/usr/local/bin/warpgate run")?; writeln!(unit, "Restart=on-failure")?; writeln!(unit, "RestartSec=10")?; writeln!(unit, "KillMode=mixed")?; writeln!(unit, "TimeoutStopSec=30")?; writeln!(unit)?; writeln!(unit, "[Install]")?; writeln!(unit, "WantedBy=multi-user.target")?; Ok(unit) } /// Install the single `warpgate.service` unit and reload systemd. pub fn install_run_unit(config: &Config) -> Result<()> { let systemd_dir = Path::new(SYSTEMD_DIR); let unit_content = generate_run_unit(config)?; fs::write(systemd_dir.join(RUN_SERVICE), unit_content) .with_context(|| format!("Failed to write {RUN_SERVICE}"))?; // Reload systemd daemon let status = Command::new("systemctl") .arg("daemon-reload") .status() .context("Failed to run systemctl daemon-reload")?; if !status.success() { anyhow::bail!("systemctl daemon-reload failed with exit code: {}", status); } Ok(()) } #[cfg(test)] mod tests { use super::*; fn test_config() -> Config { toml::from_str( r#" [[connections]] name = "nas" host = "10.0.0.1" protocol = "sftp" user = "admin" [cache] dir = "/tmp/cache" [read] [bandwidth] [writeback] [directory_cache] [protocols] [[shares]] name = "photos" connection = "nas" remote_path = "/photos" mount_point = "/mnt/photos" "#, ) .unwrap() } #[test] fn test_generate_run_unit_sections() { let config = test_config(); let unit = generate_run_unit(&config).unwrap(); assert!(unit.contains("[Unit]")); assert!(unit.contains("[Service]")); assert!(unit.contains("[Install]")); } #[test] fn test_generate_run_unit_content() { let config = test_config(); let unit = generate_run_unit(&config).unwrap(); assert!(unit.contains("Description=Warpgate NAS cache proxy")); assert!(unit.contains("After=network-online.target")); assert!(unit.contains("Type=simple")); assert!(unit.contains("ExecStart=/usr/local/bin/warpgate run")); assert!(unit.contains("Restart=on-failure")); assert!(unit.contains("RestartSec=10")); assert!(unit.contains("KillMode=mixed")); assert!(unit.contains("WantedBy=multi-user.target")); } #[test] fn test_systemd_constants() { assert_eq!(SYSTEMD_DIR, "/etc/systemd/system"); assert_eq!(RUN_SERVICE, "warpgate.service"); } } /// Enable and start the single `warpgate.service`. pub fn enable_and_start_run() -> Result<()> { let status = Command::new("systemctl") .args(["enable", "--now", RUN_SERVICE]) .status() .with_context(|| format!("Failed to run systemctl enable --now {RUN_SERVICE}"))?; if !status.success() { anyhow::bail!("systemctl enable --now {RUN_SERVICE} failed with exit code: {status}"); } Ok(()) }