feat(self-update): launchd-daemon supervisor — rootless restart on headless Macs

A KeepAlive system-level LaunchDaemon (the right setup for headless Macs,
where no GUI login means LaunchAgents never start) is now detected as
supervisor 'launchd-daemon': the updater kills the server PID (passed via
--server-pid) and launchd respawns it on the new dist/ — no root needed.
Detection requires the daemon plist to be bootstrapped AND KeepAlive=true.

Also: on boot, a 'completed-needs-manual-restart' status auto-completes
when the running version matches the staged target, so the stale
'restart Codeman to apply' instruction no longer lingers in the UI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-06-10 17:08:36 +02:00
co-authored by Claude Fable 5
parent 0be3d09603
commit c1458d8ab8
5 changed files with 63 additions and 4 deletions
+13
View File
@@ -153,4 +153,17 @@ describe('reconcileStatusDecision (boot handoff state machine)', () => {
expect(out?.phase).toBe('failed');
expect(out?.error).toContain('building');
});
it('needs-manual-restart + now running the target version → completed', () => {
const out = reconcileStatusDecision(base({ phase: 'completed-needs-manual-restart' }), '0.9.4', NOW);
expect(out?.phase).toBe('completed');
expect(out?.message).toContain('0.9.4');
expect(out?.updatedAt).toBe(NOW);
});
it('needs-manual-restart + still on the old version → untouched (restart pending)', () => {
expect(reconcileStatusDecision(base({ phase: 'completed-needs-manual-restart' }), '0.9.3', NOW)).toBeNull();
const noTarget = base({ phase: 'completed-needs-manual-restart', toVersion: undefined });
expect(reconcileStatusDecision(noTarget, '0.9.4', NOW)).toBeNull();
});
});