Skip to main content

xbp_cli/commands/
api_install.rs

1use crate::commands::systemd_unit::{build_api_unit_spec, render_unit};
2use crate::logging::{log_error, log_info, log_success};
3use crate::utils::command_exists;
4use std::fs;
5use std::path::PathBuf;
6use tokio::process::Command;
7use tracing::debug;
8
9pub async fn install_api_service(port: u16, debug: bool) -> Result<(), String> {
10    if !cfg!(target_os = "linux") {
11        return Err("`xbp api install` is only supported on Linux/systemd hosts.".to_string());
12    }
13    if !command_exists("systemctl") {
14        return Err(
15            "`systemctl` was not found. Install systemd or manage the service manually."
16                .to_string(),
17        );
18    }
19
20    let exe = std::env::current_exe()
21        .map_err(|e| format!("Failed to resolve current executable: {}", e))?;
22    let exe_dir: PathBuf = exe
23        .parent()
24        .map(|p| p.to_path_buf())
25        .unwrap_or_else(|| PathBuf::from("."));
26
27    let unit = render_unit(&build_api_unit_spec(&exe_dir, &exe, port));
28
29    let unit_path = "/etc/systemd/system/xbp-api.service";
30    let temp_unit_path = "/tmp/xbp-api.service";
31    let _ = log_info("api", "Writing systemd unit (requires sudo)", None).await;
32
33    fs::write(temp_unit_path, unit).map_err(|e| {
34        format!(
35            "Failed to stage temporary unit file {}: {}",
36            temp_unit_path, e
37        )
38    })?;
39
40    let write_cmd = if should_use_sudo() {
41        format!("sudo install -m 0644 {} {}", temp_unit_path, unit_path)
42    } else {
43        format!("install -m 0644 {} {}", temp_unit_path, unit_path)
44    };
45    if debug {
46        debug!("Writing unit with: {}", write_cmd);
47    }
48    let write_output = Command::new("sh")
49        .arg("-c")
50        .arg(&write_cmd)
51        .output()
52        .await
53        .map_err(|e| format!("Failed to write unit file: {}", e))?;
54    if !write_output.status.success() {
55        return Err(format!(
56            "Writing unit failed: {}",
57            String::from_utf8_lossy(&write_output.stderr)
58        ));
59    }
60    let _ = fs::remove_file(temp_unit_path);
61
62    run_systemctl(&["daemon-reload"], debug).await?;
63
64    let enable_status = run_systemctl(&["enable", "--now", "xbp-api.service"], debug).await?;
65
66    if !enable_status.success() {
67        let _ = log_error(
68            "api",
69            "Failed to enable xbp-api.service",
70            Some(&format!("{:?}", enable_status)),
71        )
72        .await;
73        return Err("systemctl enable failed".into());
74    }
75
76    let _ = log_success(
77        "api",
78        &format!(
79            "Installed and started xbp-api.service on PORT_XBP_API={}",
80            port
81        ),
82        None,
83    )
84    .await;
85    Ok(())
86}
87
88fn should_use_sudo() -> bool {
89    is_root().map(|value| !value).unwrap_or(true)
90}
91
92fn is_root() -> Option<bool> {
93    if !cfg!(target_os = "linux") {
94        return None;
95    }
96    let uid = std::process::Command::new("id")
97        .arg("-u")
98        .output()
99        .ok()
100        .filter(|output| output.status.success())?;
101    let raw = String::from_utf8_lossy(&uid.stdout);
102    Some(raw.trim() == "0")
103}
104
105async fn run_systemctl(args: &[&str], debug: bool) -> Result<std::process::ExitStatus, String> {
106    let mut command = if should_use_sudo() {
107        if !command_exists("sudo") {
108            return Err(
109                "This action requires elevated privileges. Re-run as root or install `sudo`."
110                    .to_string(),
111            );
112        }
113        let mut cmd = Command::new("sudo");
114        cmd.arg("systemctl");
115        for arg in args {
116            cmd.arg(arg);
117        }
118        cmd
119    } else {
120        let mut cmd = Command::new("systemctl");
121        for arg in args {
122            cmd.arg(arg);
123        }
124        cmd
125    };
126
127    if debug {
128        debug!("Running systemctl {:?}", args);
129    }
130
131    command
132        .status()
133        .await
134        .map_err(|e| format!("Failed to run systemctl {:?}: {}", args, e))
135}