feat(api): add online console update with pre-install backup

- Add console update planning, download validation and staged installation.

- Create a component backup before applying online updates.

- Expose controlled update routes from the production control plane.
This commit is contained in:
chick
2026-09-07 00:44:58 +08:00
parent 961eb928c4
commit 7168e9dba2
5 changed files with 1427 additions and 0 deletions
+15
View File
@@ -4,6 +4,10 @@ import {
type ControlPlaneApp,
type SafeControlPlaneUpstream,
} from './control-plane.js';
import {
createCommandRunner,
createLaunchdRestartLauncher,
} from './application/system/console-update-service.js';
import { openDatabase } from './infrastructure/database/database.js';
import { migrateDatabase } from './infrastructure/database/migrations.js';
import { MacOSKeychainSecretStore } from './infrastructure/secrets/keychain-secret-store.js';
@@ -23,6 +27,8 @@ export interface ProductionControlPlaneOptions {
readonly upstreamGatewayCheck?: () => boolean | Promise<boolean>;
/** Production keeps device online state fresh on its own; tests and canaries can opt out. */
readonly heartbeatEnabled?: boolean;
/** Environment used to find the launchd label that can restart this process. */
readonly env?: Readonly<Record<string, string | undefined>>;
}
export function buildProductionControlPlane(
@@ -39,6 +45,9 @@ export function buildProductionControlPlane(
try {
migrateDatabase(db);
const store = options.store ?? new MacOSKeychainSecretStore();
const env = options.env ?? process.env;
const updateRoot = env.MULTI_SIMADMIN_UPDATE_ROOT ?? process.cwd();
const updateRunner = createCommandRunner({ cwd: updateRoot, timeoutMs: 120_000 });
const app = buildControlPlaneApp({
db,
store,
@@ -46,6 +55,12 @@ export function buildProductionControlPlane(
runtimeVersion: options.runtimeVersion ?? '0.1.0',
backupDirectory: join(dirname(options.databasePath), 'backups'),
heartbeatEnabled: options.heartbeatEnabled ?? true,
update: {
remote: env.MULTI_SIMADMIN_UPDATE_REMOTE ?? 'origin',
root: updateRoot,
runner: updateRunner,
launcher: createLaunchdRestartLauncher({ env, runner: updateRunner }),
},
// This route-local decision is safe only in this composition: when a token is
// configured, buildApp's global onRequest hook rejects the request first.
// Keep all other control-plane compositions fail-closed by default.