use std::path::PathBuf;
use std::process::Command;
use anyhow::Result;
use framework_system::CommandExt;
use crate::config::StartupConfig;
use crate::directory::{self, SYSTEM_UNIT_DIRECTORY};
use crate::file;
use crate::platform::Platform;
use crate::scope::Scope;
const UNIT_SUFFIX: &str = ".service";
pub(crate) struct LinuxStartup;
impl Platform for LinuxStartup {
fn exists(&self, config: &StartupConfig, scope: Scope) -> Result<bool> {
Ok(unit_path(config, scope)?.exists())
}
fn is_run(&self, config: &StartupConfig, scope: Scope) -> Result<bool> {
let output = Command::new("systemctl")
.args(arguments(scope, &["is-active", &config.name]))
.command_stdout();
Ok(output.is_some_and(|output| output.trim() == "active"))
}
fn install(&self, config: &StartupConfig, scope: Scope) -> Result<()> {
file::write_file(&unit_path(config, scope)?, &unit_content(config, scope))?;
Command::new("systemctl")
.args(arguments(scope, &["daemon-reload"]))
.command_run()?;
Command::new("systemctl")
.args(arguments(scope, &["enable", &config.name]))
.command_run()
}
fn uninstall(&self, config: &StartupConfig, scope: Scope) -> Result<()> {
let _ = Command::new("systemctl")
.args(arguments(scope, &["disable", &config.name]))
.command_run();
file::remove_file(&unit_path(config, scope)?)?;
Command::new("systemctl")
.args(arguments(scope, &["daemon-reload"]))
.command_run()
}
fn start(&self, config: &StartupConfig, scope: Scope) -> Result<()> {
Command::new("systemctl")
.args(arguments(scope, &["start", &config.name]))
.command_run()
}
fn stop(&self, config: &StartupConfig, scope: Scope) -> Result<()> {
Command::new("systemctl")
.args(arguments(scope, &["stop", &config.name]))
.command_run()
}
}
fn arguments(scope: Scope, args: &[&str]) -> Vec<String> {
let mut arguments = match scope {
Scope::Root => Vec::new(),
Scope::User => vec!["--user".to_string()],
};
arguments.extend(args.iter().map(|arg| (*arg).to_string()));
arguments
}
fn unit_path(config: &StartupConfig, scope: Scope) -> Result<PathBuf> {
let root = directory::directory(SYSTEM_UNIT_DIRECTORY, directory::user_unit_directory, scope)?;
Ok(root.join(format!("{}{UNIT_SUFFIX}", config.name)))
}
fn unit_content(config: &StartupConfig, scope: Scope) -> String {
let working_directory = config
.working_directory
.as_ref()
.map(|path| format!("WorkingDirectory={}\n", path.display()))
.unwrap_or_default();
let wanted_by = match scope {
Scope::Root => "multi-user.target",
Scope::User => "default.target",
};
format!(
"[Unit]\n\
Description={description}\n\
After=network-online.target\n\
Wants=network-online.target\n\
\n\
[Service]\n\
Type=simple\n\
ExecStart={command}\n\
{working_directory}\
Restart=on-failure\n\
RestartSec=5\n\
\n\
[Install]\n\
WantedBy={wanted_by}\n",
description = config.description,
command = config.command_line(),
)
}