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:
2026-09-12 00:23:51 -04:00
commit d99711563e
67 changed files with 9415 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
import Database from 'better-sqlite3';
import { mkdirSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { config } from '../config.js';
import schemaSql from './schema.sql?raw';
let _db: Database.Database | null = null;
export function db(): Database.Database {
if (_db) return _db;
mkdirSync(dirname(resolve(config.dbPath)), { recursive: true });
_db = new Database(config.dbPath);
_db.pragma('journal_mode = WAL');
_db.pragma('foreign_keys = ON');
_db.exec(schemaSql);
return _db;
}
export function closeDb(): void {
if (_db) {
_db.close();
_db = null;
}
}
+9
View File
@@ -0,0 +1,9 @@
import { db } from './index.js';
// The heavy lifting is idempotent DDL inside schema.sql, which db() runs on
// first handle. This module exists as an explicit hook for future migrations
// (numbered .sql files applied via a schema_version row) and to be called
// from hooks.server.ts so boot-time failures surface early.
export function migrate(): void {
db().prepare('SELECT 1').get();
}
+4
View File
@@ -0,0 +1,4 @@
declare module '*.sql?raw' {
const content: string;
export default content;
}
+93
View File
@@ -0,0 +1,93 @@
-- Electric Cherry schema. Idempotent — safe to run at every boot via migrate.ts.
-- Mirrors the shapes tracked in the legacy script_metadata.json but in real SQL.
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS servers (
name TEXT PRIMARY KEY,
komodo_type TEXT NOT NULL DEFAULT 'deployment', -- deployment|stack|container
komodo_name TEXT NOT NULL,
komodo_server TEXT,
iw4m_match TEXT, -- port|id|name
iw4m_value TEXT,
last_players INTEGER,
last_checked_at TEXT,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
);
CREATE TABLE IF NOT EXISTS betas (
slug TEXT PRIMARY KEY,
name TEXT NOT NULL,
folder TEXT NOT NULL,
opened_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
revision INTEGER NOT NULL DEFAULT 1
);
CREATE TABLE IF NOT EXISTS beta_scripts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
beta_slug TEXT NOT NULL REFERENCES betas(slug) ON DELETE CASCADE,
staged_as TEXT NOT NULL,
target TEXT NOT NULL,
beta_file TEXT NOT NULL,
backup TEXT NOT NULL,
sha256 TEXT NOT NULL,
revision INTEGER NOT NULL DEFAULT 1
);
CREATE INDEX IF NOT EXISTS idx_beta_scripts_slug ON beta_scripts(beta_slug);
CREATE TABLE IF NOT EXISTS beta_deploys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
beta_script_id INTEGER NOT NULL REFERENCES beta_scripts(id) ON DELETE CASCADE,
server TEXT NOT NULL REFERENCES servers(name),
had_original INTEGER NOT NULL,
UNIQUE(beta_script_id, server)
);
CREATE INDEX IF NOT EXISTS idx_beta_deploys_server ON beta_deploys(server);
CREATE TABLE IF NOT EXISTS deployments (
id TEXT PRIMARY KEY,
action TEXT NOT NULL,
started_at TEXT NOT NULL,
finished_at TEXT,
triggered_by TEXT
);
CREATE INDEX IF NOT EXISTS idx_deployments_started ON deployments(started_at DESC);
CREATE TABLE IF NOT EXISTS deployment_servers (
deployment_id TEXT NOT NULL REFERENCES deployments(id) ON DELETE CASCADE,
server TEXT NOT NULL REFERENCES servers(name),
files_changed INTEGER NOT NULL,
restart_state TEXT NOT NULL, -- pending | done | failed | manual | held
players INTEGER,
checked_at TEXT,
restarted_at TEXT,
attempts INTEGER NOT NULL DEFAULT 0,
error TEXT,
PRIMARY KEY (deployment_id, server)
);
CREATE INDEX IF NOT EXISTS idx_deployment_servers_state ON deployment_servers(restart_state);
CREATE TABLE IF NOT EXISTS deployment_files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
deployment_id TEXT NOT NULL REFERENCES deployments(id) ON DELETE CASCADE,
server TEXT NOT NULL,
verb TEXT NOT NULL, -- added | updated | removed | held
rel_path TEXT NOT NULL,
sha256_before TEXT,
sha256_after TEXT,
content_before BLOB,
content_after BLOB
);
CREATE INDEX IF NOT EXISTS idx_deployment_files_dep ON deployment_files(deployment_id);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);