args-cli 0.1.1

Command-line interface for args
Documentation
use std::process::Command;

use super::service::{Service, ServiceManager};
use crate::{config::runner::SYSTEM_USER, util::run_command};

pub fn install_service() -> anyhow::Result<()> {
    ensure_args_user()?;

    let manager = ServiceManager::new()?;

    println!("Removing any existing runner service...");
    manager.uninstall()?;

    println!("Installing runner service...");
    manager.install()?;
    println!("Runner installed.");

    Ok(())
}

fn user_exists(username: &str) -> bool {
    Command::new("id")
        .arg(username)
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::null())
        .status()
        .map(|s| s.success())
        .unwrap_or(false)
}

fn ensure_args_user() -> anyhow::Result<()> {
    if user_exists(SYSTEM_USER) {
        return Ok(());
    }

    println!("Creating system user '{SYSTEM_USER}'...");

    #[cfg(target_os = "macos")]
    {
        let uid = find_free_uid_macos()?;
        let uid_str = uid.to_string();
        let user_path = format!("/Users/{SYSTEM_USER}");
        let cmds: &[&[&str]] = &[
            &["dscl", ".", "-create", &user_path],
            &["dscl", ".", "-create", &user_path, "UniqueID", &uid_str],
            &["dscl", ".", "-create", &user_path, "PrimaryGroupID", "20"],
            &[
                "dscl",
                ".",
                "-create",
                &user_path,
                "UserShell",
                "/usr/bin/false",
            ],
            &[
                "dscl",
                ".",
                "-create",
                &user_path,
                "RealName",
                "Args Runner",
            ],
            &[
                "dscl",
                ".",
                "-create",
                &user_path,
                "NFSHomeDirectory",
                "/var/empty",
            ],
        ];
        for cmd in cmds {
            run_command("sudo", cmd)?;
        }
    }

    #[cfg(not(target_os = "macos"))]
    {
        run_command(
            "sudo",
            &[
                "useradd",
                "--system",
                "--shell",
                "/usr/sbin/nologin",
                "--no-create-home",
                SYSTEM_USER,
            ],
        )?;
    }

    println!("System user '{SYSTEM_USER}' created.");
    Ok(())
}

#[cfg(target_os = "macos")]
fn find_free_uid_macos() -> anyhow::Result<u32> {
    for uid in 200u32..=400 {
        let out = Command::new("dscl")
            .args([".", "-search", "/Users", "UniqueID", &uid.to_string()])
            .output()?;
        if String::from_utf8_lossy(&out.stdout).trim().is_empty() {
            return Ok(uid);
        }
    }
    anyhow::bail!("No free UID found in range 200-400 for args user");
}