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 apps from './apps.js' ;
import assert from 'node:assert' ;
import blobs from './blobs.js' ;
import BoxError from './boxerror.js' ;
import constants from './constants.js' ;
2026-03-12 22:55:28 +05:30
import logger from './logger.js' ;
2026-02-14 15:43:24 +01:00
import docker from './docker.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 hat from './hat.js' ;
import infra from './infra_version.js' ;
import mounts from './mounts.js' ;
import path from 'node:path' ;
import paths from './paths.js' ;
2026-04-01 09:40:28 +02:00
import safe from '@cloudron/safetydance' ;
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 services from './services.js' ;
import shellModule from './shell.js' ;
2026-02-14 15:43:24 +01:00
import volumes from './volumes.js' ;
2019-04-04 20:46:01 -07:00
2026-03-12 23:23:23 +05:30
const { log } = logger ( 'sftp' ) ;
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 ( 'sftp' ) ;
2021-01-21 12:53:38 -08: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 DEFAULT _MEMORY _LIMIT = 256 * 1024 * 1024 ;
2019-04-04 20:46:01 -07:00
2021-11-16 21:50:09 -08:00
async function ensureKeys ( ) {
2023-03-09 10:55:14 +01:00
for ( const keyType of [ 'rsa' , 'ed25519' ] ) {
const privateKey = await blobs . get ( ` sftp_ ${ keyType } _private_key ` ) ;
const publicKey = await blobs . get ( ` sftp_ ${ keyType } _public_key ` ) ;
const publicKeyFile = path . join ( paths . SFTP _KEYS _DIR , ` ssh_host_ ${ keyType } _key.pub ` ) ;
const privateKeyFile = path . join ( paths . SFTP _KEYS _DIR , ` ssh_host_ ${ keyType } _key ` ) ;
if ( ! privateKey || ! publicKey ) {
2026-03-12 22:55:28 +05:30
log ( ` ensureSecrets: generating new sftp keys of type ${ keyType } ` ) ;
2023-04-27 20:03:41 +02:00
safe . fs . unlinkSync ( publicKeyFile ) ;
safe . fs . unlinkSync ( privateKeyFile ) ;
2024-10-16 10:25:07 +02:00
const [ error ] = await safe ( shell . spawn ( 'ssh-keygen' , [ '-m' , 'PEM' , '-t' , keyType , '-f' , ` ${ paths . SFTP _KEYS _DIR } /ssh_host_ ${ keyType } _key ` , '-q' , '-N' , '' ] , { } ) ) ;
2024-02-20 22:57:36 +01:00
if ( error ) throw new BoxError ( BoxError . OPENSSL _ERROR , ` Could not generate sftp ${ keyType } keys: ${ error . message } ` ) ;
2023-03-09 10:55:14 +01:00
const newPublicKey = safe . fs . readFileSync ( publicKeyFile ) ;
await blobs . set ( ` sftp_ ${ keyType } _public_key ` , newPublicKey ) ;
const newPrivateKey = safe . fs . readFileSync ( privateKeyFile ) ;
await blobs . set ( ` sftp_ ${ keyType } _private_key ` , newPrivateKey ) ;
} else {
if ( ! safe . fs . writeFileSync ( publicKeyFile , publicKey ) ) throw new BoxError ( BoxError . FS _ERROR , ` Could not save sftp public ${ keyType } key: ${ safe . error . message } ` ) ;
if ( ! safe . fs . writeFileSync ( privateKeyFile , privateKey , { mode : 0o600 } ) ) throw new BoxError ( BoxError . FS _ERROR , ` Could not save sftp private ${ keyType } key: ${ safe . error . message } ` ) ;
}
2021-11-16 21:50:09 -08:00
}
}
2021-09-26 22:48:14 -07:00
async function start ( existingInfra ) {
assert . strictEqual ( typeof existingInfra , 'object' ) ;
2020-07-24 14:44:41 +02:00
2026-03-12 22:55:28 +05:30
log ( 'start: re-creating container' ) ;
2020-07-24 14:44:41 +02:00
2023-08-03 12:52:47 +05:30
const serviceConfig = await services . getServiceConfig ( 'sftp' ) ;
2023-08-08 10:42:16 +05:30
const image = infra . images . sftp ;
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 memoryLimit = serviceConfig . memoryLimit || DEFAULT _MEMORY _LIMIT ;
2020-10-19 17:26:20 -07:00
const cloudronToken = hat ( 8 * 128 ) ;
2019-04-04 20:46:01 -07:00
2021-11-16 21:50:09 -08:00
await ensureKeys ( ) ;
2021-09-26 18:36:33 -07:00
const resolvedAppDataDir = safe . fs . realpathSync ( paths . APPS _DATA _DIR ) ;
if ( ! resolvedAppDataDir ) throw new BoxError ( BoxError . FS _ERROR , ` Could not resolve apps data dir: ${ safe . error . message } ` ) ;
2021-06-24 16:19:30 -07:00
2021-09-25 17:07:07 -07:00
const dataDirs = [ { hostDir : resolvedAppDataDir , mountDir : '/mnt/appsdata' } ] ;
2021-08-25 19:41:46 -07:00
2021-09-25 17:07:07 -07:00
// custom app data directories
const allApps = await apps . list ( ) ;
for ( const app of allApps ) {
2023-11-13 21:58:44 +01:00
if ( ! app . manifest . addons ? . localstorage || ! app . storageVolumeId ) continue ;
2021-08-25 19:41:46 -07:00
2022-06-01 22:44:52 -07:00
const hostDir = await apps . getStorageDir ( app ) , mountDir = ` /mnt/app- ${ app . id } ` ; // see also sftp:userSearchSftp
2024-06-06 15:22:33 +02:00
if ( hostDir === null || ! safe . fs . existsSync ( hostDir ) ) { // this can fail if external mount does not have permissions for yellowtent user
2021-08-25 19:41:46 -07:00
// do not create host path when cloudron is restoring. this will then create dir with root perms making restore logic fail
2026-03-12 22:55:28 +05:30
log ( ` Ignoring app data dir ${ hostDir } for ${ app . id } since it does not exist ` ) ;
2021-09-25 17:07:07 -07:00
continue ;
2021-08-25 19:41:46 -07:00
}
dataDirs . push ( { hostDir , mountDir } ) ;
2021-09-25 17:07:07 -07:00
}
2021-08-25 19:41:46 -07:00
2021-09-25 17:07:07 -07:00
// volume directories
2021-12-23 22:35:20 -08:00
dataDirs . push ( { hostDir : '/mnt/volumes' , mountDir : '/mnt/volumes' } ) ; // managed volumes
2021-09-25 17:07:07 -07:00
const allVolumes = await volumes . list ( ) ;
for ( const volume of allVolumes ) {
2024-08-08 14:12:06 +02:00
if ( mounts . isManagedProvider ( volume . mountType ) ) continue ; // skip managed volume. these are acessed via /mnt/volumes mount above
2021-08-25 19:41:46 -07:00
if ( ! safe . fs . existsSync ( volume . hostPath ) ) {
2026-03-12 22:55:28 +05:30
log ( ` Ignoring volume host path ${ volume . hostPath } since it does not exist ` ) ;
2021-09-25 17:07:07 -07:00
continue ;
2021-08-25 19:41:46 -07:00
}
2021-12-24 10:43:39 -08:00
dataDirs . push ( { hostDir : volume . hostPath , mountDir : ` /mnt/volume- ${ volume . id } ` } ) ;
2021-09-25 17:07:07 -07:00
}
2021-08-25 19:41:46 -07:00
2021-09-25 17:19:58 -07:00
// mail data dir
2021-09-26 18:36:33 -07:00
const resolvedMailDataDir = safe . fs . realpathSync ( paths . MAIL _DATA _DIR ) ;
if ( ! resolvedMailDataDir ) throw new BoxError ( BoxError . FS _ERROR , ` Could not resolve mail data dir: ${ safe . error . message } ` ) ;
dataDirs . push ( { hostDir : resolvedMailDataDir , mountDir : '/mnt/maildata' } ) ;
2021-09-25 17:19:58 -07:00
2021-10-01 12:09:13 -07:00
const readOnly = ! serviceConfig . recoveryMode ? '--read-only' : '' ;
const cmd = serviceConfig . recoveryMode ? '/bin/bash -c \'echo "Debug mode. Sleeping" && sleep infinity\'' : '' ;
2024-08-08 14:12:06 +02:00
const volumeMounts = dataDirs . map ( v => ` -v " ${ v . hostDir } : ${ v . mountDir } " ` ) . join ( ' ' ) ;
2025-06-14 17:51:35 +02:00
const runCmd = ` docker run --restart=unless-stopped -d --name=sftp \
2021-08-25 19:41:46 -07:00
-- hostname sftp \
-- net cloudron \
-- net - alias sftp \
2025-12-04 09:09:19 +01:00
-- ip $ { constants . SFTP _SERVICE _IPv4 } \
2021-08-25 19:41:46 -07:00
-- log - driver syslog \
2024-03-21 17:30:50 +01:00
-- log - opt syslog - address = unix : //${paths.SYSLOG_SOCKET_FILE} \
2021-08-25 19:41:46 -07:00
-- log - opt syslog - format = rfc5424 \
-- log - opt tag = sftp \
2024-04-09 18:59:40 +02:00
- m $ { memoryLimit } \
-- memory - swap - 1 \
2021-08-25 19:41:46 -07:00
- p 222 : 22 \
2024-08-08 14:12:06 +02:00
$ { volumeMounts } \
2024-02-22 10:34:56 +01:00
- e CLOUDRON _SFTP _TOKEN = $ { cloudronToken } \
- v $ { paths . SFTP _KEYS _DIR } : / e t c / s s h : r o \
2021-08-25 19:41:46 -07:00
-- label isCloudronManaged = true \
2024-02-22 10:34:56 +01:00
$ { readOnly } - v / tmp - v / run $ { image } $ { cmd } ` ;
2021-08-25 19:41:46 -07:00
2026-03-12 22:55:28 +05:30
log ( 'startSftp: stopping and deleting previous sftp container' ) ;
2024-02-28 18:47:53 +01:00
await docker . stopContainer ( 'sftp' ) ;
await docker . deleteContainer ( 'sftp' ) ;
2026-03-12 22:55:28 +05:30
log ( 'startSftp: starting sftp container' ) ;
2024-12-19 16:59:28 +01:00
await shell . bash ( runCmd , { encoding : 'utf8' } ) ;
2024-12-14 20:47:35 +01:00
if ( existingInfra . version !== 'none' && existingInfra . images . sftp !== image ) await docker . deleteImage ( existingInfra . images . sftp ) ;
2019-04-04 20:46:01 -07:00
}
2026-02-14 15:43:24 +01:00
export default {
start ,
DEFAULT _MEMORY _LIMIT ,
} ;