This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/crates/maestro-installer/src/install.rs
T
Miguel Palhas c856333e4a
ci / rust (push) Successful in 2m40s
docs(maestro): describe the single-host architecture
Rewrites CLAUDE.md as the current-architecture reference, drops the
registry from INSTALL/PRODUCT/DESIGN and the web README, deletes the
registry Dockerfile, and marks the dated design specs superseded where
they describe the registry, JWT auth, or cross-host attach.

The installer loses its configure step — every config field now has a
default, so a daemon starts with no config file at all.
2026-08-22 10:26:49 +01:00

212 lines
6.3 KiB
Rust

//! Builders for the external commands the wizard shells out to.
//!
//! Kept pure so unit tests can assert the exact argv we'd hand to
//! `std::process::Command` without ever exec'ing `cargo` or `systemctl`.
use std::path::PathBuf;
/// Where to fetch the maestro crates from when running `cargo install`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CargoSource {
/// `cargo install --git <url> [--branch <branch>] <crate>`.
Git { url: String, branch: Option<String> },
/// `cargo install --path <workspace_root> -p <crate>`.
Path(PathBuf),
}
/// `systemctl --user` verbs the wizard runs.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SystemctlAction {
DaemonReload,
Enable,
Start,
Restart,
}
impl SystemctlAction {
pub fn verb(self) -> &'static str {
match self {
Self::DaemonReload => "daemon-reload",
Self::Enable => "enable",
Self::Start => "start",
Self::Restart => "restart",
}
}
fn takes_unit_arg(self) -> bool {
!matches!(self, Self::DaemonReload)
}
}
/// Build the args for `cargo install <args...>` (excluding the `cargo` and
/// `install` words themselves). `locked` adds `--locked` so the install uses
/// the committed `Cargo.lock`.
/// Build the `cargo install` argument vector.
///
/// `features`, when `Some`, restricts the build to an explicit feature set via
/// `--no-default-features --features <list>`.
pub fn cargo_install_args(
source: &CargoSource,
crate_name: &str,
locked: bool,
features: Option<&str>,
) -> Vec<String> {
let mut args: Vec<String> = Vec::new();
match source {
CargoSource::Git { url, branch } => {
args.push("--git".into());
args.push(url.clone());
if let Some(b) = branch {
args.push("--branch".into());
args.push(b.clone());
}
args.push(crate_name.to_string());
}
CargoSource::Path(root) => {
args.push("--path".into());
args.push(root.display().to_string());
args.push("-p".into());
args.push(crate_name.to_string());
}
}
if let Some(list) = features {
args.push("--no-default-features".into());
args.push("--features".into());
args.push(list.to_string());
}
if locked {
args.push("--locked".into());
}
args
}
/// Build the args for `systemctl --user <verb> [unit]`.
pub fn systemctl_user_args(action: SystemctlAction, unit: &str) -> Vec<String> {
let mut args = vec!["--user".to_string(), action.verb().to_string()];
if action.takes_unit_arg() {
args.push(unit.to_string());
}
args
}
/// `cargo` binary path. We prefer `$CARGO` (set by `cargo install` itself when
/// it invokes child processes, and by `rustup`), then `~/.cargo/bin/cargo`, and
/// finally a bare `cargo` so PATH lookup applies.
pub fn cargo_bin() -> PathBuf {
if let Ok(p) = std::env::var("CARGO") {
return PathBuf::from(p);
}
if let Some(home) = dirs::home_dir() {
let in_cargo_home = home.join(".cargo").join("bin").join("cargo");
if in_cargo_home.exists() {
return in_cargo_home;
}
}
PathBuf::from("cargo")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn git_args_no_branch() {
let src = CargoSource::Git {
url: "https://git.naps.pt/naps62/maestro".into(),
branch: None,
};
let args = cargo_install_args(&src, "maestro", false, None);
assert_eq!(
args,
vec!["--git", "https://git.naps.pt/naps62/maestro", "maestro",]
);
}
#[test]
fn git_args_with_features_omit_defaults() {
let src = CargoSource::Git {
url: "https://git.naps.pt/naps62/maestro".into(),
branch: None,
};
let args = cargo_install_args(&src, "maestro", true, Some("cli,daemon,tui"));
assert_eq!(
args,
vec![
"--git",
"https://git.naps.pt/naps62/maestro",
"maestro",
"--no-default-features",
"--features",
"cli,daemon,tui",
"--locked",
]
);
}
#[test]
fn git_args_with_branch_and_locked() {
let src = CargoSource::Git {
url: "https://git.example/x".into(),
branch: Some("main".into()),
};
let args = cargo_install_args(&src, "maestro", true, None);
assert_eq!(
args,
vec![
"--git",
"https://git.example/x",
"--branch",
"main",
"maestro",
"--locked",
]
);
}
#[test]
fn path_args_use_p_flag() {
let src = CargoSource::Path(PathBuf::from("/repos/maestro"));
let args = cargo_install_args(&src, "maestro", true, None);
assert_eq!(
args,
vec!["--path", "/repos/maestro", "-p", "maestro", "--locked",]
);
}
#[test]
fn systemctl_daemon_reload_omits_unit() {
let args = systemctl_user_args(SystemctlAction::DaemonReload, "maestro.service");
assert_eq!(args, vec!["--user", "daemon-reload"]);
}
#[test]
fn systemctl_enable_includes_unit() {
let args = systemctl_user_args(SystemctlAction::Enable, "maestro.service");
assert_eq!(args, vec!["--user", "enable", "maestro.service"]);
}
#[test]
fn systemctl_start_includes_unit() {
let args = systemctl_user_args(SystemctlAction::Start, "maestro.service");
assert_eq!(args, vec!["--user", "start", "maestro.service"]);
}
#[test]
fn systemctl_restart_includes_unit() {
let args = systemctl_user_args(SystemctlAction::Restart, "maestro.service");
assert_eq!(args, vec!["--user", "restart", "maestro.service"]);
}
#[test]
fn cargo_bin_honors_env_var() {
// Save & restore $CARGO to keep the test hermetic.
let prev = std::env::var("CARGO").ok();
std::env::set_var("CARGO", "/opt/rust/bin/cargo");
assert_eq!(cargo_bin(), PathBuf::from("/opt/rust/bin/cargo"));
match prev {
Some(v) => std::env::set_var("CARGO", v),
None => std::env::remove_var("CARGO"),
}
}
}