cloudpub_client/service/
linux.rs

1use crate::service::{ServiceConfig, ServiceManager, ServiceStatus};
2use anyhow::{Context, Result};
3use std::fs;
4use std::path::Path;
5use std::process::Command;
6
7// Include the systemd service template at compile time
8const SYSTEMD_SERVICE_TEMPLATE: &str = include_str!("templates/systemd.service");
9
10pub struct LinuxServiceManager {
11    config: ServiceConfig,
12}
13
14impl LinuxServiceManager {
15    pub fn new(config: ServiceConfig) -> Self {
16        Self { config }
17    }
18
19    fn service_file_path(&self) -> String {
20        format!("/etc/systemd/system/{}.service", self.config.name)
21    }
22
23    fn create_service_file(&self) -> Result<()> {
24        let executable = self.config.executable_path.to_string_lossy();
25        let args = self.config.args.join(" ");
26
27        // Replace placeholders in the template
28        let service_content = SYSTEMD_SERVICE_TEMPLATE
29            .replace("{DESCRIPTION}", &self.config.description)
30            .replace("{EXECUTABLE}", &executable)
31            .replace("{ARGS}", &args);
32
33        fs::write(self.service_file_path(), service_content)
34            .context("Failed to write systemd service file")
35    }
36}
37
38impl ServiceManager for LinuxServiceManager {
39    fn install(&self) -> Result<()> {
40        // Copy config to system location
41        self.config.copy_config_to_system()?;
42
43        // Create the service file
44        self.create_service_file()?;
45
46        // Reload systemd to recognize the new service
47        Command::new("systemctl")
48            .args(["daemon-reload"])
49            .status()
50            .context("Failed to reload systemd")?;
51
52        // Enable the service to start on boot
53        Command::new("systemctl")
54            .args(["enable", &self.config.name])
55            .status()
56            .context("Failed to enable service")?;
57
58        Ok(())
59    }
60
61    fn uninstall(&self) -> Result<()> {
62        // Stop the service if it's running
63        let _ = self.stop();
64
65        // Disable the service
66        Command::new("systemctl")
67            .args(["disable", &self.config.name])
68            .status()
69            .context("Failed to disable service")?;
70
71        // Remove the service file
72        if Path::new(&self.service_file_path()).exists() {
73            fs::remove_file(self.service_file_path()).context("Failed to remove service file")?;
74        }
75
76        // Reload systemd
77        Command::new("systemctl")
78            .args(["daemon-reload"])
79            .status()
80            .context("Failed to reload systemd")?;
81
82        Ok(())
83    }
84
85    fn start(&self) -> Result<()> {
86        Command::new("systemctl")
87            .args(["start", &self.config.name])
88            .status()
89            .context("Failed to start service")?;
90        Ok(())
91    }
92
93    fn stop(&self) -> Result<()> {
94        Command::new("systemctl")
95            .args(["stop", &self.config.name])
96            .status()
97            .context("Failed to stop service")?;
98        Ok(())
99    }
100
101    fn status(&self) -> Result<ServiceStatus> {
102        let service_path = self.service_file_path();
103        let service_file = Path::new(&service_path);
104        if !service_file.exists() {
105            return Ok(ServiceStatus::NotInstalled);
106        }
107
108        let output = Command::new("systemctl")
109            .args(["is-active", &self.config.name])
110            .output()
111            .context("Failed to check service status")?;
112
113        let status_str = String::from_utf8_lossy(&output.stdout).trim().to_string();
114
115        match status_str.as_str() {
116            "active" => Ok(ServiceStatus::Running),
117            "inactive" => Ok(ServiceStatus::Stopped),
118            _ => Ok(ServiceStatus::Unknown),
119        }
120    }
121}