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 assert from 'node:assert' ;
import BoxError from './boxerror.js' ;
import constants from './constants.js' ;
2026-02-14 15:43:24 +01:00
import cron from './cron.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 fs from 'node:fs' ;
2026-02-14 15:43:24 +01:00
import ipaddr from './ipaddr.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 path from 'node:path' ;
import paths from './paths.js' ;
2026-04-01 09:40:28 +02:00
import safe from '@cloudron/safetydance' ;
2026-02-14 15:43:24 +01:00
import settings from './settings.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 shellModule from './shell.js' ;
2026-02-14 15:43:24 +01:00
import noopProvider from './network/noop.js' ;
import fixedProvider from './network/fixed.js' ;
import networkInterfaceProvider from './network/network-interface.js' ;
import genericProvider from './network/generic.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
const shell = shellModule ( 'network' ) ;
2020-08-31 18:22:33 -07:00
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
const SET _BLOCKLIST _CMD = path . join ( import . meta . dirname , 'scripts/setblocklist.sh' ) ;
2020-08-31 18:22:33 -07:00
2025-11-28 09:56:13 +01:00
let gDefaultIface = null ; // cache
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
const NETWORK _PROVIDERS = {
'noop' : noopProvider ,
'fixed' : fixedProvider ,
'network-interface' : networkInterfaceProvider
} ;
2023-08-03 13:38:42 +05:30
function api ( provider ) {
assert . strictEqual ( typeof provider , 'string' ) ;
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
return NETWORK _PROVIDERS [ provider ] || genericProvider ;
2023-08-03 13:38:42 +05:30
}
function hasIPv6 ( ) {
const IPV6 _PROC _FILE = '/proc/net/if_inet6' ;
// on contabo, /proc/net/if_inet6 is an empty file. so just exists is not enough
return fs . existsSync ( IPV6 _PROC _FILE ) && fs . readFileSync ( IPV6 _PROC _FILE , 'utf8' ) . trim ( ) . length !== 0 ;
}
async function testIPv4Config ( config ) {
assert . strictEqual ( typeof config , 'object' ) ;
return await api ( config . provider ) . testIPv4Config ( config ) ;
}
async function testIPv6Config ( config ) {
assert . strictEqual ( typeof config , 'object' ) ;
return await api ( config . provider ) . testIPv6Config ( config ) ;
}
2021-08-18 15:31:07 -07:00
async function getBlocklist ( ) {
2023-08-02 19:17:22 +05:30
const value = await settings . getBlob ( settings . FIREWALL _BLOCKLIST _KEY ) ;
return value ? value . toString ( 'utf8' ) : '' ;
2020-08-31 18:22:33 -07:00
}
2026-03-10 21:34:09 +05:30
async function applyBlocklist ( ) {
const blocklist = await getBlocklist ( ) ;
if ( ! safe . fs . writeFileSync ( paths . FIREWALL _BLOCKLIST _FILE , blocklist + '\n' , 'utf8' ) ) throw new BoxError ( BoxError . FS _ERROR , safe . error . message ) ;
const [ error ] = await safe ( shell . sudo ( [ SET _BLOCKLIST _CMD ] , { } ) ) ;
if ( error ) throw new BoxError ( BoxError . IPTABLES _ERROR , ` Error setting blocklist: ${ error . message } ` ) ;
}
2021-08-18 15:31:07 -07:00
async function setBlocklist ( blocklist , auditSource ) {
2020-09-14 10:29:48 -07:00
assert . strictEqual ( typeof blocklist , 'string' ) ;
assert . strictEqual ( typeof auditSource , 'object' ) ;
2020-08-31 18:22:33 -07:00
2023-12-07 21:52:51 +01:00
let count = 0 ;
2020-09-14 10:29:48 -07:00
for ( const line of blocklist . split ( '\n' ) ) {
if ( ! line || line . startsWith ( '#' ) ) continue ;
const rangeOrIP = line . trim ( ) ;
2025-09-08 18:59:47 +02:00
if ( ! ipaddr . isValid ( rangeOrIP ) && ! ipaddr . isValidCIDR ( rangeOrIP ) ) throw new BoxError ( BoxError . BAD _FIELD , ` ' ${ rangeOrIP } ' is not a valid IP or range ` ) ;
2020-09-02 23:04:42 -07:00
2020-09-14 10:29:48 -07:00
if ( rangeOrIP . indexOf ( '/' ) === - 1 ) {
2025-09-08 18:59:47 +02:00
if ( ipaddr . isEqual ( rangeOrIP , auditSource . ip ) ) throw new BoxError ( BoxError . BAD _FIELD , ` IP ' ${ rangeOrIP } ' is the client IP. Cannot block yourself ` ) ;
2020-09-14 10:29:48 -07:00
} else {
2025-09-08 18:59:47 +02:00
if ( ipaddr . includes ( rangeOrIP , auditSource . ip ) ) throw new BoxError ( BoxError . BAD _FIELD , ` range ' ${ rangeOrIP } ' includes client IP. Cannot block yourself ` ) ;
2020-09-14 10:29:48 -07:00
}
2025-05-06 16:32:11 +02:00
// this won't work in cases where it's a bigger subnet
if ( rangeOrIP . startsWith ( '172.18.' ) || rangeOrIP . toLowerCase ( ) . startsWith ( 'fd00:c107:d509:' ) ) throw new BoxError ( BoxError . BAD _FIELD , ` ${ rangeOrIP } includes internal docker network. This cannot be blocked ` ) ;
2023-12-07 21:52:51 +01:00
++ count ;
2020-09-14 10:29:48 -07:00
}
2020-08-31 18:22:33 -07:00
2023-12-07 22:39:36 +01:00
if ( count >= 262144 ) throw new BoxError ( BoxError . CONFLICT , 'Blocklist is too large. Max 262144 entries are allowed' ) ; // see the cloudron-firewall.sh
2024-01-13 21:15:41 +01:00
if ( constants . DEMO ) throw new BoxError ( BoxError . BAD _STATE , 'Not allowed in demo mode' ) ;
2021-05-04 15:21:38 -07:00
2023-08-02 19:17:22 +05:30
// store in blob since the value field is TEXT and has 16kb size limit
await settings . setBlob ( settings . FIREWALL _BLOCKLIST _KEY , Buffer . from ( blocklist ) ) ;
2020-08-31 18:22:33 -07:00
2026-03-10 21:34:09 +05:30
await applyBlocklist ( ) ;
2020-08-31 18:22:33 -07:00
}
2023-08-02 22:53:29 +05:30
async function getDynamicDns ( ) {
const enabled = await settings . get ( settings . DYNAMIC _DNS _KEY ) ;
return enabled ? ! ! enabled : false ; // db holds string values only
}
async function setDynamicDns ( enabled ) {
assert . strictEqual ( typeof enabled , 'boolean' ) ;
await settings . set ( settings . DYNAMIC _DNS _KEY , enabled ? 'enabled' : '' ) ; // db holds string values only
2023-08-04 11:43:39 +05:30
await cron . handleDynamicDnsChanged ( enabled ) ;
2023-08-02 22:53:29 +05:30
}
2023-08-03 06:05:29 +05:30
async function getIPv4Config ( ) {
2023-08-03 11:34:33 +05:30
const value = await settings . getJson ( settings . IPV4 _CONFIG _KEY ) ;
return value || { provider : 'generic' } ;
2023-08-03 06:05:29 +05:30
}
async function setIPv4Config ( ipv4Config ) {
assert . strictEqual ( typeof ipv4Config , 'object' ) ;
2024-01-13 21:15:41 +01:00
if ( constants . DEMO ) throw new BoxError ( BoxError . BAD _STATE , 'Not allowed in demo mode' ) ;
2023-08-03 06:05:29 +05:30
2023-08-03 13:38:42 +05:30
const error = await testIPv4Config ( ipv4Config ) ;
2023-08-03 06:05:29 +05:30
if ( error ) throw error ;
2023-08-03 11:34:33 +05:30
await settings . setJson ( settings . IPV4 _CONFIG _KEY , ipv4Config ) ;
2023-08-03 06:05:29 +05:30
}
async function getIPv6Config ( ) {
2023-08-03 11:34:33 +05:30
const value = await settings . getJson ( settings . IPV6 _CONFIG _KEY ) ;
return value || { provider : 'noop' } ;
2023-08-03 06:05:29 +05:30
}
async function setIPv6Config ( ipv6Config ) {
assert . strictEqual ( typeof ipv6Config , 'object' ) ;
2024-01-13 21:15:41 +01:00
if ( constants . DEMO ) throw new BoxError ( BoxError . BAD _STATE , 'Not allowed in demo mode' ) ;
2023-08-03 06:05:29 +05:30
2023-08-03 13:38:42 +05:30
const error = await testIPv6Config ( ipv6Config ) ;
2023-08-03 06:05:29 +05:30
if ( error ) throw error ;
2023-08-03 11:34:33 +05:30
await settings . setJson ( settings . IPV6 _CONFIG _KEY , ipv6Config ) ;
2023-08-03 06:05:29 +05:30
}
2023-08-03 13:38:42 +05:30
async function getIPv4 ( ) {
const config = await getIPv4Config ( ) ;
return await api ( config . provider ) . getIPv4 ( config ) ;
}
async function getIPv6 ( ) {
const config = await getIPv6Config ( ) ;
2025-05-07 09:37:18 +02:00
return await api ( config . provider ) . getIPv6 ( config ) ;
2023-08-03 13:38:42 +05:30
}
2024-04-26 20:36:23 +02:00
async function detectIP ( ) {
const [ error4 , ipv4 ] = await safe ( genericProvider . getIPv4 ( { } ) ) ;
const [ error6 , ipv6 ] = await safe ( genericProvider . getIPv6 ( { } ) ) ;
return {
ipv4 : error4 ? null : ipv4 ,
ipv6 : error6 ? null : ipv6
} ;
}
2025-11-28 09:56:13 +01:00
async function getDefaultInterface ( ) {
if ( gDefaultIface ) return gDefaultIface ;
const contents4 = await fs . promises . readFile ( '/proc/net/route' , { encoding : 'utf8' } ) ;
const lines4 = contents4 . trim ( ) . split ( '\n' ) . slice ( 1 ) ; // skip header
for ( const line of lines4 ) {
const cols = line . trim ( ) . split ( /\s+/ ) ; // Iface, dest, gw, flags, refcount, use, metric, mask, mtu, window, irtt
if ( cols [ 1 ] === '00000000' ) { // && cols[7] === '00000000'
gDefaultIface = cols [ 0 ] ;
return gDefaultIface ;
}
}
const contents6 = await fs . promises . readFile ( '/proc/net/ipv6_route' , { encoding : 'utf8' } ) ;
const lines6 = contents6 . trim ( ) . split ( '\n' ) ; // no header!
for ( const line of lines6 ) {
const cols = line . trim ( ) . split ( /\s+/ ) ; // dest, dest_prefix_len, src, src_prefix_len, next_hop, metric, refcount, use, flags, iface
if ( cols [ 0 ] === '00000000000000000000000000000000' && cols [ 1 ] === '00' ) {
gDefaultIface = cols . at ( - 1 ) ;
return gDefaultIface ;
}
}
throw new BoxError ( BoxError . EXTERNAL _ERROR , 'Could not detect default interface' ) ;
}
2026-02-14 15:43:24 +01:00
export default {
testIPv4Config ,
testIPv6Config ,
2026-03-10 21:34:09 +05:30
applyBlocklist ,
2026-02-14 15:43:24 +01:00
getBlocklist ,
setBlocklist ,
getDynamicDns ,
setDynamicDns ,
getIPv4Config ,
setIPv4Config ,
getIPv6Config ,
setIPv6Config ,
getIPv4 ,
hasIPv6 ,
getIPv6 ,
detectIP ,
getDefaultInterface
} ;