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,38 @@
|
||||
import { resolveLogPath, tailLog } from './logs.js';
|
||||
|
||||
// Match markers for T5 Plutonium's `logs/games_sp.log`. Verified against a live
|
||||
// sample: each game session is bracketed by `InitGame:` and `ShutdownGame:`.
|
||||
// A REAL completed match always emits `ExitLevel: executed` right before
|
||||
// `ShutdownGame:`. Idle map cycles print `InitGame:` / `ShutdownGame:` at
|
||||
// 0:00 with no ExitLevel — those don't count as a played match.
|
||||
//
|
||||
// So the reliable discriminator is `ExitLevel: executed`: seeing that line
|
||||
// after `healthy_at` means a game actually ended normally (no crash — a crash
|
||||
// would kill the container before ExitLevel fires and take RCON with it).
|
||||
export const MATCH_START = /InitGame:/;
|
||||
export const MATCH_END = /ExitLevel:\s*executed/;
|
||||
|
||||
/**
|
||||
* Poll a server's games_sp.log tail for match markers. Returns whether a
|
||||
* completed match end has been observed since `sinceIso` (the server's
|
||||
* `healthy_at` timestamp).
|
||||
*/
|
||||
export async function scanForMatchEnd(
|
||||
serverName: string,
|
||||
sinceIso: string
|
||||
): Promise<{ ended: boolean; started: boolean; reason?: string }> {
|
||||
const path = resolveLogPath(serverName, 'games_sp');
|
||||
if (!path) return { ended: false, started: false, reason: 'log path resolution failed' };
|
||||
|
||||
const tail = await tailLog(path, 2000);
|
||||
if (!tail) return { ended: false, started: false, reason: 'log not written yet' };
|
||||
if (tail.mtime < sinceIso) return { ended: false, started: false, reason: 'no log activity since healthy' };
|
||||
|
||||
let started = false;
|
||||
let ended = false;
|
||||
for (const line of tail.lines) {
|
||||
if (MATCH_START.test(line)) started = true;
|
||||
if (MATCH_END.test(line)) ended = true;
|
||||
}
|
||||
return { ended, started };
|
||||
}
|
||||
Reference in New Issue
Block a user