xbp_cli/commands/
api_install.rs1use 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}