Skip to main content

horto_os_ui_shared/kits/
systemd.rs

1//! `systemctl` helpers that respect [`HostContext`] dry-run vs apply.
2
3use crate::context::HostContext;
4use crate::error::{HortoError, Result};
5use std::process::Command;
6
7fn systemctl(ctx: &mut HostContext, args: &[&str]) -> Result<()> {
8    let summary = format!("systemctl {}", args.join(" "));
9    if ctx.is_dry_run() {
10        ctx.plan_action(summary);
11        return Ok(());
12    }
13    ctx.log(&summary);
14    let status = Command::new("systemctl")
15        .args(args)
16        .status()
17        .map_err(|e| HortoError::command("systemctl", e.to_string()))?;
18    if !status.success() {
19        return Err(HortoError::command(
20            "systemctl",
21            format!("{} -> {status}", args.join(" ")),
22        ));
23    }
24    Ok(())
25}
26
27/// Stop a systemd unit.
28///
29/// In dry-run mode, records `systemctl stop` and returns `Ok`.
30///
31/// # Errors
32///
33/// Returns [`HortoError::CommandFailed`] when `systemctl` cannot be spawned or exits non-zero.
34pub fn stop(ctx: &mut HostContext, unit: &str) -> Result<()> {
35    systemctl(ctx, &["stop", unit])
36}
37
38/// Disable a systemd unit.
39///
40/// In dry-run mode, records `systemctl disable` and returns `Ok`.
41///
42/// # Errors
43///
44/// Returns [`HortoError::CommandFailed`] when `systemctl` cannot be spawned or exits non-zero.
45pub fn disable(ctx: &mut HostContext, unit: &str) -> Result<()> {
46    systemctl(ctx, &["disable", unit])
47}
48
49/// Enable a systemd unit.
50///
51/// In dry-run mode, records `systemctl enable` and returns `Ok`.
52///
53/// # Errors
54///
55/// Returns [`HortoError::CommandFailed`] when `systemctl` cannot be spawned or exits non-zero.
56pub fn enable(ctx: &mut HostContext, unit: &str) -> Result<()> {
57    systemctl(ctx, &["enable", unit])
58}
59
60/// Start a systemd unit.
61///
62/// In dry-run mode, records `systemctl start` and returns `Ok`.
63///
64/// # Errors
65///
66/// Returns [`HortoError::CommandFailed`] when `systemctl` cannot be spawned or exits non-zero.
67pub fn start(ctx: &mut HostContext, unit: &str) -> Result<()> {
68    systemctl(ctx, &["start", unit])
69}
70
71/// Restart a systemd unit.
72///
73/// In dry-run mode, records `systemctl restart` and returns `Ok`.
74///
75/// # Errors
76///
77/// Returns [`HortoError::CommandFailed`] when `systemctl` cannot be spawned or exits non-zero.
78pub fn restart(ctx: &mut HostContext, unit: &str) -> Result<()> {
79    systemctl(ctx, &["restart", unit])
80}
81
82/// Unmask a systemd unit.
83///
84/// In dry-run mode, records `systemctl unmask` and returns `Ok`.
85///
86/// # Errors
87///
88/// Returns [`HortoError::CommandFailed`] when `systemctl` cannot be spawned or exits non-zero.
89pub fn unmask(ctx: &mut HostContext, unit: &str) -> Result<()> {
90    systemctl(ctx, &["unmask", unit])
91}
92
93/// Restart `unit`; on failure append a warning to [`HostContext::logs`].
94pub fn try_restart(ctx: &mut HostContext, unit: &str) {
95    if let Err(e) = restart(ctx, unit) {
96        ctx.log(format!("warning: restart {unit}: {e}"));
97    }
98}
99
100/// Enable `unit`; on failure append a warning to [`HostContext::logs`].
101pub fn try_enable(ctx: &mut HostContext, unit: &str) {
102    if let Err(e) = enable(ctx, unit) {
103        ctx.log(format!("warning: enable {unit}: {e}"));
104    }
105}
106
107/// Unmask `unit`; on failure append a warning to [`HostContext::logs`].
108pub fn try_unmask(ctx: &mut HostContext, unit: &str) {
109    if let Err(e) = unmask(ctx, unit) {
110        ctx.log(format!("warning: unmask {unit}: {e}"));
111    }
112}
113
114/// Start `unit`; on failure append a warning to [`HostContext::logs`].
115pub fn try_start(ctx: &mut HostContext, unit: &str) {
116    if let Err(e) = start(ctx, unit) {
117        ctx.log(format!("warning: start {unit}: {e}"));
118    }
119}
120
121/// Stop `unit`; on failure append a warning to [`HostContext::logs`].
122pub fn try_stop(ctx: &mut HostContext, unit: &str) {
123    if let Err(e) = stop(ctx, unit) {
124        ctx.log(format!("warning: stop {unit}: {e}"));
125    }
126}
127
128/// Disable `unit`; on failure append a warning to [`HostContext::logs`].
129pub fn try_disable(ctx: &mut HostContext, unit: &str) {
130    if let Err(e) = disable(ctx, unit) {
131        ctx.log(format!("warning: disable {unit}: {e}"));
132    }
133}
134
135/// True when `systemctl list-unit-files` reports `{unit}.service`.
136#[must_use]
137pub fn unit_present(unit: &str) -> bool {
138    Command::new("systemctl")
139        .args(["list-unit-files", &format!("{unit}.service")])
140        .output()
141        .is_ok_and(|o| o.status.success() && !o.stdout.is_empty())
142}