Use current promoted files for blueprint launches
This commit is contained in:
@@ -167,7 +167,9 @@ export function previewCreation(input: { game?: unknown; mapSlug?: unknown; blue
|
||||
if (typeof input.deploymentId !== 'string') throw new Error('Invalid deployment.');
|
||||
const source = recipeForDeployment(input.deploymentId);
|
||||
if (!source) throw new Error('This deployment has no recorded launch recipe.');
|
||||
({ recipe, blueprintName } = source);
|
||||
blueprintName = source.blueprintName;
|
||||
// History describes the previous launch; only its map selection is reusable.
|
||||
recipe = captureRecipe(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);
|
||||
@@ -176,13 +178,12 @@ export function previewCreation(input: { game?: unknown; mapSlug?: unknown; blue
|
||||
const name = lobby === null ? null : `${recipe.game}_zombie_${recipe.mapSlug}_${lobby}`;
|
||||
const port = nextAvailablePort();
|
||||
const configured = !!(config.komodo.address && config.komodo.apiKey && config.komodo.apiSecret && config.komodo.targetServer);
|
||||
const compatible = recipe.configFile === configFileForGame(recipe.game);
|
||||
const checks: LaunchPreview['checks'] = [
|
||||
{ label: 'Komodo settings', state: configured ? 'pass' : 'blocked', detail: configured ? 'Connection settings and target host are configured.' : 'Configure Komodo credentials and a target host before launching.' },
|
||||
{ label: 'Health verification', state: config.rcon.password ? 'pass' : 'blocked', detail: config.rcon.password ? 'RCON is configured. Container, RCON, and map checks run after launch.' : 'Configure RCON_PASSWORD so the new server can be verified.' },
|
||||
{ label: 'Lobby', state: name ? 'pass' : 'blocked', detail: name ?? 'All 20 lobbies for this map are occupied or reserved.' },
|
||||
{ label: 'Game port', state: port === null ? 'blocked' : 'pass', detail: port === null ? 'No free game port. Free capacity before launching.' : `${port} / UDP and TCP. Availability is checked again at launch.` },
|
||||
{ label: 'Configuration layout', state: compatible ? 'pass' : 'blocked', detail: compatible ? recipe.configSource : 'The configured filename has changed since this recipe was saved. Create a fresh recipe.' },
|
||||
{ label: 'Promoted configuration', state: 'pass', detail: recipe.configSource },
|
||||
{ label: 'Game files and host readiness', state: 'info', detail: 'The host must have the selected game and DLC files. This preview does not probe volumes, images, credentials, or network access.' }
|
||||
];
|
||||
const id = randomUUID();
|
||||
@@ -208,15 +209,16 @@ export function queueCreateServer(input: CreateServerInput): CreateServerResult
|
||||
if (preview && (!preview.server || !preview.port || preview.server !== nextName(input) || preview.port !== nextAvailablePort())) {
|
||||
return { ok: false, kind: 'no-slot', message: 'Available lobby or port changed. Refresh the preview and review the new server key.' };
|
||||
}
|
||||
if (recipe && (recipe.configFile !== configFileForGame(recipe.game) || !config.rcon.password)) {
|
||||
return { ok: false, kind: 'unconfigured', message: 'Configuration or health-check settings changed. Refresh the preview.' };
|
||||
if (recipe && !config.rcon.password) {
|
||||
return { ok: false, kind: 'unconfigured', message: 'Health-check settings changed. Refresh the preview.' };
|
||||
}
|
||||
const prepared = prepareCreation(input);
|
||||
if (!prepared.ok) return prepared;
|
||||
const frozenRecipe = recipe ?? captureRecipe(input.game ?? 't5', prepared.mapEntry.slug);
|
||||
// A preview records choices, not version pins. Promotions remain authoritative.
|
||||
const currentRecipe = captureRecipe(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(frozenRecipe), preview?.blueprint_name ?? null);
|
||||
.run(id, JSON.stringify(currentRecipe), preview?.blueprint_name ?? null);
|
||||
if (preview) db().prepare('UPDATE server_launch_previews SET deployment_id = ? WHERE id = ?').run(id, preview.id);
|
||||
db().prepare("UPDATE server_deployments SET state = 'queued' WHERE id = ?").run(id);
|
||||
db().prepare('INSERT INTO server_creation_jobs (deployment_id, server, port, game_mode, map_slug, expected_map) VALUES (?, ?, ?, ?, ?, ?)')
|
||||
@@ -293,9 +295,13 @@ function prepareCreation(input: CreateServerInput): PreparedCreation | Extract<C
|
||||
async function createServerUnlocked(input: CreateServerInput, prepared: PreparedCreation, operation: string): Promise<CreateServerResult> {
|
||||
const { name, port, plutoKey, mapEntry } = prepared;
|
||||
const game = gameOfServer(name);
|
||||
const recipe = recipeForDeployment(operation)?.recipe ?? captureRecipe(game, mapEntry.slug);
|
||||
// Resolve once more at provisioning so promotions made while queued also apply.
|
||||
// Persist the exact attempted files for history, never as input to future launches.
|
||||
const recipe = captureRecipe(game, mapEntry.slug);
|
||||
db().prepare(`INSERT INTO server_launch_recipes (deployment_id, recipe) VALUES (?, ?)
|
||||
ON CONFLICT(deployment_id) DO UPDATE SET recipe = excluded.recipe`)
|
||||
.run(operation, JSON.stringify(recipe));
|
||||
const configFile = recipe.configFile;
|
||||
if (configFile !== configFileForGame(game)) return { ok: false, kind: 'bad-request', message: 'Server config filename changed after this launch was queued.' };
|
||||
deploymentStage(operation, 'Preparing server files');
|
||||
const hostDir = serverDir(name);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user