Use database fleet registry and gate RCON polling on container readiness

This commit is contained in:
2026-09-17 17:28:38 -04:00
parent 3b1c9bf3d9
commit 885abbdffe
19 changed files with 208 additions and 41 deletions
+2 -2
View File
@@ -271,11 +271,11 @@ async function createServerUnlocked(input: CreateServerInput, prepared: Prepared
`INSERT INTO servers
(name, komodo_type, komodo_name, game, map,
game_mode, map_slug, host_dir, config_file, game_id, image,
game_port, created_by)
game_port, created_by, container_state)
VALUES
(?, 'deployment', ?, 't5', ?,
?, ?, ?, ?, ?, ?,
?, ?)`
?, ?, 'starting')`
).run(
name, name, mapEntry.canonical,
input.gameMode, mapEntry.slug, hostDir,
+2 -2
View File
@@ -14,7 +14,7 @@ import {
import { getBaselineForScope, getVersion } from './scripts.js';
import { getPromotedServerConfig, getServerMapConfig, MAP_CONFIG_FILE } from './server-config.js';
import { T5_ZOMBIE_MAPS } from './map-aliases.js';
import { allServers, getServer, listServers } from './servers.js';
import { serverDirectories, getServer, listServers } from './servers.js';
import { promotedArtifacts } from './promoted-artifacts.js';
// Destinations drift scans, in a stable order so the UI shows scripts first.
@@ -228,7 +228,7 @@ function composeExpectedCfg(
export async function scanFleetDrift(): Promise<DriftEntry[]> {
const servers = listServers().map((s) => s.name);
const registered = new Set(servers);
const allDirs = new Set(await allServers());
const allDirs = new Set(await serverDirectories());
// Only touch server dirs that are both registered AND present on disk.
const eligible = servers.filter((n) => registered.has(n) && allDirs.has(n));
const out: DriftEntry[] = [];
+4 -2
View File
@@ -1,4 +1,4 @@
export type Health = 'online' | 'stale' | 'offline';
export type Health = 'online' | 'stale' | 'offline' | 'starting';
const ONLINE_MAX_MS = 30_000;
const STALE_MAX_MS = 120_000;
@@ -9,7 +9,9 @@ const STALE_MAX_MS = 120_000;
* successful reply is >120s old. `stale` if the last reply is 30-120s old
* (RCON glitchy). `online` if fresh.
*/
export function classifyHealth(lastSeenAt: string | null, lastError: string | null): Health {
export function classifyHealth(lastSeenAt: string | null, lastError: string | null, containerState?: string | null): Health {
if (containerState === 'starting' || containerState === 'restarting') return 'starting';
if (containerState && containerState !== 'running') return 'offline';
if (!lastSeenAt) return 'offline';
const age = Date.now() - new Date(lastSeenAt).getTime();
if (!Number.isFinite(age) || age < 0) return 'offline';
+11 -3
View File
@@ -8,7 +8,7 @@ import { parseServerName } from './map-aliases.js';
* All server folders under SERVERS_ROOT. Used by seedServerRegistry to backfill
* the servers table for any on-disk server directory that isn't yet tracked.
*/
export async function allServers(): Promise<string[]> {
export async function serverDirectories(): Promise<string[]> {
let entries;
try {
entries = await readdir(config.serversRoot, { withFileTypes: true });
@@ -21,6 +21,11 @@ export async function allServers(): Promise<string[]> {
.sort();
}
/** Fleet membership is owned by the database, independent of mounted files. */
export async function allServers(): Promise<string[]> {
return listServers().map((server) => server.name);
}
/**
* Match a server name against a list of glob-like patterns (supports `*` and
* `?` and comma-separated lists).
@@ -64,6 +69,7 @@ export interface ServerRow {
komodo_type: 'deployment' | 'stack' | 'container';
komodo_name: string;
komodo_server: string | null;
container_state: string | null;
game_port: number | null;
rcon_host: string | null;
game: string | null;
@@ -93,7 +99,8 @@ export interface ServerRow {
}
export async function seedServerRegistry(): Promise<number> {
const rows = await allServers();
// Explicit legacy import only; never run as part of application startup.
const rows = await serverDirectories();
// Under the new naming scheme Komodo entities match EC server names 1:1
// (both underscore-separated), so komodo_name = name — no transformation.
const insert = db().prepare(
@@ -143,7 +150,7 @@ export function getServer(name: string): ServerRow | undefined {
}
export function listServers(): ServerRow[] {
return db().prepare('SELECT * FROM servers ORDER BY name').all() as ServerRow[];
return db().prepare('SELECT * FROM servers WHERE destroyed_at IS NULL ORDER BY name').all() as ServerRow[];
}
export function setServerField(
@@ -155,6 +162,7 @@ export function setServerField(
'komodo_type',
'komodo_name',
'komodo_server',
'container_state',
'game_port',
'rcon_host',
'game',