Fix blocking collection matching with large plugins
This commit is contained in:
@@ -4,7 +4,7 @@ import { probePlayerEvidence, probeServerStatus } from '../fleet/status-evidence
|
||||
import { beginDeployment, deploymentStage, finishDeployment, recordDeploymentEvent, REMOVING_SERVER, resumeCleanup, resumeVerification, claimRemoval } from './history.js';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import type { LaunchPreview } from '$lib/deployment-plan.js';
|
||||
import { blueprintRecipe, captureRecipe, recipeForDeployment, summarizeRecipe, type LaunchRecipe } from '../fleet/launch-recipes.js';
|
||||
import { blueprintRecipe, captureRecipe, captureRecipePlan, recipeForDeployment, summarizeRecipe, type LaunchRecipe, type LaunchRecipePlan } from '../fleet/launch-recipes.js';
|
||||
import { mkdir, chmod, rm, writeFile } from 'node:fs/promises';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { config, destOfKind, serverDir, gameOfServer, configFileForGame } from '../config.js';
|
||||
@@ -179,7 +179,7 @@ interface PreviewRow {
|
||||
export function previewCreation(input: { game?: unknown; mapSlug?: unknown; blueprintId?: unknown; deploymentId?: unknown; hostId?: unknown; libraryId?: unknown }): LaunchPreview {
|
||||
if (input.hostId !== undefined || !config.komodo.address || listHosts().length > 0) return previewNativeCreation(input);
|
||||
if (input.blueprintId !== undefined && input.deploymentId !== undefined) throw new Error('Choose one starting point.');
|
||||
let recipe: LaunchRecipe;
|
||||
let recipe: LaunchRecipePlan;
|
||||
let blueprintName: string | null = null;
|
||||
if (input.blueprintId !== undefined) {
|
||||
if (typeof input.blueprintId !== 'string') throw new Error('Invalid blueprint.');
|
||||
@@ -190,10 +190,10 @@ export function previewCreation(input: { game?: unknown; mapSlug?: unknown; blue
|
||||
if (!source) throw new Error('This deployment has no recorded launch recipe.');
|
||||
blueprintName = source.blueprintName;
|
||||
// History describes the previous launch; only its map selection is reusable.
|
||||
recipe = captureRecipe(source.recipe.game, source.recipe.mapSlug);
|
||||
recipe = captureRecipePlan(source.recipe.game, source.recipe.mapSlug);
|
||||
} else {
|
||||
if ((input.game !== 't5' && input.game !== 't6') || typeof input.mapSlug !== 'string') throw new Error('Choose a supported game and map.');
|
||||
recipe = captureRecipe(input.game, input.mapSlug);
|
||||
recipe = captureRecipePlan(input.game, input.mapSlug);
|
||||
}
|
||||
const lobby = nextLobbyNumber(`${recipe.game}_zombie_${recipe.mapSlug}`);
|
||||
const name = lobby === null ? null : `${recipe.game}_zombie_${recipe.mapSlug}_${lobby}`;
|
||||
@@ -223,7 +223,7 @@ export function queueCreateServer(input: CreateServerInput, releasingServers: st
|
||||
return db().transaction((): CreateServerResult => {
|
||||
const preview = input.previewId ? db().prepare('SELECT * FROM server_launch_previews WHERE id = ?').get(input.previewId) as PreviewRow | undefined : undefined;
|
||||
if (input.previewId && !preview) return { ok: false, kind: 'bad-request', message: 'Launch preview expired or was not found. Review a fresh preview.' };
|
||||
const recipe: LaunchRecipe | undefined = preview ? JSON.parse(preview.recipe) : undefined;
|
||||
const recipe: LaunchRecipePlan | undefined = preview ? JSON.parse(preview.recipe) : undefined;
|
||||
if (recipe && (recipe.game !== (input.game ?? 't5') || recipe.mapSlug !== input.mapSlug || input.gameMode !== 'zombie')) {
|
||||
return { ok: false, kind: 'bad-request', message: 'The selection does not match the reviewed launch recipe.' };
|
||||
}
|
||||
@@ -239,7 +239,7 @@ export function queueCreateServer(input: CreateServerInput, releasingServers: st
|
||||
const prepared = prepareCreation(input, releasingServers);
|
||||
if (!prepared.ok) return prepared;
|
||||
// A preview records choices, not version pins. Promotions remain authoritative.
|
||||
const currentRecipe = captureRecipe(input.game ?? 't5', prepared.mapEntry.slug);
|
||||
const currentRecipe = captureRecipePlan(input.game ?? 't5', prepared.mapEntry.slug);
|
||||
const id = beginDeployment(prepared.name, 'create', input.createdBy);
|
||||
db().prepare('INSERT INTO server_launch_recipes (deployment_id, recipe, blueprint_name) VALUES (?, ?, ?)')
|
||||
.run(id, JSON.stringify(currentRecipe), preview?.blueprint_name ?? null);
|
||||
@@ -268,7 +268,7 @@ export function queueRebuildServer(name: string, triggeredBy = 'ui'): CreateServ
|
||||
if (!config.komodo.targetServer || (!config.rcon.password && !triggeredBy.startsWith('watchdog:'))) return { ok: false, kind: 'unconfigured', message: 'Configure the Komodo target host and RCON before rebuilding.' };
|
||||
if (!getServerKey(name)) return { ok: false, kind: 'bad-request', message: `Save a server key for ${name} in Keys before rebuilding.` };
|
||||
// Validate that the replacement setup can be composed before destruction.
|
||||
const recipe = captureRecipe(game, map.slug);
|
||||
const recipe = captureRecipePlan(game, map.slug);
|
||||
return db().transaction(() => {
|
||||
const id = beginDeployment(name, 'rebuild', triggeredBy);
|
||||
db().prepare("UPDATE server_deployments SET state = 'waiting' WHERE id = ?").run(id);
|
||||
@@ -304,7 +304,7 @@ export async function rebuildServer(id: string): Promise<void> {
|
||||
}
|
||||
// Recheck prerequisites while the original instance still exists.
|
||||
if (!getServerKey(job.server)) { finishDeployment(id, { ok: false, message: 'Saved server key was removed; rebuild did not begin.' }); return; }
|
||||
try { captureRecipe(gameOfServer(job.server), job.map_slug); }
|
||||
try { captureRecipePlan(gameOfServer(job.server), job.map_slug); }
|
||||
catch (err) { finishDeployment(id, { ok: false, message: (err as Error).message }); return; }
|
||||
db().prepare("UPDATE server_deployments SET state = 'running' WHERE id = ? AND state = 'waiting'").run(id);
|
||||
destroying.add(job.server);
|
||||
|
||||
@@ -7,7 +7,7 @@ import { config, relDestForKind, serverDir } from '../config.js';
|
||||
import { nativeInstance, nativeRequest, type NativeInstance } from '../backends/native.js';
|
||||
import { getHost, getHostOperation, listHosts, queueHostOperation, releaseHostPorts, reserveHostPort } from '../hosts/index.js';
|
||||
import { newestReadyLibraryId, assertReadyLibrary } from '../game-files/index.js';
|
||||
import { blueprintRecipe, captureRecipe, recipeForDeployment, summarizeRecipe, type LaunchRecipe } from '../fleet/launch-recipes.js';
|
||||
import { blueprintRecipe, captureRecipe, captureRecipePlan, recipeForDeployment, summarizeRecipe, type LaunchRecipe, type LaunchRecipePlan } from '../fleet/launch-recipes.js';
|
||||
import { getServerKey, saveServerKey } from '../fleet/server-keys.js';
|
||||
import { activeBetaRemovalBlocker, getServer } from '../fleet/servers.js';
|
||||
import { nextLobbyNumber, type CreateServerInput, type CreateServerResult } from './instances.js';
|
||||
@@ -27,7 +27,7 @@ import { hostAgentUpdatesEnabled } from '../../hosts.js';
|
||||
import { cancelCreation, finishCreationCancellation } from './cancel-creation.js';
|
||||
|
||||
export type NativeSelection = import('../../hosts.js').NativePlacement;
|
||||
type NativeRecipe = LaunchRecipe & { native?: NativeSelection };
|
||||
type NativeRecipe = LaunchRecipePlan & { native?: NativeSelection };
|
||||
interface Job { id: string; server: string; action: ProvisioningAction; state: string; submitted_at: string | null; stage: string;
|
||||
instance_id: string; generation: number; stage_operation_id: string | null; operation_id: string | null; files_json: string | null; applied_at: string | null }
|
||||
interface FilePlan { id: number; path: string; contentBase64: string | null; sha256: string | null; checkBefore: boolean; expectedBeforeSha: string | null }
|
||||
@@ -60,12 +60,12 @@ export function previewNativeCreation(input: Record<string, unknown>): LaunchPre
|
||||
if (typeof input.deploymentId === 'string') {
|
||||
const previous = recipeForDeployment(input.deploymentId);
|
||||
if (!previous) throw new Error('The recorded deployment is unavailable.');
|
||||
recipe = captureRecipe(previous.recipe.game, previous.recipe.mapSlug);
|
||||
recipe = captureRecipePlan(previous.recipe.game, previous.recipe.mapSlug);
|
||||
blueprintName = previous.blueprintName;
|
||||
} else if (typeof input.blueprintId === 'string') ({ recipe, blueprintName } = blueprintRecipe(input.blueprintId));
|
||||
else {
|
||||
if ((input.game !== 't5' && input.game !== 't6') || typeof input.mapSlug !== 'string') throw new Error('Choose a supported game and map.');
|
||||
recipe = captureRecipe(input.game, input.mapSlug);
|
||||
recipe = captureRecipePlan(input.game, input.mapSlug);
|
||||
}
|
||||
const selection = nativeSelection(input, recipe.game);
|
||||
recipe.native = selection; recipe.image = selection.image!;
|
||||
@@ -105,7 +105,7 @@ export function queueNativeCreation(input: CreateServerInput): CreateServerResul
|
||||
const game = input.game ?? 't5';
|
||||
if (input.gameMode !== 'zombie' || (saved && (saved.game !== game || saved.mapSlug !== input.mapSlug))) throw new Error('Selection changed. Review a fresh launch preview.');
|
||||
const selection = nativeSelection((saved?.native ?? input) as unknown as Record<string, unknown>, game);
|
||||
const recipe: NativeRecipe = { ...captureRecipe(game, input.mapSlug), native: selection, image: selection.image! };
|
||||
const recipe: NativeRecipe = { ...captureRecipePlan(game, input.mapSlug), native: selection, image: selection.image! };
|
||||
const lobby = nextLobbyNumber(`${game}_zombie_${input.mapSlug}`);
|
||||
if (lobby === null) throw new Error('All lobby IDs are reserved.');
|
||||
const name = `${game}_zombie_${input.mapSlug}_${lobby}`;
|
||||
@@ -157,7 +157,7 @@ export function queueNativeRebuild(name: string, triggeredBy: string): CreateSer
|
||||
if (blocker) throw new Error(blocker);
|
||||
if (!server?.map_slug || !getServerKey(name)) throw new Error('A saved map and server key are required before rebuilding.');
|
||||
assertReadyLibrary(instance.host_id, instance.library_id);
|
||||
const recipe: NativeRecipe = { ...captureRecipe(server.game as 't5' | 't6', server.map_slug), image: instance.image,
|
||||
const recipe: NativeRecipe = { ...captureRecipePlan(server.game as 't5' | 't6', server.map_slug), image: instance.image,
|
||||
native: { hostId: instance.host_id, libraryId: instance.library_id, image: instance.image, bindAddress: instance.bind_address } };
|
||||
return db().transaction(() => {
|
||||
const id = beginDeployment(name, 'rebuild', triggeredBy);
|
||||
@@ -478,7 +478,7 @@ async function processJob(job: Job, eligibility: (job: Job) => string | null) {
|
||||
deploymentStage(job.id, 'Preparing launch files for the host agent', { phase: 'prepare', recipeStatus: 'attempted',
|
||||
checkpoint: { key: 'startup-queue', label: 'Startup capacity available' } });
|
||||
const prior = recipeForDeployment(job.id)!.recipe as NativeRecipe;
|
||||
const recipe: NativeRecipe = { ...captureRecipe(prior.game, prior.mapSlug), native: prior.native, image: instance!.image };
|
||||
const recipe = { ...captureRecipe(prior.game, prior.mapSlug), native: prior.native, image: instance!.image };
|
||||
payload = createPayload(instance!, recipe);
|
||||
if (job.action === 'rebuild') payload.nextGeneration = instance!.generation + 1;
|
||||
db().prepare('UPDATE server_launch_recipes SET recipe=? WHERE deployment_id=?').run(JSON.stringify(recipe), job.id);
|
||||
|
||||
Reference in New Issue
Block a user