Admin: Gameserver-Einstellungen, Docker-Hosts und Image-Update im Admin-Bereich
This commit is contained in:
1 parent
472bda31a9
commit
929134f2bc
10 files changed
+710
-12
No files matched your search
@@ -46,7 +46,7 @@ export class DockerServerAdapter {
|
||||
}
|
||||
|
||||
async docker(args, timeout = 60_000) {
|
||||
if (this.runDocker) return this.runDocker(args);
|
||||
if (this.runDocker) return this.runDocker(args, this.options.name);
|
||||
const env = this.options.dockerHost ? { ...process.env, DOCKER_HOST: this.options.dockerHost } : process.env;
|
||||
const { stdout } = await exec(this.options.bin, args, { timeout, maxBuffer: 4 * 1024 * 1024, env });
|
||||
return stdout.trim();
|
||||
@@ -220,7 +220,18 @@ export class DockerHostPool {
|
||||
this.name = 'docker';
|
||||
this.simulated = false;
|
||||
this.depsGetUsedPorts = getUsedPorts;
|
||||
const hosts = options.hosts ?? [{
|
||||
this.runDocker = runDocker;
|
||||
this.options = options;
|
||||
this.diskCache = new Map();
|
||||
this.diskInflight = new Map();
|
||||
this.pull = { running: false, startedAt: null, finishedAt: null, results: [] };
|
||||
this.setHosts(options.hosts);
|
||||
}
|
||||
|
||||
/** Baut die Host-Liste neu auf (beim Start und wenn der Admin die Hosts ändert). */
|
||||
setHosts(list) {
|
||||
const options = this.options;
|
||||
const hosts = list ?? [{
|
||||
name: 'default',
|
||||
dockerHost: process.env.DOCKER_HOST || null,
|
||||
publicHost: options.host,
|
||||
@@ -240,14 +251,64 @@ export class DockerHostPool {
|
||||
portRangeEnd: h.portRangeEnd ?? options.portRangeEnd,
|
||||
volume: h.volume ?? options.volume,
|
||||
};
|
||||
return { ...options, ...host, host: host.publicHost };
|
||||
const merged = { ...options, ...host, host: host.publicHost };
|
||||
// Die Startzeit kann der Admin zur Laufzeit ändern, deshalb immer aus den gemeinsamen Einstellungen lesen
|
||||
Object.defineProperty(merged, 'startTimeoutMinutes', { get: () => options.startTimeoutMinutes, enumerable: true });
|
||||
return merged;
|
||||
});
|
||||
this.adapters = new Map(this.hosts.map((h) => [h.name, new DockerServerAdapter(h, {
|
||||
getUsedPorts: () => (getUsedPorts ? getUsedPorts(h.publicHost) : new Set()),
|
||||
runDocker,
|
||||
getUsedPorts: () => (this.depsGetUsedPorts ? this.depsGetUsedPorts(h.publicHost) : new Set()),
|
||||
runDocker: this.runDocker,
|
||||
})]));
|
||||
this.diskCache = new Map();
|
||||
this.diskInflight = new Map();
|
||||
this.diskCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Docker-Befehl direkt auf dem Host ausführen. Bei ssh:// per SSH, damit der Pull die Anmeldedaten des Hosts
|
||||
* nutzt und nicht den Credential-Helper des Rechners, auf dem die App läuft.
|
||||
*/
|
||||
async hostDocker(name, args, timeout) {
|
||||
const host = this.hosts.find((h) => h.name === name);
|
||||
if (!this.runDocker && host?.dockerHost?.startsWith('ssh://')) {
|
||||
const { stdout } = await exec('ssh', [
|
||||
'-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', host.dockerHost.slice('ssh://'.length),
|
||||
['docker', ...args].map((a) => `'${a}'`).join(' '),
|
||||
], { timeout, maxBuffer: 4 * 1024 * 1024 });
|
||||
return stdout.trim();
|
||||
}
|
||||
return this.adapters.get(name).docker(args, timeout);
|
||||
}
|
||||
|
||||
/** Lädt das CS2-Image auf allen Hosts gleichzeitig neu. Laufende Server bleiben unberührt. */
|
||||
async pullImage() {
|
||||
if (this.pull.running) throw new Error('Das Image wird bereits aktualisiert');
|
||||
if (!/^[\w./:@-]+$/.test(this.options.image)) throw new Error('Ungültiger Image-Name');
|
||||
const hosts = this.hosts.map((h) => h.name);
|
||||
this.pull = {
|
||||
running: true,
|
||||
startedAt: Date.now(),
|
||||
finishedAt: null,
|
||||
image: this.options.image,
|
||||
results: hosts.map((name) => ({ name, state: 'running', message: null })),
|
||||
};
|
||||
await Promise.all(hosts.map(async (name, i) => {
|
||||
const result = this.pull.results[i];
|
||||
try {
|
||||
await this.hostDocker(name, ['pull', '--quiet', this.options.image], 20 * 60_000);
|
||||
const digest = await this.hostDocker(name, ['image', 'inspect', this.options.image, '--format', '{{index .RepoDigests 0}}'], 30_000)
|
||||
.catch(() => '');
|
||||
Object.assign(result, { state: 'ok', message: digest.split('@')[1]?.slice(0, 19) ?? 'aktualisiert' });
|
||||
} catch (err) {
|
||||
Object.assign(result, { state: 'failed', message: String(err.stderr || err.message).trim().split('\n').pop().slice(0, 200) });
|
||||
}
|
||||
}));
|
||||
this.pull.running = false;
|
||||
this.pull.finishedAt = Date.now();
|
||||
return this.pull;
|
||||
}
|
||||
|
||||
pullStatus() {
|
||||
return this.pull;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user