Still using Docker, added support for proper shutdown

- It still doesn't work with tsc-watch in Docker, hopefully the final image will work
This commit is contained in:
Norbi Peti 2022-11-19 02:12:16 +01:00
parent 9257f94dbe
commit d382de1262
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
7 changed files with 59 additions and 16 deletions

View file

@ -8,7 +8,7 @@ services:
- DGID=${DGID} - DGID=${DGID}
image: dockermc image: dockermc
environment: environment:
- MC_VERSION=1.19 - MC_VERSION=1.19.2
- DGID=${DGID} - DGID=${DGID}
volumes: volumes:
- ../src:/src - ../src:/src

View file

@ -15,7 +15,7 @@ listeners:
host: 0.0.0.0:25577 host: 0.0.0.0:25577
max_players: 1 max_players: 1
tab_size: 60 tab_size: 60
force_default_server: false force_default_server: true
remote_ping_cache: -1 remote_ping_cache: -1
network_compression_threshold: 256 network_compression_threshold: 256
permissions: permissions:

View file

@ -25,7 +25,7 @@ RUN groupadd -g $DGID hostDocker || :
RUN usermod -aG $DGID node RUN usermod -aG $DGID node
USER node USER node
ENTRYPOINT ["npm", "run", "start:watch"] ENTRYPOINT ["node_modules/.bin/tsc-watch", "--target", "es2017", "--outDir", "./dist", "--onSuccess", "node -r source-map-support/register .", "--noClear"]
FROM dev AS prod FROM dev AS prod

View file

@ -7,38 +7,58 @@ import debounce from 'debounce';
const running: { const running: {
watch: chokidar.FSWatcher, watch: chokidar.FSWatcher,
server1: boolean, server1: boolean,
serverName: () => string, serverName: () => 'server1' | 'server2',
prevServerName: () => string prevServerName: () => 'server1' | 'server2'
} = { } = {
watch: null, watch: null,
server1: false, server1: false,
serverName: () => 'server' + (running.server1 ? '1' : '2'), serverName: () => (running.server1 ? 'server1' : 'server2'),
prevServerName: () => 'server' + (running.server1 ? '2' : '1') prevServerName: () => (running.server1 ? 'server2' : 'server1')
}; };
let isShuttingDown = false;
async function main() { async function main() {
console.log("Checking for server updates...", "MC", process.env.MC_VERSION); console.log("Checking for server updates...", "MC", process.env.MC_VERSION);
await updateServer('paper'); await updateServer('paper');
await updateServer('waterfall'); await updateServer('waterfall');
console.log('Starting server proxy...'); console.log('Starting server proxy...');
exec('docker compose -f /docker-compose.server.yml up -d bungee', loggingCallback); execCompose('bungee', 'up');
const listener = async (changedPath) => { const listener = async (changedPath) => {
console.log(`Plugin change detected at ${changedPath}, switching to ${running.prevServerName()}...`); console.log(`Plugin change detected at ${changedPath}, switching to ${running.prevServerName()}...`);
await startNextServer(); await startNextServer();
}; };
running.watch = chokidar.watch(`/mcserver/plugins/*.jar`).on('change', debounce(listener, 500)); running.watch = chokidar.watch(`/mcserver/plugins/*.jar`).on('change', debounce(listener, 500));
await startNextServer(); await startNextServer();
process.on('SIGTERM', () => shutdown());
process.on('SIGINT', () => shutdown());
}
async function shutdown() {
if (isShuttingDown) return;
isShuttingDown = true;
console.log("Shutting down...");
await running.watch?.close();
await new Promise<void>(resolve => execCompose(running.serverName(), 'stop').on('exit', () => resolve()));
await new Promise<void>(resolve => execCompose('bungee', 'stop').on('exit', () => resolve()));
console.log("Shutdown complete");
process.exit();
} }
async function updateServer(project: 'paper' | 'waterfall') { async function updateServer(project: 'paper' | 'waterfall') {
const res: { builds: Build[] } = await (await fetch(`https://api.papermc.io/v2/projects/${project}/versions/${process.env.MC_VERSION}/builds`)).json() as any; let mcver = process.env.MC_VERSION;
mcver = project === 'waterfall' ? mcver.split('.').slice(0, 2).join('.') : mcver;
const res: { builds?: Build[] } = await (await fetch(`https://api.papermc.io/v2/projects/${project}/versions/${mcver}/builds`)).json() as any;
if (!res.builds) {
throw new Error(`No builds found for MC version ${mcver}`);
}
const lastBuild = res.builds[res.builds.length - 1]; const lastBuild = res.builds[res.builds.length - 1];
console.log(`Latest ${project} build is ${lastBuild.build} at ${new Date(Date.parse(lastBuild.time)).toLocaleString()}`); console.log(`Latest ${project} build is ${lastBuild.build} at ${new Date(Date.parse(lastBuild.time)).toLocaleString()}`); // TODO: Delete all old builds
try { try {
await promises.access('/mcserver/' + lastBuild.downloads.application.name) await promises.access('/mcserver/' + lastBuild.downloads.application.name)
} catch { } catch {
console.log("Build not found locally, downloading..."); console.log("Build not found locally, downloading...");
const newVerRes = await fetch(`https://api.papermc.io/v2/projects/${project}/versions/${process.env.MC_VERSION}/builds/${lastBuild.build}/downloads/${lastBuild.downloads.application.name}`); const newVerRes = await fetch(`https://api.papermc.io/v2/projects/${project}/versions/${mcver}/builds/${lastBuild.build}/downloads/${lastBuild.downloads.application.name}`);
if (newVerRes.status > 299) { if (newVerRes.status > 299) {
console.log("Error while downloading", await newVerRes.json()); console.log("Error while downloading", await newVerRes.json());
throw new Error("Error while downloading"); throw new Error("Error while downloading");
@ -57,14 +77,15 @@ async function startNextServer() {
console.log('Copying config files...'); console.log('Copying config files...');
await promises.cp('/mcserver/configs', `/mcserver/${running.serverName()}`, {recursive: true}); await promises.cp('/mcserver/configs', `/mcserver/${running.serverName()}`, {recursive: true});
console.log("Starting server", running.server1 ? 'one...' : 'two...'); console.log("Starting server", running.server1 ? 'one...' : 'two...');
exec('docker compose -f /docker-compose.server.yml up -d ' + running.serverName(), loggingCallback); execCompose(running.serverName(), 'up');
await waitForStartup(); await waitForStartup();
await stopPrevServer(); await stopPrevServer();
console.log("Server", running.server1 ? 'one' : 'two', "reachable at localhost:25565");
} }
async function stopPrevServer() { async function stopPrevServer() {
console.log("Stopping previous server..."); console.log("Stopping previous server...");
exec('docker compose -f /docker-compose.server.yml stop ' + running.prevServerName(), loggingCallback); execCompose(running.prevServerName(), 'stop');
} }
function waitForStartup() { function waitForStartup() {
@ -88,11 +109,18 @@ function loggingCallback(error?: ExecException, stdout?: string, stderr?: string
if (stderr) { if (stderr) {
console.error(stderr); console.error(stderr);
} }
if (error) { if (error && error.signal !== 'SIGTERM') { // docker compose stop results in SIGTERM if not running I guess
console.error(error); console.error(error);
} }
} }
function execCompose(container: 'bungee' | 'server1' | 'server2', command: 'up' | 'stop') {
return exec(`docker compose -f /docker-compose.server.yml ${command}${command === 'up' ? ' -d' : ''} ${container}`, loggingCallback);
}
// Begin reading from stdin so the process does not exit.
process.stdin.resume();
main().catch(reason => console.error('An error occurred while running DockerMC.', reason)); main().catch(reason => console.error('An error occurred while running DockerMC.', reason));
type Build = { build: number, time: string, changes: { summary: string }[], downloads: { application: { name: string }, 'mojang-mappings': { name: string } } }; type Build = { build: number, time: string, changes: { summary: string }[], downloads: { application: { name: string }, 'mojang-mappings': { name: string } } };

13
src/package-lock.json generated
View file

@ -14,12 +14,19 @@
"read-last-lines": "^1.8.0" "read-last-lines": "^1.8.0"
}, },
"devDependencies": { "devDependencies": {
"@types/debounce": "^1.2.1",
"@types/node": "^18.0.0", "@types/node": "^18.0.0",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"tsc-watch": "^5.0.3", "tsc-watch": "^5.0.3",
"typescript": "^4.7.4" "typescript": "^4.7.4"
} }
}, },
"node_modules/@types/debounce": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.1.tgz",
"integrity": "sha512-epMsEE85fi4lfmJUH/89/iV/LI+F5CvNIvmgs5g5jYFPfhO2S/ae8WSsLOKWdwtoaZw9Q2IhJ4tQ5tFCcS/4HA==",
"dev": true
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "18.0.0", "version": "18.0.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz",
@ -583,6 +590,12 @@
} }
}, },
"dependencies": { "dependencies": {
"@types/debounce": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.1.tgz",
"integrity": "sha512-epMsEE85fi4lfmJUH/89/iV/LI+F5CvNIvmgs5g5jYFPfhO2S/ae8WSsLOKWdwtoaZw9Q2IhJ4tQ5tFCcS/4HA==",
"dev": true
},
"@types/node": { "@types/node": {
"version": "18.0.0", "version": "18.0.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz",

View file

@ -20,6 +20,7 @@
"author": "NorbiPeti", "author": "NorbiPeti",
"license": "", "license": "",
"devDependencies": { "devDependencies": {
"@types/debounce": "^1.2.1",
"@types/node": "^18.0.0", "@types/node": "^18.0.0",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"tsc-watch": "^5.0.3", "tsc-watch": "^5.0.3",

View file

@ -2,7 +2,8 @@
"compilerOptions": { "compilerOptions": {
"outDir": "dist", "outDir": "dist",
"rootDir": ".", "rootDir": ".",
"moduleResolution": "Node" "moduleResolution": "Node",
"allowSyntheticDefaultImports": true
}, },
"include": ["."], "include": ["."]
} }