1use std::io;
7use std::path::{Path, PathBuf};
8use std::process::Command;
9
10fn home() -> PathBuf {
11 std::env::var("HOME")
12 .map(PathBuf::from)
13 .unwrap_or_else(|_| ".".into())
14}
15
16pub fn write_service_unit(
19 bin: &Path,
20 env_file: &Path,
21 config: Option<&Path>,
22) -> io::Result<PathBuf> {
23 let dir = home().join(".config/systemd/user");
24 std::fs::create_dir_all(&dir)?;
25 let path = dir.join("bupha-watch.service");
26 std::fs::write(
27 &path,
28 format!(
29 "[Unit]
30Description=bupha — headless CI watcher (webhook -> queue -> runner)
31After=network-online.target
32Wants=network-online.target
33
34[Service]
35EnvironmentFile={}
36ExecStart={} watch{}
37Restart=always
38RestartSec=5
39
40[Install]
41WantedBy=default.target
42",
43 env_file.display(),
44 bin.display(),
45 config
46 .map(|c| format!(" --config {}", c.display()))
47 .unwrap_or_default()
48 ),
49 )?;
50 Ok(path)
51}
52
53pub fn enable_service(unit: &str) -> io::Result<()> {
56 run("systemctl", &["--user", "daemon-reload"])?;
57 run("systemctl", &["--user", "enable", "--now", unit])
58}
59
60pub fn write_env_skeleton(dir: &Path) -> io::Result<PathBuf> {
63 std::fs::create_dir_all(dir)?;
64 let path = dir.join("env");
65 if !path.exists() {
66 std::fs::write(
67 &path,
68 "# Secrets only — everything else lives in bupha.toml\n\
69 BUPHA_WEBHOOK_SECRET=\nBUPHA_TOKEN=\n# CODECOV_TOKEN=\n",
70 )?;
71 }
72 #[cfg(unix)]
73 {
74 use std::os::unix::fs::PermissionsExt;
75 std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))?;
77 }
78 Ok(path)
79}
80
81pub fn disable_service(unit: &str) -> io::Result<PathBuf> {
84 let _ = run("systemctl", &["--user", "disable", "--now", unit]);
85 let path = home().join(".config/systemd/user").join(unit);
86 if path.exists() {
87 std::fs::remove_file(&path)?;
88 }
89 run("systemctl", &["--user", "daemon-reload"])?;
90 Ok(path)
91}
92
93fn run(bin: &str, args: &[&str]) -> io::Result<()> {
94 let st = Command::new(bin).args(args).status()?;
95 if st.success() {
96 Ok(())
97 } else {
98 Err(io::Error::other(format!("{bin} {args:?} -> {st}")))
99 }
100}
101
102pub struct SystemdService {
104 pub unit: String,
106 pub bin: PathBuf,
108 pub env: PathBuf,
110 pub config: Option<PathBuf>,
112}
113
114impl bupha_service::Service for SystemdService {
115 fn write(&self) -> io::Result<PathBuf> {
116 write_service_unit(&self.bin, &self.env, self.config.as_deref())
117 }
118 fn enable(&self) -> io::Result<()> {
119 enable_service(&self.unit)
120 }
121 fn disable(&self) -> io::Result<PathBuf> {
122 disable_service(&self.unit)
123 }
124}