2015-07-20 00:09:47 -07:00
'use strict' ;
exports = module . exports = {
initialize : initialize ,
uninitialize : uninitialize ,
getConfig : getConfig ,
2017-04-18 15:15:35 -07:00
getLogs : getLogs ,
2015-07-20 00:09:47 -07:00
reboot : reboot ,
2018-11-25 17:02:01 +01:00
isRebootRequired : isRebootRequired ,
2015-10-27 16:00:31 -07:00
2018-01-29 15:47:26 -08:00
onActivated : onActivated ,
2018-12-15 15:27:16 -08:00
prepareDashboardDomain : prepareDashboardDomain ,
2018-12-08 18:18:45 -08:00
setDashboardDomain : setDashboardDomain ,
2019-02-26 19:43:18 -08:00
setDashboardAndMailDomain : setDashboardAndMailDomain ,
2018-12-10 20:20:53 -08:00
renewCerts : renewCerts ,
2018-11-11 09:29:11 -08:00
2019-07-27 19:21:49 -07:00
setupDashboard : setupDashboard ,
2019-02-06 11:15:46 -08:00
runSystemChecks : runSystemChecks ,
2015-09-28 23:10:09 -07:00
} ;
2015-07-20 00:09:47 -07:00
2020-03-31 14:52:09 +02:00
var addons = require ( './addons.js' ) ,
apps = require ( './apps.js' ) ,
2020-02-14 12:20:15 +01:00
appstore = require ( './appstore.js' ) ,
2019-03-18 19:13:44 -07:00
assert = require ( 'assert' ) ,
2015-07-20 00:09:47 -07:00
async = require ( 'async' ) ,
2019-08-03 13:59:11 -07:00
auditSource = require ( './auditsource.js' ) ,
2019-02-28 16:46:30 -08:00
backups = require ( './backups.js' ) ,
2019-10-22 14:06:19 -07:00
BoxError = require ( './boxerror.js' ) ,
2018-12-14 09:57:28 -08:00
constants = require ( './constants.js' ) ,
2017-01-09 11:00:09 -08:00
cron = require ( './cron.js' ) ,
2015-07-20 00:09:47 -07:00
debug = require ( 'debug' ) ( 'box:cloudron' ) ,
2018-11-11 09:29:11 -08:00
domains = require ( './domains.js' ) ,
2019-02-04 20:24:28 -08:00
eventlog = require ( './eventlog.js' ) ,
2018-11-25 17:02:01 +01:00
fs = require ( 'fs' ) ,
2019-02-04 14:49:51 -08:00
mail = require ( './mail.js' ) ,
2019-02-06 15:47:56 +01:00
notifications = require ( './notifications.js' ) ,
2015-07-20 00:09:47 -07:00
path = require ( 'path' ) ,
paths = require ( './paths.js' ) ,
2017-01-07 23:33:20 -08:00
platform = require ( './platform.js' ) ,
2018-01-30 12:23:27 -08:00
reverseProxy = require ( './reverseproxy.js' ) ,
2019-08-03 13:59:11 -07:00
safe = require ( 'safetydance' ) ,
2015-07-20 00:09:47 -07:00
settings = require ( './settings.js' ) ,
shell = require ( './shell.js' ) ,
2017-04-18 15:15:35 -07:00
spawn = require ( 'child_process' ) . spawn ,
split = require ( 'split' ) ,
2018-12-10 20:20:53 -08:00
tasks = require ( './tasks.js' ) ,
2019-10-22 14:06:19 -07:00
users = require ( './users.js' ) ;
2015-07-20 00:09:47 -07:00
2018-07-31 11:35:23 -07:00
var REBOOT _CMD = path . join ( _ _dirname , 'scripts/reboot.sh' ) ;
2015-07-20 00:09:47 -07:00
2019-10-22 14:06:19 -07:00
const NOOP _CALLBACK = function ( error ) { if ( error ) debug ( error ) ; } ;
2015-07-20 00:09:47 -07:00
function initialize ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2018-11-10 18:21:15 -08:00
runStartupTasks ( ) ;
2019-05-08 15:24:37 -07:00
2019-08-03 13:59:11 -07:00
notifyUpdate ( callback ) ;
2015-07-20 00:09:47 -07:00
}
function uninitialize ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2017-01-09 09:24:32 -08:00
async . series ( [
2018-09-26 12:44:38 -07:00
cron . stopJobs ,
2018-11-10 13:58:18 -08:00
platform . stop
2017-01-09 09:24:32 -08:00
] , callback ) ;
2015-07-20 00:09:47 -07:00
}
2017-11-22 21:31:30 -08:00
function onActivated ( callback ) {
2018-11-10 18:21:15 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2017-11-22 21:31:30 -08:00
// Starting the platform after a user is available means:
// 1. mail bounces can now be sent to the cloudron owner
// 2. the restore code path can run without sudo (since mail/ is non-root)
2018-11-10 18:21:15 -08:00
async . series ( [
platform . start ,
2019-05-08 15:24:37 -07:00
cron . startJobs
2018-11-10 18:21:15 -08:00
] , callback ) ;
}
2019-08-03 13:59:11 -07:00
function notifyUpdate ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
const version = safe . fs . readFileSync ( paths . VERSION _FILE , 'utf8' ) ;
if ( version === constants . VERSION ) return callback ( ) ;
2019-10-14 09:30:20 -07:00
eventlog . add ( eventlog . ACTION _UPDATE _FINISH , auditSource . CRON , { errorMessage : '' , oldVersion : version || 'dev' , newVersion : constants . VERSION } , function ( error ) {
2019-10-22 14:06:19 -07:00
if ( error ) return callback ( error ) ;
2019-08-04 08:59:46 -07:00
2019-09-05 11:29:46 -07:00
tasks . setCompletedByType ( tasks . TASK _UPDATE , { error : null } , function ( error ) {
2019-10-22 20:12:44 -07:00
if ( error && error . reason !== BoxError . NOT _FOUND ) return callback ( error ) ; // when hotfixing, task may not exist
2019-08-03 13:59:11 -07:00
2019-08-04 08:59:46 -07:00
safe . fs . writeFileSync ( paths . VERSION _FILE , constants . VERSION , 'utf8' ) ;
2019-08-03 13:59:11 -07:00
2019-08-04 08:59:46 -07:00
callback ( ) ;
} ) ;
2019-08-03 13:59:11 -07:00
} ) ;
}
2018-11-10 18:21:15 -08:00
// each of these tasks can fail. we will add some routes to fix/re-run them
function runStartupTasks ( ) {
// configure nginx to be reachable by IP
2019-09-30 15:28:05 -07:00
reverseProxy . writeDefaultConfig ( NOOP _CALLBACK ) ;
2018-11-10 18:21:15 -08:00
2020-03-06 17:32:29 -08:00
// this configures collectd to collect backup storage metrics if filesystem is used. This is also triggerd when the settings change with the rest api
settings . getBackupConfig ( function ( error , backupConfig ) {
if ( error ) return console . error ( 'Failed to read backup config.' , error ) ;
backups . configureCollectd ( backupConfig , NOOP _CALLBACK ) ;
} ) ;
2018-11-11 08:19:24 -08:00
// always generate webadmin config since we have no versioning mechanism for the ejs
2019-07-26 10:49:29 -07:00
if ( settings . adminDomain ( ) ) reverseProxy . writeAdminConfig ( settings . adminDomain ( ) , NOOP _CALLBACK ) ;
2018-11-11 08:19:24 -08:00
2018-11-10 18:21:15 -08:00
// check activation state and start the platform
2018-11-10 18:08:08 -08:00
users . isActivated ( function ( error , activated ) {
2018-11-10 18:21:15 -08:00
if ( error ) return debug ( error ) ;
if ( ! activated ) return debug ( 'initialize: not activated yet' ) ; // not activated
2017-11-22 21:31:30 -08:00
2018-11-10 18:21:15 -08:00
onActivated ( NOOP _CALLBACK ) ;
2017-11-22 21:31:30 -08:00
} ) ;
}
2015-07-20 00:09:47 -07:00
function getConfig ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2018-08-03 18:25:43 -07:00
settings . getAll ( function ( error , allSettings ) {
2019-10-22 14:06:19 -07:00
if ( error ) return callback ( error ) ;
2018-01-02 13:05:30 -08:00
2018-06-28 17:40:57 -07:00
// be picky about what we send out here since this is sent for 'normal' users as well
2018-06-28 17:18:15 -07:00
callback ( null , {
2019-07-26 10:49:29 -07:00
apiServerOrigin : settings . apiServerOrigin ( ) ,
webServerOrigin : settings . webServerOrigin ( ) ,
adminDomain : settings . adminDomain ( ) ,
adminFqdn : settings . adminFqdn ( ) ,
mailFqdn : settings . mailFqdn ( ) ,
2019-07-25 14:40:52 -07:00
version : constants . VERSION ,
2019-07-26 10:49:29 -07:00
isDemo : settings . isDemo ( ) ,
2019-10-29 15:46:33 -07:00
provider : settings . provider ( ) ,
2019-05-07 09:34:23 -07:00
cloudronName : allSettings [ settings . CLOUDRON _NAME _KEY ] ,
2020-03-06 18:08:26 -08:00
footer : allSettings [ settings . FOOTER _KEY ] || constants . FOOTER ,
2020-02-14 12:20:15 +01:00
features : appstore . getFeatures ( )
2015-07-20 00:09:47 -07:00
} ) ;
} ) ;
}
function reboot ( callback ) {
2020-03-06 00:39:37 -08:00
notifications . alert ( notifications . ALERT _REBOOT , 'Reboot Required' , '' , function ( error ) {
if ( error ) console . error ( 'Failed to clear reboot notification.' , error ) ;
shell . sudo ( 'reboot' , [ REBOOT _CMD ] , { } , callback ) ;
} ) ;
2015-07-20 00:09:47 -07:00
}
2018-11-25 17:02:01 +01:00
function isRebootRequired ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
// https://serverfault.com/questions/92932/how-does-ubuntu-keep-track-of-the-system-restart-required-flag-in-motd
callback ( null , fs . existsSync ( '/var/run/reboot-required' ) ) ;
}
2019-02-06 15:47:56 +01:00
// called from cron.js
2019-12-12 20:41:03 -08:00
function runSystemChecks ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2019-02-06 15:47:56 +01:00
async . parallel ( [
checkBackupConfiguration ,
2019-02-19 09:19:56 -08:00
checkMailStatus ,
checkRebootRequired
2019-12-12 20:41:03 -08:00
] , callback ) ;
2019-02-06 15:47:56 +01:00
}
function checkBackupConfiguration ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2019-11-22 12:31:41 +01:00
debug ( 'checking backup configuration' ) ;
2019-02-06 15:47:56 +01:00
2019-02-28 16:46:30 -08:00
backups . checkConfiguration ( function ( error , message ) {
if ( error ) return callback ( error ) ;
2019-02-28 14:52:14 -08:00
2019-03-07 14:49:38 -08:00
notifications . alert ( notifications . ALERT _BACKUP _CONFIG , 'Backup configuration is unsafe' , message , callback ) ;
2019-02-06 15:47:56 +01:00
} ) ;
}
2019-02-06 14:58:45 -08:00
function checkMailStatus ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
debug ( 'checking mail status' ) ;
2019-02-28 16:46:30 -08:00
mail . checkConfiguration ( function ( error , message ) {
2019-02-06 14:58:45 -08:00
if ( error ) return callback ( error ) ;
2019-03-07 14:49:38 -08:00
notifications . alert ( notifications . ALERT _MAIL _STATUS , 'Email is not configured properly' , message , callback ) ;
2019-02-06 14:58:45 -08:00
} ) ;
}
2019-02-19 09:19:56 -08:00
function checkRebootRequired ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
debug ( 'checking if reboot required' ) ;
isRebootRequired ( function ( error , rebootRequired ) {
if ( error ) return callback ( error ) ;
2020-03-05 21:01:15 -08:00
notifications . alert ( notifications . ALERT _REBOOT , 'Reboot Required' , rebootRequired ? 'To finish ubuntu security updates, a reboot is necessary.' : '' , callback ) ;
2019-02-19 09:19:56 -08:00
} ) ;
}
2018-06-11 20:09:38 +02:00
function getLogs ( unit , options , callback ) {
assert . strictEqual ( typeof unit , 'string' ) ;
2017-04-18 20:32:57 -07:00
assert ( options && typeof options === 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-01-08 12:10:53 -08:00
assert . strictEqual ( typeof options . lines , 'number' ) ;
assert . strictEqual ( typeof options . format , 'string' ) ;
assert . strictEqual ( typeof options . follow , 'boolean' ) ;
2017-04-18 15:15:35 -07:00
2019-01-08 12:10:53 -08:00
var lines = options . lines === - 1 ? '+1' : options . lines ,
format = options . format || 'json' ,
follow = options . follow ;
2017-04-18 15:15:35 -07:00
2018-06-11 20:09:38 +02:00
debug ( 'Getting logs for %s as %s' , unit , format ) ;
2018-11-16 13:05:11 +01:00
let args = [ '--lines=' + lines ] ;
if ( follow ) args . push ( '--follow' ) ;
2017-04-18 15:15:35 -07:00
2018-11-16 13:05:11 +01:00
// need to handle box.log without subdir
if ( unit === 'box' ) args . push ( path . join ( paths . LOG _DIR , 'box.log' ) ) ;
2019-03-01 15:45:44 -08:00
else if ( unit . startsWith ( 'crash-' ) ) args . push ( path . join ( paths . CRASH _LOG _DIR , unit . slice ( 6 ) + '.log' ) ) ;
2019-10-22 14:06:19 -07:00
else return callback ( new BoxError ( BoxError . BAD _FIELD , 'No such unit' , { field : 'unit' } ) ) ;
2017-04-18 15:15:35 -07:00
2018-11-16 13:05:11 +01:00
var cp = spawn ( '/usr/bin/tail' , args ) ;
2017-04-18 20:32:57 -07:00
2018-11-16 13:05:11 +01:00
var transformStream = split ( function mapper ( line ) {
if ( format !== 'json' ) return line + '\n' ;
2017-04-18 15:15:35 -07:00
2018-11-16 13:05:11 +01:00
var data = line . split ( ' ' ) ; // logs are <ISOtimestamp> <msg>
var timestamp = ( new Date ( data [ 0 ] ) ) . getTime ( ) ;
if ( isNaN ( timestamp ) ) timestamp = 0 ;
return JSON . stringify ( {
realtimeTimestamp : timestamp * 1000 ,
message : line . slice ( data [ 0 ] . length + 1 ) ,
source : unit
} ) + '\n' ;
} ) ;
2017-04-18 15:15:35 -07:00
transformStream . close = cp . kill . bind ( cp , 'SIGKILL' ) ; // closing stream kills the child process
cp . stdout . pipe ( transformStream ) ;
return callback ( null , transformStream ) ;
}
2018-11-11 09:29:11 -08:00
2018-12-15 15:27:16 -08:00
function prepareDashboardDomain ( domain , auditSource , callback ) {
2018-12-14 09:57:28 -08:00
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof auditSource , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2018-12-15 15:27:16 -08:00
debug ( ` prepareDashboardDomain: ${ domain } ` ) ;
2018-12-14 09:57:28 -08:00
2020-02-19 10:45:55 -08:00
if ( settings . isDemo ( ) ) return callback ( new BoxError ( BoxError . CONFLICT , 'Not allowed in demo mode' ) ) ;
2019-03-18 19:13:44 -07:00
domains . get ( domain , function ( error , domainObject ) {
2019-10-23 10:02:04 -07:00
if ( error ) return callback ( error ) ;
2019-03-18 19:13:44 -07:00
const fqdn = domains . fqdn ( constants . ADMIN _LOCATION , domainObject ) ;
apps . getAll ( function ( error , result ) {
2019-10-24 18:32:33 -07:00
if ( error ) return callback ( error ) ;
2019-03-18 19:13:44 -07:00
const conflict = result . filter ( app => app . fqdn === fqdn ) ;
2019-10-22 14:06:19 -07:00
if ( conflict . length ) return callback ( new BoxError ( BoxError . BAD _STATE , 'Dashboard location conflicts with an existing app' ) ) ;
2019-03-18 19:13:44 -07:00
2019-08-27 22:39:59 -07:00
tasks . add ( tasks . TASK _PREPARE _DASHBOARD _DOMAIN , [ domain , auditSource ] , function ( error , taskId ) {
2019-10-24 18:32:33 -07:00
if ( error ) return callback ( error ) ;
2019-08-27 22:39:59 -07:00
tasks . startTask ( taskId , { } , NOOP _CALLBACK ) ;
callback ( null , taskId ) ;
} ) ;
2019-03-18 19:13:44 -07:00
} ) ;
} ) ;
2018-12-14 09:57:28 -08:00
}
2019-02-26 19:43:18 -08:00
// call this only pre activation since it won't start mail server
2019-02-04 20:24:28 -08:00
function setDashboardDomain ( domain , auditSource , callback ) {
2018-12-07 16:15:21 -08:00
assert . strictEqual ( typeof domain , 'string' ) ;
2019-02-04 20:24:28 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2018-12-07 16:15:21 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-12-08 18:18:45 -08:00
debug ( ` setDashboardDomain: ${ domain } ` ) ;
2018-12-07 16:15:21 -08:00
2018-12-14 09:57:28 -08:00
domains . get ( domain , function ( error , domainObject ) {
2019-10-23 10:02:04 -07:00
if ( error ) return callback ( error ) ;
2018-12-07 16:15:21 -08:00
2019-01-16 21:36:48 -08:00
reverseProxy . writeAdminConfig ( domain , function ( error ) {
2019-10-24 18:32:33 -07:00
if ( error ) return callback ( error ) ;
2018-12-14 09:57:28 -08:00
2019-01-16 21:36:48 -08:00
const fqdn = domains . fqdn ( constants . ADMIN _LOCATION , domainObject ) ;
2018-12-07 16:15:21 -08:00
2020-02-06 16:57:33 +01:00
settings . setAdmin ( domain , fqdn , function ( error ) {
2019-10-24 18:32:33 -07:00
if ( error ) return callback ( error ) ;
2019-01-16 21:36:48 -08:00
2019-02-04 20:24:28 -08:00
eventlog . add ( eventlog . ACTION _DASHBOARD _DOMAIN _UPDATE , auditSource , { domain : domain , fqdn : fqdn } ) ;
2019-01-16 21:36:48 -08:00
callback ( null ) ;
} ) ;
2018-12-07 16:15:21 -08:00
} ) ;
} ) ;
}
2018-12-10 20:20:53 -08:00
2019-02-26 19:43:18 -08:00
// call this only post activation because it will restart mail server
function setDashboardAndMailDomain ( domain , auditSource , callback ) {
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof auditSource , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
debug ( ` setDashboardAndMailDomain: ${ domain } ` ) ;
2020-02-19 10:45:55 -08:00
if ( settings . isDemo ( ) ) return callback ( new BoxError ( BoxError . CONFLICT , 'Not allowed in demo mode' ) ) ;
2019-02-26 19:43:18 -08:00
setDashboardDomain ( domain , auditSource , function ( error ) {
if ( error ) return callback ( error ) ;
mail . onMailFqdnChanged ( NOOP _CALLBACK ) ; // this will update dns and re-configure mail server
2020-03-31 14:52:09 +02:00
addons . restartService ( 'turn' , NOOP _CALLBACK ) ; // to update the realm variable
2019-02-26 19:43:18 -08:00
callback ( null ) ;
} ) ;
}
2019-07-27 19:21:49 -07:00
function setupDashboard ( auditSource , progressCallback , callback ) {
assert . strictEqual ( typeof auditSource , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
async . series ( [
domains . prepareDashboardDomain . bind ( null , settings . adminDomain ( ) , auditSource , progressCallback ) ,
setDashboardDomain . bind ( null , settings . adminDomain ( ) , auditSource )
] , callback ) ;
}
2018-12-10 20:20:53 -08:00
function renewCerts ( options , auditSource , callback ) {
2018-12-11 12:00:47 +01:00
assert . strictEqual ( typeof options , 'object' ) ;
2018-12-10 20:20:53 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-08-27 22:39:59 -07:00
tasks . add ( tasks . TASK _RENEW _CERTS , [ options , auditSource ] , function ( error , taskId ) {
2019-10-24 18:32:33 -07:00
if ( error ) return callback ( error ) ;
2019-08-27 22:39:59 -07:00
tasks . startTask ( taskId , { } , NOOP _CALLBACK ) ;
callback ( null , taskId ) ;
} ) ;
2018-12-10 20:20:53 -08:00
}