2019-04-04 20:46:01 -07:00
'use strict' ;
exports = module . exports = {
2021-01-21 12:53:38 -08:00
start ,
2021-10-19 15:51:22 -07:00
status ,
2021-01-21 12:53:38 -08:00
DEFAULT _MEMORY _LIMIT : 256 * 1024 * 1024
2019-04-04 20:46:01 -07:00
} ;
2021-05-11 17:50:48 -07:00
const apps = require ( './apps.js' ) ,
2020-07-24 14:44:41 +02:00
assert = require ( 'assert' ) ,
2021-11-16 21:50:09 -08:00
blobs = require ( './blobs.js' ) ,
2021-06-24 16:19:30 -07:00
BoxError = require ( './boxerror.js' ) ,
2020-07-24 14:44:41 +02:00
debug = require ( 'debug' ) ( 'box:sftp' ) ,
2021-10-19 15:51:22 -07:00
docker = require ( './docker.js' ) ,
2020-10-19 17:26:20 -07:00
hat = require ( './hat.js' ) ,
2019-04-04 20:46:01 -07:00
infra = require ( './infra_version.js' ) ,
2020-11-26 11:45:43 -08:00
paths = require ( './paths.js' ) ,
2020-08-09 12:10:20 -07:00
safe = require ( 'safetydance' ) ,
2021-09-26 22:48:14 -07:00
settings = require ( './settings.js' ) ,
2021-10-19 15:51:22 -07:00
services = require ( './services.js' ) ,
2020-08-20 11:04:31 +02:00
shell = require ( './shell.js' ) ,
2021-01-21 12:53:38 -08:00
system = require ( './system.js' ) ,
2021-06-24 16:19:30 -07:00
volumes = require ( './volumes.js' ) ;
2019-04-04 20:46:01 -07:00
2021-11-16 21:50:09 -08:00
async function ensureKeys ( ) {
2022-03-30 11:29:14 -07:00
const sftpPrivateKey = await blobs . get ( blobs . SFTP _PRIVATE _KEY ) ;
const sftpPublicKey = await blobs . get ( blobs . SFTP _PUBLIC _KEY ) ;
2021-11-16 21:50:09 -08:00
if ( ! sftpPrivateKey || ! sftpPublicKey ) {
debug ( 'ensureSecrets: generating new sftp keys' ) ;
if ( ! safe . child _process . execSync ( ` ssh-keygen -m PEM -t rsa -f " ${ paths . SFTP _KEYS _DIR } /ssh_host_rsa_key" -q -N "" ` ) ) throw new BoxError ( BoxError . OPENSSL _ERROR , ` Could not generate sftp ssh keys: ${ safe . error . message } ` ) ;
2022-03-30 11:29:14 -07:00
const newSftpPublicKey = safe . fs . readFileSync ( paths . SFTP _PUBLIC _KEY _FILE ) ;
await blobs . set ( blobs . SFTP _PUBLIC _KEY , newSftpPublicKey ) ;
const newSftpPrivateKey = safe . fs . readFileSync ( paths . SFTP _PRIVATE _KEY _FILE ) ;
await blobs . set ( blobs . SFTP _PRIVATE _KEY , newSftpPrivateKey ) ;
} else {
if ( ! safe . fs . writeFileSync ( paths . SFTP _PUBLIC _KEY _FILE , sftpPublicKey ) ) throw new BoxError ( BoxError . FS _ERROR , ` Could not save sftp public key: ${ safe . error . message } ` ) ;
if ( ! safe . fs . writeFileSync ( paths . SFTP _PRIVATE _KEY _FILE , sftpPrivateKey , { mode : 0o600 } ) ) throw new BoxError ( BoxError . FS _ERROR , ` Could not save sftp private 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
2021-09-26 22:48:14 -07:00
debug ( 'start: re-creating container' ) ;
2020-07-24 14:44:41 +02:00
2021-09-26 22:48:14 -07:00
const servicesConfig = await settings . getServicesConfig ( ) ;
const serviceConfig = servicesConfig [ 'sftp' ] || { } ;
2019-04-04 20:46:01 -07:00
const tag = infra . images . sftp . tag ;
2021-01-21 12:53:38 -08:00
const memoryLimit = serviceConfig . memoryLimit || exports . DEFAULT _MEMORY _LIMIT ;
2022-11-04 15:09:37 +01:00
const memory = await system . getMemoryAllocation ( memoryLimit ) ;
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 ) {
2022-06-01 22:44:52 -07: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
2021-08-25 19:41:46 -07:00
if ( ! safe . fs . existsSync ( hostDir ) ) { // this can fail if external mount does not have permissions for yellowtent user
// do not create host path when cloudron is restoring. this will then create dir with root perms making restore logic fail
debug ( ` 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 ) {
2021-12-23 22:35:20 -08:00
if ( volume . hostPath . startsWith ( '/mnt/volumes/' ) ) continue ; // skip managed volume
2021-08-25 19:41:46 -07:00
if ( ! safe . fs . existsSync ( volume . hostPath ) ) {
debug ( ` 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\'' : '' ;
2021-09-25 17:07:07 -07:00
const mounts = dataDirs . map ( v => ` -v " ${ v . hostDir } : ${ v . mountDir } " ` ) . join ( ' ' ) ;
2021-10-01 12:09:13 -07:00
const runCmd = ` docker run --restart=always -d --name="sftp" \
2021-08-25 19:41:46 -07:00
-- hostname sftp \
-- net cloudron \
-- net - alias sftp \
-- log - driver syslog \
-- log - opt syslog - address = udp : //127.0.0.1:2514 \
-- log - opt syslog - format = rfc5424 \
-- log - opt tag = sftp \
- m $ { memory } \
-- memory - swap $ { memoryLimit } \
-- dns 172.18 . 0.1 \
-- dns - search = . \
- p 222 : 22 \
$ { mounts } \
- e CLOUDRON _SFTP _TOKEN = "${cloudronToken}" \
- v "${paths.SFTP_KEYS_DIR}:/etc/ssh:ro" \
-- label isCloudronManaged = true \
2021-10-01 12:09:13 -07:00
$ { readOnly } - v / tmp - v / run "${tag}" $ { cmd } ` ;
2021-08-25 19:41:46 -07:00
// ignore error if container not found (and fail later) so that this code works across restarts
await shell . promises . exec ( 'stopSftp' , 'docker stop sftp || true' ) ;
await shell . promises . exec ( 'removeSftp' , 'docker rm -f sftp || true' ) ;
2021-10-01 12:09:13 -07:00
await shell . promises . exec ( 'startSftp' , runCmd ) ;
2019-04-04 20:46:01 -07:00
}
2021-10-19 15:51:22 -07:00
async function status ( ) {
const [ error , container ] = await safe ( docker . inspect ( 'sftp' ) ) ;
if ( error && error . reason === BoxError . NOT _FOUND ) return { status : services . SERVICE _STATUS _STOPPED } ;
if ( error ) throw error ;
const result = await docker . memoryUsage ( 'sftp' ) ;
const status = container . State . Running
? ( container . HostConfig . ReadonlyRootfs ? services . SERVICE _STATUS _ACTIVE : services . SERVICE _STATUS _STARTING )
: services . SERVICE _STATUS _STOPPED ;
2022-11-24 00:40:40 +01:00
const stats = result . memory _stats || { usage : 0 , limit : 1 } ;
2021-10-19 15:51:22 -07:00
return {
status ,
2022-11-24 00:40:40 +01:00
memoryUsed : stats . usage ,
memoryPercent : parseInt ( 100 * stats . usage / stats . limit )
2021-10-19 15:51:22 -07:00
} ;
}