2015-07-20 00:09:47 -07:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
|
Migrate codebase from CommonJS to ES Modules
- Convert all require()/module.exports to import/export across 260+ files
- Add "type": "module" to package.json to enable ESM by default
- Add migrations/package.json with "type": "commonjs" to keep db-migrate compatible
- Convert eslint.config.js to ESM with sourceType: "module"
- Replace __dirname/__filename with import.meta.dirname/import.meta.filename
- Replace require.main === module with process.argv[1] === import.meta.filename
- Remove 'use strict' directives (implicit in ESM)
- Convert dynamic require() in switch statements to static import lookup maps
(dns.js, domains.js, backupformats.js, backupsites.js, network.js)
- Extract self-referencing exports.CONSTANT patterns into standalone const
declarations (apps.js, services.js, locks.js, users.js, mail.js, etc.)
- Lazify SERVICES object in services.js to avoid circular dependency TDZ issues
- Add clearMailQueue() to mailer.js for ESM-safe queue clearing in tests
- Add _setMockApp() to ldapserver.js for ESM-safe test mocking
- Add _setMockResolve() wrapper to dig.js for ESM-safe DNS mocking in tests
- Convert backupupload.js to use dynamic imports so --check exits before
loading the module graph (which requires BOX_ENV)
- Update check-install to use ESM import for infra_version.js
- Convert scripts/ (hotfix, release, remote_hotfix.js, find-unused-translations)
- All 1315 tests passing
Migration stats (AI-assisted using Cursor with Claude):
- Wall clock time: ~3-4 hours
- Assistant completions: ~80-100
- Estimated token usage: ~1-2M tokens
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-14 09:53:14 +01:00
|
|
|
import constants from './src/constants.js';
|
|
|
|
|
import fs from 'node:fs';
|
2026-02-14 15:43:24 +01:00
|
|
|
import ldapServer from './src/ldapserver.js';
|
Migrate codebase from CommonJS to ES Modules
- Convert all require()/module.exports to import/export across 260+ files
- Add "type": "module" to package.json to enable ESM by default
- Add migrations/package.json with "type": "commonjs" to keep db-migrate compatible
- Convert eslint.config.js to ESM with sourceType: "module"
- Replace __dirname/__filename with import.meta.dirname/import.meta.filename
- Replace require.main === module with process.argv[1] === import.meta.filename
- Remove 'use strict' directives (implicit in ESM)
- Convert dynamic require() in switch statements to static import lookup maps
(dns.js, domains.js, backupformats.js, backupsites.js, network.js)
- Extract self-referencing exports.CONSTANT patterns into standalone const
declarations (apps.js, services.js, locks.js, users.js, mail.js, etc.)
- Lazify SERVICES object in services.js to avoid circular dependency TDZ issues
- Add clearMailQueue() to mailer.js for ESM-safe queue clearing in tests
- Add _setMockApp() to ldapserver.js for ESM-safe test mocking
- Add _setMockResolve() wrapper to dig.js for ESM-safe DNS mocking in tests
- Convert backupupload.js to use dynamic imports so --check exits before
loading the module graph (which requires BOX_ENV)
- Update check-install to use ESM import for infra_version.js
- Convert scripts/ (hotfix, release, remote_hotfix.js, find-unused-translations)
- All 1315 tests passing
Migration stats (AI-assisted using Cursor with Claude):
- Wall clock time: ~3-4 hours
- Assistant completions: ~80-100
- Estimated token usage: ~1-2M tokens
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-14 09:53:14 +01:00
|
|
|
import net from 'node:net';
|
2026-02-14 15:43:24 +01:00
|
|
|
import oidcServer from './src/oidcserver.js';
|
Migrate codebase from CommonJS to ES Modules
- Convert all require()/module.exports to import/export across 260+ files
- Add "type": "module" to package.json to enable ESM by default
- Add migrations/package.json with "type": "commonjs" to keep db-migrate compatible
- Convert eslint.config.js to ESM with sourceType: "module"
- Replace __dirname/__filename with import.meta.dirname/import.meta.filename
- Replace require.main === module with process.argv[1] === import.meta.filename
- Remove 'use strict' directives (implicit in ESM)
- Convert dynamic require() in switch statements to static import lookup maps
(dns.js, domains.js, backupformats.js, backupsites.js, network.js)
- Extract self-referencing exports.CONSTANT patterns into standalone const
declarations (apps.js, services.js, locks.js, users.js, mail.js, etc.)
- Lazify SERVICES object in services.js to avoid circular dependency TDZ issues
- Add clearMailQueue() to mailer.js for ESM-safe queue clearing in tests
- Add _setMockApp() to ldapserver.js for ESM-safe test mocking
- Add _setMockResolve() wrapper to dig.js for ESM-safe DNS mocking in tests
- Convert backupupload.js to use dynamic imports so --check exits before
loading the module graph (which requires BOX_ENV)
- Update check-install to use ESM import for infra_version.js
- Convert scripts/ (hotfix, release, remote_hotfix.js, find-unused-translations)
- All 1315 tests passing
Migration stats (AI-assisted using Cursor with Claude):
- Wall clock time: ~3-4 hours
- Assistant completions: ~80-100
- Estimated token usage: ~1-2M tokens
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-14 09:53:14 +01:00
|
|
|
import paths from './src/paths.js';
|
2026-02-14 15:43:24 +01:00
|
|
|
import proxyAuth from './src/proxyauth.js';
|
Migrate codebase from CommonJS to ES Modules
- Convert all require()/module.exports to import/export across 260+ files
- Add "type": "module" to package.json to enable ESM by default
- Add migrations/package.json with "type": "commonjs" to keep db-migrate compatible
- Convert eslint.config.js to ESM with sourceType: "module"
- Replace __dirname/__filename with import.meta.dirname/import.meta.filename
- Replace require.main === module with process.argv[1] === import.meta.filename
- Remove 'use strict' directives (implicit in ESM)
- Convert dynamic require() in switch statements to static import lookup maps
(dns.js, domains.js, backupformats.js, backupsites.js, network.js)
- Extract self-referencing exports.CONSTANT patterns into standalone const
declarations (apps.js, services.js, locks.js, users.js, mail.js, etc.)
- Lazify SERVICES object in services.js to avoid circular dependency TDZ issues
- Add clearMailQueue() to mailer.js for ESM-safe queue clearing in tests
- Add _setMockApp() to ldapserver.js for ESM-safe test mocking
- Add _setMockResolve() wrapper to dig.js for ESM-safe DNS mocking in tests
- Convert backupupload.js to use dynamic imports so --check exits before
loading the module graph (which requires BOX_ENV)
- Update check-install to use ESM import for infra_version.js
- Convert scripts/ (hotfix, release, remote_hotfix.js, find-unused-translations)
- All 1315 tests passing
Migration stats (AI-assisted using Cursor with Claude):
- Wall clock time: ~3-4 hours
- Assistant completions: ~80-100
- Estimated token usage: ~1-2M tokens
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-14 09:53:14 +01:00
|
|
|
import safe from 'safetydance';
|
2026-02-14 15:43:24 +01:00
|
|
|
import server from './src/server.js';
|
|
|
|
|
import directoryServer from './src/directoryserver.js';
|
2026-03-12 22:55:28 +05:30
|
|
|
import logger from './src/logger.js';
|
2015-07-20 00:09:47 -07:00
|
|
|
|
2026-03-12 22:55:28 +05:30
|
|
|
const { log, trace } = logger('box');
|
2015-07-20 00:09:47 -07:00
|
|
|
|
2021-09-07 11:23:54 -07:00
|
|
|
let logFd;
|
|
|
|
|
|
|
|
|
|
async function setupLogging() {
|
2023-10-01 13:52:19 +05:30
|
|
|
if (constants.TEST) return;
|
2020-08-04 22:16:38 -07:00
|
|
|
|
2021-09-07 11:23:54 -07:00
|
|
|
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]);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
network: fix premature connection closures with node 20 and above
the happy eyeballs implementation in node is buggy. ipv4 and ipv6 connections
are made in parallel and whichever responds first is chosen. when there is no
ipv6 (immediately errors with ENETUNREACH/EHOSTUNREACH) and when ipv4 is > 250ms,
the code erroneously times out.
see also https://github.com/nodejs/node/issues/54359
reproduction for those servers:
const options = {
hostname: 'www.cloudron.io', port: 80, path: '/', method: 'HEAD',
// family: 4, // uncomment to make it work
};
const req = require('http').request(options, (res) => {
console.log('statusCode:', res.statusCode);
res.on('data', () => {}); // drain
});
req.on('socket', (socket) => console.log('Socket assigned to request', socket););
req.on('error', (e) => console.error(e));
req.end();
2024-10-31 09:38:40 +01:00
|
|
|
// 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() {
|
2024-10-31 10:07:11 +01:00
|
|
|
net.setDefaultAutoSelectFamilyAttemptTimeout(2500);
|
network: fix premature connection closures with node 20 and above
the happy eyeballs implementation in node is buggy. ipv4 and ipv6 connections
are made in parallel and whichever responds first is chosen. when there is no
ipv6 (immediately errors with ENETUNREACH/EHOSTUNREACH) and when ipv4 is > 250ms,
the code erroneously times out.
see also https://github.com/nodejs/node/issues/54359
reproduction for those servers:
const options = {
hostname: 'www.cloudron.io', port: 80, path: '/', method: 'HEAD',
// family: 4, // uncomment to make it work
};
const req = require('http').request(options, (res) => {
console.log('statusCode:', res.statusCode);
res.on('data', () => {}); // drain
});
req.on('socket', (socket) => console.log('Socket assigned to request', socket););
req.on('error', (e) => console.error(e));
req.end();
2024-10-31 09:38:40 +01:00
|
|
|
}
|
|
|
|
|
|
2021-09-07 11:23:54 -07:00
|
|
|
// this is also used as the 'uncaughtException' handler which can only have synchronous functions
|
|
|
|
|
function exitSync(status) {
|
2024-10-14 16:21:25 +02:00
|
|
|
const ts = new Date().toISOString();
|
2025-06-30 17:34:55 +02:00
|
|
|
if (status.message) fs.write(logFd, `${ts} ${status.message}\n`, function () {});
|
2024-10-14 16:21:25 +02:00
|
|
|
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 () {});
|
2021-09-07 11:23:54 -07:00
|
|
|
fs.fsyncSync(logFd);
|
|
|
|
|
fs.closeSync(logFd);
|
|
|
|
|
process.exit(status.code);
|
2021-09-07 09:57:49 -07:00
|
|
|
}
|
2020-08-04 09:34:03 -07:00
|
|
|
|
2021-09-07 09:57:49 -07:00
|
|
|
async function startServers() {
|
2021-09-07 11:23:54 -07:00
|
|
|
await setupLogging();
|
network: fix premature connection closures with node 20 and above
the happy eyeballs implementation in node is buggy. ipv4 and ipv6 connections
are made in parallel and whichever responds first is chosen. when there is no
ipv6 (immediately errors with ENETUNREACH/EHOSTUNREACH) and when ipv4 is > 250ms,
the code erroneously times out.
see also https://github.com/nodejs/node/issues/54359
reproduction for those servers:
const options = {
hostname: 'www.cloudron.io', port: 80, path: '/', method: 'HEAD',
// family: 4, // uncomment to make it work
};
const req = require('http').request(options, (res) => {
console.log('statusCode:', res.statusCode);
res.on('data', () => {}); // drain
});
req.on('socket', (socket) => console.log('Socket assigned to request', socket););
req.on('error', (e) => console.error(e));
req.end();
2024-10-31 09:38:40 +01:00
|
|
|
await setupNetworking();
|
2021-09-07 09:57:49 -07:00
|
|
|
await server.start(); // do this first since it also inits the database
|
|
|
|
|
await proxyAuth.start();
|
2024-01-06 13:29:23 +01:00
|
|
|
await ldapServer.start();
|
2021-11-24 17:59:21 +01:00
|
|
|
|
2023-08-03 06:34:55 +05:30
|
|
|
const conf = await directoryServer.getConfig();
|
2022-08-15 19:14:02 +02:00
|
|
|
if (conf.enabled) await directoryServer.start();
|
2020-08-04 09:34:03 -07:00
|
|
|
}
|
2015-07-20 00:09:47 -07:00
|
|
|
|
2026-02-14 16:52:16 +01:00
|
|
|
const [error] = await safe(startServers());
|
|
|
|
|
if (error) exitSync({ error, code: 1, message: 'Error starting servers' });
|
2021-09-07 09:57:49 -07:00
|
|
|
|
2026-02-14 16:52:16 +01:00
|
|
|
process.on('SIGHUP', async function () {
|
2026-03-12 22:55:28 +05:30
|
|
|
log('Received SIGHUP. Re-reading configs.');
|
2026-02-14 16:52:16 +01:00
|
|
|
const conf = await directoryServer.getConfig();
|
|
|
|
|
if (conf.enabled) await directoryServer.checkCertificate();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
process.on('SIGINT', async function () {
|
2026-03-12 22:55:28 +05:30
|
|
|
log('Received SIGINT. Shutting down.');
|
2026-02-14 16:52:16 +01:00
|
|
|
|
|
|
|
|
await proxyAuth.stop();
|
|
|
|
|
await server.stop();
|
|
|
|
|
await directoryServer.stop();
|
|
|
|
|
await ldapServer.stop();
|
|
|
|
|
await oidcServer.stop();
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
2026-03-12 22:55:28 +05:30
|
|
|
log('Shutdown complete');
|
2026-02-14 16:52:16 +01:00
|
|
|
process.exit();
|
|
|
|
|
}, 2000); // need to wait for the task processes to die
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
process.on('SIGTERM', async function () {
|
2026-03-12 22:55:28 +05:30
|
|
|
log('Received SIGTERM. Shutting down.');
|
2026-02-14 16:52:16 +01:00
|
|
|
|
|
|
|
|
await proxyAuth.stop();
|
|
|
|
|
await server.stop();
|
|
|
|
|
await directoryServer.stop();
|
|
|
|
|
await ldapServer.stop();
|
|
|
|
|
await oidcServer.stop();
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
2026-03-12 22:55:28 +05:30
|
|
|
log('Shutdown complete');
|
2026-02-14 16:52:16 +01:00
|
|
|
process.exit();
|
|
|
|
|
}, 2000); // need to wait for the task processes to die
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
process.on('uncaughtException', (error) => exitSync({ error, code: 1, message: 'From uncaughtException handler.' }));
|