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' ) ,
2023-03-09 10:55:14 +01:00
path = require ( 'path' ) ,
2020-11-26 11:45:43 -08:00
paths = require ( './paths.js' ) ,
2020-08-09 12:10:20 -07:00
safe = require ( 'safetydance' ) ,
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-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 ( ) {
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 ) {
debug ( ` 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-02-28 18:47:53 +01:00
const [ error ] = await safe ( shell . exec ( 'ensureKeys' , ` ssh-keygen -m PEM -t ${ keyType } -f ${ paths . SFTP _KEYS _DIR } /ssh_host_ ${ keyType } _key -q -N "" ` , { shell : '/bin/bash' } ) ) ;
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
2021-09-26 22:48:14 -07:00
debug ( '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 ;
2021-01-21 12:53:38 -08:00
const memoryLimit = serviceConfig . memoryLimit || exports . 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
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 ( ' ' ) ;
2024-02-22 10:34:56 +01: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 \
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 \
$ { mounts } \
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
2024-02-28 18:47:53 +01:00
debug ( 'startSftp: stopping and deleting previous sftp container' ) ;
await docker . stopContainer ( 'sftp' ) ;
await docker . deleteContainer ( 'sftp' ) ;
debug ( 'startSftp: starting sftp container' ) ;
2024-02-22 10:34:56 +01:00
await shell . exec ( 'startSftp' , runCmd , { shell : '/bin/bash' } ) ;
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
} ;
}