diff --git a/crates/maestro-installer/src/install.rs b/crates/maestro-installer/src/install.rs index 5b38b86..c1ccfc7 100644 --- a/crates/maestro-installer/src/install.rs +++ b/crates/maestro-installer/src/install.rs @@ -20,6 +20,7 @@ pub enum SystemctlAction { DaemonReload, Enable, Start, + Restart, } impl SystemctlAction { @@ -28,6 +29,7 @@ impl SystemctlAction { Self::DaemonReload => "daemon-reload", Self::Enable => "enable", Self::Start => "start", + Self::Restart => "restart", } } @@ -164,6 +166,12 @@ mod tests { assert_eq!(args, vec!["--user", "start", "maestrod.service"]); } + #[test] + fn systemctl_restart_includes_unit() { + let args = systemctl_user_args(SystemctlAction::Restart, "maestrod.service"); + assert_eq!(args, vec!["--user", "restart", "maestrod.service"]); + } + #[test] fn cargo_bin_honors_env_var() { // Save & restore $CARGO to keep the test hermetic. diff --git a/crates/maestro-installer/src/main.rs b/crates/maestro-installer/src/main.rs index 3033c6c..2cf12f1 100644 --- a/crates/maestro-installer/src/main.rs +++ b/crates/maestro-installer/src/main.rs @@ -96,18 +96,23 @@ fn run(args: Args) -> Result<(), Box> { // 2. Offer to run `maestrod configure` now (must happen before systemd // start, since the daemon refuses to run without registry_url / token). - // In non-interactive mode (--yes / no TTY) we skip — `maestrod configure` - // would prompt for required fields and we don't want to surprise scripts. - let want_configure = interactive - && !args.no_configure - && confirm( - "Run `maestrod configure` now (writes ~/.config/maestrod/config.toml)?", - true, - )?; - let configured = if want_configure { - run_configure_passthrough()? + // Re-runs of the wizard auto-skip when the config already exists; that's + // what makes "re-run to update" pleasant. In non-interactive mode (--yes / + // no TTY) we also skip — `maestrod configure` would prompt for required + // fields and we don't want to surprise scripts. + let configured = if let Some(path) = maestrod_config_path() { + if path.exists() { + eprintln!( + "\nconfig already exists at {} — skipping configure step.", + path.display() + ); + eprintln!("(run `maestrod configure --force` to rewrite it.)"); + true + } else { + run_configure_step(&args, interactive)? + } } else { - false + run_configure_step(&args, interactive)? }; // 3. systemd integration. @@ -170,6 +175,24 @@ fn install_crate( Ok(()) } +fn maestrod_config_path() -> Option { + Some(dirs::config_dir()?.join("maestrod").join("config.toml")) +} + +fn run_configure_step(args: &Args, interactive: bool) -> Result> { + let want = interactive + && !args.no_configure + && confirm( + "Run `maestrod configure` now (writes ~/.config/maestrod/config.toml)?", + true, + )?; + if want { + run_configure_passthrough() + } else { + Ok(false) + } +} + fn run_configure_passthrough() -> Result> { // Run the freshly-installed `maestrod configure` interactively. We don't // hard-code the path; rely on it being on PATH after `cargo install`. @@ -203,8 +226,18 @@ fn handle_systemd( }; eprintln!(); - eprintln!("systemd detected."); - let want_unit = if !interactive { + let already_installed = unit_path.exists(); + if already_installed { + eprintln!( + "systemd unit already present at {} — refreshing.", + unit_path.display() + ); + } else { + eprintln!("systemd detected."); + } + + // First-install: ask before dropping the unit. Update: just refresh it. + let want_unit = if already_installed || !interactive { true } else { confirm( @@ -225,30 +258,42 @@ fn handle_systemd( run_systemctl(SystemctlAction::DaemonReload, UNIT_FILE_NAME)?; - let want_enable = if !interactive { - true - } else { - confirm("Enable maestrod.service (start on login)?", true)? - }; - if want_enable { - run_systemctl(SystemctlAction::Enable, UNIT_FILE_NAME)?; + if !already_installed { + let want_enable = if !interactive { + true + } else { + confirm("Enable maestrod.service (start on login)?", true)? + }; + if want_enable { + run_systemctl(SystemctlAction::Enable, UNIT_FILE_NAME)?; + } } - // Auto-start only when the user actually ran configure this session; - // otherwise the daemon will fail immediately for lack of credentials. - let want_start = if !interactive { - false - } else if !configured { - eprintln!( - "Note: starting now will fail until you run `maestrod configure` \ - (no registry_url / token yet)." - ); - confirm("Start maestrod.service now anyway?", false)? + // Update path: restart so the freshly-installed binary actually runs. + // First-install path: only start if the user has a config to start with. + let (action, want) = if already_installed { + let want = if !interactive { + true + } else { + confirm("Restart maestrod.service to pick up the new binary?", true)? + }; + (SystemctlAction::Restart, want) } else { - confirm("Start maestrod.service now?", true)? + let want = if !interactive { + false + } else if !configured { + eprintln!( + "Note: starting now will fail until you run `maestrod configure` \ + (no registry_url / token yet)." + ); + confirm("Start maestrod.service now anyway?", false)? + } else { + confirm("Start maestrod.service now?", true)? + }; + (SystemctlAction::Start, want) }; - if want_start { - run_systemctl(SystemctlAction::Start, UNIT_FILE_NAME)?; + if want { + run_systemctl(action, UNIT_FILE_NAME)?; } Ok(()) diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 132288a..267dcaf 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -14,7 +14,12 @@ The repo is private, so grab `install.sh` while signed in to gitea, then run it: The bootstrap script builds `maestro-installer` into a temp directory, runs the wizard, and cleans up — only `maestrod` and `maestro` end up persisted in -`~/.cargo/bin`. Re-run any time to update; `cargo install` overwrites in place. +`~/.cargo/bin`. + +**Updating is the same command.** Re-running `./install.sh` detects an +existing `~/.config/maestrod/config.toml` and existing systemd unit, skips the +configure prompt, refreshes the unit, and runs `systemctl --user restart +maestrod` instead of `start`. No `--update` flag needed. Inside the script, `cargo install --git https://git.naps.pt/naps62/maestro …` uses your already-configured git credentials for this host (HTTPS helper or