- Neue Server meiden Hosts, auf denen gerade ein Container per SteamCMD lädt (Warteschlange im Prozess und laufende Starts laut Datenbank). Vorher warteten alle Starts hinter einem langen Download, obwohl andere Hosts frei waren. - Vor dem Einrichten des Matches wird eine laufende Messerrunde in die Aufwärmphase gesetzt, und Logzeilen zählen erst, wenn die Map eingerichtet ist. Vorher wurde das Ende der Messerrunde als Ende von Map 1 gewertet und die nächste Map automatisch geladen.
81 lines
3.2 KiB
JavaScript
81 lines
3.2 KiB
JavaScript
import fs from 'node:fs';
|
|
import http from 'node:http';
|
|
import path from 'node:path';
|
|
import express from 'express';
|
|
import { REPO_ROOT } from './config.js';
|
|
import { openDatabase } from './db.js';
|
|
import { createApiRouter } from './http.js';
|
|
import { createDiscordNotifier } from './discord.js';
|
|
import { loadMapPools } from './mapPools.js';
|
|
import { MatchService } from './matchService.js';
|
|
import { attachRealtime } from './realtime.js';
|
|
import { createServerAdapter } from './servers/adapter.js';
|
|
import { ServerManager } from './servers/serverManager.js';
|
|
import { KnifeServers } from './servers/knifeServers.js';
|
|
import { ServerSettings } from './serverSettings.js';
|
|
|
|
/** Baut die komplette Anwendung zusammen (auch für Tests nutzbar). */
|
|
export function createApp(config, { adapter: adapterOverride, logger = console } = {}) {
|
|
const db = openDatabase(config.databaseFile);
|
|
// Vom Admin geänderte Gameserver-Einstellungen auf die Konfiguration legen, bevor der Adapter entsteht
|
|
const settings = new ServerSettings({ db, config });
|
|
settings.load();
|
|
const pools = loadMapPools(config.mapPoolFile);
|
|
// Im Admin-Bereich gewählte Maps (Einstellungen → Map-Pools)
|
|
settings.applyMapSelection(pools);
|
|
const notifier = createDiscordNotifier(config.discordWebhookUrl, { logger });
|
|
const service = new MatchService({ db, pools, config, notifier });
|
|
|
|
let manager;
|
|
const adapter = adapterOverride ?? createServerAdapter(config, {
|
|
getUsedPorts: (host) => (host ? manager.usedPortsOn(host) : manager.usedPorts()),
|
|
getStarting: (host, exclude) => manager.startingOn(host, exclude),
|
|
});
|
|
manager = new ServerManager({ db, service, adapter, config, logger });
|
|
manager.init();
|
|
const knife = new KnifeServers({ db, adapter, config, pools, logger });
|
|
service.startVetoTimer();
|
|
|
|
const app = express();
|
|
if (config.trustProxy) app.set('trust proxy', 1);
|
|
app.disable('x-powered-by');
|
|
app.use((req, res, next) => {
|
|
res.set('X-Content-Type-Options', 'nosniff');
|
|
res.set('Referrer-Policy', 'no-referrer');
|
|
next();
|
|
});
|
|
app.use('/api', createApiRouter({ service, manager, config, pools, adapter, settings, knife }));
|
|
|
|
// Gebautes Frontend ausliefern (npm run build), SPA-Fallback auf index.html.
|
|
const dist = path.join(REPO_ROOT, 'client', 'dist');
|
|
if (fs.existsSync(dist)) {
|
|
app.use(express.static(dist, { index: false, maxAge: '1h' }));
|
|
app.get('*', (req, res) => res.sendFile(path.join(dist, 'index.html')));
|
|
}
|
|
|
|
const server = http.createServer(app);
|
|
const io = attachRealtime(server, { service, config });
|
|
|
|
return {
|
|
app, server, io, db, service, manager, adapter, knife,
|
|
async start() {
|
|
await manager.resume();
|
|
manager.startLoops();
|
|
knife.startLoop();
|
|
await new Promise((resolve) => server.listen(config.port, resolve));
|
|
return server.address().port;
|
|
},
|
|
async close() {
|
|
manager.stopLoops();
|
|
knife.stopLoop();
|
|
service.stopVetoTimer();
|
|
// Laufende Server-Aktionen (Start, Konfiguration) kurz abwarten, bevor die Datenbank schließt
|
|
await manager.idle(5000);
|
|
io.disposeRealtime();
|
|
io.close();
|
|
await new Promise((resolve) => server.close(() => resolve()));
|
|
db.close();
|
|
},
|
|
};
|
|
}
|