From 4247b5706e2062d1df85f1139beaa8a6ffad96d9 Mon Sep 17 00:00:00 2001 From: naps62-yolo Date: Tue, 16 Jun 2026 09:19:12 +0000 Subject: [PATCH] fix(maestro): installer unit sets KillMode=process so restarts keep sessions (NAP-330) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The generated `maestro.service` used systemd's default `KillMode=control-group`, so `systemctl restart` (any update/auto-update) SIGKILLed the daemon's whole cgroup — including the tmux server and every `maestro-` session. recover() then found nothing, silently destroying all live sessions and their processes on every restart. Emit `KillMode=process` in the daemon unit so only the daemon's MainPID is signalled; the tmux server survives and recover() re-adopts the sessions on the next start. (The web unit spawns no tmux, so it's left unchanged.) Tests assert the daemon unit carries KillMode=process and the web unit does not. INSTALL.md notes that updating keeps sessions, and that units written before this default need a refresh. Verified live on a host: with KillMode=process a restart leaves the session's PID unchanged (recovered=1); without it, recovered=0 and the process is gone. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/maestro-installer/src/systemd.rs | 27 +++++++++++++++++++++++++ docs/INSTALL.md | 5 ++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/crates/maestro-installer/src/systemd.rs b/crates/maestro-installer/src/systemd.rs index a2a1aa7..4dc4e80 100644 --- a/crates/maestro-installer/src/systemd.rs +++ b/crates/maestro-installer/src/systemd.rs @@ -58,6 +58,11 @@ pub fn render_user_unit(exec_start: &Path) -> String { Type=simple\n\ ExecStart={exec} daemon\n\ ExecReload=/bin/kill -HUP $MAINPID\n\ + # Signal only the daemon on stop/restart, not its whole cgroup —\n\ + # otherwise systemd kills the tmux server (and every session) the\n\ + # daemon spawned, defeating tmux persistence. recover() re-adopts the\n\ + # surviving sessions on the next start (NAP-330).\n\ + KillMode=process\n\ Restart=on-failure\n\ RestartSec=5\n\ \n\ @@ -137,6 +142,28 @@ mod tests { assert!(body.contains("Restart=on-failure")); } + #[test] + fn unit_sets_killmode_process_so_restart_keeps_sessions() { + // NAP-330: without KillMode=process, `systemctl restart` SIGKILLs the + // daemon's whole cgroup — including the tmux server and every session. + let body = render_user_unit(Path::new("/x/maestro")); + assert!( + body.contains("\nKillMode=process\n"), + "daemon unit must set KillMode=process; got:\n{body}" + ); + } + + #[test] + fn web_unit_does_not_set_killmode() { + // The web unit spawns no tmux, so it has no cgroup-kill problem and + // shouldn't carry the override. + let body = render_web_unit(Path::new("/x/maestro-web")); + assert!( + !body.contains("KillMode"), + "web unit needs no KillMode; got:\n{body}" + ); + } + #[test] fn unit_wires_execreload_to_sighup() { // NAP-237: `systemctl --user reload maestro` must trigger our diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 8efb48f..677be4a 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -20,7 +20,10 @@ wizard, and cleans up — only the unified `maestro` binary ends up persisted in existing `[daemon]` section in `~/.config/maestro/config.toml` and an existing systemd unit, skips the configure prompt, refreshes the unit, and runs `systemctl --user restart maestro` instead of `start`. No `--update` -flag needed. +flag needed. **The restart keeps your sessions:** the unit sets +`KillMode=process`, so systemd signals only the daemon — the tmux server (and +every session) survives, and the new daemon re-adopts them on start. A unit +written before this default lacks the line; re-run `./install.sh` to refresh it. Inside the script, `cargo install --git https://git.naps.pt/naps62/maestro …` uses your already-configured git credentials for this host (HTTPS helper or