Initial commit — SvelteKit fleet manager for T5 Plutonium servers
Ports push_scripts.sh (1497 lines of bash) to a SvelteKit + SQLite web app that manages a fleet of T5 Plutonium servers running on Komodo. Same push and beta workflow as the shell script, with a live SSE pipeline view, a !rt-token-gated auth modal that hard-pauses deployments on stale tokens, per-file audit diffs, and a Komodo-container deployment target. - Push/beta engines port the exact filter, held-by-beta, conflict, and stale-backup rules from the shell script - SQLite (better-sqlite3) replaces script_metadata.json; a one-shot importer moves legacy state over on first boot - 16 vitest tests cover push add/update/skip, beta open→rollback, open→promote, conflict guards, sweep frozen/manual branches, and IW4M login/status parsing - docker/ + komodo/stack.toml deploy as a managed container on the same host as the game servers, /root/zombies bind-mounted at the same path the shell script used
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import { sweep } from './sweep.js';
|
||||
import { emit } from './bus.js';
|
||||
import { fetchStatus } from '../integrations/iw4m.js';
|
||||
import { listServers, seedServerRegistry } from '../fleet/servers.js';
|
||||
import { getAuthState } from '../integrations/auth-state.js';
|
||||
|
||||
const TICK_MS = 15_000;
|
||||
const STATUS_REFRESH_MS = 30_000;
|
||||
|
||||
let started = false;
|
||||
let tickTimer: NodeJS.Timeout | null = null;
|
||||
let statusTimer: NodeJS.Timeout | null = null;
|
||||
|
||||
async function tick(): Promise<void> {
|
||||
try {
|
||||
await sweep();
|
||||
} catch (err) {
|
||||
console.error('[worker] sweep error:', err);
|
||||
}
|
||||
}
|
||||
|
||||
async function pushServerStatus(): Promise<void> {
|
||||
const st = await fetchStatus();
|
||||
if (!st.ok) return;
|
||||
const rows = listServers();
|
||||
const byName = new Map(rows.map((r) => [r.name, r]));
|
||||
// For the dashboard: emit a lightweight snapshot. Actual per-server
|
||||
// resolution is handled by playersOn() during a sweep, which caches
|
||||
// iw4m_match/value into the servers table.
|
||||
emit({
|
||||
type: 'server-status',
|
||||
entries: st.entries.map((e) => ({
|
||||
server: e.name ?? String(e.port ?? ''),
|
||||
players: e.currentPlayers ?? null,
|
||||
name: e.name
|
||||
}))
|
||||
});
|
||||
// Also fold current auth ttl into the auth channel so subscribers get a
|
||||
// heartbeat and a countdown without waiting for a transition.
|
||||
const auth = getAuthState();
|
||||
emit({
|
||||
type: 'auth-state',
|
||||
state: auth,
|
||||
msRemaining: auth.kind === 'ready' ? Math.max(0, auth.expiresAt - Date.now()) : undefined
|
||||
});
|
||||
void byName; // reserved: future join between /api/status and server registry
|
||||
}
|
||||
|
||||
export function startWorker(): void {
|
||||
if (started) return;
|
||||
started = true;
|
||||
|
||||
// Seed the servers table from the fs so first-boot has rows to reference.
|
||||
seedServerRegistry().catch((err) => console.error('[worker] seed error:', err));
|
||||
|
||||
void tick();
|
||||
void pushServerStatus();
|
||||
tickTimer = setInterval(tick, TICK_MS);
|
||||
statusTimer = setInterval(pushServerStatus, STATUS_REFRESH_MS);
|
||||
|
||||
console.log('[worker] started — sweep every', TICK_MS, 'ms, status every', STATUS_REFRESH_MS, 'ms');
|
||||
}
|
||||
|
||||
export function stopWorker(): void {
|
||||
if (tickTimer) clearInterval(tickTimer);
|
||||
if (statusTimer) clearInterval(statusTimer);
|
||||
tickTimer = null;
|
||||
statusTimer = null;
|
||||
started = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* External trigger — called from `/api/push` and beta endpoints so a sweep
|
||||
* fires immediately after a state change instead of waiting for the next tick.
|
||||
*/
|
||||
export async function kick(): Promise<void> {
|
||||
await tick();
|
||||
}
|
||||
Reference in New Issue
Block a user