feat: add in-app self-updater (App Settings → Updates)

Update Codeman from the web UI: a "Check for updates" button queries GitHub
for the latest tagged release (git ls-remote fallback) and shows release
notes; "Update now" runs git checkout <tag> → npm install → npm run build →
restart, streaming live progress that survives the service restart.

- Release-tag channel; dirty trees auto-stashed (left for manual git stash pop)
- Cross-platform restart: systemd / launchd / manual, detected at runtime
- Updater runs detached (systemd-run --scope on Linux, setsid on macOS) so the
  restart it triggers can't kill the build mid-flight
- Build-failure rollback to the pre-update commit; boot reconcile with an
  update-id/freshness guard; 409 concurrency lock; runner staged outside the
  repo; strict tag validation; CODEMAN_DISABLE_SELF_UPDATE kill-switch
- Endpoints: GET /api/system/update/check, POST /api/system/update,
  GET /api/system/update/status

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arkon
2026-06-09 01:55:54 +02:00
co-authored by Claude Opus 4.8
parent 3503b6ae55
commit 543be8a85b
9 changed files with 1222 additions and 0 deletions
+9
View File
@@ -85,6 +85,8 @@ import {
type RespawnWiringDeps,
} from './respawn-event-wiring.js';
import { reconcileUpdateOnBoot } from './self-update.js';
// Load version from package.json
const require = createRequire(import.meta.url);
const { version: APP_VERSION } = require('../../package.json');
@@ -1665,6 +1667,13 @@ export class WebServer extends EventEmitter {
lifecycleLog.log({ event: 'server_started', sessionId: '*' });
await lifecycleLog.trimIfNeeded();
// If a self-update restarted us into this process, finalize its status file
// (flip the persisted "restarting" marker → completed/failed based on the
// version we actually booted). No-op on a normal boot. See web/self-update.ts.
if (!this.testMode) {
reconcileUpdateOnBoot();
}
// Restore mux sessions BEFORE accepting connections
// This prevents race conditions where clients connect before state is ready
// CRITICAL: Skip in test mode to prevent tests from picking up user sessions