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
7pub struct LinuxServiceManager {
8    config: ServiceConfig,
9}
10
11impl LinuxServiceManager {
12    pub fn new(config: ServiceConfig) -> Self {
13        Self { config }
14    }
15
16    fn service_file_path(&self) -> String {
17        format!("/etc/systemd/system/{}.service", self.config.name)
18    }
19
20    fn create_service_file(&self) -> Result<()> {
21        let executable = self.config.executable_path.to_string_lossy();
22        let args = self.config.args.join(" ");
23
24        let service_content = format!(
25            r#"[Unit]
26Description={}
27After=network.target
28
29[Service]
30Type=simple
31ExecStart={} {}
32Restart=on-failure
33RestartSec=5s
34
35[Install]
36WantedBy=multi-user.target
37"#,
38            self.config.description, executable, args
39        );
40
41        fs::write(self.service_file_path(), service_content)
42            .context("Failed to write systemd service file")
43    }
44}
45
46impl ServiceManager for LinuxServiceManager {
47    fn install(&self) -> Result<()> {
48        // Create the service file
49        self.create_service_file()?;
50
51        // Reload systemd to recognize the new service
52        Command::new("systemctl")
53            .args(["daemon-reload"])
54            .status()
55            .context("Failed to reload systemd")?;
56
57        // Enable the service to start on boot
58        Command::new("systemctl")
59            .args(["enable", &self.config.name])
60            .status()
61            .context("Failed to enable service")?;
62
63        Ok(())
64    }
65
66    fn uninstall(&self) -> Result<()> {
67        // Stop the service if it's running
68        let _ = self.stop();
69
70        // Disable the service
71        Command::new("systemctl")
72            .args(["disable", &self.config.name])
73            .status()
74            .context("Failed to disable service")?;
75
76        // Remove the service file
77        if Path::new(&self.service_file_path()).exists() {
78            fs::remove_file(self.service_file_path()).context("Failed to remove service file")?;
79        }
80
81        // Reload systemd
82        Command::new("systemctl")
83            .args(["daemon-reload"])
84            .status()
85            .context("Failed to reload systemd")?;
86
87        Ok(())
88    }
89
90    fn start(&self) -> Result<()> {
91        Command::new("systemctl")
92            .args(["start", &self.config.name])
93            .status()
94            .context("Failed to start service")?;
95        Ok(())
96    }
97
98    fn stop(&self) -> Result<()> {
99        Command::new("systemctl")
100            .args(["stop", &self.config.name])
101            .status()
102            .context("Failed to stop service")?;
103        Ok(())
104    }
105
106    fn status(&self) -> Result<ServiceStatus> {
107        let service_path = self.service_file_path();
108        let service_file = Path::new(&service_path);
109        if !service_file.exists() {
110            return Ok(ServiceStatus::NotInstalled);
111        }
112
113        let output = Command::new("systemctl")
114            .args(["is-active", &self.config.name])
115            .output()
116            .context("Failed to check service status")?;
117
118        let status_str = String::from_utf8_lossy(&output.stdout).trim().to_string();
119
120        match status_str.as_str() {
121            "active" => Ok(ServiceStatus::Running),
122            "inactive" => Ok(ServiceStatus::Stopped),
123            _ => Ok(ServiceStatus::Unknown),
124        }
125    }
126}