Block server destruction while linked to an active beta
Protect individual, bulk, and queued removals and rebuilds until beta closure. Recheck ownership before teardown and reject beta admission during pending or unresolved destruction. Fixes #36
This commit is contained in:
@@ -9,7 +9,7 @@ import { deploymentStage, finishDeployment, recordDeploymentEvent } from './depl
|
||||
import { housePartyTargets } from './house-party.js';
|
||||
import { getServerKey } from './server-keys.js';
|
||||
import { serverObservation } from './status.js';
|
||||
import { getServer } from './servers.js';
|
||||
import { activeBetaRemovalBlocker, getServer } from './servers.js';
|
||||
import { parseServerName, zombieMapBySlug, zombieMaps } from './map-aliases.js';
|
||||
|
||||
function coverage(entries: BlueprintEntry[]) {
|
||||
@@ -93,6 +93,16 @@ function blockersFor(servers: CollectionPreview['servers']): string[] {
|
||||
return blockers;
|
||||
}
|
||||
|
||||
function removalBlockersFor(removals: CollectionPreview['removals']): string[] {
|
||||
const blockers: string[] = [];
|
||||
if (removals.length && (!komodoConfigured() || !rconConfigured())) blockers.push('Configure Komodo and RCON before removing servers.');
|
||||
for (const server of removals) {
|
||||
const blocker = activeBetaRemovalBlocker(server.name);
|
||||
if (blocker) blockers.push(blocker);
|
||||
}
|
||||
return blockers;
|
||||
}
|
||||
|
||||
export function previewCollection(blueprintId: unknown, mode: unknown = 'fill'): CollectionPreview {
|
||||
if (typeof blueprintId !== 'string' || !blueprintId) throw new Error('Choose a collection to launch.');
|
||||
if (mode !== 'fill' && mode !== 'exact') throw new Error('Choose fill or exact collection mode.');
|
||||
@@ -100,8 +110,7 @@ export function previewCollection(blueprintId: unknown, mode: unknown = 'fill'):
|
||||
const { entries, blueprintName } = blueprintEntries(blueprintId);
|
||||
const { counts, retainedServers, removals } = collectionPlan(entries, mode);
|
||||
const servers = allocate(entries, removals.map(server => server.name));
|
||||
const blockers = blockersFor(servers);
|
||||
if (removals.length && (!komodoConfigured() || !rconConfigured())) blockers.push('Configure Komodo and RCON before removing servers.');
|
||||
const blockers = [...blockersFor(servers), ...removalBlockersFor(removals)];
|
||||
const id = randomUUID();
|
||||
const expiresAt = new Date(Date.now() + 30 * 60_000).toISOString();
|
||||
db().prepare('DELETE FROM server_collection_previews WHERE expires_at < ? AND collection_id IS NULL').run(new Date().toISOString());
|
||||
@@ -132,8 +141,7 @@ export function queueCollection(input: { previewId?: unknown; keys?: unknown }):
|
||||
const { retainedServers, removals } = collectionPlan(entries, preview.mode);
|
||||
const releasingServers = removals.map(server => server.name);
|
||||
const servers = allocate(entries, releasingServers);
|
||||
const blockers = blockersFor(servers);
|
||||
if (removals.length && (!komodoConfigured() || !rconConfigured())) blockers.push('Configure Komodo and RCON before removing servers.');
|
||||
const blockers = [...blockersFor(servers), ...removalBlockersFor(removals)];
|
||||
if (blockers.length) throw new Error(blockers.join(' '));
|
||||
if (servers.length !== reviewed.length || servers.some((server, i) => server.entryIndex !== reviewed[i]?.entryIndex || server.name !== reviewed[i]?.name || server.port !== reviewed[i]?.port)) {
|
||||
throw new Error('Fleet counts, lobby or port availability changed. Nothing was queued. Refresh the preview and review each server key.');
|
||||
@@ -155,7 +163,8 @@ export function queueCollection(input: { previewId?: unknown; keys?: unknown }):
|
||||
if (queued && !getServer(server.name)) {
|
||||
// No container or files exist yet: cancel the reservation instead of booting just to remove it.
|
||||
const result = destroyServer(server.name, 'blueprint-exact');
|
||||
if (!result.ok || !result.deploymentId) throw new Error('Could not queue removal for ' + server.name);
|
||||
if (!result.ok) throw new Error(result.message);
|
||||
if (!result.deploymentId) throw new Error('Could not queue removal for ' + server.name);
|
||||
id = result.deploymentId;
|
||||
db().prepare("UPDATE server_deployments SET state = 'cancelled', stage = 'Cancelled by exact collection', finished_at = ? WHERE id = ?").run(new Date().toISOString(), queued.id);
|
||||
recordDeploymentEvent(queued.id, 'Queued creation cancelled by exact collection; saved key retained.');
|
||||
@@ -163,7 +172,8 @@ export function queueCollection(input: { previewId?: unknown; keys?: unknown }):
|
||||
finishDeployment(id, { ok: true });
|
||||
} else {
|
||||
const result = destroyServer(server.name, 'blueprint-exact');
|
||||
if (!result.ok || !result.deploymentId) throw new Error('Could not queue removal for ' + server.name);
|
||||
if (!result.ok) throw new Error(result.message);
|
||||
if (!result.deploymentId) throw new Error('Could not queue removal for ' + server.name);
|
||||
id = result.deploymentId;
|
||||
}
|
||||
db().prepare('INSERT INTO server_collection_removals (collection_id, deployment_id, position) VALUES (?, ?, ?)').run(collectionId, id, position);
|
||||
|
||||
Reference in New Issue
Block a user