2015-07-20 00:09:47 -07:00
'use strict' ;
exports = module . exports = {
2023-08-03 12:52:47 +05:30
getServiceConfig ,
2021-09-20 09:15:28 -07:00
listServices ,
2021-01-21 12:40:41 -08:00
getServiceStatus ,
2020-09-23 15:13:23 -07:00
getServiceLogs ,
2021-01-21 12:53:38 -08:00
configureService ,
2020-09-23 15:13:23 -07:00
restartService ,
2020-09-23 15:14:06 -07:00
rebuildService ,
2018-11-15 19:59:08 +01:00
2020-05-18 14:10:00 -07:00
startAppServices ,
stopAppServices ,
2020-09-23 15:13:23 -07:00
startServices ,
2018-10-16 14:07:41 -07:00
2020-09-23 15:13:23 -07:00
setupAddons ,
teardownAddons ,
backupAddons ,
restoreAddons ,
clearAddons ,
2024-03-30 18:31:57 +01:00
checkAddonsSupport ,
2015-07-20 00:09:47 -07:00
2020-09-23 15:13:23 -07:00
getEnvironment ,
getContainerNamesSync ,
2015-07-20 00:09:47 -07:00
2020-09-23 15:13:23 -07:00
getContainerDetails ,
2020-02-07 14:11:52 -08:00
2024-03-04 13:09:38 +01:00
// exported only for apptask.js to update immich pgvectors extension - can be removed later
_postgreSqlNames : postgreSqlNames ,
2018-12-02 19:40:27 -08:00
SERVICE _STATUS _STARTING : 'starting' , // container up, waiting for healthcheck
SERVICE _STATUS _ACTIVE : 'active' ,
2024-04-01 12:04:48 +02:00
SERVICE _STATUS _STOPPED : 'stopped' ,
SERVICE _STATUS _DISABLED : 'disabled' , // feature not supported
2015-07-20 00:09:47 -07:00
} ;
2021-08-19 21:39:27 -07:00
const addonConfigs = require ( './addonconfigs.js' ) ,
2018-12-20 14:33:29 -08:00
apps = require ( './apps.js' ) ,
2015-07-20 00:09:47 -07:00
assert = require ( 'assert' ) ,
2021-05-02 23:28:41 -07:00
blobs = require ( './blobs.js' ) ,
2019-09-23 12:13:21 -07:00
BoxError = require ( './boxerror.js' ) ,
2019-07-25 15:33:34 -07:00
constants = require ( './constants.js' ) ,
2018-05-16 19:49:29 -07:00
crypto = require ( 'crypto' ) ,
2023-08-11 19:41:05 +05:30
dashboard = require ( './dashboard.js' ) ,
2021-02-08 23:20:45 -08:00
debug = require ( 'debug' ) ( 'box:services' ) ,
2023-03-26 20:52:58 +02:00
dig = require ( './dig.js' ) ,
2016-04-18 10:37:33 -07:00
docker = require ( './docker.js' ) ,
2021-09-24 10:22:45 -07:00
eventlog = require ( './eventlog.js' ) ,
2015-07-20 00:09:47 -07:00
fs = require ( 'fs' ) ,
2018-06-11 12:38:15 -07:00
hat = require ( './hat.js' ) ,
2021-12-15 17:35:56 -08:00
http = require ( 'http' ) ,
2016-05-24 13:06:59 -07:00
infra = require ( './infra_version.js' ) ,
2023-03-27 10:38:09 +02:00
logs = require ( './logs.js' ) ,
2018-03-07 20:39:58 -08:00
mail = require ( './mail.js' ) ,
2023-08-04 20:54:16 +05:30
mailServer = require ( './mailserver.js' ) ,
2023-04-14 19:20:14 +02:00
oidc = require ( './oidc.js' ) ,
2018-10-16 14:07:41 -07:00
os = require ( 'os' ) ,
2015-07-20 00:09:47 -07:00
path = require ( 'path' ) ,
paths = require ( './paths.js' ) ,
2021-12-20 10:52:42 -08:00
{ pipeline } = require ( 'stream' ) ,
2021-08-25 19:41:46 -07:00
promiseRetry = require ( './promise-retry.js' ) ,
2015-07-20 00:09:47 -07:00
safe = require ( 'safetydance' ) ,
2018-11-20 16:53:27 +01:00
settings = require ( './settings.js' ) ,
2019-08-20 12:06:49 +02:00
sftp = require ( './sftp.js' ) ,
2015-07-20 00:09:47 -07:00
shell = require ( './shell.js' ) ,
2024-04-09 18:59:40 +02:00
superagent = require ( 'superagent' ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
const NOOP = async function ( /*app, options*/ ) { } ;
2018-11-25 14:43:29 -08:00
const RMADDONDIR _CMD = path . join ( _ _dirname , 'scripts/rmaddondir.sh' ) ;
2021-03-23 11:01:14 -07:00
const RESTART _SERVICE _CMD = path . join ( _ _dirname , 'scripts/restartservice.sh' ) ;
2022-06-02 10:43:01 -07:00
const CLEARVOLUME _CMD = path . join ( _ _dirname , 'scripts/clearvolume.sh' ) ;
const MKDIRVOLUME _CMD = path . join ( _ _dirname , 'scripts/mkdirvolume.sh' ) ;
2015-07-20 00:09:47 -07:00
// setup can be called multiple times for the same app (configure crash restart) and existing data must not be lost
// teardown is destructive. app data stored with the addon is lost
2024-03-30 18:31:57 +01:00
// addons have 1-1 mapping with the manifest
2021-08-19 21:39:27 -07:00
const ADDONS = {
2020-03-27 21:37:06 +01:00
turn : {
2020-03-30 08:24:45 +02:00
setup : setupTurn ,
teardown : teardownTurn ,
2020-03-27 21:37:06 +01:00
backup : NOOP ,
restore : NOOP ,
2023-04-24 15:29:57 +02:00
getDynamicEnvironment : NOOP ,
clear : NOOP ,
2020-03-27 21:37:06 +01:00
} ,
2016-05-12 08:54:59 -07:00
email : {
setup : setupEmail ,
teardown : teardownEmail ,
backup : NOOP ,
2018-09-15 17:05:04 -07:00
restore : setupEmail ,
2023-04-24 15:29:57 +02:00
getDynamicEnvironment : NOOP ,
2018-11-15 19:59:08 +01:00
clear : NOOP ,
2016-05-12 08:54:59 -07:00
} ,
2015-07-20 00:09:47 -07:00
ldap : {
setup : setupLdap ,
teardown : teardownLdap ,
backup : NOOP ,
2018-09-15 17:05:04 -07:00
restore : setupLdap ,
2023-04-24 15:29:57 +02:00
getDynamicEnvironment : NOOP ,
2018-11-15 19:59:08 +01:00
clear : NOOP ,
2015-07-20 00:09:47 -07:00
} ,
2015-10-18 09:52:37 -07:00
localstorage : {
2019-01-18 14:48:31 -08:00
setup : setupLocalStorage ,
2018-09-13 13:55:49 -07:00
teardown : teardownLocalStorage ,
2015-10-18 09:52:37 -07:00
backup : NOOP , // no backup because it's already inside app data
2018-09-15 17:05:04 -07:00
restore : NOOP ,
2023-04-24 15:29:57 +02:00
getDynamicEnvironment : NOOP ,
2018-11-15 19:59:08 +01:00
clear : clearLocalStorage ,
2015-10-18 09:52:37 -07:00
} ,
mongodb : {
setup : setupMongoDb ,
teardown : teardownMongoDb ,
backup : backupMongoDb ,
2018-09-15 17:05:04 -07:00
restore : restoreMongoDb ,
2023-04-24 15:29:57 +02:00
getDynamicEnvironment : NOOP ,
2018-11-15 19:59:08 +01:00
clear : clearMongodb ,
2015-07-20 00:09:47 -07:00
} ,
mysql : {
setup : setupMySql ,
teardown : teardownMySql ,
backup : backupMySql ,
restore : restoreMySql ,
2023-04-24 15:29:57 +02:00
getDynamicEnvironment : NOOP ,
2018-11-15 19:59:08 +01:00
clear : clearMySql ,
2015-07-20 00:09:47 -07:00
} ,
postgresql : {
setup : setupPostgreSql ,
teardown : teardownPostgreSql ,
backup : backupPostgreSql ,
2018-09-15 17:05:04 -07:00
restore : restorePostgreSql ,
2023-04-24 15:29:57 +02:00
getDynamicEnvironment : NOOP ,
2018-11-15 19:59:08 +01:00
clear : clearPostgreSql ,
2015-07-20 00:09:47 -07:00
} ,
2020-11-10 09:59:28 -08:00
proxyAuth : {
2020-11-26 15:04:25 -08:00
setup : setupProxyAuth ,
teardown : teardownProxyAuth ,
2020-11-10 09:59:28 -08:00
backup : NOOP ,
restore : NOOP ,
2023-04-24 15:29:57 +02:00
getDynamicEnvironment : NOOP ,
2020-11-10 09:59:28 -08:00
clear : NOOP
} ,
2016-05-13 14:13:25 -07:00
recvmail : {
setup : setupRecvMail ,
teardown : teardownRecvMail ,
backup : NOOP ,
2018-09-15 17:05:04 -07:00
restore : setupRecvMail ,
2023-04-24 15:29:57 +02:00
getDynamicEnvironment : NOOP ,
2018-11-15 19:59:08 +01:00
clear : NOOP ,
2016-05-13 14:13:25 -07:00
} ,
2015-07-20 00:09:47 -07:00
redis : {
setup : setupRedis ,
teardown : teardownRedis ,
2015-10-12 13:29:27 -07:00
backup : backupRedis ,
2018-09-18 12:28:03 -07:00
restore : restoreRedis ,
2023-04-24 15:29:57 +02:00
getDynamicEnvironment : NOOP ,
2018-11-15 19:59:08 +01:00
clear : clearRedis ,
2015-07-20 00:09:47 -07:00
} ,
2015-10-18 09:52:37 -07:00
sendmail : {
setup : setupSendMail ,
teardown : teardownSendMail ,
backup : NOOP ,
2018-09-15 17:05:04 -07:00
restore : setupSendMail ,
2023-04-24 15:29:57 +02:00
getDynamicEnvironment : NOOP ,
2018-11-15 19:59:08 +01:00
clear : NOOP ,
2015-10-18 09:52:37 -07:00
} ,
2015-10-18 08:40:24 -07:00
scheduler : {
setup : NOOP ,
teardown : NOOP ,
backup : NOOP ,
2018-09-15 17:05:04 -07:00
restore : NOOP ,
2023-04-24 15:29:57 +02:00
getDynamicEnvironment : NOOP ,
2018-11-15 19:59:08 +01:00
clear : NOOP ,
2018-08-09 11:54:32 +02:00
} ,
2018-08-10 12:31:46 -07:00
docker : {
2024-03-01 10:31:41 +01:00
setup : setupDocker ,
teardown : teardownDocker ,
2018-08-09 11:54:32 +02:00
backup : NOOP ,
2018-09-15 17:05:04 -07:00
restore : NOOP ,
2024-03-30 18:24:07 +01:00
getDynamicEnvironment : NOOP ,
2018-11-15 19:59:08 +01:00
clear : NOOP ,
2020-04-18 10:07:43 -07:00
} ,
2021-02-17 22:53:50 -08:00
tls : {
2022-11-28 22:32:34 +01:00
setup : setupTls ,
teardown : teardownTls ,
2021-02-17 22:53:50 -08:00
backup : NOOP ,
restore : NOOP ,
2023-04-24 15:29:57 +02:00
getDynamicEnvironment : NOOP ,
2021-02-17 22:53:50 -08:00
clear : NOOP ,
} ,
2020-04-18 10:07:43 -07:00
oauth : { // kept for backward compatibility. keep teardown for uninstall to work
setup : NOOP ,
teardown : teardownOauth ,
backup : NOOP ,
restore : NOOP ,
2023-04-24 15:29:57 +02:00
getDynamicEnvironment : NOOP ,
2020-04-18 10:07:43 -07:00
clear : NOOP ,
2023-04-14 19:20:14 +02:00
} ,
oidc : {
setup : setupOidc ,
teardown : teardownOidc ,
backup : NOOP ,
restore : setupOidc ,
2023-04-24 15:29:57 +02:00
getDynamicEnvironment : getDynamicEnvironmentOidc ,
2023-04-14 19:20:14 +02:00
clear : NOOP ,
} ,
2018-12-02 18:05:19 -08:00
} ;
2020-04-28 15:17:01 -07:00
// services are actual containers that are running. addons are the concepts requested by app
2020-04-27 22:59:11 -07:00
const SERVICES = {
2020-03-27 21:37:06 +01:00
turn : {
status : statusTurn ,
2021-01-21 12:53:38 -08:00
restart : docker . restartContainer . bind ( null , 'turn' ) ,
2020-03-27 21:37:06 +01:00
defaultMemoryLimit : 256 * 1024 * 1024
} ,
2018-12-02 18:05:19 -08:00
mail : {
status : containerStatus . bind ( null , 'mail' , 'CLOUDRON_MAIL_TOKEN' ) ,
2023-08-04 20:54:16 +05:30
restart : mailServer . restart ,
defaultMemoryLimit : mailServer . DEFAULT _MEMORY _LIMIT
2018-12-02 18:05:19 -08:00
} ,
mongodb : {
2024-04-01 12:04:48 +02:00
status : statusMongodb ,
restart : restartMongodb ,
2020-12-04 11:03:26 -08:00
defaultMemoryLimit : ( 1 + Math . round ( os . totalmem ( ) / ( 1024 * 1024 * 1024 ) / 4 ) ) * 256 * 1024 * 1024
2018-12-02 18:05:19 -08:00
} ,
mysql : {
status : containerStatus . bind ( null , 'mysql' , 'CLOUDRON_MYSQL_TOKEN' ) ,
2021-01-21 12:53:38 -08:00
restart : docker . restartContainer . bind ( null , 'mysql' ) ,
2018-12-02 18:05:19 -08:00
defaultMemoryLimit : ( 1 + Math . round ( os . totalmem ( ) / ( 1024 * 1024 * 1024 ) / 4 ) ) * 256 * 1024 * 1024
} ,
postgresql : {
status : containerStatus . bind ( null , 'postgresql' , 'CLOUDRON_POSTGRESQL_TOKEN' ) ,
2021-01-21 12:53:38 -08:00
restart : docker . restartContainer . bind ( null , 'postgresql' ) ,
2018-12-02 18:05:19 -08:00
defaultMemoryLimit : ( 1 + Math . round ( os . totalmem ( ) / ( 1024 * 1024 * 1024 ) / 4 ) ) * 256 * 1024 * 1024
} ,
docker : {
2018-11-21 16:06:58 +01:00
status : statusDocker ,
2018-12-02 18:05:19 -08:00
restart : restartDocker ,
defaultMemoryLimit : 0
2018-12-02 19:38:21 -08:00
} ,
unbound : {
status : statusUnbound ,
restart : restartUnbound ,
defaultMemoryLimit : 0
2019-03-18 19:02:32 -07:00
} ,
2019-04-04 20:46:01 -07:00
sftp : {
2021-10-19 15:51:22 -07:00
status : sftp . status ,
2021-01-21 12:53:38 -08:00
restart : docker . restartContainer . bind ( null , 'sftp' ) ,
defaultMemoryLimit : sftp . DEFAULT _MEMORY _LIMIT
2019-03-19 15:56:29 -07:00
} ,
graphite : {
status : statusGraphite ,
2021-03-23 11:01:14 -07:00
restart : restartGraphite ,
2021-03-23 10:48:13 -07:00
defaultMemoryLimit : 256 * 1024 * 1024
2019-04-12 09:47:05 -07:00
} ,
nginx : {
status : statusNginx ,
restart : restartNginx ,
defaultMemoryLimit : 0
2015-07-20 00:09:47 -07:00
}
} ;
2020-05-03 17:38:15 -07:00
const APP _SERVICES = {
redis : {
status : ( instance , done ) => containerStatus ( ` redis- ${ instance } ` , 'CLOUDRON_REDIS_TOKEN' , done ) ,
2020-05-18 14:10:00 -07:00
start : ( instance , done ) => docker . startContainer ( ` redis- ${ instance } ` , done ) ,
stop : ( instance , done ) => docker . stopContainer ( ` redis- ${ instance } ` , done ) ,
2021-01-21 12:53:38 -08:00
restart : ( instance , done ) => docker . restartContainer ( ` redis- ${ instance } ` , done ) ,
2023-09-04 18:22:10 +05:30
defaultMemoryLimit : 256 * 1024 * 1024
2020-05-03 17:38:15 -07:00
}
} ;
2023-08-08 10:42:16 +05:30
function requiresUpgrade ( existingObjOrImageName , currentImageName ) {
// we removed image.tag. remove this after 7.6
const existingTag = typeof existingObjOrImageName === 'object' ? existingObjOrImageName . tag : existingObjOrImageName ;
let etag = docker . parseImageName ( existingTag ) , ctag = docker . parseImageName ( currentImageName ) ;
2018-10-16 14:07:41 -07:00
return etag . version . major !== ctag . version . major ;
}
2018-11-09 12:02:38 -08:00
// paths for dumps
function dumpPath ( addon , appId ) {
switch ( addon ) {
2018-11-12 09:32:02 -08:00
case 'postgresql' : return path . join ( paths . APPS _DATA _DIR , appId , 'postgresqldump' ) ;
2018-11-09 12:02:38 -08:00
case 'mysql' : return path . join ( paths . APPS _DATA _DIR , appId , 'mysqldump' ) ;
case 'mongodb' : return path . join ( paths . APPS _DATA _DIR , appId , 'mongodbdump' ) ;
case 'redis' : return path . join ( paths . APPS _DATA _DIR , appId , 'dump.rdb' ) ;
}
}
2021-08-25 19:41:46 -07:00
async function getContainerDetails ( containerName , tokenEnvName ) {
2019-03-19 15:56:29 -07:00
assert . strictEqual ( typeof containerName , 'string' ) ;
assert . strictEqual ( typeof tokenEnvName , 'string' ) ;
2021-08-25 19:41:46 -07:00
const result = await docker . inspect ( containerName ) ;
2019-03-19 15:56:29 -07:00
2021-08-25 19:41:46 -07:00
const ip = safe . query ( result , 'NetworkSettings.Networks.cloudron.IPAddress' , null ) ;
if ( ! ip ) throw new BoxError ( BoxError . INACTIVE , ` Error getting IP of ${ containerName } service ` ) ;
2019-03-19 15:56:29 -07:00
2021-08-25 19:41:46 -07:00
// extract the cloudron token for auth
const env = safe . query ( result , 'Config.Env' , null ) ;
if ( ! env ) throw new BoxError ( BoxError . DOCKER _ERROR , ` Error inspecting environment of ${ containerName } service ` ) ;
const tmp = env . find ( function ( e ) { return e . indexOf ( tokenEnvName ) === 0 ; } ) ;
if ( ! tmp ) throw new BoxError ( BoxError . DOCKER _ERROR , ` Error getting token of ${ containerName } service ` ) ;
const token = tmp . slice ( tokenEnvName . length + 1 ) ; // +1 for the = sign
if ( ! token ) throw new BoxError ( BoxError . DOCKER _ERROR , ` Error getting token of ${ containerName } service ` ) ;
2019-03-19 15:56:29 -07:00
2021-08-25 19:41:46 -07:00
return { ip : ip , token : token , state : result . State } ;
2019-03-19 15:56:29 -07:00
}
2021-08-25 19:41:46 -07:00
async function containerStatus ( containerName , tokenEnvName ) {
2020-04-28 15:17:01 -07:00
assert . strictEqual ( typeof containerName , 'string' ) ;
assert . strictEqual ( typeof tokenEnvName , 'string' ) ;
2018-11-21 17:28:44 +01:00
2021-08-25 19:41:46 -07:00
const [ error , addonDetails ] = await safe ( getContainerDetails ( containerName , tokenEnvName ) ) ;
if ( error && ( error . reason === BoxError . NOT _FOUND || error . reason === BoxError . INACTIVE ) ) return { status : exports . SERVICE _STATUS _STOPPED } ;
if ( error ) throw error ;
2018-11-21 17:28:44 +01:00
2021-12-17 13:39:06 -08:00
const [ networkError , response ] = await safe ( superagent . get ( ` http:// ${ addonDetails . ip } :3000/healthcheck?access_token= ${ addonDetails . token } ` )
2021-08-25 19:41:46 -07:00
. timeout ( 20000 )
. ok ( ( ) => true ) ) ;
2018-11-21 17:28:44 +01:00
2021-08-25 19:41:46 -07:00
if ( networkError ) return { status : exports . SERVICE _STATUS _STARTING , error : ` Error waiting for ${ containerName } : ${ networkError . message } ` } ;
if ( response . status !== 200 || ! response . body . status ) return { status : exports . SERVICE _STATUS _STARTING , error : ` Error waiting for ${ containerName } . Status code: ${ response . statusCode } message: ${ response . body . message } ` } ;
2018-11-28 10:39:12 +01:00
2021-08-25 19:41:46 -07:00
const result = await docker . memoryUsage ( containerName ) ;
2022-11-24 00:40:40 +01:00
const stats = result . memory _stats || { usage : 0 , limit : 1 } ;
2018-11-28 10:39:12 +01:00
2021-08-25 19:41:46 -07:00
return {
status : addonDetails . state . Running ? exports . SERVICE _STATUS _ACTIVE : exports . SERVICE _STATUS _STOPPED ,
2022-11-24 00:40:40 +01:00
memoryUsed : stats . usage ,
memoryPercent : parseInt ( 100 * stats . usage / stats . limit ) ,
2021-08-25 19:41:46 -07:00
healthcheck : response . body
} ;
2018-11-21 17:28:44 +01:00
}
2021-09-20 09:15:28 -07:00
async function listServices ( ) {
2022-10-13 20:32:36 +02:00
const serviceIds = Object . keys ( SERVICES ) ;
2018-11-15 19:59:08 +01:00
2021-08-20 09:19:44 -07:00
const result = await apps . list ( ) ;
for ( let app of result ) {
2023-07-14 18:01:21 +05:30
if ( app . manifest . addons ? . redis && app . enableRedis ) serviceIds . push ( ` redis: ${ app . id } ` ) ;
2021-08-20 09:19:44 -07:00
}
2020-05-03 17:38:15 -07:00
2021-08-20 09:19:44 -07:00
return serviceIds ;
2018-11-15 19:59:08 +01:00
}
2023-08-03 12:52:47 +05:30
async function getConfig ( ) {
return await settings . getJson ( settings . SERVICES _CONFIG _KEY ) || { } ;
}
2021-08-25 19:41:46 -07:00
async function getServiceConfig ( id ) {
2020-05-03 17:38:15 -07:00
assert . strictEqual ( typeof id , 'string' ) ;
2021-01-21 12:53:38 -08:00
const [ name , instance ] = id . split ( ':' ) ;
2020-05-03 17:38:15 -07:00
if ( ! instance ) {
2023-08-03 12:52:47 +05:30
const servicesConfig = await getConfig ( ) ;
2021-08-25 19:41:46 -07:00
return servicesConfig [ name ] || { } ;
2020-05-03 17:38:15 -07:00
}
2021-08-25 19:41:46 -07:00
const app = await apps . get ( instance ) ;
if ( ! app ) throw new BoxError ( BoxError . NOT _FOUND , 'App not found' ) ;
2020-05-03 17:38:15 -07:00
2021-08-25 19:41:46 -07:00
return app . servicesConfig [ name ] || { } ;
2020-05-03 17:38:15 -07:00
}
2021-08-25 19:41:46 -07:00
async function getServiceStatus ( id ) {
2020-05-03 17:38:15 -07:00
assert . strictEqual ( typeof id , 'string' ) ;
2018-11-15 19:59:08 +01:00
2020-05-03 17:38:15 -07:00
const [ name , instance ] = id . split ( ':' ) ;
2021-01-21 12:53:38 -08:00
let containerStatusFunc , service ;
2020-05-03 17:38:15 -07:00
if ( instance ) {
2021-01-21 12:53:38 -08:00
service = APP _SERVICES [ name ] ;
2021-08-25 19:41:46 -07:00
if ( ! service ) throw new BoxError ( BoxError . NOT _FOUND , 'Service not found' ) ;
2021-01-21 12:53:38 -08:00
containerStatusFunc = service . status . bind ( null , instance ) ;
2020-05-03 17:38:15 -07:00
} else if ( SERVICES [ name ] ) {
2021-01-21 12:53:38 -08:00
service = SERVICES [ name ] ;
containerStatusFunc = service . status ;
2020-05-03 17:38:15 -07:00
} else {
2021-08-25 19:41:46 -07:00
throw new BoxError ( BoxError . NOT _FOUND , 'Service not found' ) ;
2020-05-03 17:38:15 -07:00
}
2018-11-20 16:53:27 +01:00
2024-04-10 12:17:14 +02:00
const result = {
2020-05-03 17:38:15 -07:00
name : name ,
2018-11-20 16:53:27 +01:00
status : null ,
2019-12-16 15:21:26 +01:00
memoryUsed : 0 ,
memoryPercent : 0 ,
error : null ,
2020-12-02 17:05:16 -08:00
healthcheck : null ,
2021-01-19 19:05:35 -08:00
config : { }
2018-11-20 16:53:27 +01:00
} ;
2018-11-15 19:59:08 +01:00
2024-04-10 12:17:14 +02:00
const status = await containerStatusFunc ( ) ;
result . status = status . status ;
result . memoryUsed = status . memoryUsed ;
result . memoryPercent = status . memoryPercent ;
result . defaultMemoryLimit = service . defaultMemoryLimit ;
result . error = status . error || null ;
result . healthcheck = status . healthcheck || null ;
2018-11-20 16:53:27 +01:00
2024-04-10 12:17:14 +02:00
result . config = await getServiceConfig ( id ) ;
2018-11-16 17:53:22 +01:00
2024-04-10 12:17:14 +02:00
if ( ! result . config . memoryLimit && service . defaultMemoryLimit ) {
result . config . memoryLimit = service . defaultMemoryLimit ;
2021-08-25 19:41:46 -07:00
}
2018-11-20 16:53:27 +01:00
2024-04-10 12:17:14 +02:00
return result ;
2018-11-15 19:59:08 +01:00
}
2021-09-24 10:22:45 -07:00
async function configureService ( id , data , auditSource ) {
2020-05-03 17:38:15 -07:00
assert . strictEqual ( typeof id , 'string' ) ;
2018-11-21 15:47:34 +01:00
assert . strictEqual ( typeof data , 'object' ) ;
2021-09-24 10:22:45 -07:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2018-11-21 15:47:34 +01:00
2020-05-03 17:38:15 -07:00
const [ name , instance ] = id . split ( ':' ) ;
2021-10-01 12:09:13 -07:00
let needsRebuild = false ;
2018-11-21 15:47:34 +01:00
2020-05-03 17:38:15 -07:00
if ( instance ) {
2021-08-25 19:41:46 -07:00
if ( ! APP _SERVICES [ name ] ) throw new BoxError ( BoxError . NOT _FOUND , 'Service not found' ) ;
2020-05-03 17:38:15 -07:00
2021-08-25 19:41:46 -07:00
const app = await apps . get ( instance ) ;
if ( ! app ) throw new BoxError ( BoxError . NOT _FOUND , 'App not found' ) ;
2018-11-21 15:47:34 +01:00
2021-08-25 19:41:46 -07:00
const servicesConfig = app . servicesConfig ;
2021-10-01 12:09:13 -07:00
needsRebuild = servicesConfig [ name ] ? . recoveryMode != data . recoveryMode ;
2021-08-25 19:41:46 -07:00
servicesConfig [ name ] = data ;
2018-11-21 15:47:34 +01:00
2021-08-25 19:41:46 -07:00
await apps . update ( instance , { servicesConfig } ) ;
2021-01-21 12:53:38 -08:00
} else if ( SERVICES [ name ] ) {
2023-08-03 12:52:47 +05:30
const servicesConfig = await getConfig ( ) ;
2021-10-09 07:24:16 -07:00
needsRebuild = servicesConfig [ name ] ? . recoveryMode != data . recoveryMode ; // intentional != since 'recoveryMode' may or may not be there
2024-01-16 11:22:27 +01:00
2021-08-25 19:41:46 -07:00
servicesConfig [ name ] = data ;
2021-01-21 12:53:38 -08:00
2023-08-03 11:34:33 +05:30
await settings . setJson ( settings . SERVICES _CONFIG _KEY , servicesConfig ) ;
2021-01-21 12:53:38 -08:00
} else {
2021-08-25 19:41:46 -07:00
throw new BoxError ( BoxError . NOT _FOUND , 'No such service' ) ;
2021-01-21 12:53:38 -08:00
}
2021-09-24 10:22:45 -07:00
2021-10-09 07:24:16 -07:00
debug ( ` configureService: ${ id } rebuild= ${ needsRebuild } ` ) ;
2021-10-01 12:09:13 -07:00
// do this in background
if ( needsRebuild ) {
safe ( rebuildService ( id , auditSource ) , { debug } ) ;
} else {
safe ( applyMemoryLimit ( id ) , { debug } ) ;
}
2021-09-24 10:22:45 -07:00
await eventlog . add ( eventlog . ACTION _SERVICE _CONFIGURE , auditSource , { id , data } ) ;
2018-11-21 15:47:34 +01:00
}
2021-08-25 19:41:46 -07:00
async function getServiceLogs ( id , options ) {
2020-05-03 17:38:15 -07:00
assert . strictEqual ( typeof id , 'string' ) ;
2018-11-15 19:59:08 +01:00
assert ( options && typeof options === 'object' ) ;
2020-05-03 17:38:15 -07:00
const [ name , instance ] = id . split ( ':' ) ;
2018-11-15 19:59:08 +01:00
2020-05-03 17:38:15 -07:00
if ( instance ) {
2021-08-25 19:41:46 -07:00
if ( ! APP _SERVICES [ name ] ) throw new BoxError ( BoxError . NOT _FOUND , 'Service not found' ) ;
2020-05-03 17:38:15 -07:00
} else if ( ! SERVICES [ name ] ) {
2021-08-25 19:41:46 -07:00
throw new BoxError ( BoxError . NOT _FOUND , 'Service not found' ) ;
2020-05-03 17:38:15 -07:00
}
2023-04-16 10:49:59 +02:00
debug ( ` getServiceLogs: getting logs for ${ name } ` ) ;
2018-11-15 19:59:08 +01:00
2023-03-27 10:38:09 +02:00
let cp ;
2018-11-21 18:38:19 +01:00
2020-05-03 17:38:15 -07:00
if ( name === 'docker' || name === 'unbound' ) {
2023-03-27 10:38:09 +02:00
cp = logs . journalctl ( name , options ) ;
2020-06-15 17:12:37 +02:00
} else if ( name === 'nginx' ) {
2023-03-27 10:38:09 +02:00
cp = logs . tail ( [ '/var/log/nginx/access.log' , '/var/log/nginx/error.log' ] , { lines : options . lines , follow : options . follow } ) ;
2018-12-05 16:19:10 +01:00
} else {
2020-05-03 17:38:15 -07:00
const containerName = APP _SERVICES [ name ] ? ` ${ name } - ${ instance } ` : name ;
2023-03-27 10:38:09 +02:00
cp = logs . tail ( [ path . join ( paths . LOG _DIR , containerName , 'app.log' ) ] , { lines : options . lines , follow : options . follow } ) ;
2018-12-05 16:19:10 +01:00
}
2023-03-27 10:38:09 +02:00
const logStream = new logs . LogStream ( { format : options . format || 'json' , source : name } ) ;
2024-02-24 17:18:38 +01:00
logStream . on ( 'close' , ( ) => cp . terminate ( ) ) ; // the caller has to call destroy() on logStream. destroy() of Transform emits 'close'
2018-11-15 19:59:08 +01:00
2022-11-06 13:44:47 +01:00
cp . stdout . pipe ( logStream ) ;
2018-11-15 19:59:08 +01:00
2022-11-06 13:44:47 +01:00
return logStream ;
2018-11-15 19:59:08 +01:00
}
2021-09-24 10:22:45 -07:00
async function rebuildService ( id , auditSource ) {
2021-01-21 12:53:38 -08:00
assert . strictEqual ( typeof id , 'string' ) ;
2021-09-24 10:22:45 -07:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2021-01-21 12:53:38 -08:00
// this attempts to recreate the service docker container if they don't exist but platform infra version is unchanged
2021-09-24 10:22:45 -07:00
// passing an infra version of 'none' will not attempt to purge existing data
2022-06-23 15:52:59 -07:00
const [ name , instance ] = id . split ( ':' ) ;
switch ( name ) {
2021-09-24 10:22:45 -07:00
case 'turn' :
2021-09-26 22:48:14 -07:00
await startTurn ( { version : 'none' } ) ;
2021-09-24 10:22:45 -07:00
break ;
case 'mongodb' :
await startMongodb ( { version : 'none' } ) ;
break ;
case 'postgresql' :
await startPostgresql ( { version : 'none' } ) ;
break ;
case 'mysql' :
await startMysql ( { version : 'none' } ) ;
break ;
case 'sftp' :
2021-09-26 22:48:14 -07:00
await sftp . start ( { version : 'none' } ) ;
2021-09-24 10:22:45 -07:00
break ;
case 'graphite' :
2021-09-26 22:48:14 -07:00
await startGraphite ( { version : 'none' } ) ;
2021-09-24 10:22:45 -07:00
break ;
2021-10-09 07:24:16 -07:00
case 'mail' :
2023-08-04 20:54:16 +05:30
await mailServer . start ( { version : 'none' } ) ;
2021-10-09 07:24:16 -07:00
break ;
2022-06-23 15:52:59 -07:00
case 'redis' : {
2024-02-21 19:40:27 +01:00
await safe ( shell . exec ( 'removeRedis' , ` docker rm -f redis- ${ instance } ` , { } ) ) ; // ignore error
2022-06-23 15:52:59 -07:00
const app = await apps . get ( instance ) ;
if ( app ) await setupRedis ( app , app . manifest . addons . redis ) ; // starts the container
break ;
}
2021-09-24 10:22:45 -07:00
default :
// nothing to rebuild for now.
}
2021-08-25 19:41:46 -07:00
2021-10-01 12:09:13 -07:00
safe ( applyMemoryLimit ( id ) , { debug } ) ; // do this in background. ok to fail
2021-09-24 10:22:45 -07:00
await eventlog . add ( eventlog . ACTION _SERVICE _REBUILD , auditSource , { id } ) ;
2021-01-21 12:53:38 -08:00
}
2021-09-24 10:22:45 -07:00
async function restartService ( id , auditSource ) {
2020-05-03 17:38:15 -07:00
assert . strictEqual ( typeof id , 'string' ) ;
2021-09-24 10:22:45 -07:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2018-11-15 19:59:08 +01:00
2020-05-03 17:38:15 -07:00
const [ name , instance ] = id . split ( ':' ) ;
if ( instance ) {
2021-08-25 19:41:46 -07:00
if ( ! APP _SERVICES [ name ] ) throw new BoxError ( BoxError . NOT _FOUND , 'Service not found' ) ;
2018-11-19 14:30:14 +01:00
2021-08-25 19:41:46 -07:00
await APP _SERVICES [ name ] . restart ( instance ) ;
2020-05-03 17:38:15 -07:00
} else if ( SERVICES [ name ] ) {
2021-08-25 19:41:46 -07:00
await SERVICES [ name ] . restart ( ) ;
2020-05-03 17:38:15 -07:00
} else {
2021-08-25 19:41:46 -07:00
throw new BoxError ( BoxError . NOT _FOUND , 'Service not found' ) ;
2020-05-03 17:38:15 -07:00
}
2021-09-24 10:22:45 -07:00
await eventlog . add ( eventlog . ACTION _SERVICE _RESTART , auditSource , { id } ) ;
2018-11-15 19:59:08 +01:00
}
2020-05-18 14:10:00 -07:00
// in the future, we can refcount and lazy start global services
2021-08-25 19:41:46 -07:00
async function startAppServices ( app ) {
2020-05-18 14:10:00 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
const instance = app . id ;
2021-08-25 19:41:46 -07:00
for ( const addon of Object . keys ( app . manifest . addons || { } ) ) {
if ( ! ( addon in APP _SERVICES ) ) continue ;
2020-05-18 14:10:00 -07:00
2021-08-25 19:41:46 -07:00
const [ error ] = await safe ( APP _SERVICES [ addon ] . start ( instance ) ) ; // assume addons name is service name
// error ignored because we don't want "start app" to error. use can fix it from Services
2023-04-16 10:49:59 +02:00
if ( error ) debug ( ` startAppServices: ${ addon } : ${ instance } . %o ` , error ) ;
2021-08-25 19:41:46 -07:00
}
2020-05-18 14:10:00 -07:00
}
// in the future, we can refcount and stop global services as well
2021-08-25 19:41:46 -07:00
async function stopAppServices ( app ) {
2020-05-18 14:10:00 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
const instance = app . id ;
2021-08-25 19:41:46 -07:00
for ( const addon of Object . keys ( app . manifest . addons || { } ) ) {
if ( ! ( addon in APP _SERVICES ) ) continue ;
2020-05-18 14:10:00 -07:00
2021-08-25 19:41:46 -07:00
const [ error ] = await safe ( APP _SERVICES [ addon ] . stop ( instance ) ) ; // assume addons name is service name
// error ignored because we don't want "start app" to error. use can fix it from Services
2023-04-16 10:49:59 +02:00
if ( error ) debug ( ` stopAppServices: ${ addon } : ${ instance } . %o ` , error ) ;
2021-08-25 19:41:46 -07:00
}
2020-05-18 14:10:00 -07:00
}
2021-08-25 19:41:46 -07:00
async function waitForContainer ( containerName , tokenEnvName ) {
2018-10-14 13:26:01 -07:00
assert . strictEqual ( typeof containerName , 'string' ) ;
assert . strictEqual ( typeof tokenEnvName , 'string' ) ;
debug ( ` Waiting for ${ containerName } ` ) ;
2021-08-25 19:41:46 -07:00
const result = await getContainerDetails ( containerName , tokenEnvName ) ;
2018-10-14 13:26:01 -07:00
2023-04-11 09:36:09 +02:00
await promiseRetry ( { times : 20 , interval : 15000 , debug } , async ( ) => {
2021-12-20 10:52:42 -08:00
const [ networkError , response ] = await safe ( superagent . get ( ` http:// ${ result . ip } :3000/healthcheck?access_token= ${ result . token } ` )
2023-05-16 09:51:39 +02:00
. timeout ( 20000 )
2021-08-25 19:41:46 -07:00
. ok ( ( ) => true ) ) ;
2018-10-14 13:26:01 -07:00
2021-08-25 19:41:46 -07:00
if ( networkError ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Network error waiting for ${ containerName } : ${ networkError . message } ` ) ;
if ( response . status !== 200 || ! response . body . status ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Error waiting for ${ containerName } . Status code: ${ response . status } message: ${ response . body . message } ` ) ;
2018-10-14 13:26:01 -07:00
} ) ;
}
2021-08-25 19:41:46 -07:00
async function setupAddons ( app , addons ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert ( ! addons || typeof addons === 'object' ) ;
2021-08-25 19:41:46 -07:00
if ( ! addons ) return ;
2015-07-20 00:09:47 -07:00
2021-10-01 09:17:44 -07:00
debug ( 'setupAddons: Setting up %j' , Object . keys ( addons ) ) ;
2015-07-20 11:03:11 -07:00
2021-08-25 19:41:46 -07:00
for ( const addon of Object . keys ( addons ) ) {
if ( ! ( addon in ADDONS ) ) throw new BoxError ( BoxError . NOT _FOUND , ` No such addon: ${ addon } ` ) ;
2015-07-20 00:09:47 -07:00
2021-10-01 09:17:44 -07:00
debug ( ` setupAddons: setting up addon ${ addon } with options ${ JSON . stringify ( addons [ addon ] ) } ` ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
await ADDONS [ addon ] . setup ( app , addons [ addon ] ) ;
}
2015-07-20 00:09:47 -07:00
}
2021-08-25 19:41:46 -07:00
async function teardownAddons ( app , addons ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert ( ! addons || typeof addons === 'object' ) ;
2021-08-25 19:41:46 -07:00
if ( ! addons ) return ;
2015-07-20 00:09:47 -07:00
2021-10-01 09:17:44 -07:00
debug ( 'teardownAddons: Tearing down %j' , Object . keys ( addons ) ) ;
2015-07-20 11:03:11 -07:00
2021-08-25 19:41:46 -07:00
for ( const addon of Object . keys ( addons ) ) {
if ( ! ( addon in ADDONS ) ) throw new BoxError ( BoxError . NOT _FOUND , ` No such addon: ${ addon } ` ) ;
2015-07-20 00:09:47 -07:00
2021-10-01 09:17:44 -07:00
debug ( ` teardownAddons: Tearing down addon ${ addon } with options ${ JSON . stringify ( addons [ addon ] ) } ` ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
await ADDONS [ addon ] . teardown ( app , addons [ addon ] ) ;
}
2015-07-20 00:09:47 -07:00
}
2021-08-25 19:41:46 -07:00
async function backupAddons ( app , addons ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert ( ! addons || typeof addons === 'object' ) ;
2021-10-01 09:17:44 -07:00
debug ( 'backupAddons' ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
if ( ! addons ) return ;
2015-07-20 00:09:47 -07:00
2021-10-01 09:17:44 -07:00
debug ( 'backupAddons: backing up %j' , Object . keys ( addons ) ) ;
2015-07-20 11:03:11 -07:00
2021-08-25 19:41:46 -07:00
for ( const addon of Object . keys ( addons ) ) {
if ( ! ( addon in ADDONS ) ) throw new BoxError ( BoxError . NOT _FOUND , ` No such addon: ${ addon } ` ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
await ADDONS [ addon ] . backup ( app , addons [ addon ] ) ;
}
2015-07-20 00:09:47 -07:00
}
2021-08-25 19:41:46 -07:00
async function clearAddons ( app , addons ) {
2018-09-15 17:05:04 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert ( ! addons || typeof addons === 'object' ) ;
2021-10-01 09:17:44 -07:00
debug ( 'clearAddons' ) ;
2018-09-15 17:05:04 -07:00
2021-08-25 19:41:46 -07:00
if ( ! addons ) return ;
2018-09-15 17:05:04 -07:00
2021-10-01 09:17:44 -07:00
debug ( 'clearAddons: clearing %j' , Object . keys ( addons ) ) ;
2018-09-15 17:05:04 -07:00
2021-08-25 19:41:46 -07:00
for ( const addon of Object . keys ( addons ) ) {
if ( ! ( addon in ADDONS ) ) throw new BoxError ( BoxError . NOT _FOUND , ` No such addon: ${ addon } ` ) ;
2018-09-15 17:05:04 -07:00
2021-08-25 19:41:46 -07:00
await ADDONS [ addon ] . clear ( app , addons [ addon ] ) ;
}
2018-09-15 17:05:04 -07:00
}
2021-08-25 19:41:46 -07:00
async function restoreAddons ( app , addons ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert ( ! addons || typeof addons === 'object' ) ;
2021-10-01 09:17:44 -07:00
debug ( 'restoreAddons' ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
if ( ! addons ) return ;
2015-07-20 00:09:47 -07:00
2021-10-01 09:17:44 -07:00
debug ( 'restoreAddons: restoring %j' , Object . keys ( addons ) ) ;
2015-07-20 11:03:11 -07:00
2021-08-25 19:41:46 -07:00
for ( const addon of Object . keys ( addons ) ) {
if ( ! ( addon in ADDONS ) ) throw new BoxError ( BoxError . NOT _FOUND , ` No such addon: ${ addon } ` ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
await ADDONS [ addon ] . restore ( app , addons [ addon ] ) ;
}
2015-07-20 00:09:47 -07:00
}
2021-12-17 07:47:20 -08:00
async function importAppDatabase ( app , addon ) {
2018-11-11 21:58:02 -08:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof addon , 'string' ) ;
2021-12-17 07:47:20 -08:00
if ( ! ( addon in ADDONS ) ) throw new BoxError ( BoxError . NOT _FOUND , ` No such addon: ${ addon } ` ) ;
2018-11-11 21:58:02 -08:00
2021-12-17 07:47:20 -08:00
await ADDONS [ addon ] . setup ( app , app . manifest . addons [ addon ] ) ;
await ADDONS [ addon ] . clear ( app , app . manifest . addons [ addon ] ) ; // clear in case we crashed in a restore
await ADDONS [ addon ] . restore ( app , app . manifest . addons [ addon ] ) ;
2018-11-11 21:58:02 -08:00
}
2021-08-25 19:41:46 -07:00
async function importDatabase ( addon ) {
2018-10-15 09:18:08 -07:00
assert . strictEqual ( typeof addon , 'string' ) ;
debug ( ` importDatabase: Importing ${ addon } ` ) ;
2021-08-25 19:41:46 -07:00
const allApps = await apps . list ( ) ;
2018-10-15 09:18:08 -07:00
2021-08-25 19:41:46 -07:00
for ( const app of allApps ) {
if ( ! ( addon in app . manifest . addons ) ) continue ; // app doesn't use the addon
2018-10-15 14:31:35 -07:00
2021-08-25 19:41:46 -07:00
debug ( ` importDatabase: Importing addon ${ addon } of app ${ app . id } ` ) ;
2018-10-15 14:31:35 -07:00
2021-08-25 19:41:46 -07:00
const [ error ] = await safe ( importAppDatabase ( app , addon ) ) ;
if ( ! error ) continue ;
2018-11-11 10:35:26 -08:00
2023-04-16 10:49:59 +02:00
debug ( ` importDatabase: Error importing ${ addon } of app ${ app . id } . Marking as errored. %o ` , error ) ;
2021-08-25 19:41:46 -07:00
// FIXME: there is no way to 'repair' if we are here. we need to make a separate apptask that re-imports db
// not clear, if repair workflow should be part of addon or per-app
await safe ( apps . update ( app . id , { installationState : apps . ISTATE _ERROR , error : { message : error . message } } ) ) ;
}
2020-07-30 10:21:46 -07:00
2021-08-25 19:41:46 -07:00
safe . fs . unlinkSync ( path . join ( paths . ADDON _CONFIG _DIR , ` exported- ${ addon } ` ) ) ; // clean up for future migrations
2020-07-30 10:21:46 -07:00
}
2021-08-25 19:41:46 -07:00
async function exportDatabase ( addon ) {
2020-07-30 10:21:46 -07:00
assert . strictEqual ( typeof addon , 'string' ) ;
debug ( ` exportDatabase: Exporting ${ addon } ` ) ;
if ( fs . existsSync ( path . join ( paths . ADDON _CONFIG _DIR , ` exported- ${ addon } ` ) ) ) {
debug ( ` exportDatabase: Already exported addon ${ addon } in previous run ` ) ;
2021-08-25 19:41:46 -07:00
return ;
2020-07-30 10:21:46 -07:00
}
2021-08-25 19:41:46 -07:00
const allApps = await apps . list ( ) ;
2020-07-30 10:21:46 -07:00
2021-08-25 19:41:46 -07:00
for ( const app of allApps ) {
if ( ! app . manifest . addons || ! ( addon in app . manifest . addons ) ) continue ; // app doesn't use the addon
if ( app . installationState === apps . ISTATE _ERROR ) continue ; // missing db causes crash in old app addon containers
2020-07-30 10:21:46 -07:00
2021-08-25 19:41:46 -07:00
debug ( ` exportDatabase: Exporting addon ${ addon } of app ${ app . id } ` ) ;
2020-07-30 10:21:46 -07:00
2021-08-25 19:41:46 -07:00
const [ error ] = await safe ( ADDONS [ addon ] . backup ( app , app . manifest . addons [ addon ] ) ) ;
if ( error ) {
2023-04-16 10:49:59 +02:00
debug ( ` exportDatabase: Error exporting ${ addon } of app ${ app . id } . %o ` , error ) ;
2021-08-25 19:41:46 -07:00
// for errored apps, we can ignore if export had an error
if ( app . installationState === apps . ISTATE _ERROR ) continue ;
throw error ;
}
}
2020-07-30 10:21:46 -07:00
2021-08-25 19:41:46 -07:00
safe . fs . writeFileSync ( path . join ( paths . ADDON _CONFIG _DIR , ` exported- ${ addon } ` ) , '' , 'utf8' ) ;
if ( safe . error ) throw BoxError ( BoxError . FS _ERROR , 'Error writing export checkpoint file' ) ;
// note: after this point, we are restart safe. it's ok if the box code crashes at this point
2024-02-21 19:40:27 +01:00
await shell . exec ( ` exportDatabase - remove ${ addon } ` , ` docker rm -f ${ addon } ` , { } ) ; // what if db writes something when quitting ...
2021-08-25 19:41:46 -07:00
await shell . promises . sudo ( ` exportDatabase - removeAddonDir ${ addon } ` , [ RMADDONDIR _CMD , addon ] , { } ) ; // ready to start afresh
2018-10-15 09:18:08 -07:00
}
2021-10-01 12:09:13 -07:00
async function applyMemoryLimit ( id ) {
2021-01-21 12:53:38 -08:00
assert . strictEqual ( typeof id , 'string' ) ;
2018-10-16 14:07:41 -07:00
2021-01-21 12:53:38 -08:00
const [ name , instance ] = id . split ( ':' ) ;
let containerName , memoryLimit ;
2021-10-01 12:09:13 -07:00
const serviceConfig = await getServiceConfig ( id ) ;
2018-10-16 14:07:41 -07:00
2021-01-21 12:53:38 -08:00
if ( instance ) {
2021-08-25 19:41:46 -07:00
if ( ! APP _SERVICES [ name ] ) throw new BoxError ( BoxError . NOT _FOUND , 'Service not found' ) ;
2020-05-03 17:38:15 -07:00
2021-01-21 12:53:38 -08:00
containerName = ` ${ name } - ${ instance } ` ;
memoryLimit = serviceConfig && serviceConfig . memoryLimit ? serviceConfig . memoryLimit : APP _SERVICES [ name ] . defaultMemoryLimit ;
} else if ( SERVICES [ name ] ) {
containerName = name ;
memoryLimit = serviceConfig && serviceConfig . memoryLimit ? serviceConfig . memoryLimit : SERVICES [ name ] . defaultMemoryLimit ;
2020-05-03 17:38:15 -07:00
} else {
2021-08-25 19:41:46 -07:00
throw new BoxError ( BoxError . NOT _FOUND , 'No such service' ) ;
2020-05-03 17:38:15 -07:00
}
2021-10-09 07:24:16 -07:00
debug ( ` applyMemoryLimit: ${ containerName } ${ JSON . stringify ( serviceConfig ) } ` ) ;
2021-01-21 12:53:38 -08:00
2024-04-09 18:59:40 +02:00
await docker . update ( containerName , memoryLimit ) ;
2020-05-03 17:38:15 -07:00
}
2021-08-25 19:41:46 -07:00
async function startServices ( existingInfra ) {
2018-10-16 14:07:41 -07:00
assert . strictEqual ( typeof existingInfra , 'object' ) ;
2021-01-21 12:53:38 -08:00
2021-09-26 22:48:14 -07:00
const startFuncs = [ ] ;
2021-08-25 19:41:46 -07:00
// always start addons on any infra change, regardless of minor or major update
if ( existingInfra . version !== infra . version ) {
debug ( ` startServices: ${ existingInfra . version } -> ${ infra . version } . starting all services ` ) ;
startFuncs . push (
2023-08-04 20:54:16 +05:30
mailServer . start , // start this first to reduce email downtime
2021-09-26 22:48:14 -07:00
startTurn ,
startMysql ,
startPostgresql ,
startMongodb ,
startRedis ,
startGraphite ,
sftp . start ,
2021-08-25 19:41:46 -07:00
) ;
} else {
assert . strictEqual ( typeof existingInfra . images , 'object' ) ;
2023-08-08 10:42:16 +05:30
if ( infra . images . mail !== existingInfra . images . mail ) startFuncs . push ( mailServer . start ) ; // start this first to reduce email downtime
if ( infra . images . turn !== existingInfra . images . turn ) startFuncs . push ( startTurn ) ;
if ( infra . images . mysql !== existingInfra . images . mysql ) startFuncs . push ( startMysql ) ;
if ( infra . images . postgresql !== existingInfra . images . postgresql ) startFuncs . push ( startPostgresql ) ;
if ( infra . images . mongodb !== existingInfra . images . mongodb ) startFuncs . push ( startMongodb ) ;
if ( infra . images . redis !== existingInfra . images . redis ) startFuncs . push ( startRedis ) ;
if ( infra . images . graphite !== existingInfra . images . graphite ) startFuncs . push ( startGraphite ) ;
if ( infra . images . sftp !== existingInfra . images . sftp ) startFuncs . push ( sftp . start ) ;
2021-08-25 19:41:46 -07:00
debug ( 'startServices: existing infra. incremental service create %j' , startFuncs . map ( function ( f ) { return f . name ; } ) ) ;
}
2021-01-27 11:33:27 -08:00
2021-08-25 19:41:46 -07:00
for ( const func of startFuncs ) {
2021-09-26 22:48:14 -07:00
await func ( existingInfra ) ;
2021-08-25 19:41:46 -07:00
}
2021-01-27 11:33:27 -08:00
2021-08-25 19:41:46 -07:00
// we always start db containers with unlimited memory. we then scale them down per configuration
2021-09-26 22:48:14 -07:00
for ( const id of [ 'mysql' , 'postgresql' , 'mongodb' ] ) {
2021-10-01 12:09:13 -07:00
safe ( applyMemoryLimit ( id ) , { debug } ) ; // no waiting. and it's ok if applying service configs fails
2021-08-25 19:41:46 -07:00
}
2018-10-16 14:07:41 -07:00
}
2021-08-19 21:39:27 -07:00
async function getEnvironment ( app ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
2023-04-24 15:29:57 +02:00
// contains values for environment from addonConfigs db
2021-08-19 21:39:27 -07:00
const result = await addonConfigs . getByAppId ( app . id ) ;
2017-03-25 14:14:57 -07:00
2023-04-14 19:20:14 +02:00
// convert result to object to ensure unique env names if we overwrite static ones from the previously stored value in addonconfigs
2023-04-24 15:29:57 +02:00
let env = { } ;
2023-04-14 19:20:14 +02:00
result . forEach ( e => { env [ e . name ] = e . value ; } ) ;
2024-03-01 10:31:41 +01:00
// get environment configs which are dynamic e.g generated based on dashboard domain and are not stored in db
for ( let addon in ( app . manifest . addons || { } ) ) {
2023-04-24 15:29:57 +02:00
const configs = await ADDONS [ addon ] . getDynamicEnvironment ( app , { } ) ;
if ( configs ) env = { ... env , ... configs } ;
2023-04-14 19:20:14 +02:00
}
return Object . keys ( env ) . map ( function ( e ) { return e + '=' + env [ e ] ; } ) ;
2015-07-20 00:09:47 -07:00
}
2015-11-02 11:20:50 -08:00
function getContainerNamesSync ( app , addons ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert ( ! addons || typeof addons === 'object' ) ;
2021-08-25 19:41:46 -07:00
let names = [ ] ;
2015-11-02 11:20:50 -08:00
if ( ! addons ) return names ;
2021-08-25 19:41:46 -07:00
for ( const addon in addons ) {
2015-11-02 11:20:50 -08:00
switch ( addon ) {
case 'scheduler' :
// names here depend on how scheduler.js creates containers
names = names . concat ( Object . keys ( addons . scheduler ) . map ( function ( taskName ) { return app . id + '-' + taskName ; } ) ) ;
break ;
default : break ;
}
}
return names ;
}
2021-08-25 19:41:46 -07:00
async function setupLocalStorage ( app , options ) {
2018-09-13 13:55:49 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2021-10-01 09:17:44 -07:00
debug ( 'setupLocalStorage' ) ;
2018-09-13 13:55:49 -07:00
2022-06-01 22:44:52 -07:00
const volumeDataDir = await apps . getStorageDir ( app ) ;
2019-01-13 19:18:06 -08:00
2022-06-02 10:43:01 -07:00
const [ error ] = await safe ( shell . promises . sudo ( 'createVolume' , [ MKDIRVOLUME _CMD , volumeDataDir ] , { } ) ) ;
if ( error ) throw new BoxError ( BoxError . FS _ERROR , ` Error creating app storage data dir: ${ error . message } ` ) ;
2018-09-13 13:55:49 -07:00
}
2021-08-25 19:41:46 -07:00
async function clearLocalStorage ( app , options ) {
2018-09-15 17:05:04 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2021-10-01 09:17:44 -07:00
debug ( 'clearLocalStorage' ) ;
2018-09-15 17:05:04 -07:00
2022-06-01 22:44:52 -07:00
const volumeDataDir = await apps . getStorageDir ( app ) ;
2022-06-02 10:43:01 -07:00
const [ error ] = await shell . promises . sudo ( 'clearVolume' , [ CLEARVOLUME _CMD , 'clear' , volumeDataDir ] , { } ) ;
if ( error ) throw new BoxError ( BoxError . FS _ERROR , error ) ;
2018-09-15 17:05:04 -07:00
}
2021-08-25 19:41:46 -07:00
async function teardownLocalStorage ( app , options ) {
2018-09-13 13:55:49 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2021-10-01 09:17:44 -07:00
debug ( 'teardownLocalStorage' ) ;
2018-09-13 13:55:49 -07:00
2022-06-01 22:44:52 -07:00
const volumeDataDir = await apps . getStorageDir ( app ) ;
2022-06-02 10:43:01 -07:00
const [ error ] = await shell . promises . sudo ( 'clearVolume' , [ CLEARVOLUME _CMD , 'rmdir' , volumeDataDir ] , { } ) ;
if ( error ) throw new BoxError ( BoxError . FS _ERROR , error ) ;
2018-09-13 13:55:49 -07:00
}
2021-08-25 19:41:46 -07:00
async function setupTurn ( app , options ) {
2020-03-30 08:24:45 +02:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2021-05-02 23:28:41 -07:00
2023-07-13 15:06:07 +05:30
const disabled = app . manifest . addons . turn . optional && ! app . enableTurn ;
2023-07-13 16:25:01 +05:30
if ( disabled ) return await addonConfigs . unset ( app . id , 'turn' ) ;
2023-07-13 15:06:07 +05:30
2022-02-01 17:36:51 -08:00
const turnSecret = await blobs . getString ( blobs . ADDON _TURN _SECRET ) ;
2021-08-25 19:41:46 -07:00
if ( ! turnSecret ) throw new BoxError ( BoxError . ADDONS _ERROR , 'Turn secret is missing' ) ;
2023-08-11 19:41:05 +05:30
const { fqdn : dashboardFqdn } = await dashboard . getLocation ( ) ;
2021-08-25 19:41:46 -07:00
const env = [
2023-08-11 19:41:05 +05:30
{ name : 'CLOUDRON_STUN_SERVER' , value : dashboardFqdn } ,
2021-08-25 19:41:46 -07:00
{ name : 'CLOUDRON_STUN_PORT' , value : '3478' } ,
{ name : 'CLOUDRON_STUN_TLS_PORT' , value : '5349' } ,
2023-08-11 19:41:05 +05:30
{ name : 'CLOUDRON_TURN_SERVER' , value : dashboardFqdn } ,
2021-08-25 19:41:46 -07:00
{ name : 'CLOUDRON_TURN_PORT' , value : '3478' } ,
{ name : 'CLOUDRON_TURN_TLS_PORT' , value : '5349' } ,
{ name : 'CLOUDRON_TURN_SECRET' , value : turnSecret }
] ;
2020-03-30 08:24:45 +02:00
2021-10-01 09:17:44 -07:00
debug ( 'Setting up TURN' ) ;
2020-03-30 08:24:45 +02:00
2021-08-25 19:41:46 -07:00
await addonConfigs . set ( app . id , 'turn' , env ) ;
2020-03-30 08:24:45 +02:00
}
2021-09-26 22:48:14 -07:00
async function startTurn ( existingInfra ) {
2021-08-25 19:41:46 -07:00
assert . strictEqual ( typeof existingInfra , 'object' ) ;
2021-09-26 22:48:14 -07:00
const serviceConfig = await getServiceConfig ( 'turn' ) ;
2023-08-08 10:42:16 +05:30
const image = infra . images . turn ;
2021-08-25 19:41:46 -07:00
const memoryLimit = serviceConfig . memoryLimit || SERVICES [ 'turn' ] . defaultMemoryLimit ;
2023-08-11 19:41:05 +05:30
const { fqdn : realm } = await dashboard . getLocation ( ) ;
2021-08-25 19:41:46 -07:00
2022-02-01 17:36:51 -08:00
let turnSecret = await blobs . getString ( blobs . ADDON _TURN _SECRET ) ;
2021-11-16 22:37:42 -08:00
if ( ! turnSecret ) {
2023-02-02 11:32:42 +01:00
debug ( 'startTurn: generating turn secret' ) ;
2021-11-16 22:37:42 -08:00
turnSecret = 'a' + crypto . randomBytes ( 15 ) . toString ( 'hex' ) ; // prefix with a to ensure string starts with a letter
2022-02-01 17:36:51 -08:00
await blobs . setString ( blobs . ADDON _TURN _SECRET , turnSecret ) ;
2021-11-16 22:37:42 -08:00
}
2021-08-25 19:41:46 -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\'' : '' ;
2022-02-11 23:08:56 -08:00
// this exports 3478/tcp, 5349/tls and 50000-51000/udp. note that this runs on the host network because docker's userland proxy
// is spun for every port. we can disable this in some future release with --userland-proxy=false
// https://github.com/moby/moby/issues/8356 and https://github.com/moby/moby/issues/14856
2024-02-22 10:34:56 +01:00
const runCmd = ` docker run --restart=always -d --name=turn \
2021-08-25 19:41:46 -07:00
-- hostname turn \
-- net host \
-- 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 = turn \
2024-04-09 18:59:40 +02:00
- m $ { memoryLimit } \
-- memory - swap - 1 \
2021-08-25 19:41:46 -07:00
-- dns 172.18 . 0.1 \
-- dns - search = . \
2024-02-22 10:34:56 +01:00
- e CLOUDRON _TURN _SECRET = $ { turnSecret } \
2024-02-28 13:00:29 +01:00
- e CLOUDRON _REALM = $ { realm } \
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 ( 'startTurn: stopping and deleting previous turn container' ) ;
await docker . stopContainer ( 'turn' ) ;
await docker . deleteContainer ( 'turn' ) ;
debug ( 'startTurn: starting turn container' ) ;
2024-02-28 15:59:42 +01:00
await shell . exec ( 'startTurn' , runCmd , { shell : '/bin/bash' } ) ;
2021-08-25 19:41:46 -07:00
}
async function teardownTurn ( app , options ) {
2020-03-30 08:24:45 +02:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2021-10-01 09:17:44 -07:00
debug ( 'Tearing down TURN' ) ;
2020-03-30 08:24:45 +02:00
2021-08-25 19:41:46 -07:00
await addonConfigs . unset ( app . id , 'turn' ) ;
2020-03-30 08:24:45 +02:00
}
2021-08-25 19:41:46 -07:00
async function setupEmail ( app , options ) {
2016-05-12 08:54:59 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2018-03-07 20:39:58 -08:00
2021-08-25 19:41:46 -07:00
const mailDomains = await mail . listDomains ( ) ;
const mailInDomains = mailDomains . filter ( function ( d ) { return d . enabled ; } ) . map ( function ( d ) { return d . domain ; } ) . join ( ',' ) ;
2023-08-04 21:37:38 +05:30
const { fqdn } = await mailServer . getLocation ( ) ;
2018-03-07 20:39:58 -08:00
2021-08-25 19:41:46 -07:00
// note that "external" access info can be derived from MAIL_DOMAIN (since it's part of user documentation)
const env = [
2021-12-06 17:38:21 -08:00
{ name : 'CLOUDRON_EMAIL_SMTP_SERVER' , value : 'mail' } ,
{ name : 'CLOUDRON_EMAIL_SMTP_PORT' , value : '2525' } ,
2022-01-09 16:03:35 -08:00
{ name : 'CLOUDRON_EMAIL_SMTPS_PORT' , value : '2465' } ,
{ name : 'CLOUDRON_EMAIL_STARTTLS_PORT' , value : '2587' } ,
2021-12-06 17:38:21 -08:00
{ name : 'CLOUDRON_EMAIL_IMAP_SERVER' , value : 'mail' } ,
{ name : 'CLOUDRON_EMAIL_IMAPS_PORT' , value : '9993' } ,
{ name : 'CLOUDRON_EMAIL_IMAP_PORT' , value : '9393' } ,
{ name : 'CLOUDRON_EMAIL_SIEVE_SERVER' , value : 'mail' } ,
2022-01-09 16:03:35 -08:00
{ name : 'CLOUDRON_EMAIL_SIEVE_PORT' , value : '4190' } , // starttls
2021-12-06 17:38:21 -08:00
{ name : 'CLOUDRON_EMAIL_DOMAIN' , value : app . domain } ,
{ name : 'CLOUDRON_EMAIL_DOMAINS' , value : mailInDomains } ,
2023-08-04 21:37:38 +05:30
{ name : 'CLOUDRON_EMAIL_SERVER_HOST' , value : fqdn } , // this is also a hint to reconfigure on mail server name change
2021-12-06 17:38:21 -08:00
{ name : 'CLOUDRON_EMAIL_LDAP_MAILBOXES_BASE_DN' , value : 'ou=mailboxes,dc=cloudron' }
2021-08-25 19:41:46 -07:00
] ;
2016-05-12 08:54:59 -07:00
2021-10-01 09:17:44 -07:00
debug ( 'Setting up Email' ) ;
2016-05-12 08:54:59 -07:00
2021-08-25 19:41:46 -07:00
await addonConfigs . set ( app . id , 'email' , env ) ;
2016-05-12 08:54:59 -07:00
}
2021-08-25 19:41:46 -07:00
async function teardownEmail ( app , options ) {
2016-05-12 08:54:59 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2021-10-01 09:17:44 -07:00
debug ( 'Tearing down Email' ) ;
2016-05-12 08:54:59 -07:00
2021-08-25 19:41:46 -07:00
await addonConfigs . unset ( app . id , 'email' ) ;
2016-05-12 08:54:59 -07:00
}
2021-08-25 19:41:46 -07:00
async function setupLdap ( app , options ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
2015-10-07 16:10:08 -07:00
assert . strictEqual ( typeof options , 'object' ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
if ( ! app . sso ) return ;
2016-11-11 10:57:59 +05:30
2021-08-25 19:41:46 -07:00
const env = [
2021-12-06 17:43:50 -08:00
{ name : 'CLOUDRON_LDAP_SERVER' , value : '172.18.0.1' } ,
2020-09-08 19:32:19 -07:00
{ name : 'CLOUDRON_LDAP_HOST' , value : '172.18.0.1' } , // to keep things in sync with the database _HOST vars
2021-12-06 17:43:50 -08:00
{ name : 'CLOUDRON_LDAP_PORT' , value : '' + constants . LDAP _PORT } ,
{ name : 'CLOUDRON_LDAP_URL' , value : 'ldap://172.18.0.1:' + constants . LDAP _PORT } ,
{ name : 'CLOUDRON_LDAP_USERS_BASE_DN' , value : 'ou=users,dc=cloudron' } ,
{ name : 'CLOUDRON_LDAP_GROUPS_BASE_DN' , value : 'ou=groups,dc=cloudron' } ,
{ name : 'CLOUDRON_LDAP_BIND_DN' , value : 'cn=' + app . id + ',ou=apps,dc=cloudron' } ,
{ name : 'CLOUDRON_LDAP_BIND_PASSWORD' , value : hat ( 4 * 128 ) } // this is ignored
2015-07-20 00:09:47 -07:00
] ;
2021-10-01 09:17:44 -07:00
debug ( 'Setting up LDAP' ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
await addonConfigs . set ( app . id , 'ldap' , env ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-25 19:41:46 -07:00
async function teardownLdap ( app , options ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
2015-10-07 16:10:08 -07:00
assert . strictEqual ( typeof options , 'object' ) ;
2015-07-20 00:09:47 -07:00
2021-10-01 09:17:44 -07:00
debug ( 'Tearing down LDAP' ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
await addonConfigs . unset ( app . id , 'ldap' ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-25 19:41:46 -07:00
async function setupSendMail ( app , options ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
2015-10-07 16:10:08 -07:00
assert . strictEqual ( typeof options , 'object' ) ;
2015-07-20 00:09:47 -07:00
2021-10-01 09:17:44 -07:00
debug ( 'Setting up SendMail' ) ;
2016-01-14 12:56:35 -08:00
2021-03-16 22:38:59 -07:00
const disabled = app . manifest . addons . sendmail . optional && ! app . enableMailbox ;
2023-07-13 16:25:01 +05:30
if ( disabled ) return await addonConfigs . unset ( app . id , 'sendmail' ) ;
2021-08-25 19:41:46 -07:00
const existingPassword = await addonConfigs . getByName ( app . id , 'sendmail' , '%MAIL_SMTP_PASSWORD' ) ;
const password = existingPassword || hat ( 4 * 48 ) ; // see box#565 for password length
const env = [
2021-12-06 17:43:50 -08:00
{ name : 'CLOUDRON_MAIL_SMTP_SERVER' , value : 'mail' } ,
{ name : 'CLOUDRON_MAIL_SMTP_PORT' , value : '2525' } ,
{ name : 'CLOUDRON_MAIL_SMTPS_PORT' , value : '2465' } ,
{ name : 'CLOUDRON_MAIL_STARTTLS_PORT' , value : '2587' } ,
{ name : 'CLOUDRON_MAIL_SMTP_USERNAME' , value : app . mailboxName + '@' + app . mailboxDomain } ,
{ name : 'CLOUDRON_MAIL_SMTP_PASSWORD' , value : password } ,
{ name : 'CLOUDRON_MAIL_FROM' , value : app . mailboxName + '@' + app . mailboxDomain } ,
{ name : 'CLOUDRON_MAIL_DOMAIN' , value : app . mailboxDomain }
2021-08-25 19:41:46 -07:00
] ;
2022-06-08 09:43:55 -07:00
if ( app . manifest . addons . sendmail . supportsDisplayName ) env . push ( { name : 'CLOUDRON_MAIL_FROM_DISPLAY_NAME' , value : app . mailboxDisplayName } ) ;
2021-10-01 09:17:44 -07:00
debug ( 'Setting sendmail addon config to %j' , env ) ;
2021-08-25 19:41:46 -07:00
await addonConfigs . set ( app . id , 'sendmail' , env ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-25 19:41:46 -07:00
async function teardownSendMail ( app , options ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
2015-10-07 16:10:08 -07:00
assert . strictEqual ( typeof options , 'object' ) ;
2015-07-20 00:09:47 -07:00
2021-10-01 09:17:44 -07:00
debug ( 'Tearing down sendmail' ) ;
2016-05-15 21:23:44 -07:00
2021-08-25 19:41:46 -07:00
await addonConfigs . unset ( app . id , 'sendmail' ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-25 19:41:46 -07:00
async function setupRecvMail ( app , options ) {
2016-05-13 14:13:25 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2021-10-01 12:09:13 -07:00
debug ( 'setupRecvMail: setting up recvmail' ) ;
2023-07-13 16:25:01 +05:30
if ( ! app . enableInbox ) return await addonConfigs . unset ( app . id , 'recvmail' ) ;
2016-05-13 14:13:25 -07:00
2021-08-25 19:41:46 -07:00
const existingPassword = await addonConfigs . getByName ( app . id , 'recvmail' , '%MAIL_IMAP_PASSWORD' ) ;
2016-05-13 14:13:25 -07:00
2021-08-25 19:41:46 -07:00
const password = existingPassword || hat ( 4 * 48 ) ; // see box#565 for password length
2016-09-27 12:51:25 -07:00
2021-08-25 19:41:46 -07:00
const env = [
2021-12-06 17:43:50 -08:00
{ name : 'CLOUDRON_MAIL_IMAP_SERVER' , value : 'mail' } ,
{ name : 'CLOUDRON_MAIL_IMAP_PORT' , value : '9393' } ,
{ name : 'CLOUDRON_MAIL_IMAPS_PORT' , value : '9993' } ,
{ name : 'CLOUDRON_MAIL_POP3_PORT' , value : '9595' } ,
{ name : 'CLOUDRON_MAIL_POP3S_PORT' , value : '9995' } ,
{ name : 'CLOUDRON_MAIL_IMAP_USERNAME' , value : app . inboxName + '@' + app . inboxDomain } ,
{ name : 'CLOUDRON_MAIL_IMAP_PASSWORD' , value : password } ,
{ name : 'CLOUDRON_MAIL_TO' , value : app . inboxName + '@' + app . inboxDomain } ,
{ name : 'CLOUDRON_MAIL_TO_DOMAIN' , value : app . inboxDomain } ,
2021-08-25 19:41:46 -07:00
] ;
2018-05-17 19:48:54 -07:00
2021-10-01 12:09:13 -07:00
debug ( 'setupRecvMail: setting recvmail addon config to %j' , env ) ;
2021-08-25 19:41:46 -07:00
await addonConfigs . set ( app . id , 'recvmail' , env ) ;
2016-05-13 14:13:25 -07:00
}
2021-08-25 19:41:46 -07:00
async function teardownRecvMail ( app , options ) {
2016-05-13 14:13:25 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2021-10-01 12:09:13 -07:00
debug ( 'teardownRecvMail: tearing down recvmail' ) ;
2016-05-13 14:13:25 -07:00
2021-08-25 19:41:46 -07:00
await addonConfigs . unset ( app . id , 'recvmail' ) ;
2016-05-13 14:13:25 -07:00
}
2018-06-04 12:23:46 -07:00
function mysqlDatabaseName ( appId ) {
2018-05-16 19:49:29 -07:00
assert . strictEqual ( typeof appId , 'string' ) ;
2022-04-14 17:41:41 -05:00
const md5sum = crypto . createHash ( 'md5' ) ; // get rid of "-"
2018-05-16 19:49:29 -07:00
md5sum . update ( appId ) ;
2018-06-04 12:23:46 -07:00
return md5sum . digest ( 'hex' ) . substring ( 0 , 16 ) ; // max length of mysql usernames is 16
2018-05-16 19:49:29 -07:00
}
2021-08-25 19:41:46 -07:00
async function startMysql ( existingInfra ) {
2018-10-16 14:07:41 -07:00
assert . strictEqual ( typeof existingInfra , 'object' ) ;
2023-08-08 10:42:16 +05:30
const image = infra . images . mysql ;
2018-10-16 14:07:41 -07:00
const dataDir = paths . PLATFORM _DATA _DIR ;
const rootPassword = hat ( 8 * 128 ) ;
const cloudronToken = hat ( 8 * 128 ) ;
2023-08-08 10:42:16 +05:30
const upgrading = existingInfra . version !== 'none' && requiresUpgrade ( existingInfra . images . mysql , image ) ;
2018-10-16 14:07:41 -07:00
2021-08-25 19:41:46 -07:00
if ( upgrading ) {
debug ( 'startMysql: mysql will be upgraded' ) ;
await exportDatabase ( 'mysql' ) ;
}
2018-10-16 14:07:41 -07:00
2021-10-01 12:09:13 -07:00
const serviceConfig = await getServiceConfig ( 'mysql' ) ;
const readOnly = ! serviceConfig . recoveryMode ? '--read-only' : '' ;
const cmd = serviceConfig . recoveryMode ? '/bin/bash -c \'echo "Debug mode. Sleeping" && sleep infinity\'' : '' ;
2021-08-25 19:41:46 -07:00
// memory options are applied dynamically. import requires all the memory we can get
2024-02-22 10:34:56 +01:00
const runCmd = ` docker run --restart=always -d --name=mysql \
2021-08-25 19:41:46 -07:00
-- hostname mysql \
-- net cloudron \
-- net - alias mysql \
-- 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 = mysql \
-- dns 172.18 . 0.1 \
-- dns - search = . \
2023-02-21 12:03:58 +01:00
-- ip $ { constants . MYSQL _SERVICE _IPv4 } \
2021-08-25 19:41:46 -07:00
- e CLOUDRON _MYSQL _TOKEN = $ { cloudronToken } \
- e CLOUDRON _MYSQL _ROOT _HOST = 172.18 . 0.1 \
- e CLOUDRON _MYSQL _ROOT _PASSWORD = $ { rootPassword } \
2024-02-22 10:34:56 +01:00
- v $ { dataDir } / mysql : / v a r / l i b / m y s q l \
2021-08-25 19:41:46 -07:00
-- label isCloudronManaged = true \
-- cap - add SYS _NICE \
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 ( 'startMysql: stopping and deleting previous mysql container' ) ;
await docker . stopContainer ( 'mysql' ) ;
await docker . deleteContainer ( 'mysql' ) ;
debug ( 'startMysql: starting mysql container' ) ;
2024-02-22 10:34:56 +01:00
await shell . exec ( 'startMysql' , runCmd , { shell : '/bin/bash' } ) ;
2021-08-25 19:41:46 -07:00
2021-10-01 14:38:47 -07:00
if ( ! serviceConfig . recoveryMode ) {
await waitForContainer ( 'mysql' , 'CLOUDRON_MYSQL_TOKEN' ) ;
if ( upgrading ) await importDatabase ( 'mysql' ) ;
}
2021-08-25 19:41:46 -07:00
}
async function setupMySql ( app , options ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
2015-10-07 16:10:08 -07:00
assert . strictEqual ( typeof options , 'object' ) ;
2015-07-20 00:09:47 -07:00
2021-10-01 09:17:44 -07:00
debug ( 'Setting up mysql' ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
const existingPassword = await addonConfigs . getByName ( app . id , 'mysql' , '%MYSQL_PASSWORD' ) ;
const tmp = mysqlDatabaseName ( app . id ) ;
const data = {
database : tmp ,
prefix : tmp ,
username : tmp ,
password : existingPassword || hat ( 4 * 48 ) // see box#362 for password length
} ;
const result = await getContainerDetails ( 'mysql' , 'CLOUDRON_MYSQL_TOKEN' ) ;
2021-12-20 10:52:42 -08:00
const [ networkError , response ] = await safe ( superagent . post ( ` http:// ${ result . ip } :3000/ ` + ( options . multipleDatabases ? 'prefixes' : 'databases' ) + ` ?access_token= ${ result . token } ` )
2021-08-25 19:41:46 -07:00
. send ( data )
. ok ( ( ) => true ) ) ;
if ( networkError ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Network error setting up mysql: ${ networkError . message } ` ) ;
if ( response . status !== 201 ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Error setting up mysql. Status code: ${ response . status } message: ${ response . body . message } ` ) ;
let env = [
2021-12-06 17:43:50 -08:00
{ name : 'CLOUDRON_MYSQL_USERNAME' , value : data . username } ,
{ name : 'CLOUDRON_MYSQL_PASSWORD' , value : data . password } ,
{ name : 'CLOUDRON_MYSQL_HOST' , value : 'mysql' } ,
{ name : 'CLOUDRON_MYSQL_PORT' , value : '3306' }
2021-08-25 19:41:46 -07:00
] ;
if ( options . multipleDatabases ) {
2021-12-06 17:43:50 -08:00
env = env . concat ( { name : 'CLOUDRON_MYSQL_DATABASE_PREFIX' , value : ` ${ data . prefix } _ ` } ) ;
2021-08-25 19:41:46 -07:00
} else {
env = env . concat (
2021-12-06 17:43:50 -08:00
{ name : 'CLOUDRON_MYSQL_URL' , value : ` mysql:// ${ data . username } : ${ data . password } @mysql/ ${ data . database } ` } ,
{ name : 'CLOUDRON_MYSQL_DATABASE' , value : data . database }
2021-08-25 19:41:46 -07:00
) ;
}
2021-10-01 09:17:44 -07:00
debug ( 'Setting mysql addon config to %j' , env ) ;
2021-08-25 19:41:46 -07:00
await addonConfigs . set ( app . id , 'mysql' , env ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-25 19:41:46 -07:00
async function clearMySql ( app , options ) {
2018-09-15 17:05:04 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2018-09-18 19:41:24 +02:00
const database = mysqlDatabaseName ( app . id ) ;
2018-09-15 17:05:04 -07:00
2021-08-25 19:41:46 -07:00
const result = await getContainerDetails ( 'mysql' , 'CLOUDRON_MYSQL_TOKEN' ) ;
2018-09-15 17:05:04 -07:00
2021-12-20 10:52:42 -08:00
const [ networkError , response ] = await safe ( superagent . post ( ` http:// ${ result . ip } :3000/ ` + ( options . multipleDatabases ? 'prefixes' : 'databases' ) + ` / ${ database } /clear?access_token= ${ result . token } ` )
2021-08-25 19:41:46 -07:00
. ok ( ( ) => true ) ) ;
2019-12-04 13:17:58 -08:00
2021-08-25 19:41:46 -07:00
if ( networkError ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Network error clearing mysql: ${ networkError . message } ` ) ;
if ( response . status !== 200 ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Error clearing mysql. Status code: ${ response . status } message: ${ response . body . message } ` ) ;
2018-09-15 17:05:04 -07:00
}
2021-08-25 19:41:46 -07:00
async function teardownMySql ( app , options ) {
2015-10-07 16:10:08 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2018-09-18 19:41:24 +02:00
const database = mysqlDatabaseName ( app . id ) ;
const username = database ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
const result = await getContainerDetails ( 'mysql' , 'CLOUDRON_MYSQL_TOKEN' ) ;
2015-07-20 00:09:47 -07:00
2021-12-20 10:52:42 -08:00
const [ networkError , response ] = await safe ( superagent . del ( ` http:// ${ result . ip } :3000/ ` + ( options . multipleDatabases ? 'prefixes' : 'databases' ) + ` / ${ database } ?access_token= ${ result . token } &username= ${ username } ` )
2021-08-25 19:41:46 -07:00
. ok ( ( ) => true ) ) ;
2018-09-18 19:41:24 +02:00
2021-08-25 19:41:46 -07:00
if ( networkError ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Error tearing down mysql: ${ networkError . message } ` ) ;
if ( response . status !== 200 ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Error tearing down mysql. Status code: ${ response . status } message: ${ response . body . message } ` ) ;
await addonConfigs . unset ( app . id , 'mysql' ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-25 19:41:46 -07:00
async function pipeRequestToFile ( url , filename ) {
2018-11-14 18:33:39 -08:00
assert . strictEqual ( typeof url , 'string' ) ;
assert . strictEqual ( typeof filename , 'string' ) ;
2021-12-15 17:35:56 -08:00
return new Promise ( ( resolve , reject ) => {
const writeStream = fs . createWriteStream ( filename ) ;
const request = http . request ( url , { method : 'POST' } ) ; // ClientRequest
2023-04-04 10:28:15 +02:00
request . setTimeout ( 4 * 60 * 60 * 1000 , ( ) => {
2022-05-09 17:08:43 -07:00
debug ( 'pipeRequestToFile: timeout - connect or post-connect idle timeout' ) ;
request . destroy ( ) ; // connect OR post-connect idle timeout
reject ( new Error ( 'Request timedout' ) ) ;
} ) ;
2021-12-15 17:35:56 -08:00
request . on ( 'error' , ( error ) => reject ( new BoxError ( BoxError . NETWORK _ERROR , ` Could not pipe ${ url } to ${ filename } : ${ error . message } ` ) ) ) ; // network error, dns error
request . on ( 'response' , ( response ) => {
2022-05-09 17:08:43 -07:00
debug ( ` pipeRequestToFile: connected with status code ${ response . statusCode } ` ) ;
2021-12-15 17:35:56 -08:00
if ( response . statusCode !== 200 ) return reject ( new BoxError ( BoxError . ADDONS _ERROR , ` Unexpected response code or HTTP error when piping ${ url } to ${ filename } : status ${ response . statusCode } ` ) ) ;
pipeline ( response , writeStream , ( error ) => {
if ( error ) return reject ( new BoxError ( BoxError . ADDONS _ERROR , ` Error piping ${ url } to ${ filename } : ${ error . message } ` ) ) ;
if ( ! response . complete ) return reject ( new BoxError ( BoxError . ADDONS _ERROR , ` Response not complete when piping ${ url } to ${ filename } ` ) ) ;
resolve ( ) ;
} ) ;
} ) ;
request . end ( ) ; // make the request
} ) ;
}
async function pipeFileToRequest ( filename , url ) {
assert . strictEqual ( typeof filename , 'string' ) ;
assert . strictEqual ( typeof url , 'string' ) ;
return new Promise ( ( resolve , reject ) => {
const readStream = fs . createReadStream ( filename ) ;
const request = http . request ( url , { method : 'POST' } ) ; // ClientRequest
2023-04-04 10:28:15 +02:00
request . setTimeout ( 4 * 60 * 60 * 1000 , ( ) => {
2022-05-09 17:08:43 -07:00
debug ( 'pipeFileToRequest: timeout - connect or post-connect idle timeout' ) ;
request . destroy ( ) ;
reject ( new Error ( 'Request timedout' ) ) ;
} ) ;
2021-12-15 17:35:56 -08:00
request . on ( 'response' , ( response ) => {
2022-05-09 17:08:43 -07:00
debug ( ` pipeFileToRequest: request completed with status code ${ response . statusCode } ` ) ;
2021-12-15 17:35:56 -08:00
response . resume ( ) ; // drain the response
if ( response . statusCode !== 200 ) return reject ( new BoxError ( BoxError . ADDONS _ERROR , ` Unexpected response code or HTTP error when piping ${ filename } to ${ url } : status ${ response . statusCode } complete ${ response . complete } ` ) ) ;
resolve ( ) ;
} ) ;
2022-05-09 17:08:43 -07:00
debug ( ` pipeFileToRequest: piping ${ filename } to ${ url } ` ) ;
2021-12-15 17:35:56 -08:00
pipeline ( readStream , request , function ( error ) {
if ( error ) return reject ( new BoxError ( BoxError . ADDONS _ERROR , ` Error piping file ${ filename } to request ${ url } ` ) ) ;
2022-05-09 17:08:43 -07:00
debug ( ` pipeFileToRequest: piped ${ filename } to ${ url } ` ) ; // now we have to wait for 'response' above
2021-12-15 17:35:56 -08:00
} ) ;
} ) ;
}
2021-08-25 19:41:46 -07:00
async function backupMySql ( app , options ) {
2018-02-08 15:07:49 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2018-09-18 19:41:24 +02:00
const database = mysqlDatabaseName ( app . id ) ;
2021-10-01 09:17:44 -07:00
debug ( 'Backing up mysql' ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
const result = await getContainerDetails ( 'mysql' , 'CLOUDRON_MYSQL_TOKEN' ) ;
2015-07-20 00:09:47 -07:00
2021-12-20 10:52:42 -08:00
const url = ` http:// ${ result . ip } :3000/ ` + ( options . multipleDatabases ? 'prefixes' : 'databases' ) + ` / ${ database } /backup?access_token= ${ result . token } ` ;
2021-08-25 19:41:46 -07:00
await pipeRequestToFile ( url , dumpPath ( 'mysql' , app . id ) ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-25 19:41:46 -07:00
async function restoreMySql ( app , options ) {
2018-02-08 15:07:49 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2018-09-18 19:41:24 +02:00
const database = mysqlDatabaseName ( app . id ) ;
2015-07-20 00:09:47 -07:00
2021-10-01 09:17:44 -07:00
debug ( 'restoreMySql' ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
const result = await getContainerDetails ( 'mysql' , 'CLOUDRON_MYSQL_TOKEN' ) ;
2018-09-18 19:41:24 +02:00
2021-12-20 10:52:42 -08:00
const url = ` http:// ${ result . ip } :3000/ ` + ( options . multipleDatabases ? 'prefixes' : 'databases' ) + ` / ${ database } /restore?access_token= ${ result . token } ` ;
await pipeFileToRequest ( dumpPath ( 'mysql' , app . id ) , url ) ;
2015-07-20 00:09:47 -07:00
}
2018-09-19 15:13:04 -07:00
function postgreSqlNames ( appId ) {
appId = appId . replace ( /-/g , '' ) ;
2018-09-19 15:46:29 -07:00
return { database : ` db ${ appId } ` , username : ` user ${ appId } ` } ;
2018-09-19 15:13:04 -07:00
}
2021-08-25 19:41:46 -07:00
async function startPostgresql ( existingInfra ) {
2018-10-16 14:07:41 -07:00
assert . strictEqual ( typeof existingInfra , 'object' ) ;
2023-08-08 10:42:16 +05:30
const image = infra . images . postgresql ;
2018-10-16 14:07:41 -07:00
const dataDir = paths . PLATFORM _DATA _DIR ;
const rootPassword = hat ( 8 * 128 ) ;
const cloudronToken = hat ( 8 * 128 ) ;
2023-08-08 10:42:16 +05:30
const upgrading = existingInfra . version !== 'none' && requiresUpgrade ( existingInfra . images . postgresql , image ) ;
2018-10-16 14:07:41 -07:00
2021-08-25 19:41:46 -07:00
if ( upgrading ) {
debug ( 'startPostgresql: postgresql will be upgraded' ) ;
await exportDatabase ( 'postgresql' ) ;
}
2018-10-16 14:07:41 -07:00
2021-10-01 12:09:13 -07:00
const serviceConfig = await getServiceConfig ( 'postgresql' ) ;
const readOnly = ! serviceConfig . recoveryMode ? '--read-only' : '' ;
const cmd = serviceConfig . recoveryMode ? '/bin/bash -c \'echo "Debug mode. Sleeping" && sleep infinity\'' : '' ;
2021-08-25 19:41:46 -07:00
// memory options are applied dynamically. import requires all the memory we can get
2024-02-22 10:34:56 +01:00
const runCmd = ` docker run --restart=always -d --name=postgresql \
2021-08-25 19:41:46 -07:00
-- hostname postgresql \
-- net cloudron \
-- net - alias postgresql \
-- 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 = postgresql \
-- dns 172.18 . 0.1 \
-- dns - search = . \
2023-02-21 12:03:58 +01:00
-- ip $ { constants . POSTGRESQL _SERVICE _IPv4 } \
2021-09-03 08:01:50 -07:00
-- shm - size = 128 M \
2024-02-22 10:34:56 +01:00
- e CLOUDRON _POSTGRESQL _ROOT _PASSWORD = $ { rootPassword } \
- e CLOUDRON _POSTGRESQL _TOKEN = $ { cloudronToken } \
- v $ { dataDir } / postgresql : / v a r / l i b / p o s t g r e s q l \
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 ( 'startPostgresqk: stopping and deleting previous postgresql container' ) ;
await docker . stopContainer ( 'postgresql' ) ;
await docker . deleteContainer ( 'postgresql' ) ;
debug ( 'startPostgresql: starting postgresql container' ) ;
2024-02-22 10:34:56 +01:00
await shell . exec ( 'startPostgresql' , runCmd , { shell : '/bin/bash' } ) ;
2021-08-25 19:41:46 -07:00
2021-10-01 14:38:47 -07:00
if ( ! serviceConfig . recoveryMode ) {
await waitForContainer ( 'postgresql' , 'CLOUDRON_POSTGRESQL_TOKEN' ) ;
if ( upgrading ) await importDatabase ( 'postgresql' ) ;
}
2021-08-25 19:41:46 -07:00
}
async function setupPostgreSql ( app , options ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
2015-10-07 16:10:08 -07:00
assert . strictEqual ( typeof options , 'object' ) ;
2015-07-20 00:09:47 -07:00
2021-10-01 09:17:44 -07:00
debug ( 'Setting up postgresql' ) ;
2015-07-20 00:09:47 -07:00
2018-09-19 15:13:04 -07:00
const { database , username } = postgreSqlNames ( app . id ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
const existingPassword = await addonConfigs . getByName ( app . id , 'postgresql' , '%POSTGRESQL_PASSWORD' ) ;
2018-05-16 20:04:35 -07:00
2021-08-25 19:41:46 -07:00
const data = {
database : database ,
username : username ,
password : existingPassword || hat ( 4 * 128 ) ,
locale : options . locale || 'C'
} ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
const result = await getContainerDetails ( 'postgresql' , 'CLOUDRON_POSTGRESQL_TOKEN' ) ;
2018-05-17 19:48:54 -07:00
2021-12-16 22:23:41 -08:00
const [ networkError , response ] = await safe ( superagent . post ( ` http:// ${ result . ip } :3000/databases?access_token= ${ result . token } ` )
2021-08-25 19:41:46 -07:00
. send ( data )
. ok ( ( ) => true ) ) ;
if ( networkError ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Network error setting up postgresql: ${ networkError . message } ` ) ;
if ( response . status !== 201 ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Error setting up postgresql. Status code: ${ response . status } message: ${ response . body . message } ` ) ;
2018-09-16 22:21:34 +02:00
2021-08-25 19:41:46 -07:00
const env = [
2021-12-06 17:43:50 -08:00
{ name : 'CLOUDRON_POSTGRESQL_URL' , value : ` postgres:// ${ data . username } : ${ data . password } @postgresql/ ${ data . database } ` } ,
{ name : 'CLOUDRON_POSTGRESQL_USERNAME' , value : data . username } ,
{ name : 'CLOUDRON_POSTGRESQL_PASSWORD' , value : data . password } ,
{ name : 'CLOUDRON_POSTGRESQL_HOST' , value : 'postgresql' } ,
{ name : 'CLOUDRON_POSTGRESQL_PORT' , value : '5432' } ,
{ name : 'CLOUDRON_POSTGRESQL_DATABASE' , value : data . database }
2021-08-25 19:41:46 -07:00
] ;
2018-09-16 22:21:34 +02:00
2021-10-01 09:17:44 -07:00
debug ( 'Setting postgresql addon config to %j' , env ) ;
2021-11-16 18:48:13 +01:00
await addonConfigs . set ( app . id , 'postgresql' , env ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-25 19:41:46 -07:00
async function clearPostgreSql ( app , options ) {
2018-09-15 17:05:04 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2018-09-19 15:13:04 -07:00
const { database , username } = postgreSqlNames ( app . id ) ;
2020-10-12 18:57:34 -07:00
const locale = options . locale || 'C' ;
2018-09-15 17:05:04 -07:00
2021-10-01 09:17:44 -07:00
debug ( 'Clearing postgresql' ) ;
2018-09-15 17:05:04 -07:00
2021-08-25 19:41:46 -07:00
const result = await getContainerDetails ( 'postgresql' , 'CLOUDRON_POSTGRESQL_TOKEN' ) ;
2018-09-15 17:05:04 -07:00
2021-12-16 22:23:41 -08:00
const [ networkError , response ] = await safe ( superagent . post ( ` http:// ${ result . ip } :3000/databases/ ${ database } /clear?access_token= ${ result . token } &username= ${ username } &locale= ${ locale } ` )
2021-08-25 19:41:46 -07:00
. ok ( ( ) => true ) ) ;
if ( networkError ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Network error clearing postgresql: ${ networkError . message } ` ) ;
if ( response . status !== 200 ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Error clearing postgresql. Status code: ${ response . status } message: ${ response . body . message } ` ) ;
2018-09-15 17:05:04 -07:00
}
2021-08-25 19:41:46 -07:00
async function teardownPostgreSql ( app , options ) {
2015-10-07 16:10:08 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2018-09-19 15:13:04 -07:00
const { database , username } = postgreSqlNames ( app . id ) ;
2018-05-16 20:04:35 -07:00
2021-08-25 19:41:46 -07:00
const result = await getContainerDetails ( 'postgresql' , 'CLOUDRON_POSTGRESQL_TOKEN' ) ;
2015-07-20 00:09:47 -07:00
2021-12-16 22:23:41 -08:00
const [ networkError , response ] = await safe ( superagent . del ( ` http:// ${ result . ip } :3000/databases/ ${ database } ?access_token= ${ result . token } &username= ${ username } ` )
2021-08-25 19:41:46 -07:00
. ok ( ( ) => true ) ) ;
if ( networkError ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Network error tearing down postgresql: ${ networkError . message } ` ) ;
if ( response . status !== 200 ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Error tearing down postgresql. Status code: ${ response . status } message: ${ response . body . message } ` ) ;
2018-09-16 22:21:34 +02:00
2021-08-25 19:41:46 -07:00
await addonConfigs . unset ( app . id , 'postgresql' ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-25 19:41:46 -07:00
async function backupPostgreSql ( app , options ) {
2018-02-08 15:07:49 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2021-10-01 09:17:44 -07:00
debug ( 'Backing up postgresql' ) ;
2015-07-20 00:09:47 -07:00
2018-09-19 15:13:04 -07:00
const { database } = postgreSqlNames ( app . id ) ;
2021-08-25 19:41:46 -07:00
const result = await getContainerDetails ( 'postgresql' , 'CLOUDRON_POSTGRESQL_TOKEN' ) ;
2021-12-20 10:52:42 -08:00
await pipeRequestToFile ( ` http:// ${ result . ip } :3000/databases/ ${ database } /backup?access_token= ${ result . token } ` , dumpPath ( 'postgresql' , app . id ) ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-25 19:41:46 -07:00
async function restorePostgreSql ( app , options ) {
2018-02-08 15:07:49 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2021-10-01 09:17:44 -07:00
debug ( 'Restore postgresql' ) ;
2015-07-20 00:09:47 -07:00
2018-09-19 15:13:04 -07:00
const { database , username } = postgreSqlNames ( app . id ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
const result = await getContainerDetails ( 'postgresql' , 'CLOUDRON_POSTGRESQL_TOKEN' ) ;
2015-07-20 00:09:47 -07:00
2021-12-16 22:23:41 -08:00
await pipeFileToRequest ( dumpPath ( 'postgresql' , app . id ) , ` http:// ${ result . ip } :3000/databases/ ${ database } /restore?access_token= ${ result . token } &username= ${ username } ` ) ;
2015-07-20 00:09:47 -07:00
}
2024-03-30 18:31:57 +01:00
async function hasAVX ( ) {
// mongodb 5 and above requires AVX
2024-04-01 22:49:22 +02:00
const [ error ] = await safe ( shell . exec ( 'hasAVX' , 'grep -q avx /proc/cpuinfo' , { } ) ) ;
2024-03-30 18:31:57 +01:00
return ! error ;
}
2021-08-25 19:41:46 -07:00
async function startMongodb ( existingInfra ) {
2020-03-27 21:37:06 +01:00
assert . strictEqual ( typeof existingInfra , 'object' ) ;
2018-10-16 14:07:41 -07:00
2023-08-08 10:42:16 +05:30
const image = infra . images . mongodb ;
2018-10-16 14:07:41 -07:00
const dataDir = paths . PLATFORM _DATA _DIR ;
const rootPassword = hat ( 8 * 128 ) ;
const cloudronToken = hat ( 8 * 128 ) ;
2023-08-08 10:42:16 +05:30
const upgrading = existingInfra . version !== 'none' && requiresUpgrade ( existingInfra . images . mongodb , image ) ;
2018-10-16 14:07:41 -07:00
2021-08-25 19:41:46 -07:00
if ( upgrading ) {
debug ( 'startMongodb: mongodb will be upgraded' ) ;
await exportDatabase ( 'mongodb' ) ;
}
2018-10-16 14:07:41 -07:00
2021-10-01 12:09:13 -07:00
const serviceConfig = await getServiceConfig ( 'mongodb' ) ;
const readOnly = ! serviceConfig . recoveryMode ? '--read-only' : '' ;
const cmd = serviceConfig . recoveryMode ? '/bin/bash -c \'echo "Debug mode. Sleeping" && sleep infinity\'' : '' ;
2021-08-25 19:41:46 -07:00
// memory options are applied dynamically. import requires all the memory we can get
2024-02-22 10:34:56 +01:00
const runCmd = ` docker run --restart=always -d --name=mongodb \
2021-08-25 19:41:46 -07:00
-- hostname mongodb \
-- net cloudron \
-- net - alias mongodb \
-- 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 = mongodb \
-- dns 172.18 . 0.1 \
-- dns - search = . \
2023-02-21 12:03:58 +01:00
-- ip $ { constants . MONGODB _SERVICE _IPv4 } \
2024-02-22 10:34:56 +01:00
- e CLOUDRON _MONGODB _ROOT _PASSWORD = $ { rootPassword } \
- e CLOUDRON _MONGODB _TOKEN = $ { cloudronToken } \
- v $ { dataDir } / mongodb : / v a r / l i b / m o n g o d b \
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 ( 'startMongodb: stopping and deleting previous mongodb container' ) ;
await docker . stopContainer ( 'mongodb' ) ;
await docker . deleteContainer ( 'mongodb' ) ;
2024-03-30 18:31:57 +01:00
if ( ! await hasAVX ( ) ) {
debug ( 'startMongodb: not starting mongodb because CPU does not have AVX' ) ;
return ;
}
2024-02-28 18:47:53 +01:00
debug ( 'startMongodb: starting mongodb container' ) ;
2024-02-22 10:34:56 +01:00
await shell . exec ( 'startMongodb' , runCmd , { shell : '/bin/bash' } ) ;
2021-08-25 19:41:46 -07:00
2021-10-01 14:38:47 -07:00
if ( ! serviceConfig . recoveryMode ) {
await waitForContainer ( 'mongodb' , 'CLOUDRON_MONGODB_TOKEN' ) ;
if ( upgrading ) await importDatabase ( 'mongodb' ) ;
}
2021-08-25 19:41:46 -07:00
}
async function setupMongoDb ( app , options ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
2015-10-07 16:10:08 -07:00
assert . strictEqual ( typeof options , 'object' ) ;
2015-07-20 00:09:47 -07:00
2021-10-01 09:17:44 -07:00
debug ( 'Setting up mongodb' ) ;
2015-07-20 00:09:47 -07:00
2024-03-30 18:31:57 +01:00
if ( ! await hasAVX ( ) ) throw new BoxError ( BoxError . ADDONS _ERROR , 'Error setting up mongodb. CPU has no AVX support' ) ;
2021-08-25 19:41:46 -07:00
const existingPassword = await addonConfigs . getByName ( app . id , 'mongodb' , '%MONGODB_PASSWORD' ) ;
let database = await addonConfigs . getByName ( app . id , 'mongodb' , '%MONGODB_DATABASE' ) ;
database = database || hat ( 8 * 8 ) ; // 16 bytes. keep this short, so as to not overflow the 127 byte index length in MongoDB < 4.4
2021-08-22 15:35:46 -07:00
2021-08-25 19:41:46 -07:00
const data = {
database ,
username : app . id ,
password : existingPassword || hat ( 4 * 128 ) ,
oplog : ! ! options . oplog
} ;
2020-08-17 10:02:46 -07:00
2021-08-25 19:41:46 -07:00
const result = await getContainerDetails ( 'mongodb' , 'CLOUDRON_MONGODB_TOKEN' ) ;
2018-05-17 19:48:54 -07:00
2021-12-16 22:23:41 -08:00
const [ networkError , response ] = await safe ( superagent . post ( ` http:// ${ result . ip } :3000/databases?access_token= ${ result . token } ` )
2021-08-25 19:41:46 -07:00
. send ( data )
. ok ( ( ) => true ) ) ;
2017-03-25 14:14:57 -07:00
2021-08-25 19:41:46 -07:00
if ( networkError ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Network error setting up mongodb: ${ networkError . message } ` ) ;
if ( response . status !== 201 ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Error setting up mongodb. Status code: ${ response . status } message: ${ response . body . message } ` ) ;
2018-09-11 19:27:06 +02:00
2021-08-25 19:41:46 -07:00
const env = [
2021-12-06 17:43:50 -08:00
{ name : 'CLOUDRON_MONGODB_URL' , value : ` mongodb:// ${ data . username } : ${ data . password } @mongodb:27017/ ${ data . database } ` } ,
{ name : 'CLOUDRON_MONGODB_USERNAME' , value : data . username } ,
{ name : 'CLOUDRON_MONGODB_PASSWORD' , value : data . password } ,
{ name : 'CLOUDRON_MONGODB_HOST' , value : 'mongodb' } ,
{ name : 'CLOUDRON_MONGODB_PORT' , value : '27017' } ,
{ name : 'CLOUDRON_MONGODB_DATABASE' , value : data . database }
2021-08-25 19:41:46 -07:00
] ;
2018-09-11 19:27:06 +02:00
2021-08-25 19:41:46 -07:00
if ( options . oplog ) {
2021-12-06 17:43:50 -08:00
env . push ( { name : 'CLOUDRON_MONGODB_OPLOG_URL' , value : ` mongodb:// ${ data . username } : ${ data . password } @mongodb:27017/local?authSource= ${ data . database } ` } ) ;
2021-08-25 19:41:46 -07:00
}
2019-06-26 21:49:25 -07:00
2021-10-01 09:17:44 -07:00
debug ( 'Setting mongodb addon config to %j' , env ) ;
2021-08-25 19:41:46 -07:00
await addonConfigs . set ( app . id , 'mongodb' , env ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-25 19:41:46 -07:00
async function clearMongodb ( app , options ) {
2018-09-15 17:05:04 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2024-03-30 18:31:57 +01:00
if ( ! await hasAVX ( ) ) throw new BoxError ( BoxError . ADDONS _ERROR , 'Error clearing mongodb. CPU has no AVX support' ) ;
2021-08-25 19:41:46 -07:00
const result = await getContainerDetails ( 'mongodb' , 'CLOUDRON_MONGODB_TOKEN' ) ;
2018-09-15 17:05:04 -07:00
2021-08-25 19:41:46 -07:00
const database = await addonConfigs . getByName ( app . id , 'mongodb' , '%MONGODB_DATABASE' ) ;
if ( ! database ) throw new BoxError ( BoxError . NOT _FOUND , 'Error clearing mongodb. No database' ) ;
2018-09-15 17:05:04 -07:00
2021-12-16 22:23:41 -08:00
const [ networkError , response ] = await safe ( superagent . post ( ` http:// ${ result . ip } :3000/databases/ ${ database } /clear?access_token= ${ result . token } ` )
2021-08-25 19:41:46 -07:00
. ok ( ( ) => true ) ) ;
2020-08-17 10:02:46 -07:00
2021-08-25 19:41:46 -07:00
if ( networkError ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Network error clearing mongodb: ${ networkError . message } ` ) ;
if ( response . status !== 200 ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Error clearing mongodb. Status code: ${ response . status } message: ${ response . body . message } ` ) ;
2018-09-15 17:05:04 -07:00
}
2021-08-25 19:41:46 -07:00
async function teardownMongoDb ( app , options ) {
2015-10-07 16:10:08 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2024-03-30 18:31:57 +01:00
if ( ! await hasAVX ( ) ) throw new BoxError ( BoxError . ADDONS _ERROR , 'Error tearing down mongodb. CPU has no AVX support' ) ;
2021-08-25 19:41:46 -07:00
const result = await getContainerDetails ( 'mongodb' , 'CLOUDRON_MONGODB_TOKEN' ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
const database = await addonConfigs . getByName ( app . id , 'mongodb' , '%MONGODB_DATABASE' ) ;
if ( ! database ) return ;
2018-09-11 19:27:06 +02:00
2021-12-16 22:23:41 -08:00
const [ networkError , response ] = await safe ( superagent . del ( ` http:// ${ result . ip } :3000/databases/ ${ database } ?access_token= ${ result . token } ` )
2021-08-25 19:41:46 -07:00
. ok ( ( ) => true ) ) ;
2020-08-17 10:02:46 -07:00
2021-08-25 19:41:46 -07:00
if ( networkError ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Error tearing down mongodb: ${ networkError . message } ` ) ;
if ( response . status !== 200 ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Error tearing down mongodb. Status code: ${ response . status } message: ${ response . body . message } ` ) ;
addonConfigs . unset ( app . id , 'mongodb' ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-25 19:41:46 -07:00
async function backupMongoDb ( app , options ) {
2018-02-08 15:07:49 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2021-10-01 09:17:44 -07:00
debug ( 'Backing up mongodb' ) ;
2015-07-20 00:09:47 -07:00
2024-03-30 18:31:57 +01:00
if ( ! await hasAVX ( ) ) throw new BoxError ( BoxError . ADDONS _ERROR , 'Error backing up mongodb. CPU has no AVX support' ) ;
2021-08-25 19:41:46 -07:00
const result = await getContainerDetails ( 'mongodb' , 'CLOUDRON_MONGODB_TOKEN' ) ;
2018-09-11 21:43:16 +02:00
2021-08-25 19:41:46 -07:00
const database = await addonConfigs . getByName ( app . id , 'mongodb' , '%MONGODB_DATABASE' ) ;
if ( ! database ) throw new BoxError ( BoxError . NOT _FOUND , 'Error backing up mongodb. No database' ) ;
2020-08-17 10:02:46 -07:00
2021-12-20 10:52:42 -08:00
await pipeRequestToFile ( ` http:// ${ result . ip } :3000/databases/ ${ database } /backup?access_token= ${ result . token } ` , dumpPath ( 'mongodb' , app . id ) ) ;
2015-07-20 00:09:47 -07:00
}
2021-08-25 19:41:46 -07:00
async function restoreMongoDb ( app , options ) {
2018-02-08 15:07:49 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2015-07-20 00:09:47 -07:00
2021-10-01 09:17:44 -07:00
debug ( 'restoreMongoDb' ) ;
2018-09-15 17:05:04 -07:00
2024-03-30 18:31:57 +01:00
if ( ! await hasAVX ( ) ) throw new BoxError ( BoxError . ADDONS _ERROR , 'Error restoring mongodb. CPU has no AVX support' ) ;
2021-08-25 19:41:46 -07:00
const result = await getContainerDetails ( 'mongodb' , 'CLOUDRON_MONGODB_TOKEN' ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
const database = await addonConfigs . getByName ( app . id , 'mongodb' , '%MONGODB_DATABASE' ) ;
if ( ! database ) throw new BoxError ( BoxError . NOT _FOUND , 'Error restoring mongodb. No database' ) ;
2015-07-20 00:09:47 -07:00
2021-12-16 22:23:41 -08:00
await pipeFileToRequest ( dumpPath ( 'mongodb' , app . id ) , ` http:// ${ result . ip } :3000/databases/ ${ database } /restore?access_token= ${ result . token } ` ) ;
2015-07-20 00:09:47 -07:00
}
2024-04-01 12:04:48 +02:00
async function statusMongodb ( ) {
if ( ! await hasAVX ( ) ) return { status : exports . SERVICE _STATUS _DISABLED } ;
return await containerStatus ( 'mongodb' , 'CLOUDRON_MONGODB_TOKEN' ) ;
}
async function restartMongodb ( ) {
2024-04-01 22:49:22 +02:00
if ( ! await hasAVX ( ) ) throw new BoxError ( BoxError . ADDONS _ERROR , 'MongoDB is disabled. CPU has no AVX support' ) ;
2024-04-01 12:04:48 +02:00
return await docker . restartContainer ( 'mongodb' ) ;
}
2021-09-26 22:48:14 -07:00
async function startGraphite ( existingInfra ) {
2021-03-23 10:48:13 -07:00
assert . strictEqual ( typeof existingInfra , 'object' ) ;
2021-09-26 22:48:14 -07:00
const serviceConfig = await getServiceConfig ( 'graphite' ) ;
2023-08-08 10:42:16 +05:30
const image = infra . images . graphite ;
2021-03-23 10:48:13 -07:00
const memoryLimit = serviceConfig . memoryLimit || 256 * 1024 * 1024 ;
2023-08-08 10:42:16 +05:30
const upgrading = existingInfra . version !== 'none' && requiresUpgrade ( existingInfra . images . graphite , image ) ;
2021-03-23 10:48:13 -07:00
if ( upgrading ) debug ( 'startGraphite: graphite will be upgraded' ) ;
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\'' : '' ;
2022-10-11 12:44:37 +02:00
// port 2003 is used by collectd
2024-02-22 10:34:56 +01:00
const runCmd = ` docker run --restart=always -d --name=graphite \
2021-03-23 10:48:13 -07:00
-- hostname graphite \
-- net cloudron \
-- net - alias graphite \
-- log - driver syslog \
2024-03-21 17:30:50 +01:00
-- log - opt syslog - address = unix : //${paths.SYSLOG_SOCKET_FILE} \
2021-03-23 10:48:13 -07:00
-- log - opt syslog - format = rfc5424 \
-- log - opt tag = graphite \
2024-04-09 18:59:40 +02:00
- m $ { memoryLimit } \
-- memory - swap - 1 \
2021-03-23 10:48:13 -07:00
-- dns 172.18 . 0.1 \
-- dns - search = . \
- p 127.0 . 0.1 : 2003 : 2003 \
2024-02-22 10:34:56 +01:00
- v $ { paths . PLATFORM _DATA _DIR } / graphite : / v a r / l i b / g r a p h i t e \
2021-03-23 10:48:13 -07:00
-- label isCloudronManaged = true \
2024-02-22 10:34:56 +01:00
$ { readOnly } - v / tmp - v / run $ { image } $ { cmd } ` ;
2021-03-23 10:48:13 -07:00
2024-02-28 18:47:53 +01:00
debug ( 'startGraphite: stopping and deleting previous graphite container' ) ;
await docker . stopContainer ( 'graphite' ) ;
await docker . deleteContainer ( 'graphite' ) ;
2021-08-25 19:41:46 -07:00
if ( upgrading ) await shell . promises . sudo ( 'removeGraphiteDir' , [ RMADDONDIR _CMD , 'graphite' ] , { } ) ;
2024-02-28 18:47:53 +01:00
debug ( 'startGraphite: starting graphite container' ) ;
2024-02-22 10:34:56 +01:00
await shell . exec ( 'startGraphite' , runCmd , { shell : '/bin/bash' } ) ;
2021-08-25 19:41:46 -07:00
// restart collectd to get the disk stats after graphite starts. currently, there is no way to do graphite health check
2021-08-30 09:24:55 -07:00
setTimeout ( async ( ) => await safe ( shell . promises . sudo ( 'restartcollectd' , [ RESTART _SERVICE _CMD , 'collectd' ] , { } ) ) , 60000 ) ;
2021-03-23 10:48:13 -07:00
}
2021-08-25 19:41:46 -07:00
async function setupProxyAuth ( app , options ) {
2020-11-26 15:04:25 -08:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2021-10-01 09:17:44 -07:00
debug ( 'Setting up proxyAuth' ) ;
2020-11-26 15:04:25 -08:00
const enabled = app . sso && app . manifest . addons && app . manifest . addons . proxyAuth ;
2021-08-25 19:41:46 -07:00
if ( ! enabled ) return ;
2020-11-26 15:04:25 -08:00
const env = [ { name : 'CLOUDRON_PROXY_AUTH' , value : '1' } ] ;
2021-08-25 19:41:46 -07:00
await addonConfigs . set ( app . id , 'proxyauth' , env ) ;
2024-04-15 12:35:03 +02:00
debug ( 'Creating OpenID client for proxyAuth' ) ;
// openid client_id is appId for now
const [ error , result ] = await safe ( oidc . clients . get ( app . id ) ) ;
if ( error ) throw error ;
// ensure we keep the secret
const data = {
secret : result ? result . secret : hat ( 4 * 128 ) ,
loginRedirectUri : ` https:// ${ app . fqdn } /callback ` ,
logoutRedirectUri : '' ,
tokenSignatureAlgorithm : 'RS256' ,
name : '' ,
appId : app . id
} ;
if ( result ) await oidc . clients . update ( app . id , data ) ;
else await oidc . clients . add ( app . id , data ) ;
2020-11-26 15:04:25 -08:00
}
2021-08-25 19:41:46 -07:00
async function teardownProxyAuth ( app , options ) {
2020-11-26 15:04:25 -08:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2021-08-25 19:41:46 -07:00
await addonConfigs . unset ( app . id , 'proxyauth' ) ;
2024-04-15 12:35:03 +02:00
debug ( 'Deleting OpenID client for proxyAuth' ) ;
const [ error ] = await safe ( oidc . clients . del ( app . id ) ) ;
if ( error && error . reason !== BoxError . NOT _FOUND ) throw error ;
2020-11-26 15:04:25 -08:00
}
2024-03-01 10:31:41 +01:00
async function setupDocker ( app , options ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
debug ( 'Setting up docker' ) ;
const env = [ { name : 'CLOUDRON_DOCKER_HOST' , value : ` tcp://172.18.0.1: ${ constants . DOCKER _PROXY _PORT } ` } ] ;
await addonConfigs . set ( app . id , 'docker' , env ) ;
}
async function teardownDocker ( app , options ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
await addonConfigs . unset ( app . id , 'docker' ) ;
}
2021-08-25 19:41:46 -07:00
async function startRedis ( existingInfra ) {
2018-10-16 15:40:40 -07:00
assert . strictEqual ( typeof existingInfra , 'object' ) ;
2023-08-08 10:42:16 +05:30
const image = infra . images . redis ;
const upgrading = existingInfra . version !== 'none' && requiresUpgrade ( existingInfra . images . redis , image ) ;
2018-10-17 10:48:19 -07:00
2021-08-25 19:41:46 -07:00
const allApps = await apps . list ( ) ;
2018-10-17 10:48:19 -07:00
2021-08-25 19:41:46 -07:00
for ( const app of allApps ) {
if ( ! ( 'redis' in app . manifest . addons ) ) continue ; // app doesn't use the addon
2019-11-06 09:38:20 -08:00
2022-06-23 15:52:59 -07:00
const redisName = ` redis- ${ app . id } ` ;
2020-07-30 14:36:11 -07:00
2021-08-25 19:41:46 -07:00
if ( upgrading ) await backupRedis ( app , { } ) ;
2019-11-06 09:38:20 -08:00
2024-02-28 18:47:53 +01:00
debug ( ` startRedis: stopping and deleting previous redis container ${ redisName } ` ) ;
await docker . stopContainer ( redisName ) ;
await docker . deleteContainer ( redisName ) ;
debug ( ` startRedis: starting redis container ${ redisName } ` ) ;
2021-08-25 19:41:46 -07:00
await setupRedis ( app , app . manifest . addons . redis ) ; // starts the container
}
2019-11-06 09:38:20 -08:00
2021-08-25 19:41:46 -07:00
if ( upgrading ) await importDatabase ( 'redis' ) ;
2018-10-16 15:40:40 -07:00
}
2015-07-20 00:09:47 -07:00
// Ensures that app's addon redis container is running. Can be called when named container already exists/running
2021-08-25 19:41:46 -07:00
async function setupRedis ( app , options ) {
2015-10-07 16:10:08 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2018-09-18 14:15:23 -07:00
const redisName = 'redis-' + app . id ;
2015-07-20 00:09:47 -07:00
2023-07-13 16:37:33 +05:30
const disabled = app . manifest . addons . redis . optional && ! app . enableRedis ;
if ( disabled ) return await addonConfigs . unset ( app . id , 'redis' ) ;
2021-08-25 19:41:46 -07:00
const existingPassword = await addonConfigs . getByName ( app . id , 'redis' , '%REDIS_PASSWORD' ) ;
const redisPassword = options . noPassword ? '' : ( existingPassword || hat ( 4 * 48 ) ) ; // see box#362 for password length
const redisServiceToken = hat ( 4 * 48 ) ;
// Compute redis memory limit based on app's memory limit (this is arbitrary)
2021-10-01 12:09:13 -07:00
const memoryLimit = app . servicesConfig [ 'redis' ] ? . memoryLimit || APP _SERVICES [ 'redis' ] . defaultMemoryLimit ;
2021-08-25 19:41:46 -07:00
2021-10-01 12:09:13 -07:00
const recoveryMode = app . servicesConfig [ 'redis' ] ? . recoveryMode || false ;
const readOnly = ! recoveryMode ? '--read-only' : '' ;
const cmd = recoveryMode ? '/bin/bash -c \'echo "Debug mode. Sleeping" && sleep infinity\'' : '' ;
2023-08-08 10:42:16 +05:30
const image = infra . images . redis ;
2021-08-25 19:41:46 -07:00
const label = app . fqdn ;
// note that we do not add appId label because this interferes with the stop/start app logic
2021-10-01 12:09:13 -07:00
const runCmd = ` docker run --restart=always -d --name= ${ redisName } \
2021-08-25 19:41:46 -07:00
-- hostname $ { redisName } \
-- label = location = $ { label } \
-- net cloudron \
-- net - alias $ { redisName } \
-- 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 \
2024-02-22 10:34:56 +01:00
-- log - opt tag = $ { redisName } \
2024-04-09 18:59:40 +02:00
- m $ { memoryLimit } \
-- memory - swap - 1 \
2021-08-25 19:41:46 -07:00
-- dns 172.18 . 0.1 \
-- dns - search = . \
2024-02-22 10:34:56 +01:00
- e CLOUDRON _REDIS _PASSWORD = $ { redisPassword } \
- e CLOUDRON _REDIS _TOKEN = $ { redisServiceToken } \
- v $ { paths . PLATFORM _DATA _DIR } / redis / $ { app . id } : / v a r / l i b / r e d i s \
2021-08-25 19:41:46 -07:00
-- label isCloudronManaged = true \
2023-08-08 10:42:16 +05:30
$ { readOnly } - v / tmp - v / run $ { image } $ { cmd } ` ;
2021-08-25 19:41:46 -07:00
const env = [
2023-11-14 10:50:22 +01:00
{ name : 'CLOUDRON_REDIS_URL' , value : 'redis://default:' + redisPassword + '@redis-' + app . id } , // https://github.com/redis/node-redis/issues/1591
2021-12-06 17:43:50 -08:00
{ name : 'CLOUDRON_REDIS_PASSWORD' , value : redisPassword } ,
{ name : 'CLOUDRON_REDIS_HOST' , value : redisName } ,
{ name : 'CLOUDRON_REDIS_PORT' , value : '6379' }
2021-08-25 19:41:46 -07:00
] ;
2021-08-30 18:52:02 -07:00
const [ inspectError , result ] = await safe ( docker . inspect ( redisName ) ) ;
2021-08-25 19:41:46 -07:00
if ( inspectError ) {
2024-02-22 10:34:56 +01:00
await shell . exec ( 'startRedis' , runCmd , { shell : '/bin/bash' } ) ;
2021-08-25 19:41:46 -07:00
} else { // fast path
debug ( ` Re-using existing redis container with state: ${ JSON . stringify ( result . State ) } ` ) ;
}
2021-10-01 14:38:47 -07:00
if ( ! recoveryMode ) {
await addonConfigs . set ( app . id , 'redis' , env ) ;
await waitForContainer ( 'redis-' + app . id , 'CLOUDRON_REDIS_TOKEN' ) ;
}
2015-07-20 00:09:47 -07:00
}
2021-08-25 19:41:46 -07:00
async function clearRedis ( app , options ) {
2018-09-15 17:05:04 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2021-10-01 09:17:44 -07:00
debug ( 'Clearing redis' ) ;
2018-09-15 17:05:04 -07:00
2023-10-11 14:53:25 +05:30
const disabled = app . manifest . addons . redis . optional && ! app . enableRedis ;
if ( disabled ) return ;
2021-08-25 19:41:46 -07:00
const result = await getContainerDetails ( 'redis-' + app . id , 'CLOUDRON_REDIS_TOKEN' ) ;
2018-09-15 17:05:04 -07:00
2021-12-15 17:35:56 -08:00
const [ networkError , response ] = await safe ( superagent . post ( ` http:// ${ result . ip } :3000/clear?access_token= ${ result . token } ` )
2021-08-25 19:41:46 -07:00
. ok ( ( ) => true ) ) ;
2018-09-15 17:05:04 -07:00
2021-08-25 19:41:46 -07:00
if ( networkError ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Network error clearing redis: ${ networkError . message } ` ) ;
if ( response . status !== 200 ) throw new BoxError ( BoxError . ADDONS _ERROR , ` Error clearing redis. Status code: ${ response . status } message: ${ response . body . message } ` ) ;
2018-09-15 17:05:04 -07:00
}
2021-08-25 19:41:46 -07:00
async function teardownRedis ( app , options ) {
2015-10-07 16:10:08 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2021-08-30 11:42:46 -07:00
await docker . deleteContainer ( ` redis- ${ app . id } ` ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
const [ error ] = await safe ( shell . promises . sudo ( 'removeVolume' , [ RMADDONDIR _CMD , 'redis' , app . id ] , { } ) ) ;
if ( error ) throw new BoxError ( BoxError . FS _ERROR , ` Error removing redis data: ${ error . message } ` ) ;
2015-07-20 00:09:47 -07:00
2021-08-25 19:41:46 -07:00
safe . fs . rmSync ( path . join ( paths . LOG _DIR , ` redis- ${ app . id } ` ) , { recursive : true , force : true } ) ;
2023-04-16 10:49:59 +02:00
if ( safe . error ) debug ( 'teardownRedis: cannot cleanup logs: %o' , safe . error ) ;
2018-09-18 14:15:23 -07:00
2021-08-25 19:41:46 -07:00
await addonConfigs . unset ( app . id , 'redis' ) ;
2015-07-20 00:09:47 -07:00
}
2015-10-12 13:29:27 -07:00
2021-08-25 19:41:46 -07:00
async function backupRedis ( app , options ) {
2018-02-08 15:07:49 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2023-07-14 09:03:23 +05:30
const disabled = app . manifest . addons . redis . optional && ! app . enableRedis ;
if ( disabled ) return ;
2021-10-01 09:17:44 -07:00
debug ( 'Backing up redis' ) ;
2015-10-12 13:29:27 -07:00
2021-08-25 19:41:46 -07:00
const result = await getContainerDetails ( 'redis-' + app . id , 'CLOUDRON_REDIS_TOKEN' ) ;
2021-12-20 10:52:42 -08:00
await pipeRequestToFile ( ` http:// ${ result . ip } :3000/backup?access_token= ${ result . token } ` , dumpPath ( 'redis' , app . id ) ) ;
2018-09-18 12:28:03 -07:00
}
2021-08-25 19:41:46 -07:00
async function restoreRedis ( app , options ) {
2018-09-18 12:28:03 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2023-07-14 09:03:23 +05:30
const disabled = app . manifest . addons . redis . optional && ! app . enableRedis ;
if ( disabled ) return ;
2021-10-01 09:17:44 -07:00
debug ( 'Restoring redis' ) ;
2018-09-18 12:28:03 -07:00
2021-08-25 19:41:46 -07:00
const result = await getContainerDetails ( 'redis-' + app . id , 'CLOUDRON_REDIS_TOKEN' ) ;
2021-12-15 17:35:56 -08:00
await pipeFileToRequest ( dumpPath ( 'redis' , app . id ) , ` http:// ${ result . ip } :3000/restore?access_token= ${ result . token } ` ) ;
2015-10-12 13:29:27 -07:00
}
2018-11-15 19:59:08 +01:00
2022-11-28 22:32:34 +01:00
async function setupTls ( app , options ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
if ( ! safe . fs . mkdirSync ( ` ${ paths . PLATFORM _DATA _DIR } /tls/ ${ app . id } ` , { recursive : true } ) ) {
debug ( 'Error creating tls directory' ) ;
throw new BoxError ( BoxError . FS _ERROR , safe . error . message ) ;
}
}
async function teardownTls ( app , options ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
safe . fs . rmSync ( ` ${ paths . PLATFORM _DATA _DIR } /tls/ ${ app . id } ` , { recursive : true , force : true } ) ;
}
2021-08-25 19:41:46 -07:00
async function statusTurn ( ) {
const [ error , container ] = await safe ( docker . inspect ( 'turn' ) ) ;
if ( error && error . reason === BoxError . NOT _FOUND ) return { status : exports . SERVICE _STATUS _STOPPED } ;
if ( error ) throw error ;
2020-03-27 21:37:06 +01:00
2021-08-25 19:41:46 -07:00
const result = await docker . memoryUsage ( container . Id ) ;
2020-03-27 21:37:06 +01:00
2021-10-19 15:51:22 -07:00
const status = container . State . Running
? ( container . HostConfig . ReadonlyRootfs ? exports . SERVICE _STATUS _ACTIVE : exports . SERVICE _STATUS _STARTING )
: exports . 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
2021-08-25 19:41:46 -07:00
return {
2021-10-19 15:51:22 -07:00
status ,
2022-11-24 00:40:40 +01:00
memoryUsed : stats . usage ,
memoryPercent : parseInt ( 100 * stats . usage / stats . limit )
2021-08-25 19:41:46 -07:00
} ;
2020-03-27 21:37:06 +01:00
}
2021-08-25 19:41:46 -07:00
async function statusDocker ( ) {
const [ error ] = await safe ( docker . ping ( ) ) ;
return { status : error ? exports . SERVICE _STATUS _STOPPED : exports . SERVICE _STATUS _ACTIVE } ;
2018-11-15 19:59:08 +01:00
}
2018-11-23 15:49:47 +01:00
2021-08-25 19:41:46 -07:00
async function restartDocker ( ) {
const [ error ] = await safe ( shell . promises . sudo ( 'restartdocker' , [ RESTART _SERVICE _CMD , 'docker' ] , { } ) ) ;
if ( error ) debug ( ` restartDocker: error restarting docker. ${ error . message } ` ) ;
2018-11-25 14:43:29 -08:00
}
2018-12-02 19:38:21 -08:00
2021-08-25 19:41:46 -07:00
async function statusUnbound ( ) {
2024-02-21 19:40:27 +01:00
const [ error ] = await safe ( shell . exec ( 'statusUnbound' , 'systemctl is-active unbound' , { } ) ) ;
2023-03-26 20:52:58 +02:00
if ( error ) return { status : exports . SERVICE _STATUS _STOPPED } ;
const [ digError , digResult ] = await safe ( dig . resolve ( 'ipv4.api.cloudron.io' , 'A' , { server : '127.0.0.1' , timeout : 10000 } ) ) ;
if ( ! digError && Array . isArray ( digResult ) && digResult . length !== 0 ) return { status : exports . SERVICE _STATUS _ACTIVE } ;
2023-04-16 10:49:59 +02:00
debug ( 'statusUnbound: unbound is up, but failed to resolve ipv4.api.cloudron.io . %o %j' , digError , digResult ) ;
2023-03-26 20:52:58 +02:00
return { status : exports . SERVICE _STATUS _STARTING } ;
2018-12-02 19:38:21 -08:00
}
2021-08-25 19:41:46 -07:00
async function restartUnbound ( ) {
const [ error ] = await safe ( shell . promises . sudo ( 'restartunbound' , [ RESTART _SERVICE _CMD , 'unbound' ] , { } ) ) ;
if ( error ) debug ( ` restartDocker: error restarting unbound. ${ error . message } ` ) ;
2018-12-02 19:38:21 -08:00
}
2019-03-18 19:02:32 -07:00
2021-08-25 19:41:46 -07:00
async function statusNginx ( ) {
2024-02-21 19:40:27 +01:00
const [ error ] = await safe ( shell . exec ( 'statusNginx' , 'systemctl is-active nginx' , { } ) ) ;
2021-08-25 19:41:46 -07:00
return { status : error ? exports . SERVICE _STATUS _STOPPED : exports . SERVICE _STATUS _ACTIVE } ;
2019-04-12 09:47:05 -07:00
}
2021-08-25 19:41:46 -07:00
async function restartNginx ( ) {
const [ error ] = await safe ( shell . promises . sudo ( 'restartnginx' , [ RESTART _SERVICE _CMD , 'nginx' ] , { } ) ) ;
if ( error ) debug ( ` restartNginx: error restarting unbound. ${ error . message } ` ) ;
2019-04-12 09:47:05 -07:00
}
2021-08-25 19:41:46 -07:00
async function statusGraphite ( ) {
const [ error , container ] = await safe ( docker . inspect ( 'graphite' ) ) ;
if ( error && error . reason === BoxError . NOT _FOUND ) return { status : exports . SERVICE _STATUS _STOPPED } ;
if ( error ) throw error ;
2019-03-19 15:56:29 -07:00
2022-10-11 12:44:37 +02:00
const ip = safe . query ( container , 'NetworkSettings.Networks.cloudron.IPAddress' , null ) ;
if ( ! ip ) throw new BoxError ( BoxError . INACTIVE , 'Error getting IP of graphite service' ) ;
const [ networkError , response ] = await safe ( superagent . get ( ` http:// ${ ip } :8000/graphite-web/dashboard ` )
2021-08-25 19:41:46 -07:00
. timeout ( 20000 )
. ok ( ( ) => true ) ) ;
2019-03-19 15:56:29 -07:00
2021-08-25 19:41:46 -07:00
if ( networkError ) return { status : exports . SERVICE _STATUS _STARTING , error : ` Error waiting for graphite: ${ networkError . message } ` } ;
if ( response . status !== 200 ) return { status : exports . SERVICE _STATUS _STARTING , error : ` Error waiting for graphite. Status code: ${ response . status } message: ${ response . body . message } ` } ;
2019-03-19 15:56:29 -07:00
2021-08-25 19:41:46 -07:00
const result = await docker . memoryUsage ( 'graphite' ) ;
2022-11-24 00:40:40 +01:00
const stats = result . memory _stats || { usage : 0 , limit : 1 } ;
2019-03-19 15:56:29 -07:00
2021-08-25 19:41:46 -07:00
return {
status : container . State . Running ? exports . SERVICE _STATUS _ACTIVE : exports . SERVICE _STATUS _STOPPED ,
2022-11-24 00:40:40 +01:00
memoryUsed : stats . usage ,
memoryPercent : parseInt ( 100 * stats . usage / stats . limit )
2021-08-25 19:41:46 -07:00
} ;
2019-03-19 15:56:29 -07:00
}
2020-04-18 10:07:43 -07:00
2021-08-25 19:41:46 -07:00
async function restartGraphite ( ) {
await docker . restartContainer ( 'graphite' ) ;
2021-03-23 11:01:14 -07:00
2021-08-25 19:41:46 -07:00
setTimeout ( async ( ) => {
const [ error ] = await safe ( shell . promises . sudo ( 'restartcollectd' , [ RESTART _SERVICE _CMD , 'collectd' ] , { } ) ) ;
if ( error ) debug ( ` restartGraphite: error restarting collected. ${ error . message } ` ) ;
} , 60000 ) ;
2021-03-23 11:01:14 -07:00
}
2021-08-25 19:41:46 -07:00
async function teardownOauth ( app , options ) {
2020-04-18 10:07:43 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2021-10-01 09:17:44 -07:00
debug ( 'teardownOauth' ) ;
2020-04-18 10:07:43 -07:00
2021-08-25 19:41:46 -07:00
await addonConfigs . unset ( app . id , 'oauth' ) ;
2020-04-18 10:07:43 -07:00
}
2023-04-14 19:20:14 +02:00
async function setupOidc ( app , options ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
if ( ! app . sso ) return ;
debug ( 'Setting up OpenID connect' ) ;
2024-02-26 17:20:00 +01:00
// openid client_id is appId for now
const [ error , result ] = await safe ( oidc . clients . get ( app . id ) ) ;
if ( error ) throw error ;
// ensure we keep the secret
2023-04-14 19:20:14 +02:00
const data = {
2024-02-26 17:20:00 +01:00
secret : result ? result . secret : hat ( 4 * 128 ) ,
2023-09-20 14:33:04 +05:30
loginRedirectUri : options . loginRedirectUri || '' ,
2023-04-14 19:20:14 +02:00
logoutRedirectUri : options . logoutRedirectUri || '' ,
2023-04-14 21:18:44 +02:00
tokenSignatureAlgorithm : options . tokenSignatureAlgorithm || 'RS256' ,
2023-04-14 19:20:14 +02:00
name : '' ,
appId : app . id
} ;
2024-02-26 17:20:00 +01:00
if ( result ) await oidc . clients . update ( app . id , data ) ;
else await oidc . clients . add ( app . id , data ) ;
2023-04-14 19:20:14 +02:00
}
async function teardownOidc ( app , options ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
debug ( 'Tearing down OpenID connect' ) ;
const [ error ] = await safe ( oidc . clients . del ( app . id ) ) ;
if ( error && error . reason !== BoxError . NOT _FOUND ) throw error ;
}
2023-04-24 15:29:57 +02:00
async function getDynamicEnvironmentOidc ( app , options ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2023-08-11 19:41:05 +05:30
const { fqdn : dashboardFqdn } = await dashboard . getLocation ( ) ;
2024-03-01 10:38:49 +01:00
if ( ! app . sso ) return { } ;
const client = await oidc . clients . get ( app . id ) ;
if ( ! client ) throw new BoxError ( BoxError . NOT _FOUND , ` OIDC client for ${ app . id } has not been allocated yet ` ) ; // happens with overzealous scheduler logic
const tmp = { } ;
tmp [ 'CLOUDRON_OIDC_DISCOVERY_URL' ] = ` https:// ${ dashboardFqdn } /openid/.well-known/openid-configuration ` ;
tmp [ 'CLOUDRON_OIDC_ISSUER' ] = ` https:// ${ dashboardFqdn } /openid ` ;
tmp [ 'CLOUDRON_OIDC_AUTH_ENDPOINT' ] = ` https:// ${ dashboardFqdn } /openid/auth ` ;
tmp [ 'CLOUDRON_OIDC_TOKEN_ENDPOINT' ] = ` https:// ${ dashboardFqdn } /openid/token ` ;
tmp [ 'CLOUDRON_OIDC_KEYS_ENDPOINT' ] = ` https:// ${ dashboardFqdn } /openid/jwks ` ;
tmp [ 'CLOUDRON_OIDC_PROFILE_ENDPOINT' ] = ` https:// ${ dashboardFqdn } /openid/me ` ;
// following is only available if rpInitiatedLogout would be enabled https://github.com/panva/node-oidc-provider/blob/main/docs/README.md#featuresrpinitiatedlogout
// tmp['CLOUDRON_OIDC_LOGOUT_URL'] = `https://${dashboardFqdn}/openid/session/end`;
tmp [ 'CLOUDRON_OIDC_CLIENT_ID' ] = client . id ;
tmp [ 'CLOUDRON_OIDC_CLIENT_SECRET' ] = client . secret ;
2023-04-24 15:29:57 +02:00
2024-04-17 15:06:17 +02:00
tmp [ 'CLOUDRON_OIDC_PROVIDER_NAME' ] = 'Cloudron' ;
2023-04-24 15:29:57 +02:00
return tmp ;
}
2024-03-30 18:31:57 +01:00
async function checkAddonsSupport ( addons ) {
assert . strictEqual ( typeof addons , 'object' ) ;
2024-04-15 10:10:05 +02:00
if ( addons . mongodb && ! await hasAVX ( ) ) return new BoxError ( BoxError . BAD _FIELD , 'This app requires MongoDB, but MongoDB is disabled because the CPU does not support AVX' ) ;
2024-03-30 18:31:57 +01:00
return null ;
}