Rework as scripts-centric, upload-only, gated beta lifecycle
Scripts become first-class: named, versioned, uploaded via the UI, optionally scoped to a game or map. Any change that would land on scope-matched servers (marking-required or new version of a scoped script) must first pass a beta. The beta runs a per-server state machine — deploy -beta.gsc → wait empty → Komodo restart → wait healthy → optionally wait for a real match end (games_sp.log 'ExitLevel: executed') — before it's eligible for promotion. Rollback is always available and restores from .OLD. The shared/scripts mirror and its betas/ staging folder are gone. Every server now has a tracked script inventory (server_scripts table), populated only by explicit upload flows. New: /scripts, /scripts/[id], /betas/[id] UI; /api/scripts + /api/betas + /api/servers/[name]/scripts endpoints; scripts.ts, inventory.ts, beta.ts (rewritten), beta-runner.ts, match-log.ts, komodoInspectHealthy(). Removed: push.ts, /api/push, BetaPanel, legacy importer. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { getBeta, listBetaServers } from '$lib/server/fleet/beta.js';
|
||||
import { getScript, getVersion } from '$lib/server/fleet/scripts.js';
|
||||
|
||||
function parseId(raw: string): number | null {
|
||||
const n = Number.parseInt(raw, 10);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
export async function GET({ params }: { params: { id: string } }) {
|
||||
const id = parseId(params.id);
|
||||
if (id === null) return json({ ok: false, error: 'bad id' }, { status: 400 });
|
||||
const beta = getBeta(id);
|
||||
if (!beta) return json({ ok: false, error: 'not found' }, { status: 404 });
|
||||
|
||||
const script = getScript(beta.script_id);
|
||||
const version = getVersion(beta.script_version_id);
|
||||
const servers = listBetaServers(id);
|
||||
|
||||
return json({ beta, script, version, servers });
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { promoteBeta, BetaError } from '$lib/server/fleet/beta.js';
|
||||
|
||||
function parseId(raw: string): number | null {
|
||||
const n = Number.parseInt(raw, 10);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
export async function POST({ params }: { params: { id: string } }) {
|
||||
const id = parseId(params.id);
|
||||
if (id === null) return json({ ok: false, error: 'bad id' }, { status: 400 });
|
||||
try {
|
||||
const result = await promoteBeta(id);
|
||||
return json({ ok: true, result });
|
||||
} catch (err) {
|
||||
if (err instanceof BetaError) {
|
||||
return json({ ok: false, error: err.message, kind: err.kind, details: err.details }, { status: 409 });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { db } from '$lib/server/db/index.js';
|
||||
import { emit, type BetaStage } from '$lib/server/pipeline/bus.js';
|
||||
|
||||
function parseId(raw: string): number | null {
|
||||
const n = Number.parseInt(raw, 10);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
// Reset one failed beta_server row back to its natural retry point. The runner
|
||||
// picks it up on the next tick. Body: { server: string }.
|
||||
export async function POST({ params, request }: { params: { id: string }; request: Request }) {
|
||||
const betaId = parseId(params.id);
|
||||
if (betaId === null) return json({ ok: false, error: 'bad id' }, { status: 400 });
|
||||
const body = (await request.json().catch(() => ({}))) as { server?: string };
|
||||
if (!body.server) return json({ ok: false, error: 'server is required' }, { status: 400 });
|
||||
|
||||
const row = db()
|
||||
.prepare('SELECT stage, restarted_at, healthy_at FROM beta_servers WHERE beta_id = ? AND server = ?')
|
||||
.get(betaId, body.server) as
|
||||
| { stage: string; restarted_at: string | null; healthy_at: string | null }
|
||||
| undefined;
|
||||
if (!row) return json({ ok: false, error: 'no such beta_server' }, { status: 404 });
|
||||
if (row.stage !== 'failed') return json({ ok: false, error: `stage is ${row.stage}, not failed` }, { status: 409 });
|
||||
|
||||
// Best-effort resume point: if we already got past restart, go back to
|
||||
// health check; otherwise back to waiting for the server to be empty.
|
||||
const nextStage: BetaStage = row.restarted_at ? 'awaiting-health' : 'awaiting-empty';
|
||||
const now = new Date().toISOString();
|
||||
db()
|
||||
.prepare('UPDATE beta_servers SET stage = ?, error = NULL, updated_at = ? WHERE beta_id = ? AND server = ?')
|
||||
.run(nextStage, now, betaId, body.server);
|
||||
emit({ type: 'beta-server-stage', betaId, server: body.server, stage: nextStage, error: null });
|
||||
return json({ ok: true, stage: nextStage });
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { rollbackBeta, BetaError } from '$lib/server/fleet/beta.js';
|
||||
|
||||
function parseId(raw: string): number | null {
|
||||
const n = Number.parseInt(raw, 10);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
export async function POST({ params }: { params: { id: string } }) {
|
||||
const id = parseId(params.id);
|
||||
if (id === null) return json({ ok: false, error: 'bad id' }, { status: 400 });
|
||||
try {
|
||||
const result = await rollbackBeta(id);
|
||||
return json({ ok: true, result });
|
||||
} catch (err) {
|
||||
if (err instanceof BetaError) {
|
||||
return json({ ok: false, error: err.message, kind: err.kind, details: err.details }, { status: 409 });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user