Apply selected server maps automatically without config overlays

This commit is contained in:
2026-09-17 11:05:01 -04:00
parent 1cd4eb6f09
commit 3f0d6dd87d
12 changed files with 431 additions and 59 deletions
+47 -18
View File
@@ -1,5 +1,6 @@
import { mkdir, chmod, rm } from 'node:fs/promises';
import { config } from '../config.js';
import { mkdir, chmod, rm, writeFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import { config, destOfKind } from '../config.js';
import { db } from '../db/index.js';
import { getServer } from './servers.js';
import { t5ZombieMapBySlug, type GameMode } from './map-aliases.js';
@@ -15,10 +16,13 @@ import {
type DeploymentEnvVar
} from '../integrations/komodo.js';
import { kick } from '../pipeline/worker.js';
import { getPromotedServerConfig, getServerMapConfig, MAP_CONFIG_FILE } from './server-config.js';
import { recordScript } from './inventory.js';
import { sha256Buffer } from './files.js';
// Currently every t5 zombie server uses the same config file + game id. If we
// ever add MP or SP, split these based on mode.
const T5_ZOMBIE_CONFIG_FILE = 'dedicated_sp.cfg';
const T5_ZOMBIE_CONFIG_FILE = config.configTargetFile;
const T5_ZOMBIE_GAME_ID = 't5sp';
export type CreateServerFailure =
@@ -92,6 +96,11 @@ function buildEnv(args: {
{ variable: 'PLUTAINER_PORT', value: String(args.port) },
{ variable: 'PLUTAINER_GAME', value: T5_ZOMBIE_GAME_ID },
{ variable: 'PLUTAINER_CONFIG_FILE', value: T5_ZOMBIE_CONFIG_FILE },
// Plutainer seeds/loads the main config first. Load our generated map
// config afterward, then rotate. A separate exec avoids quoted values
// in EXTRA_ARGS, which Plutainer splits on whitespace.
{ variable: 'PLUTAINER_MAP_ROTATE', value: 'false' },
{ variable: 'PLUTAINER_EXTRA_ARGS', value: `+exec ${MAP_CONFIG_FILE} +map_rotate` },
{ variable: 'PLUTO_SERVER_KEY', value: args.plutoKey },
{ variable: 'PLUTAINER_RCON_PASSWORD', value: config.rcon.password }
];
@@ -155,12 +164,26 @@ export async function createServer(input: CreateServerInput): Promise<CreateServ
}
const hostDir = `${config.serversRoot}/${name}`;
const initialConfig = getPromotedServerConfig('t5', mapEntry.canonical);
const mapConfig = getServerMapConfig('t5', mapEntry.canonical)!;
// Step 1: filesystem. mkdir mode is masked by umask, so chmod explicitly
// after — plutainer expects to be able to write to /home/plutainer/app.
try {
await mkdir(hostDir, { recursive: true, mode: 0o777 });
await chmod(hostDir, 0o777);
const mapConfigPath = join(destOfKind(name, 'config'), MAP_CONFIG_FILE);
await mkdir(dirname(mapConfigPath), { recursive: true, mode: 0o777 });
await chmod(dirname(mapConfigPath), 0o777);
await writeFile(mapConfigPath, mapConfig);
await chmod(mapConfigPath, 0o666);
if (initialConfig) {
const configPath = join(destOfKind(name, 'config'), T5_ZOMBIE_CONFIG_FILE);
await mkdir(dirname(configPath), { recursive: true, mode: 0o777 });
await chmod(dirname(configPath), 0o777);
await writeFile(configPath, initialConfig);
await chmod(configPath, 0o666);
}
} catch (err) {
return { ok: false, kind: 'fs', message: `failed to create ${hostDir}: ${(err as Error).message}` };
}
@@ -176,21 +199,27 @@ export async function createServer(input: CreateServerInput): Promise<CreateServ
// Step 3: DB row — insert *before* execute/Deploy so the sweep sees the
// server immediately even if Deploy hangs.
try {
db().prepare(
`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)
VALUES
(?, 'deployment', ?, 't5', ?,
?, ?, ?, ?, ?, ?,
?, ?)`
).run(
name, name, mapEntry.canonical,
input.gameMode, mapEntry.slug, hostDir,
T5_ZOMBIE_CONFIG_FILE, T5_ZOMBIE_GAME_ID, config.komodo.defaultImage,
port, input.createdBy ?? 'ui'
);
db().transaction(() => {
db().prepare(
`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)
VALUES
(?, 'deployment', ?, 't5', ?,
?, ?, ?, ?, ?, ?,
?, ?)`
).run(
name, name, mapEntry.canonical,
input.gameMode, mapEntry.slug, hostDir,
T5_ZOMBIE_CONFIG_FILE, T5_ZOMBIE_GAME_ID, config.komodo.defaultImage,
port, input.createdBy ?? 'ui'
);
if (initialConfig) {
recordScript(name, T5_ZOMBIE_CONFIG_FILE, null, sha256Buffer(initialConfig), initialConfig.byteLength, 'create');
}
recordScript(name, MAP_CONFIG_FILE, null, sha256Buffer(mapConfig), mapConfig.byteLength, 'create-map');
})();
} catch (err) {
// Roll back Komodo + FS if the DB insert fails (very unlikely — most
// often a race where the same name got inserted twice).