Remove Komodo backend in favor of native hosts

This commit is contained in:
2026-10-05 18:07:07 -04:00
parent 701066aaf8
commit 4b0f341d64
116 changed files with 557 additions and 8132 deletions
@@ -9,7 +9,7 @@
const progress = $derived(detail.progress);
let expanded = $state<Record<string, boolean>>({});
const evidenceLabels = { pass: 'Passed', waiting: 'Waiting', fail: 'Failed', unknown: 'Unknown' };
const sources = { komodo: 'Komodo', agent: 'Host agent', rcon: 'RCON', telemetry: 'Telemetry', combined: 'RCON and telemetry', manifest: 'Plutonium manifest' };
const sources = { agent: 'Host agent', rcon: 'RCON', telemetry: 'Telemetry', combined: 'RCON and telemetry', manifest: 'Plutonium manifest' };
</script>
<section class="execution" aria-labelledby="execution-title">
-27
View File
@@ -1,27 +0,0 @@
export type KomodoPortFailure =
| 'unconfigured'
| 'no-name'
| 'unreachable'
| 'http-error'
| 'unparseable'
| 'no-udp-in-response';
/** Short fix hint per failure reason, shown under the Komodo probe result. */
export function komodoFixHint(reason: KomodoPortFailure, httpStatus?: number): string {
switch (reason) {
case 'unconfigured':
return 'Set KOMODO_ADDRESS, KOMODO_API_KEY, and KOMODO_API_SECRET in the container env.';
case 'no-name':
return 'This server row has no komodo_name — set it via SQL or via the fleet config.';
case 'unreachable':
return 'Network, DNS, or timeout. Check that KOMODO_ADDRESS is reachable from this container.';
case 'http-error':
if (httpStatus === 401 || httpStatus === 403) return 'Auth rejected — verify KOMODO_API_KEY and KOMODO_API_SECRET.';
if (httpStatus === 404) return 'Endpoint or deployment not found — check komodo_name matches the Komodo UI exactly.';
return `Komodo returned ${httpStatus ?? 'an error'}. Check the endpoint path and credentials.`;
case 'unparseable':
return 'Response was not JSON — is KOMODO_ADDRESS pointing at the Core API (not the Periphery)?';
case 'no-udp-in-response':
return 'Parsed the response but no UDP port matched our known shapes. See the snippet below and extend extractUdpPort.';
}
}
+15 -24
View File
@@ -8,35 +8,27 @@ export interface ProgressGroup { key: string; label: string; steps: ProgressStep
const phaseLabels: Record<ProvisioningPhase, string> = {
requested: 'Request recorded', eligibility: 'Check eligibility and capacity', 'startup-queue': 'Wait for a startup slot',
prepare: 'Prepare server files', 'create-deployment': 'Create Komodo deployment', 'container-action': 'Start container',
'remove-container': 'Stop and remove container', 'delete-deployment': 'Delete Komodo deployment', cleanup: 'Remove files and registration',
prepare: 'Prepare server files', 'container-action': 'Start container',
cleanup: 'Remove files and registration',
'verify-container': 'Confirm container readiness', 'verify-game': 'Verify game status', 'verify-map': 'Verify selected engine map',
'verify-plutonium': 'Verify Plutonium revision',
'verify-stopped': 'Confirm container is stopped'
};
type GroupDefinition = { key: string; label: string; phases: ProvisioningPhase[] };
const request: GroupDefinition = { key: 'request', label: 'Request and eligibility', phases: ['requested', 'eligibility'] };
const creation: GroupDefinition = { key: 'create', label: 'Create and start', phases: ['startup-queue', 'prepare', 'create-deployment', 'container-action'] };
const removal: GroupDefinition = { key: 'remove', label: 'Remove the instance', phases: ['remove-container', 'delete-deployment', 'cleanup'] };
const readiness: GroupDefinition = { key: 'verify', label: 'Verify readiness', phases: ['verify-container', 'verify-game', 'verify-map', 'verify-plutonium'] };
function definitions(action: ProvisioningAction, native = false): GroupDefinition[] {
if (native) {
const command: GroupDefinition = { key: 'command', label: `${actionLabels[action]} on host agent`, phases: ['container-action'] };
if (action === 'create' || action === 'rebuild') return [request,
{ key: 'create', label: action === 'rebuild' ? 'Rebuild on host agent' : 'Create and start on host agent', phases: ['startup-queue', 'prepare', 'container-action'] }, readiness];
if (action === 'destroy') return [request, command, { key: 'cleanup', label: 'Retire instance', phases: ['cleanup'] }];
}
if (action === 'create') return [{ ...request, phases: ['requested'] }, creation, readiness];
if (action === 'rebuild') return [request, removal, { ...creation, label: 'Create a fresh instance' }, readiness];
if (action === 'destroy') return [request, removal];
return [request, { key: 'command', label: `${actionLabels[action]} the existing container`, phases: ['container-action'] },
{ ...readiness, phases: action === 'stop' ? ['verify-stopped'] : ['verify-container', 'verify-game'] }];
function definitions(action: ProvisioningAction): GroupDefinition[] {
const command: GroupDefinition = { key: 'command', label: `${actionLabels[action]} on host agent`, phases: ['container-action'] };
if (action === 'create' || action === 'rebuild') return [request,
{ key: 'create', label: action === 'rebuild' ? 'Rebuild on host agent' : 'Create and start on host agent', phases: ['startup-queue', 'prepare', 'container-action'] }, readiness];
if (action === 'destroy') return [request, command, { key: 'cleanup', label: 'Retire instance', phases: ['cleanup'] }];
return [request, command, { ...readiness, phases: action === 'stop' ? ['verify-stopped'] : ['verify-container', 'verify-game'] }];
}
export function provisioningGroups(detail: ProvisioningDetail): ProgressGroup[] {
const progress = detail.progress;
if (!progress) return [];
const groups = definitions(detail.deployment.action, detail.backend === 'native');
const groups = definitions(detail.deployment.action);
const phases = groups.flatMap(group => group.phases);
const current = progress.phase ? phases.indexOf(progress.phase) : -1;
const active = activeProvisioningStates.includes(detail.deployment.state);
@@ -54,11 +46,10 @@ export function provisioningGroups(detail: ProvisioningDetail): ProgressGroup[]
: isCurrent && active ? progress.blocker || ['waiting', 'queued'].includes(detail.deployment.state) ? 'waiting' : 'current'
: current >= 0 && index >= current && detail.deployment.state === 'cancelled' ? 'cancelled'
: active && current >= 0 && index > current ? 'pending' : 'unknown';
const label = detail.backend === 'native' && key === 'startup-queue' ? 'Admit to startup capacity'
: detail.backend === 'native' && key === 'prepare' ? 'Prepare launch payload'
: detail.backend === 'native' && key === 'container-action' ? `${actionLabels[detail.deployment.action]} instance through host agent`
: key === 'container-action' && ['start', 'restart', 'stop'].includes(detail.deployment.action)
? `${actionLabels[detail.deployment.action]} existing container` : phaseLabels[key];
const label = key === 'startup-queue' ? 'Admit to startup capacity'
: key === 'prepare' ? 'Prepare launch payload'
: key === 'container-action' ? `${actionLabels[detail.deployment.action]} instance through host agent`
: phaseLabels[key];
return { key, label, state, at: check?.checkedAt ?? checkpoint?.at ?? (isCurrent ? progress.phaseStartedAt : null) };
});
const states = steps.map(step => step.state);
@@ -168,7 +159,7 @@ export function isProvisioningDetail(value: unknown, id: string): value is Provi
if (value.cancellation !== undefined && value.cancellation !== null && !(record(value.cancellation)
&& text(value.cancellation.cleanupId) && text(value.cancellation.state) && text(value.cancellation.stage)
&& nullableText(value.cancellation.error))) return false;
if (value.backend !== undefined && !['native', 'komodo'].includes(value.backend as string)) return false;
if (value.backend !== undefined && !['native'].includes(value.backend as string)) return false;
if (value.workerActivity !== undefined && (!Array.isArray(value.workerActivity) || !value.workerActivity.every(item =>
record(item) && text(item.key) && text(item.label) && timestamp(item.startedAt)))) return false;
if (value.agentOperation !== undefined && value.agentOperation !== null && !(record(value.agentOperation)
@@ -212,7 +203,7 @@ export function isProvisioningDetail(value: unknown, id: string): value is Provi
if (!Array.isArray(progress.checks) || !progress.checks.every(check => record(check)
&& ['container', 'game', 'map', 'plutonium-release', 'plutonium-version', 'occupancy', 'stopped'].includes(check.key as string)
&& ['pass', 'waiting', 'fail', 'unknown'].includes(check.result as string)
&& [null, 'komodo', 'agent', 'rcon', 'telemetry', 'combined', 'manifest'].includes(check.source as null | string)
&& [null, 'agent', 'rcon', 'telemetry', 'combined', 'manifest'].includes(check.source as null | string)
&& text(check.label) && timestamp(check.checkedAt) && nullableTime(check.receivedAt)
&& scalar(check.expected) && scalar(check.actual) && nullableText(check.message))) return false;
if (!Array.isArray(progress.checkpoints) || !progress.checkpoints.every(checkpoint => record(checkpoint)
+1 -1
View File
@@ -30,7 +30,7 @@ export function rconFixHint(reason: RconFailure): string {
case 'no-config':
return 'Set RCON_PASSWORD in the container env and restart.';
case 'no-port':
return 'Set game_port on the server row, or configure Komodo so it can resolve the port.';
return 'Check the host agent and the game port assigned to this server.';
case 'send-error':
return 'DNS or route problem — check the RCON host is reachable from this container.';
case 'timeout':
+1 -4
View File
@@ -13,7 +13,7 @@ export function createFleetStore(serverName?: string) {
});
const fleetError = writable('');
const fleetRefreshing = writable(false);
const fleetIntegrations = writable<{ rcon: boolean; komodo: boolean; native?: boolean; hostsReady?: number; hostsTotal?: number; rconTimeoutMs: number; defaultRconHost: string } | null>(null);
const fleetIntegrations = writable<{ rcon: boolean; native?: boolean; hostsReady?: number; hostsTotal?: number; rconTimeoutMs: number; defaultRconHost: string } | null>(null);
const observed = new Map<string, { server: FleetServer; status: unknown; match: unknown; report: unknown; phase: string; value: FleetServer }>();
const freshnessPhase = derived([fleet, statusByServer, telemetryByServer, uiNow], ([snapshot, live, reports, now]) => {
@@ -63,7 +63,6 @@ export function createFleetStore(serverName?: string) {
return next;
});
let snapshotServerNow = 0;
function hydrateFleet(snapshot: { servers: FleetServer[]; operations: FleetOperation[]; integrations: NonNullable<ReturnType<typeof getIntegrations>>; serverNow: number }) {
if (!browser || snapshot.serverNow < snapshotServerNow) return;
@@ -74,8 +73,6 @@ export function createFleetStore(serverName?: string) {
}
function getIntegrations() { return get(fleetIntegrations); }
let inFlight: Promise<void> | null = null;
let refreshRequested = false;
function refreshFleet(afterCurrent = false): Promise<void> {