mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 23:19:43 +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:
@@ -35,6 +35,7 @@ import {
|
||||
SETTINGS_PATH,
|
||||
} from '../route-helpers.js';
|
||||
import { SseEvent } from '../sse-events.js';
|
||||
import { getInstallInfo, checkForUpdate, startUpdate, getUpdateStatusForApi } from '../self-update.js';
|
||||
import type { SessionPort, EventPort, ConfigPort, InfraPort, AuthPort } from '../ports/index.js';
|
||||
import { AUTH_COOKIE_NAME } from '../middleware/auth.js';
|
||||
import { QR_AUTH_FAILURE_MAX } from '../../config/tunnel-config.js';
|
||||
@@ -293,6 +294,39 @@ export function registerSystemRoutes(
|
||||
}
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Self-Update (App Settings → Updates)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
// Install info + whether a newer release exists. Manual, user-triggered.
|
||||
app.get('/api/system/update/check', async () => {
|
||||
const check = await checkForUpdate();
|
||||
const info = getInstallInfo();
|
||||
return { ...info, ...check };
|
||||
});
|
||||
|
||||
// Poll target for update progress — survives the restart the update triggers.
|
||||
app.get('/api/system/update/status', async () => getUpdateStatusForApi());
|
||||
|
||||
// Kick off a detached update to the latest release. Returns immediately; the
|
||||
// browser then polls /api/system/update/status across the service restart.
|
||||
app.post('/api/system/update', async (_req, reply) => {
|
||||
const result = await startUpdate();
|
||||
if (result.ok) {
|
||||
return { success: true, updateId: result.updateId, toTag: result.toTag, toVersion: result.toVersion };
|
||||
}
|
||||
const map = {
|
||||
'in-flight': { http: 409, api: ApiErrorCode.ALREADY_EXISTS },
|
||||
'up-to-date': { http: 409, api: ApiErrorCode.ALREADY_EXISTS },
|
||||
'not-git': { http: 400, api: ApiErrorCode.INVALID_INPUT },
|
||||
disabled: { http: 403, api: ApiErrorCode.INVALID_INPUT },
|
||||
'bad-tag': { http: 400, api: ApiErrorCode.INVALID_INPUT },
|
||||
error: { http: 500, api: ApiErrorCode.INTERNAL_ERROR },
|
||||
} as const;
|
||||
const m = map[result.code];
|
||||
return reply.code(m.http).send(createErrorResponse(m.api, result.message));
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// CLI Integrations (OpenCode)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user