feat(maestro): make installer re-run safe (auto-detect = update)
ci / rust (push) Successful in 1m28s
ci / rust (push) Successful in 1m28s
The wizard now treats existing state as "we're updating, not installing": - If `~/.config/maestrod/config.toml` exists, skip the configure prompt with a clear notice. Sets `configured = true` so the systemd start logic still makes sense. - If the systemd user unit already exists, refresh the file in place, skip the enable prompt (it's either already enabled or the user said no last time), and ask to `restart` instead of `start` — so the new binary actually takes effect. Adds `SystemctlAction::Restart` and a test for it. Same `./install.sh` works for both first install and subsequent updates; no `--update` flag. Refs NAP-225.
This commit is contained in:
@@ -20,6 +20,7 @@ pub enum SystemctlAction {
|
|||||||
DaemonReload,
|
DaemonReload,
|
||||||
Enable,
|
Enable,
|
||||||
Start,
|
Start,
|
||||||
|
Restart,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SystemctlAction {
|
impl SystemctlAction {
|
||||||
@@ -28,6 +29,7 @@ impl SystemctlAction {
|
|||||||
Self::DaemonReload => "daemon-reload",
|
Self::DaemonReload => "daemon-reload",
|
||||||
Self::Enable => "enable",
|
Self::Enable => "enable",
|
||||||
Self::Start => "start",
|
Self::Start => "start",
|
||||||
|
Self::Restart => "restart",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +166,12 @@ mod tests {
|
|||||||
assert_eq!(args, vec!["--user", "start", "maestrod.service"]);
|
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]
|
#[test]
|
||||||
fn cargo_bin_honors_env_var() {
|
fn cargo_bin_honors_env_var() {
|
||||||
// Save & restore $CARGO to keep the test hermetic.
|
// Save & restore $CARGO to keep the test hermetic.
|
||||||
|
|||||||
@@ -96,18 +96,23 @@ fn run(args: Args) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
// 2. Offer to run `maestrod configure` now (must happen before systemd
|
// 2. Offer to run `maestrod configure` now (must happen before systemd
|
||||||
// start, since the daemon refuses to run without registry_url / token).
|
// start, since the daemon refuses to run without registry_url / token).
|
||||||
// In non-interactive mode (--yes / no TTY) we skip — `maestrod configure`
|
// Re-runs of the wizard auto-skip when the config already exists; that's
|
||||||
// would prompt for required fields and we don't want to surprise scripts.
|
// what makes "re-run to update" pleasant. In non-interactive mode (--yes /
|
||||||
let want_configure = interactive
|
// no TTY) we also skip — `maestrod configure` would prompt for required
|
||||||
&& !args.no_configure
|
// fields and we don't want to surprise scripts.
|
||||||
&& confirm(
|
let configured = if let Some(path) = maestrod_config_path() {
|
||||||
"Run `maestrod configure` now (writes ~/.config/maestrod/config.toml)?",
|
if path.exists() {
|
||||||
true,
|
eprintln!(
|
||||||
)?;
|
"\nconfig already exists at {} — skipping configure step.",
|
||||||
let configured = if want_configure {
|
path.display()
|
||||||
run_configure_passthrough()?
|
);
|
||||||
|
eprintln!("(run `maestrod configure --force` to rewrite it.)");
|
||||||
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
run_configure_step(&args, interactive)?
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
run_configure_step(&args, interactive)?
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. systemd integration.
|
// 3. systemd integration.
|
||||||
@@ -170,6 +175,24 @@ fn install_crate(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn maestrod_config_path() -> Option<PathBuf> {
|
||||||
|
Some(dirs::config_dir()?.join("maestrod").join("config.toml"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_configure_step(args: &Args, interactive: bool) -> Result<bool, Box<dyn std::error::Error>> {
|
||||||
|
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<bool, Box<dyn std::error::Error>> {
|
fn run_configure_passthrough() -> Result<bool, Box<dyn std::error::Error>> {
|
||||||
// Run the freshly-installed `maestrod configure` interactively. We don't
|
// Run the freshly-installed `maestrod configure` interactively. We don't
|
||||||
// hard-code the path; rely on it being on PATH after `cargo install`.
|
// hard-code the path; rely on it being on PATH after `cargo install`.
|
||||||
@@ -203,8 +226,18 @@ fn handle_systemd(
|
|||||||
};
|
};
|
||||||
|
|
||||||
eprintln!();
|
eprintln!();
|
||||||
|
let already_installed = unit_path.exists();
|
||||||
|
if already_installed {
|
||||||
|
eprintln!(
|
||||||
|
"systemd unit already present at {} — refreshing.",
|
||||||
|
unit_path.display()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
eprintln!("systemd detected.");
|
eprintln!("systemd detected.");
|
||||||
let want_unit = if !interactive {
|
}
|
||||||
|
|
||||||
|
// First-install: ask before dropping the unit. Update: just refresh it.
|
||||||
|
let want_unit = if already_installed || !interactive {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
confirm(
|
confirm(
|
||||||
@@ -225,6 +258,7 @@ fn handle_systemd(
|
|||||||
|
|
||||||
run_systemctl(SystemctlAction::DaemonReload, UNIT_FILE_NAME)?;
|
run_systemctl(SystemctlAction::DaemonReload, UNIT_FILE_NAME)?;
|
||||||
|
|
||||||
|
if !already_installed {
|
||||||
let want_enable = if !interactive {
|
let want_enable = if !interactive {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@@ -233,10 +267,19 @@ fn handle_systemd(
|
|||||||
if want_enable {
|
if want_enable {
|
||||||
run_systemctl(SystemctlAction::Enable, UNIT_FILE_NAME)?;
|
run_systemctl(SystemctlAction::Enable, UNIT_FILE_NAME)?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-start only when the user actually ran configure this session;
|
// Update path: restart so the freshly-installed binary actually runs.
|
||||||
// otherwise the daemon will fail immediately for lack of credentials.
|
// First-install path: only start if the user has a config to start with.
|
||||||
let want_start = if !interactive {
|
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 {
|
||||||
|
let want = if !interactive {
|
||||||
false
|
false
|
||||||
} else if !configured {
|
} else if !configured {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
@@ -247,8 +290,10 @@ fn handle_systemd(
|
|||||||
} else {
|
} else {
|
||||||
confirm("Start maestrod.service now?", true)?
|
confirm("Start maestrod.service now?", true)?
|
||||||
};
|
};
|
||||||
if want_start {
|
(SystemctlAction::Start, want)
|
||||||
run_systemctl(SystemctlAction::Start, UNIT_FILE_NAME)?;
|
};
|
||||||
|
if want {
|
||||||
|
run_systemctl(action, UNIT_FILE_NAME)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
+6
-1
@@ -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
|
The bootstrap script builds `maestro-installer` into a temp directory, runs the
|
||||||
wizard, and cleans up — only `maestrod` and `maestro` end up persisted in
|
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 …`
|
Inside the script, `cargo install --git https://git.naps.pt/naps62/maestro …`
|
||||||
uses your already-configured git credentials for this host (HTTPS helper or
|
uses your already-configured git credentials for this host (HTTPS helper or
|
||||||
|
|||||||
Reference in New Issue
Block a user