Files

72 lines
2.8 KiB
JavaScript

import { spawn } from 'node:child_process';
const env = { ...process.env, API_HOST: '127.0.0.1', API_PORT: '3001' };
function origin(name) {
const value = env[name];
if (!value) return undefined;
const url = new URL(value);
if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password || url.search || url.hash || url.pathname !== '/') {
throw new Error(`${name} must be an HTTP(S) origin without a path`);
}
if (env.COOKIE_SECURE !== 'false' && url.protocol !== 'https:') throw new Error(`${name} requires HTTPS with secure cookies`);
return url.origin;
}
const children = new Set();
let stopping = false;
let exitCode = 0;
let killTimer;
function stop(code) {
if (stopping) return;
stopping = true;
exitCode = code;
process.exitCode = code;
for (const child of children) child.kill('SIGTERM');
if (children.size) {
killTimer = setTimeout(() => { for (const child of children) child.kill('SIGKILL'); }, 10000);
killTimer.unref();
}
}
process.on('SIGTERM', () => stop(0));
process.on('SIGINT', () => stop(0));
function run(command, args, service = false) {
return new Promise((resolve, reject) => {
if (stopping) { resolve(); return; }
const child = spawn(command, args, { env, stdio: 'inherit' });
children.add(child);
child.once('error', () => { stop(1); reject(new Error(`Could not start ${command}`)); });
child.once('close', code => {
children.delete(child);
if (service && !stopping) {
console.error(`${command} exited unexpectedly; stopping the container`);
stop(1);
}
if (!children.size && killTimer) clearTimeout(killTimer);
if (!stopping && code !== 0) reject(new Error(`${command} failed with exit code ${code}`));
else resolve();
});
});
}
try {
const appUrl = origin('APP_URL');
const adminUrl = origin('ADMIN_URL');
if (!env.APP_ORIGINS && !appUrl) throw new Error('APP_URL (or APP_ORIGINS) is required');
env.APP_ORIGINS ||= [...new Set([appUrl, adminUrl].filter(Boolean))].join(',');
if (appUrl) env.OIDC_REDIRECT_URI ||= `${appUrl}/api/auth/callback`;
if (!env.DB_HOST || !env.DB_PASSWORD) throw new Error('DB_HOST and DB_PASSWORD are required');
if (!env.TOKEN_ENCRYPTION_KEY || Buffer.from(env.TOKEN_ENCRYPTION_KEY, 'base64').length !== 32) throw new Error('TOKEN_ENCRYPTION_KEY must contain 32 random bytes encoded as base64');
await run('node', ['dist/migrate.js']);
await run('node', ['dist/seed.js']);
if (!stopping) await Promise.all([
run('node', ['dist/server.js'], true),
run('nginx', ['-g', 'daemon off;'], true),
]);
} catch (error) {
// Configuration values, passwords, and tokens are never printed here.
console.error(error instanceof Error ? error.message : 'Container startup failed');
stop(1);
}
process.exitCode = exitCode;