Files
Cs2-Mapveto/server/src/app.js
T
mregosar d36dcacd39 Fix: Starts meiden hochfahrende Hosts, Messerrunde zählt nicht als Map-Ende
- 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.
2026-11-01 18:50:12 +01:00

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();
},
};
}