Skip to main content

horto_os_ui_shared/kits/
apt.rs

1//! Apt / dpkg helpers that respect [`HostContext`] dry-run vs apply.
2
3use crate::context::HostContext;
4use crate::error::{HortoError, Result};
5use std::process::Command;
6
7/// Run `apt update`.
8///
9/// In dry-run mode, records a planned action and returns `Ok`.
10///
11/// # Errors
12///
13/// Returns [`HortoError::CommandFailed`] when `apt` cannot be spawned or exits non-zero.
14pub fn apt_update(ctx: &mut HostContext) -> Result<()> {
15    run_apt(ctx, &["update"])
16}
17
18/// Run `apt install -y` for `packages`.
19///
20/// In dry-run mode, records a planned action and returns `Ok`.
21///
22/// # Errors
23///
24/// Returns [`HortoError::CommandFailed`] when `apt` cannot be spawned or exits non-zero.
25pub fn apt_install(ctx: &mut HostContext, packages: &[&str]) -> Result<()> {
26    let mut args = vec!["install", "-y"];
27    args.extend_from_slice(packages);
28    run_apt(ctx, &args)
29}
30
31fn run_apt(ctx: &mut HostContext, args: &[&str]) -> Result<()> {
32    let summary = format!("apt {}", args.join(" "));
33    if ctx.is_dry_run() {
34        ctx.plan_action(summary);
35        return Ok(());
36    }
37    ctx.log(&summary);
38    let status = Command::new("apt")
39        .args(args)
40        .status()
41        .map_err(|e| HortoError::command("apt", e.to_string()))?;
42    if !status.success() {
43        return Err(HortoError::command(
44            "apt",
45            format!("{} -> {status}", args.join(" ")),
46        ));
47    }
48    Ok(())
49}
50
51/// True when `dpkg-query` reports `install ok installed` for `name`.
52#[must_use]
53pub fn package_installed(name: &str) -> bool {
54    Command::new("dpkg-query")
55        .args(["-W", "-f=${Status}", name])
56        .output()
57        .is_ok_and(|o| {
58            let s = String::from_utf8_lossy(&o.stdout);
59            s.contains("install ok installed")
60        })
61}