74 lines
4.4 KiB
TypeScript
74 lines
4.4 KiB
TypeScript
import { randomUUID } from 'node:crypto';
|
|
import { db } from '../db/index.js';
|
|
import { recipeForDeployment, summarizeRecipe } from './launch-recipes.js';
|
|
|
|
export function beginDeployment(server: string, action: 'create' | 'destroy', triggeredBy = 'ui'): string {
|
|
const id = randomUUID();
|
|
db().prepare(`INSERT INTO server_deployments (id, server, action, state, stage, started_at, triggered_by)
|
|
VALUES (?, ?, ?, 'running', 'Starting', ?, ?)`).run(id, server, action, new Date().toISOString(), triggeredBy);
|
|
recordDeploymentEvent(id, action === 'create' ? 'Server creation requested' : 'Server destruction requested');
|
|
return id;
|
|
}
|
|
|
|
export function deploymentStage(id: string, stage: string): void {
|
|
const previous = db().prepare('SELECT stage FROM server_deployments WHERE id = ?').get(id) as { stage: string } | undefined;
|
|
db().prepare('UPDATE server_deployments SET stage = ? WHERE id = ?').run(stage, id);
|
|
if (previous && previous.stage !== stage) recordDeploymentEvent(id, stage);
|
|
}
|
|
|
|
export function recordDeploymentEvent(id: string, message: string): void {
|
|
db().prepare('INSERT INTO server_deployment_events (deployment_id, created_at, message) VALUES (?, ?, ?)')
|
|
.run(id, new Date().toISOString(), message);
|
|
}
|
|
|
|
export function getDeployment(id: string) {
|
|
const deployment = db().prepare(`SELECT d.*, j.expected_map, EXISTS(SELECT 1 FROM servers s WHERE s.name = d.server) AS server_exists
|
|
FROM server_deployments d LEFT JOIN server_creation_jobs j ON j.deployment_id = d.id WHERE d.id = ?`).get(id);
|
|
if (!deployment) return null;
|
|
const events = db().prepare('SELECT id, created_at, message FROM server_deployment_events WHERE deployment_id = ? ORDER BY id').all(id);
|
|
const launch = recipeForDeployment(id);
|
|
return { deployment, events, recipe: launch ? summarizeRecipe(launch.recipe) : null, blueprintName: launch?.blueprintName ?? null,
|
|
canResumeVerification: canResumeVerification(id) };
|
|
}
|
|
|
|
export function canResumeVerification(id: string): boolean {
|
|
return !!db().prepare(`SELECT d.id FROM server_deployments d
|
|
JOIN server_creation_jobs j ON j.deployment_id = d.id
|
|
JOIN servers s ON s.name = j.server AND s.game_port = j.port
|
|
WHERE d.id = ? AND d.action = 'create' AND d.state IN ('failed', 'interrupted')
|
|
AND NOT EXISTS (SELECT 1 FROM server_deployments newer WHERE newer.server = d.server AND newer.rowid > d.rowid)`)
|
|
.get(id);
|
|
}
|
|
|
|
/** Resume observation only. Never repeat a container creation or restart. */
|
|
export function resumeVerification(id: string): boolean {
|
|
return db().transaction(() => {
|
|
if (!canResumeVerification(id)) return false;
|
|
db().prepare("UPDATE server_deployments SET state = 'awaiting-health', finished_at = NULL, error = NULL WHERE id = ?").run(id);
|
|
recordDeploymentEvent(id, 'Resumed verification of the existing server');
|
|
deploymentStage(id, 'Waiting for Komodo health');
|
|
return true;
|
|
})();
|
|
}
|
|
|
|
export function finishDeployment(id: string, result: { ok: boolean; message?: string; warnings?: string[] }): void {
|
|
const state = !result.ok ? 'failed' : result.warnings?.length ? 'warning' : 'done';
|
|
db().prepare(`UPDATE server_deployments SET state = ?, finished_at = ?, error = ?, warnings = ? WHERE id = ?`)
|
|
.run(state, new Date().toISOString(), result.message ?? null, JSON.stringify(result.warnings ?? []), id);
|
|
for (const warning of result.warnings ?? []) recordDeploymentEvent(id, warning);
|
|
recordDeploymentEvent(id, result.ok ? (state === 'warning' ? 'Completed with warnings' : 'Completed') : `Failed: ${result.message ?? 'Unknown error'}`);
|
|
}
|
|
|
|
export function listDeployments() {
|
|
return db().prepare(`SELECT d.*, EXISTS(SELECT 1 FROM servers s WHERE s.name = d.server) AS server_exists
|
|
FROM server_deployments d ORDER BY started_at DESC, rowid DESC`).all();
|
|
}
|
|
|
|
export function interruptDeployments(): void {
|
|
const running = db().prepare("SELECT id FROM server_deployments WHERE state = 'running'").all() as { id: string }[];
|
|
db().prepare(`UPDATE server_deployments SET state = 'interrupted', finished_at = ?,
|
|
error = 'The application restarted before this operation finished. Check the server before retrying.'
|
|
WHERE state = 'running'`).run(new Date().toISOString());
|
|
for (const { id } of running) recordDeploymentEvent(id, 'Interrupted by application restart. Check the server before retrying.');
|
|
}
|