#!/usr/bin/env node import constants from './src/constants.js'; import fs from 'node:fs'; import ldapServer from './src/ldapserver.js'; import net from 'node:net'; import oidcServer from './src/oidcserver.js'; import paths from './src/paths.js'; import proxyAuth from './src/proxyauth.js'; import safe from '@cloudron/safetydance'; import server from './src/server.js'; import directoryServer from './src/directoryserver.js'; import logger from './src/logger.js'; const { log } = logger('box'); let logFd; async function setupLogging() { if (constants.TEST) return; logFd = fs.openSync(paths.BOX_LOG_FILE, 'a'); // we used to write using a stream before but it caches internally and there is no way to flush it when things crash process.stdout.write = process.stderr.write = function (...args) { const callback = typeof args[args.length-1] === 'function' ? args.pop() : function () {}; // callback is required for fs.write fs.write.apply(fs, [logFd, ...args, callback]); }; } // happy eyeballs workaround. when there is no ipv6, nodejs timesout prematurely since the default for ipv4 is just 250ms // https://github.com/nodejs/node/issues/54359 async function setupNetworking() { net.setDefaultAutoSelectFamilyAttemptTimeout(2500); } // this is also used as the 'uncaughtException' handler which can only have synchronous functions function exitSync(status) { const ts = new Date().toISOString(); if (status.message) fs.write(logFd, `${ts} ${status.message}\n`, function () {}); const msg = status.error.stack.replace(/\n/g, `\n${ts} `); // prefix each line with ts if (status.error) fs.write(logFd, `${ts} ${msg}\n`, function () {}); fs.fsyncSync(logFd); fs.closeSync(logFd); process.exit(status.code); } async function startServers() { await setupLogging(); await setupNetworking(); await server.start(); // do this first since it also inits the database await proxyAuth.start(); await ldapServer.start(); const conf = await directoryServer.getConfig(); if (conf.enabled) await directoryServer.start(); } const [error] = await safe(startServers()); if (error) exitSync({ error, code: 1, message: 'Error starting servers' }); process.on('SIGHUP', async function () { log('Received SIGHUP. Re-reading configs.'); const conf = await directoryServer.getConfig(); if (conf.enabled) await directoryServer.checkCertificate(); }); process.on('SIGINT', async function () { log('Received SIGINT. Shutting down.'); await proxyAuth.stop(); await server.stop(); await directoryServer.stop(); await ldapServer.stop(); await oidcServer.stop(); setTimeout(() => { log('Shutdown complete'); process.exit(); }, 2000); // need to wait for the task processes to die }); process.on('SIGTERM', async function () { log('Received SIGTERM. Shutting down.'); await proxyAuth.stop(); await server.stop(); await directoryServer.stop(); await ldapServer.stop(); await oidcServer.stop(); setTimeout(() => { log('Shutdown complete'); process.exit(); }, 2000); // need to wait for the task processes to die }); process.on('uncaughtException', (uncaughtError) => exitSync({ error: uncaughtError, code: 1, message: 'From uncaughtException handler.' }));