72 lines
2.8 KiB
JavaScript
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;
|