mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 14:09:42 +02:00
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:
@@ -66,3 +66,4 @@ export * from './teams.js';
|
||||
export * from './push.js';
|
||||
export * from './plan.js';
|
||||
export * from './orchestrator.js';
|
||||
export * from './update.js';
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @fileoverview Types for the in-app self-updater.
|
||||
*
|
||||
* Codeman can update itself from the web UI (App Settings → Updates). The flow
|
||||
* is driven by a detached `scripts/self-update.sh` that outlives the service
|
||||
* restart it triggers, and a status file at `~/.codeman/update-status.json`
|
||||
* (see `dataPath('update-status.json')`) that the browser polls across the
|
||||
* restart boundary.
|
||||
*
|
||||
* Backend logic: `src/web/self-update.ts`. Routes: `src/web/routes/system-routes.ts`
|
||||
* (`/api/system/update/check`, `POST /api/system/update`, `/api/system/update/status`).
|
||||
*
|
||||
* @module types/update
|
||||
*/
|
||||
|
||||
/** Which init system supervises the running server (decides how we restart it). */
|
||||
export type SupervisorKind = 'systemd' | 'launchd' | 'none';
|
||||
|
||||
/** How Codeman was installed — only `git` installs can self-update in place. */
|
||||
export type InstallKind = 'git' | 'npm' | 'unknown';
|
||||
|
||||
/**
|
||||
* Lifecycle of a single update run. `idle`/`completed`/`failed`/
|
||||
* `completed-needs-manual-restart` are terminal; the rest are in-flight.
|
||||
*/
|
||||
export type UpdatePhase =
|
||||
| 'idle'
|
||||
| 'queued'
|
||||
| 'preparing'
|
||||
| 'stashing'
|
||||
| 'fetching'
|
||||
| 'checkout'
|
||||
| 'installing'
|
||||
| 'building'
|
||||
| 'restarting'
|
||||
| 'completed'
|
||||
| 'completed-needs-manual-restart'
|
||||
| 'failed';
|
||||
|
||||
/** Persisted update progress, written atomically by the updater + boot reconcile. */
|
||||
export interface UpdateStatus {
|
||||
/** Nonce identifying this run; guards boot-reconcile against stale/foreign status. */
|
||||
updateId: string;
|
||||
phase: UpdatePhase;
|
||||
/** Human-readable one-liner for the UI. */
|
||||
message: string;
|
||||
/** Version the server was on when the update started. */
|
||||
fromVersion: string;
|
||||
/** Target version (parsed from the release tag). */
|
||||
toVersion?: string;
|
||||
/** Target git tag, e.g. `codeman@0.9.4`. */
|
||||
toTag?: string;
|
||||
/** Commit the repo was on before the update, for rollback. */
|
||||
prevSha?: string;
|
||||
/** Name of the stash holding local changes (when the tree was dirty), else null. */
|
||||
stashRef?: string | null;
|
||||
supervisor?: SupervisorKind;
|
||||
/** epoch ms — update start (freshness guard for boot reconcile). */
|
||||
startedAt: number;
|
||||
/** epoch ms — last write. */
|
||||
updatedAt: number;
|
||||
/** Populated on failure. */
|
||||
error?: string;
|
||||
/** Shown for the `none` supervisor — the command the user must run by hand. */
|
||||
manualRestartCommand?: string;
|
||||
}
|
||||
|
||||
/** Describes the running install — drives whether/how the Updates UI is shown. */
|
||||
export interface InstallInfo {
|
||||
installKind: InstallKind;
|
||||
installDir: string;
|
||||
/** Current git branch, or `HEAD` when detached (e.g. pinned to a release tag). */
|
||||
branch?: string;
|
||||
/** Uncommitted local changes present (true → updater will auto-stash). */
|
||||
dirty: boolean;
|
||||
supervisor: SupervisorKind;
|
||||
currentVersion: string;
|
||||
/** False when `CODEMAN_DISABLE_SELF_UPDATE=1`. */
|
||||
selfUpdateEnabled: boolean;
|
||||
}
|
||||
|
||||
/** Result of "check for updates" — current vs. latest release. */
|
||||
export interface UpdateCheckResult {
|
||||
currentVersion: string;
|
||||
latestVersion: string | null;
|
||||
latestTag: string | null;
|
||||
updateAvailable: boolean;
|
||||
/** Release notes (markdown) when available from the GitHub API. */
|
||||
notes?: string | null;
|
||||
/** Link to the release page. */
|
||||
htmlUrl?: string | null;
|
||||
/** epoch ms of the check. */
|
||||
checkedAt: number;
|
||||
source: 'github-api' | 'git-ls-remote' | 'none';
|
||||
error?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user