#![cfg_attr(
not(any(target_os = "linux", target_os = "macos", target_os = "windows")),
allow(dead_code)
)]
use std::path::PathBuf;
use crate::cli::ServiceAction;
pub mod detect;
#[cfg(any(target_os = "linux", test))]
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub mod systemd;
#[cfg(any(target_os = "linux", test))]
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub mod template;
#[cfg(any(target_os = "macos", test))]
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
pub mod launchctl;
#[cfg(any(target_os = "macos", test))]
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
pub mod launchd;
#[cfg(any(target_os = "macos", test))]
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
pub mod plist;
#[cfg(any(windows, test))]
#[cfg_attr(not(windows), allow(dead_code))]
pub mod scm;
#[cfg(windows)]
pub mod scm_backend;
#[cfg(windows)]
pub mod scm_host;
#[cfg(windows)]
pub mod scm_log;
#[cfg_attr(not(any(target_os = "linux", target_os = "windows")), allow(dead_code))]
pub const SERVICE_NAME: &str = "all-smi";
pub const EXIT_OK: i32 = 0;
pub const EXIT_ERROR: i32 = 1;
pub const EXIT_NOT_RUNNING: i32 = 3;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Scope {
System,
User,
}
impl Scope {
pub fn from_user_flag(user: bool) -> Self {
if user { Self::User } else { Self::System }
}
pub fn as_str(self) -> &'static str {
match self {
Self::System => "system",
Self::User => "user",
}
}
}
impl std::fmt::Display for Scope {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstallSpec {
pub scope: Scope,
pub service_user: Option<String>,
pub start_now: bool,
pub force: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ServiceStatus {
pub installed: bool,
pub enabled: Option<bool>,
pub running: bool,
pub pid: Option<u32>,
pub detail: String,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ServiceError {
#[error("{0}")]
NeedsElevation(String),
#[error("{0}")]
NotSupported(String),
#[error("{0}")]
PackageManaged(String),
#[error("{0}")]
Conflict(String),
#[error("the all-smi service is not installed in this scope")]
NotInstalled,
#[error("{0}")]
Io(#[from] std::io::Error),
#[error("`{cmd}` failed: {stderr}")]
CommandFailed { cmd: String, stderr: String },
}
pub trait ServiceBackend {
fn install(&self, spec: &InstallSpec) -> Result<(), ServiceError>;
fn uninstall(&self, scope: Scope) -> Result<(), ServiceError>;
fn start(&self, scope: Scope) -> Result<(), ServiceError>;
fn stop(&self, scope: Scope) -> Result<(), ServiceError>;
fn restart(&self, scope: Scope) -> Result<(), ServiceError>;
fn status(&self, scope: Scope) -> Result<ServiceStatus, ServiceError>;
fn uninstall_forced(&self, scope: Scope) -> Result<(), ServiceError> {
self.uninstall(scope)
}
}
pub fn backend() -> Result<Box<dyn ServiceBackend>, ServiceError> {
#[cfg(target_os = "linux")]
{
Ok(Box::new(systemd::SystemdBackend::new()))
}
#[cfg(target_os = "macos")]
{
Ok(Box::new(launchd::LaunchdBackend::new()))
}
#[cfg(target_os = "windows")]
{
Ok(Box::new(scm_backend::ScmBackend::new()))
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
{
Err(ServiceError::NotSupported(
"`all-smi service` has no supervisor backend for this platform. It drives systemd on \
Linux, launchd on macOS, and the Service Control Manager on Windows; adapt \
packaging/systemd/all-smi.service from the all-smi source tree to whatever \
supervises this host."
.to_string(),
))
}
}
#[cfg_attr(windows, allow(dead_code))]
pub fn require_elevation(verb: &str) -> Result<(), ServiceError> {
if is_elevated() {
return Ok(());
}
Err(ServiceError::NeedsElevation(format!(
"service {verb} requires root; re-run with sudo, or pass --user for a per-user service"
)))
}
#[cfg(unix)]
fn is_elevated() -> bool {
unsafe { libc::geteuid() == 0 }
}
#[cfg(not(unix))]
#[cfg_attr(windows, allow(dead_code))]
fn is_elevated() -> bool {
false
}
fn run_service_host() -> i32 {
#[cfg(windows)]
{
scm_host::run()
}
#[cfg(not(windows))]
{
report(&ServiceError::NotSupported(
"`all-smi service run` is the Windows Service Control Manager entry point and has no \
meaning on this platform. Use `all-smi api` to serve metrics in the foreground, or \
`all-smi service install` to register a supervised service."
.to_string(),
))
}
}
pub fn current_exe_canonical() -> Result<PathBuf, ServiceError> {
let exe = std::env::current_exe()?;
Ok(exe.canonicalize().unwrap_or(exe))
}
pub fn run(action: &ServiceAction) -> i32 {
if let ServiceAction::Run(_) = action {
return run_service_host();
}
let scope = Scope::from_user_flag(action.user_scope());
let backend = match backend() {
Ok(b) => b,
Err(e) => return report(&e),
};
match action {
ServiceAction::Install(args) => {
if let Err(e) = detect::guard(args.force) {
return report(&e);
}
if scope == Scope::User && args.service_user.is_some() {
eprintln!(
"warning: --service-user is ignored in --user scope; a user service \
always runs as the invoking user"
);
}
let spec = InstallSpec {
scope,
service_user: args.service_user.clone(),
start_now: args.now,
force: args.force,
};
match backend.install(&spec) {
Ok(()) => {
report_install_success(&spec);
EXIT_OK
}
Err(e) => report(&e),
}
}
ServiceAction::Uninstall(args) => {
let result = if args.force {
backend.uninstall_forced(scope)
} else {
backend.uninstall(scope)
};
match result {
Ok(()) => {
println!("Removed the all-smi {scope} service.");
EXIT_OK
}
Err(e) => report(&e),
}
}
ServiceAction::Start(_) => simple(backend.start(scope), scope, "Started"),
ServiceAction::Stop(_) => simple(backend.stop(scope), scope, "Stopped"),
ServiceAction::Restart(_) => simple(backend.restart(scope), scope, "Restarted"),
ServiceAction::Status(args) => match backend.status(scope) {
Ok(status) => {
if args.json {
print_status_json(&status, scope);
} else {
print_status_text(&status, scope);
}
if status.running {
EXIT_OK
} else {
EXIT_NOT_RUNNING
}
}
Err(e) => report(&e),
},
ServiceAction::Run(_) => unreachable!("service run is dispatched before backend selection"),
}
}
fn simple(result: Result<(), ServiceError>, scope: Scope, past_tense: &str) -> i32 {
match result {
Ok(()) => {
println!("{past_tense} the all-smi {scope} service.");
EXIT_OK
}
Err(e) => report(&e),
}
}
fn report_install_success(spec: &InstallSpec) {
let scope = spec.scope;
println!("Installed the all-smi {scope} service and enabled it.");
if spec.start_now {
println!("It is running now.");
} else {
let flag = if scope == Scope::User { " --user" } else { "" };
println!("It is not running yet. Start it with: all-smi service start{flag}");
}
if scope == Scope::User {
println!("{}", user_scope_persistence_note());
}
println!(
"Runtime settings live in {SETTINGS_SOURCES}, not in the service definition. Run \
`all-smi config path` to see the active TOML path."
);
}
#[cfg(target_os = "macos")]
const SETTINGS_SOURCES: &str = "the TOML config";
#[cfg(not(target_os = "macos"))]
const SETTINGS_SOURCES: &str = "the environment file and the TOML config";
#[cfg(target_os = "macos")]
fn user_scope_persistence_note() -> String {
"Note: a LaunchAgent runs only while you are logged in to a desktop session and stops at \
logout. launchd has no per-user lingering, so boot persistence on a headless node means the \
system LaunchDaemon: `sudo all-smi service install --now`."
.to_string()
}
#[cfg(not(target_os = "macos"))]
fn user_scope_persistence_note() -> String {
let user = whoami::username().unwrap_or_else(|_| "<user>".to_string());
format!(
"Note: a user service only runs while you are logged in. Run \
`loginctl enable-linger {user}` for boot persistence."
)
}
fn print_status_text(status: &ServiceStatus, scope: Scope) {
if !status.installed {
println!("all-smi ({scope} scope): not installed");
return;
}
let enabled = match status.enabled {
Some(true) => "enabled",
Some(false) => "disabled",
None => "enablement unknown",
};
let running = if status.running { "running" } else { "stopped" };
println!("all-smi ({scope} scope): installed, {enabled}, {running}");
if !status.detail.is_empty() {
println!(" state: {}", status.detail);
}
if let Some(pid) = status.pid {
println!(" main pid: {pid}");
}
}
fn print_status_json(status: &ServiceStatus, scope: Scope) {
let value = serde_json::json!({
"installed": status.installed,
"enabled": status.enabled,
"running": status.running,
"pid": status.pid,
"scope": scope.as_str(),
"detail": status.detail,
});
match serde_json::to_string_pretty(&value) {
Ok(s) => println!("{s}"),
Err(_) => print_status_text(status, scope),
}
}
fn report(err: &ServiceError) -> i32 {
eprintln!("error: {err}");
if let ServiceError::PackageManaged(_) = err {
eprintln!("hint: pass --force to install alongside the package-managed definition anyway");
}
EXIT_ERROR
}
#[cfg(test)]
#[path = "mod_tests.rs"]
mod tests;