2015-07-20 00:09:47 -07:00
'use strict' ;
exports = module . exports = {
CloudronError : CloudronError ,
initialize : initialize ,
uninitialize : uninitialize ,
activate : activate ,
getConfig : getConfig ,
getStatus : getStatus ,
2017-04-07 18:45:14 +02:00
getDisks : getDisks ,
2017-01-12 11:00:46 -08:00
dnsSetup : dnsSetup ,
2017-04-18 15:15:35 -07:00
getLogs : getLogs ,
2015-07-20 00:09:47 -07:00
2016-01-14 11:13:00 -08:00
updateToLatest : updateToLatest ,
2017-11-22 11:16:44 -08:00
restore : restore ,
2015-07-20 00:09:47 -07:00
reboot : reboot ,
2015-10-27 16:00:31 -07:00
2018-01-25 14:03:42 -08:00
checkDiskSpace : checkDiskSpace
2015-09-28 23:10:09 -07:00
} ;
2015-07-20 00:09:47 -07:00
2018-01-25 14:03:42 -08:00
var assert = require ( 'assert' ) ,
2015-07-20 00:09:47 -07:00
async = require ( 'async' ) ,
backups = require ( './backups.js' ) ,
2017-11-22 11:16:44 -08:00
BackupsError = require ( './backups.js' ) . BackupsError ,
2017-12-09 08:08:15 +05:30
caas = require ( './caas.js' ) ,
2017-01-05 17:13:31 -08:00
certificates = require ( './certificates.js' ) ,
2016-06-03 14:47:06 +02:00
clients = require ( './clients.js' ) ,
2015-07-20 00:09:47 -07:00
config = require ( './config.js' ) ,
2016-05-11 09:54:51 -07: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' ) ,
2017-04-07 18:45:14 +02:00
df = require ( '@sindresorhus/df' ) ,
2017-10-29 01:01:52 +02:00
domains = require ( './domains.js' ) ,
2017-11-08 23:42:11 +01:00
DomainError = domains . DomainError ,
2016-05-01 13:15:30 -07:00
eventlog = require ( './eventlog.js' ) ,
2015-07-20 00:09:47 -07:00
fs = require ( 'fs' ) ,
locker = require ( './locker.js' ) ,
2018-01-24 21:30:06 -08:00
mail = require ( './mail.js' ) ,
2016-01-22 17:37:41 -08:00
mailer = require ( './mailer.js' ) ,
2017-01-05 17:13:31 -08:00
nginx = require ( './nginx.js' ) ,
2015-12-31 10:30:42 +01:00
os = require ( 'os' ) ,
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' ) ,
2015-07-20 00:09:47 -07:00
progress = require ( './progress.js' ) ,
safe = require ( 'safetydance' ) ,
2017-11-23 12:59:25 -08:00
semver = require ( 'semver' ) ,
2015-07-20 00:09:47 -07:00
settings = require ( './settings.js' ) ,
2017-11-27 19:47:08 -08:00
settingsdb = require ( './settingsdb.js' ) ,
2016-07-04 22:30:25 -05:00
SettingsError = settings . SettingsError ,
2015-07-20 00:09:47 -07:00
shell = require ( './shell.js' ) ,
2017-04-18 15:15:35 -07:00
spawn = require ( 'child_process' ) . spawn ,
split = require ( 'split' ) ,
2015-07-20 00:09:47 -07:00
superagent = require ( 'superagent' ) ,
sysinfo = require ( './sysinfo.js' ) ,
2017-06-08 19:22:58 -07:00
tld = require ( 'tldjs' ) ,
2015-07-20 00:09:47 -07:00
tokendb = require ( './tokendb.js' ) ,
updateChecker = require ( './updatechecker.js' ) ,
user = require ( './user.js' ) ,
UserError = user . UserError ,
2016-07-02 10:41:10 -05:00
util = require ( 'util' ) ,
_ = require ( 'underscore' ) ;
2015-07-20 00:09:47 -07:00
2015-10-27 18:38:13 +01:00
var REBOOT _CMD = path . join ( _ _dirname , 'scripts/reboot.sh' ) ,
2016-10-31 15:10:39 +01:00
UPDATE _CMD = path . join ( _ _dirname , 'scripts/update.sh' ) ,
2017-11-22 11:16:44 -08:00
RESTART _CMD = path . join ( _ _dirname , 'scripts/restart.sh' ) ;
2015-07-20 00:09:47 -07:00
2015-10-29 12:28:50 -07:00
var NOOP _CALLBACK = function ( error ) { if ( error ) debug ( error ) ; } ;
2015-10-27 21:10:00 -07:00
2018-01-02 13:05:30 -08:00
var gWebadminStatus = { dns : false , tls : false , configuring : false , restoring : false } ;
2015-07-20 00:09:47 -07:00
function CloudronError ( reason , errorOrMessage ) {
assert . strictEqual ( typeof reason , 'string' ) ;
assert ( errorOrMessage instanceof Error || typeof errorOrMessage === 'string' || typeof errorOrMessage === 'undefined' ) ;
Error . call ( this ) ;
Error . captureStackTrace ( this , this . constructor ) ;
this . name = this . constructor . name ;
this . reason = reason ;
if ( typeof errorOrMessage === 'undefined' ) {
this . message = reason ;
} else if ( typeof errorOrMessage === 'string' ) {
this . message = errorOrMessage ;
} else {
this . message = 'Internal error' ;
this . nestedError = errorOrMessage ;
}
}
util . inherits ( CloudronError , Error ) ;
CloudronError . BAD _FIELD = 'Field error' ;
CloudronError . INTERNAL _ERROR = 'Internal Error' ;
CloudronError . EXTERNAL _ERROR = 'External Error' ;
CloudronError . ALREADY _PROVISIONED = 'Already Provisioned' ;
2017-01-12 11:00:46 -08:00
CloudronError . ALREADY _SETUP = 'Already Setup' ;
2015-07-20 00:09:47 -07:00
CloudronError . BAD _STATE = 'Bad state' ;
2016-01-14 11:13:00 -08:00
CloudronError . ALREADY _UPTODATE = 'No Update Available' ;
2015-07-20 00:09:47 -07:00
CloudronError . NOT _FOUND = 'Not found' ;
2016-06-20 14:06:46 +02:00
CloudronError . SELF _UPGRADE _NOT _SUPPORTED = 'Self upgrade not supported' ;
2015-07-20 00:09:47 -07:00
function initialize ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2017-11-22 11:16:44 -08:00
gWebadminStatus = { dns : false , tls : false , configuring : false , restoring : false } ;
2017-02-06 21:53:29 -08:00
2017-01-09 18:24:39 -08:00
async . series ( [
2017-02-07 10:30:52 -08:00
settings . initialize ,
2017-04-25 14:09:13 -07:00
configureDefaultServer ,
2018-01-26 22:52:54 -08:00
cron . initialize , // required for caas heartbeat before activation
2017-11-22 21:31:30 -08:00
onActivated
2017-04-25 15:18:09 -07:00
] , function ( error ) {
if ( error ) return callback ( error ) ;
configureWebadmin ( NOOP _CALLBACK ) ; // for restore() and caas initial setup. do not block
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 ( [
2017-01-09 11:00:09 -08:00
cron . uninitialize ,
2017-04-24 13:50:24 -07:00
platform . stop ,
2017-02-07 10:30:52 -08:00
settings . uninitialize
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 ) {
callback = callback || NOOP _CALLBACK ;
// 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)
user . count ( function ( error , count ) {
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
if ( ! count ) return callback ( ) ; // not activated
2017-11-24 13:58:40 -08:00
platform . start ( callback ) ;
2017-11-22 21:31:30 -08:00
} ) ;
}
2017-11-27 19:47:08 -08:00
function autoprovision ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2017-11-28 01:23:10 -08:00
const confJson = safe . fs . readFileSync ( paths . AUTO _PROVISION _FILE , 'utf8' ) ;
2017-11-27 19:47:08 -08:00
if ( ! confJson ) return callback ( ) ;
const conf = safe . JSON . parse ( confJson ) ;
if ( ! conf ) return callback ( ) ;
async . eachSeries ( Object . keys ( conf ) , function ( key , iteratorDone ) {
var name ;
switch ( key ) {
2018-01-17 12:26:53 -08:00
case 'appstoreConfig' : name = settings . APPSTORE _CONFIG _KEY ; break ;
case 'caasConfig' : name = settings . CAAS _CONFIG _KEY ; break ;
2018-01-17 12:25:14 -08:00
case 'tlsConfig' : name = settings . TLS _CONFIG _KEY ; break ;
case 'backupConfig' : name = settings . BACKUP _CONFIG _KEY ; break ;
2017-11-28 10:28:17 -08:00
case 'tlsCert' :
debug ( ` autoprovision: ${ key } ` ) ;
return fs . writeFile ( path . join ( paths . NGINX _CERT _DIR , 'host.cert' ) , conf [ key ] , iteratorDone ) ;
case 'tlsKey' :
debug ( ` autoprovision: ${ key } ` ) ;
return fs . writeFile ( path . join ( paths . NGINX _CERT _DIR , 'host.key' ) , conf [ key ] , iteratorDone ) ;
default :
debug ( ` autoprovision: ${ key } ignored ` ) ;
return iteratorDone ( ) ;
2017-11-27 19:47:08 -08:00
}
debug ( ` autoprovision: ${ name } ` ) ;
2017-11-28 02:27:28 -08:00
settingsdb . set ( name , JSON . stringify ( conf [ key ] ) , iteratorDone ) ;
2017-11-27 19:47:08 -08:00
} , callback ) ;
}
2018-01-10 20:40:15 -08:00
function dnsSetup ( adminFqdn , domain , zoneName , provider , dnsConfig , callback ) {
assert . strictEqual ( typeof adminFqdn , 'string' ) ;
2017-01-12 11:00:46 -08:00
assert . strictEqual ( typeof domain , 'string' ) ;
2017-06-11 22:32:05 -07:00
assert . strictEqual ( typeof zoneName , 'string' ) ;
2018-01-09 14:46:38 -08:00
assert . strictEqual ( typeof provider , 'string' ) ;
assert . strictEqual ( typeof dnsConfig , 'object' ) ;
2017-01-12 11:00:46 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-01-26 11:46:30 -08:00
if ( config . adminFqdn ( ) ) return callback ( new CloudronError ( CloudronError . ALREADY _SETUP ) ) ;
2017-01-12 11:00:46 -08:00
2018-01-09 17:50:04 -08:00
if ( ! zoneName ) zoneName = tld . getDomain ( domain ) || domain ;
2017-06-11 22:32:05 -07:00
debug ( 'dnsSetup: Setting up Cloudron with domain %s and zone %s' , domain , zoneName ) ;
2017-11-08 23:42:11 +01:00
function done ( error ) {
if ( error && error . reason === DomainError . BAD _FIELD ) return callback ( new CloudronError ( CloudronError . BAD _FIELD , error . message ) ) ;
2017-01-12 11:00:46 -08:00
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
2017-11-28 01:20:03 -08:00
autoprovision ( function ( error ) {
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
2017-01-14 12:59:13 -08:00
2017-11-28 01:20:03 -08:00
config . setFqdn ( domain ) ; // set fqdn only after dns config is valid, otherwise cannot re-setup if we failed
2018-01-10 20:40:15 -08:00
config . setAdminFqdn ( adminFqdn ) ;
config . setAdminLocation ( 'my' ) ;
2017-11-28 01:20:03 -08:00
config . setZoneName ( zoneName ) ;
2017-01-12 11:08:34 -08:00
2018-01-26 22:52:54 -08:00
clients . addDefaultClients ( config . adminOrigin ( ) , callback ) ;
2017-11-28 01:20:03 -08:00
async . series ( [ // do not block
configureWebadmin
] , NOOP _CALLBACK ) ;
} ) ;
2017-11-08 23:42:11 +01:00
}
domains . get ( domain , function ( error , result ) {
if ( error && error . reason !== DomainError . NOT _FOUND ) return callback ( new SettingsError ( SettingsError . INTERNAL _ERROR , error ) ) ;
2018-01-24 21:30:06 -08:00
if ( ! result ) {
async . series ( [
domains . add . bind ( null , domain , zoneName , provider , dnsConfig , null /* cert */ ) ,
mail . add ( null , domain )
] , done ) ;
} else {
domains . update ( domain , provider , dnsConfig , null /* cert */ , done ) ;
}
2017-01-12 11:00:46 -08:00
} ) ;
}
2017-01-30 16:17:04 -08:00
function configureDefaultServer ( callback ) {
2017-01-05 17:13:31 -08:00
callback = callback || NOOP _CALLBACK ;
2017-01-30 16:17:04 -08:00
if ( process . env . BOX _ENV === 'test' ) return callback ( ) ;
2017-01-05 17:13:31 -08:00
2017-01-30 16:17:04 -08:00
var certFilePath = path . join ( paths . NGINX _CERT _DIR , 'default.cert' ) ;
var keyFilePath = path . join ( paths . NGINX _CERT _DIR , 'default.key' ) ;
2017-01-05 17:13:31 -08:00
2017-01-28 01:44:06 -08:00
if ( ! fs . existsSync ( certFilePath ) || ! fs . existsSync ( keyFilePath ) ) {
2017-01-30 16:17:04 -08:00
debug ( 'configureDefaultServer: create new cert' ) ;
2017-01-05 17:13:31 -08:00
2017-02-25 15:38:13 -08:00
var cn = 'cloudron-' + ( new Date ( ) ) . toISOString ( ) ; // randomize date a bit to keep firefox happy
var certCommand = util . format ( 'openssl req -x509 -newkey rsa:2048 -keyout %s -out %s -days 3650 -subj /CN=%s -nodes' , keyFilePath , certFilePath , cn ) ;
2017-01-28 01:44:06 -08:00
safe . child _process . execSync ( certCommand ) ;
}
2017-01-06 14:19:38 +01:00
2017-01-30 16:17:04 -08:00
nginx . configureAdmin ( certFilePath , keyFilePath , 'default.conf' , '' , function ( error ) {
2017-01-28 01:44:06 -08:00
if ( error ) return callback ( error ) ;
2017-01-06 14:19:38 +01:00
2017-01-30 16:17:04 -08:00
debug ( 'configureDefaultServer: done' ) ;
2017-01-17 10:58:58 +01:00
2017-01-28 01:44:06 -08:00
callback ( null ) ;
2017-01-17 10:58:58 +01:00
} ) ;
}
2017-04-25 15:18:09 -07:00
function configureWebadmin ( callback ) {
2017-01-17 10:58:58 +01:00
callback = callback || NOOP _CALLBACK ;
2018-01-26 11:46:30 -08:00
debug ( 'configureWebadmin: adminFqdn:%s status:%j' , config . adminFqdn ( ) , gWebadminStatus ) ;
2017-01-17 10:58:58 +01:00
2018-01-26 11:46:30 -08:00
if ( process . env . BOX _ENV === 'test' || ! config . adminFqdn ( ) || gWebadminStatus . configuring ) return callback ( ) ;
2017-04-25 15:18:09 -07:00
gWebadminStatus . configuring = true ; // re-entracy guard
function done ( error ) {
gWebadminStatus . configuring = false ;
2017-11-09 23:51:35 +01:00
debug ( 'configureWebadmin: done error: %j' , error || { } ) ;
2017-04-25 15:18:09 -07:00
callback ( error ) ;
}
2017-01-06 12:42:21 +01:00
2017-08-28 21:00:29 -07:00
function configureNginx ( error ) {
2017-11-09 23:51:35 +01:00
debug ( 'configureNginx: dns update: %j' , error || { } ) ;
2017-08-28 21:00:29 -07:00
2018-01-09 21:03:59 -08:00
certificates . ensureCertificate ( { domain : config . fqdn ( ) , location : config . adminLocation ( ) , intrinsicFqdn : config . adminFqdn ( ) } , function ( error , certFilePath , keyFilePath ) {
2017-08-28 21:00:29 -07:00
if ( error ) return done ( error ) ;
gWebadminStatus . tls = true ;
nginx . configureAdmin ( certFilePath , keyFilePath , constants . NGINX _ADMIN _CONFIG _FILE _NAME , config . adminFqdn ( ) , done ) ;
} ) ;
}
2018-01-25 14:03:42 -08:00
function addWebadminDnsRecord ( ip , domain , callback ) {
assert . strictEqual ( typeof ip , 'string' ) ;
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
if ( process . env . BOX _ENV === 'test' ) return callback ( ) ;
async . retry ( { times : 10 , interval : 20000 } , function ( retryCallback ) {
domains . upsertDNSRecords ( config . adminLocation ( ) , domain , 'A' , [ ip ] , retryCallback ) ;
} , function ( error ) {
if ( error ) debug ( 'addWebadminDnsRecord: done updating records with error:' , error ) ;
else debug ( 'addWebadminDnsRecord: done' ) ;
callback ( error ) ;
} ) ;
}
2017-08-28 21:00:29 -07:00
// update the DNS. configure nginx regardless of whether it succeeded so that
// box is accessible even if dns creds are invalid
2017-02-23 22:03:44 -08:00
sysinfo . getPublicIp ( function ( error , ip ) {
2017-08-28 21:00:29 -07:00
if ( error ) return configureNginx ( error ) ;
2017-01-17 10:58:58 +01:00
2018-01-25 14:03:42 -08:00
addWebadminDnsRecord ( ip , config . fqdn ( ) , function ( error ) {
2017-08-28 21:00:29 -07:00
if ( error ) return configureNginx ( error ) ;
2017-01-05 17:13:31 -08:00
2017-12-06 11:33:09 +05:30
domains . waitForDNSRecord ( config . adminFqdn ( ) , config . fqdn ( ) , ip , 'A' , { interval : 30000 , times : 50000 } , function ( error ) {
2017-08-28 21:00:29 -07:00
if ( error ) return configureNginx ( error ) ;
2017-01-05 19:01:44 -08:00
2017-04-25 15:18:09 -07:00
gWebadminStatus . dns = true ;
2017-01-05 17:13:31 -08:00
2017-08-28 21:00:29 -07:00
configureNginx ( ) ;
2017-01-05 17:13:31 -08:00
} ) ;
2017-01-06 14:19:38 +01:00
} ) ;
2017-01-05 17:13:31 -08:00
} ) ;
}
2015-07-20 00:09:47 -07:00
function setTimeZone ( ip , callback ) {
assert . strictEqual ( typeof ip , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
debug ( 'setTimeZone ip:%s' , ip ) ;
2017-04-17 21:14:13 -07:00
superagent . get ( 'https://geolocation.cloudron.io/json' ) . query ( { ip : ip } ) . timeout ( 10 * 1000 ) . end ( function ( error , result ) {
2015-12-15 09:12:52 -08:00
if ( ( error && ! error . response ) || result . statusCode !== 200 ) {
2015-12-17 19:35:52 -08:00
debug ( 'Failed to get geo location: %s' , error . message ) ;
2015-07-20 00:09:47 -07:00
return callback ( null ) ;
}
2017-04-17 21:09:04 -07:00
var timezone = safe . query ( result . body , 'location.time_zone' ) ;
if ( ! timezone || typeof timezone !== 'string' ) {
2015-09-18 12:03:48 -07:00
debug ( 'No timezone in geoip response : %j' , result . body ) ;
2015-07-20 00:09:47 -07:00
return callback ( null ) ;
}
2017-04-17 21:09:04 -07:00
debug ( 'Setting timezone to ' , timezone ) ;
2015-07-20 00:09:47 -07:00
2017-04-17 21:09:04 -07:00
settings . setTimeZone ( timezone , callback ) ;
2015-07-20 00:09:47 -07:00
} ) ;
}
2016-05-01 13:27:57 -07:00
function activate ( username , password , email , displayName , ip , auditSource , callback ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof username , 'string' ) ;
assert . strictEqual ( typeof password , 'string' ) ;
assert . strictEqual ( typeof email , 'string' ) ;
2016-01-19 23:34:49 -08:00
assert . strictEqual ( typeof displayName , 'string' ) ;
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof ip , 'string' ) ;
2016-05-01 13:27:57 -07:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
debug ( 'activating user:%s email:%s' , username , email ) ;
2015-09-18 13:40:22 -07:00
setTimeZone ( ip , function ( ) { } ) ; // TODO: get this from user. note that timezone is detected based on the browser location and not the cloudron region
2015-07-20 00:09:47 -07:00
2016-05-01 20:01:34 -07:00
user . createOwner ( username , password , email , displayName , auditSource , function ( error , userObject ) {
2015-10-20 13:12:37 +02:00
if ( error && error . reason === UserError . ALREADY _EXISTS ) return callback ( new CloudronError ( CloudronError . ALREADY _PROVISIONED ) ) ;
2016-06-02 00:06:54 -07:00
if ( error && error . reason === UserError . BAD _FIELD ) return callback ( new CloudronError ( CloudronError . BAD _FIELD , error . message ) ) ;
2015-07-20 00:09:47 -07:00
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
2016-06-08 14:09:06 +02:00
clients . get ( 'cid-webadmin' , function ( error , result ) {
2015-07-20 00:09:47 -07:00
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
2015-10-20 13:12:37 +02:00
// Also generate a token so the admin creation can also act as a login
var token = tokendb . generateToken ( ) ;
2016-08-01 10:14:45 +02:00
var expires = Date . now ( ) + constants . DEFAULT _TOKEN _EXPIRATION ;
2015-07-20 00:09:47 -07:00
2016-06-03 12:59:13 +02:00
tokendb . add ( token , userObject . id , result . id , expires , '*' , function ( error ) {
2015-10-20 13:12:37 +02:00
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
2015-07-20 00:09:47 -07:00
2016-05-01 13:27:57 -07:00
eventlog . add ( eventlog . ACTION _ACTIVATE , auditSource , { } ) ;
2017-11-22 21:31:30 -08:00
onActivated ( ) ;
2017-01-17 23:14:52 -08:00
2015-10-20 13:12:37 +02:00
callback ( null , { token : token , expires : expires } ) ;
2015-07-20 00:09:47 -07:00
} ) ;
} ) ;
} ) ;
}
function getStatus ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2016-06-07 09:59:29 -07:00
user . count ( function ( error , count ) {
2015-07-20 00:09:47 -07:00
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
settings . getCloudronName ( function ( error , cloudronName ) {
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
callback ( null , {
activated : count !== 0 ,
version : config . version ( ) ,
2015-11-09 17:50:09 -08:00
apiServerOrigin : config . apiServerOrigin ( ) , // used by CaaS tool
2016-01-13 16:09:36 -08:00
provider : config . provider ( ) ,
2017-01-05 20:47:47 -08:00
cloudronName : cloudronName ,
2018-01-26 11:46:30 -08:00
adminFqdn : config . adminFqdn ( ) ,
2017-04-25 15:18:09 -07:00
webadminStatus : gWebadminStatus
2015-07-20 00:09:47 -07:00
} ) ;
} ) ;
} ) ;
}
2017-04-07 18:45:14 +02:00
function getDisks ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
var disks = {
boxDataDisk : null ,
platformDataDisk : null ,
appsDataDisk : null
} ;
df . file ( paths . BOX _DATA _DIR ) . then ( function ( result ) {
disks . boxDataDisk = result . filesystem ;
return df . file ( paths . PLATFORM _DATA _DIR ) ;
} ) . then ( function ( result ) {
disks . platformDataDisk = result . filesystem ;
return df . file ( paths . APPS _DATA _DIR ) ;
} ) . then ( function ( result ) {
disks . appsDataDisk = result . filesystem ;
callback ( null , disks ) ;
} ) . catch ( function ( error ) {
callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
} ) ;
}
2015-07-20 00:09:47 -07:00
function getConfig ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2018-01-02 13:05:30 -08:00
// result to not depend on the appstore
const BOX _AND _USER _TEMPLATE = {
box : {
region : null ,
size : null ,
plan : 'Custom Plan'
} ,
user : {
billing : false ,
currency : ''
}
} ;
caas . getBoxAndUserDetails ( function ( error , result ) {
2016-07-18 11:59:32 +02:00
if ( error ) debug ( 'Failed to fetch cloudron details.' , error . reason , error . message ) ;
2015-07-20 00:09:47 -07:00
2016-07-18 11:59:32 +02:00
result = _ . extend ( BOX _AND _USER _TEMPLATE , result || { } ) ;
2016-07-02 10:41:10 -05:00
2015-07-20 00:09:47 -07:00
settings . getCloudronName ( function ( error , cloudronName ) {
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
2017-11-23 23:41:01 +01:00
callback ( null , {
apiServerOrigin : config . apiServerOrigin ( ) ,
webServerOrigin : config . webServerOrigin ( ) ,
fqdn : config . fqdn ( ) ,
adminLocation : config . adminLocation ( ) ,
adminFqdn : config . adminFqdn ( ) ,
mailFqdn : config . mailFqdn ( ) ,
version : config . version ( ) ,
update : updateChecker . getUpdateInfo ( ) ,
progress : progress . getAll ( ) ,
isDemo : config . isDemo ( ) ,
region : result . box . region ,
size : result . box . size ,
billing : ! ! result . user . billing ,
plan : result . box . plan ,
currency : result . user . currency ,
memory : os . totalmem ( ) ,
provider : config . provider ( ) ,
cloudronName : cloudronName
2015-07-20 00:09:47 -07:00
} ) ;
} ) ;
} ) ;
}
2017-11-22 11:16:44 -08:00
function restore ( backupConfig , backupId , version , callback ) {
assert . strictEqual ( typeof backupConfig , 'object' ) ;
assert . strictEqual ( typeof backupId , 'string' ) ;
assert . strictEqual ( typeof version , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2017-11-23 12:36:43 -08:00
if ( ! semver . valid ( version ) ) return callback ( new CloudronError ( CloudronError . BAD _STATE , 'version is not a valid semver' ) ) ;
2017-11-23 12:59:25 -08:00
if ( semver . major ( config . version ( ) ) !== semver . major ( version ) || semver . minor ( config . version ( ) ) !== semver . minor ( version ) ) return callback ( new CloudronError ( CloudronError . BAD _STATE , ` Run cloudron-setup with --version ${ version } to restore from this backup ` ) ) ;
2017-11-22 11:16:44 -08:00
user . count ( function ( error , count ) {
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
if ( count ) return callback ( new CloudronError ( CloudronError . ALREADY _PROVISIONED , 'Already activated' ) ) ;
backups . testConfig ( backupConfig , function ( error ) {
if ( error && error . reason === BackupsError . BAD _FIELD ) return callback ( new CloudronError ( CloudronError . BAD _FIELD , error . message ) ) ;
if ( error && error . reason === BackupsError . EXTERNAL _ERROR ) return callback ( new CloudronError ( CloudronError . EXTERNAL _ERROR , error . message ) ) ;
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
2017-12-05 18:08:51 +05:30
debug ( ` restore: restoring from ${ backupId } from provider ${ backupConfig . provider } ` ) ;
2017-11-28 10:28:17 -08:00
2017-11-22 11:16:44 -08:00
gWebadminStatus . restoring = true ;
callback ( null ) ; // do no block
2017-11-27 19:47:08 -08:00
async . series ( [
backups . restore . bind ( null , backupConfig , backupId ) ,
autoprovision ,
shell . sudo . bind ( null , 'restart' , [ RESTART _CMD ] )
2017-11-28 15:01:59 -08:00
] , function ( error ) {
debug ( 'restore:' , error ) ;
gWebadminStatus . restoring = false ;
} ) ;
2017-11-22 11:16:44 -08:00
} ) ;
} ) ;
}
2015-07-20 00:09:47 -07:00
function reboot ( callback ) {
shell . sudo ( 'reboot' , [ REBOOT _CMD ] , callback ) ;
}
2016-06-02 19:26:42 -07:00
function update ( boxUpdateInfo , auditSource , callback ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof boxUpdateInfo , 'object' ) ;
2016-06-02 19:26:42 -07:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
if ( ! boxUpdateInfo ) return callback ( null ) ;
var error = locker . lock ( locker . OP _BOX _UPDATE ) ;
if ( error ) return callback ( new CloudronError ( CloudronError . BAD _STATE , error . message ) ) ;
2016-06-02 19:26:42 -07:00
eventlog . add ( eventlog . ACTION _UPDATE , auditSource , { boxUpdateInfo : boxUpdateInfo } ) ;
2016-01-14 11:34:49 -08:00
// ensure tools can 'wait' on progress
progress . set ( progress . UPDATE , 0 , 'Starting' ) ;
2015-07-20 00:09:47 -07:00
// initiate the update/upgrade but do not wait for it
2016-11-09 12:25:36 +01:00
if ( boxUpdateInfo . upgrade ) {
2015-07-28 14:22:08 -07:00
debug ( 'Starting upgrade' ) ;
2017-12-09 08:08:15 +05:30
caas . upgrade ( boxUpdateInfo , function ( error ) {
2015-07-28 15:28:10 -07:00
if ( error ) {
2017-02-07 10:48:51 -08:00
debug ( 'Upgrade failed with error:' , error ) ;
2015-07-28 15:28:10 -07:00
locker . unlock ( locker . OP _BOX _UPDATE ) ;
}
2015-07-20 00:09:47 -07:00
} ) ;
} else {
2015-07-28 14:22:08 -07:00
debug ( 'Starting update' ) ;
2015-07-20 00:09:47 -07:00
doUpdate ( boxUpdateInfo , function ( error ) {
2015-07-28 15:28:10 -07:00
if ( error ) {
2017-02-07 10:48:51 -08:00
debug ( 'Update failed with error:' , error ) ;
2015-07-28 15:28:10 -07:00
locker . unlock ( locker . OP _BOX _UPDATE ) ;
}
2015-07-20 00:09:47 -07:00
} ) ;
}
callback ( null ) ;
}
2016-05-01 13:15:30 -07:00
function updateToLatest ( auditSource , callback ) {
assert . strictEqual ( typeof auditSource , 'object' ) ;
2016-01-14 11:13:00 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2017-06-13 22:58:07 +02:00
var boxUpdateInfo = updateChecker . getUpdateInfo ( ) . box ;
if ( ! boxUpdateInfo ) return callback ( new CloudronError ( CloudronError . ALREADY _UPTODATE , 'No update available' ) ) ;
if ( ! boxUpdateInfo . sourceTarballUrl ) return callback ( new CloudronError ( CloudronError . BAD _STATE , 'No automatic update available' ) ) ;
2016-11-09 12:25:36 +01:00
2017-06-13 22:58:07 +02:00
// check if this is just a version number change
if ( config . version ( ) . match ( /[-+]/ ) !== null && config . version ( ) . replace ( /[-+].*/ , '' ) === boxUpdateInfo . version ) {
doShortCircuitUpdate ( boxUpdateInfo , function ( error ) {
if ( error ) debug ( 'Short-circuit update failed' , error ) ;
} ) ;
2016-11-09 12:25:36 +01:00
2017-06-13 22:58:07 +02:00
return callback ( null ) ;
}
2016-06-20 14:06:46 +02:00
2017-06-13 22:58:07 +02:00
if ( boxUpdateInfo . upgrade && config . provider ( ) !== 'caas' ) return callback ( new CloudronError ( CloudronError . SELF _UPGRADE _NOT _SUPPORTED ) ) ;
2017-06-13 21:42:30 +02:00
2017-06-13 22:58:07 +02:00
update ( boxUpdateInfo , auditSource , callback ) ;
2016-01-14 11:13:00 -08:00
}
2016-01-25 16:46:54 -08:00
function doShortCircuitUpdate ( boxUpdateInfo , callback ) {
assert ( boxUpdateInfo !== null && typeof boxUpdateInfo === 'object' ) ;
debug ( 'Starting short-circuit from prerelease version %s to release version %s' , config . version ( ) , boxUpdateInfo . version ) ;
config . setVersion ( boxUpdateInfo . version ) ;
progress . clear ( progress . UPDATE ) ;
2016-01-25 16:51:14 -08:00
updateChecker . resetUpdateInfo ( ) ;
2016-01-25 16:46:54 -08:00
callback ( ) ;
}
2015-07-20 00:09:47 -07:00
function doUpdate ( boxUpdateInfo , callback ) {
assert ( boxUpdateInfo && typeof boxUpdateInfo === 'object' ) ;
2015-07-29 13:52:59 +02:00
function updateError ( e ) {
2015-07-29 13:53:16 +02:00
progress . set ( progress . UPDATE , - 1 , e . message ) ;
2015-07-28 14:40:22 -07:00
callback ( e ) ;
}
2015-09-22 13:01:56 -07:00
progress . set ( progress . UPDATE , 5 , 'Backing up for update' ) ;
2015-07-20 00:09:47 -07:00
2016-05-03 18:36:50 -07:00
backups . backupBoxAndApps ( { userId : null , username : 'updater' } , function ( error ) {
2015-07-29 13:52:59 +02:00
if ( error ) return updateError ( error ) ;
2015-07-20 00:09:47 -07:00
2016-10-31 15:10:39 +01:00
// NOTE: this data is opaque and will be passed through the installer.sh
var data = {
provider : config . provider ( ) ,
apiServerOrigin : config . apiServerOrigin ( ) ,
webServerOrigin : config . webServerOrigin ( ) ,
fqdn : config . fqdn ( ) ,
2018-01-15 13:47:26 -08:00
adminFqdn : config . adminFqdn ( ) ,
2017-10-25 20:35:24 -07:00
adminLocation : config . adminLocation ( ) ,
2016-10-31 15:10:39 +01:00
isDemo : config . isDemo ( ) ,
appstore : {
apiServerOrigin : config . apiServerOrigin ( )
} ,
caas : {
2016-01-12 16:50:34 +01:00
apiServerOrigin : config . apiServerOrigin ( ) ,
2016-10-31 15:10:39 +01:00
webServerOrigin : config . webServerOrigin ( )
} ,
2015-07-20 00:09:47 -07:00
2017-04-13 11:34:55 -07:00
version : boxUpdateInfo . version
2016-10-31 15:10:39 +01:00
} ;
2015-07-20 00:09:47 -07:00
2017-05-19 14:39:08 -07:00
debug ( 'updating box %s %j' , boxUpdateInfo . sourceTarballUrl , _ . omit ( data , 'tlsCert' , 'tlsKey' , 'token' , 'appstore' , 'caas' ) ) ;
2015-07-20 00:09:47 -07:00
2017-01-12 13:42:55 +01:00
progress . set ( progress . UPDATE , 5 , 'Downloading and extracting new version' ) ;
2016-11-01 18:17:16 +01:00
shell . sudo ( 'update' , [ UPDATE _CMD , boxUpdateInfo . sourceTarballUrl , JSON . stringify ( data ) ] , function ( error ) {
2016-10-31 15:10:39 +01:00
if ( error ) return updateError ( error ) ;
2015-07-20 00:09:47 -07:00
2016-10-31 15:10:39 +01:00
// Do not add any code here. The installer script will stop the box code any instant
2015-07-20 00:09:47 -07:00
} ) ;
} ) ;
}
2016-01-22 17:37:41 -08:00
function checkDiskSpace ( callback ) {
2016-01-26 10:29:32 -08:00
callback = callback || NOOP _CALLBACK ;
2016-01-22 17:37:41 -08:00
debug ( 'Checking disk space' ) ;
2017-04-07 18:45:14 +02:00
getDisks ( function ( error , disks ) {
2016-01-22 17:37:41 -08:00
if ( error ) {
debug ( 'df error %s' , error . message ) ;
return callback ( ) ;
}
2017-04-07 18:45:14 +02:00
df ( ) . then ( function ( entries ) {
/ *
[ {
filesystem : '/dev/disk1' ,
size : 499046809600 ,
used : 443222245376 ,
available : 55562420224 ,
capacity : 0.89 ,
mountpoint : '/'
} , ... ]
* /
var oos = entries . some ( function ( entry ) {
// ignore other filesystems but where box, app and platform data is
if ( entry . filesystem !== disks . boxDataDisk && entry . filesystem !== disks . platformDataDisk && entry . filesystem !== disks . appsDataDisk ) return false ;
return ( entry . available <= ( 1.25 * 1024 * 1024 * 1024 ) ) ; // 1.5G
} ) ;
2016-01-22 17:37:41 -08:00
2017-04-07 18:45:14 +02:00
debug ( 'Disk space checked. ok: %s' , ! oos ) ;
2016-01-22 17:37:41 -08:00
2017-04-07 18:45:14 +02:00
if ( oos ) mailer . outOfDiskSpace ( JSON . stringify ( entries , null , 4 ) ) ;
2016-01-22 17:37:41 -08:00
2017-04-07 18:45:14 +02:00
callback ( ) ;
} ) . catch ( function ( error ) {
debug ( 'df error %s' , error . message ) ;
mailer . outOfDiskSpace ( error . message ) ;
return callback ( ) ;
} ) ;
2016-01-22 17:37:41 -08:00
} ) ;
}
2016-01-25 16:03:12 -08:00
2017-04-18 20:32:57 -07:00
function getLogs ( options , callback ) {
assert ( options && typeof options === 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
var units = options . units || [ ] ,
lines = options . lines || 100 ,
format = options . format || 'json' ,
follow = ! ! options . follow ;
2017-04-18 15:15:35 -07:00
assert ( Array . isArray ( units ) ) ;
assert . strictEqual ( typeof lines , 'number' ) ;
2017-04-18 20:32:57 -07:00
assert . strictEqual ( typeof format , 'string' ) ;
2017-04-18 15:15:35 -07:00
debug ( 'Getting logs for %j' , units ) ;
2017-04-18 20:32:57 -07:00
var args = [ '--no-pager' , '--lines=' + lines ] ;
2017-04-18 15:15:35 -07:00
units . forEach ( function ( u ) {
if ( u === 'box' ) args . push ( '--unit=box' ) ;
2017-04-18 20:32:57 -07:00
else if ( u === 'mail' ) args . push ( 'CONTAINER_NAME=mail' ) ;
2017-04-18 15:15:35 -07:00
} ) ;
2017-04-19 13:20:24 +02:00
if ( format === 'short' ) args . push ( '--output=short' , '-a' ) ; else args . push ( '--output=json' ) ;
2017-04-18 15:15:35 -07:00
if ( follow ) args . push ( '--follow' ) ;
var cp = spawn ( '/bin/journalctl' , args ) ;
var transformStream = split ( function mapper ( line ) {
2017-04-18 20:32:57 -07:00
if ( format !== 'json' ) return line + '\n' ;
2017-04-18 15:15:35 -07:00
var obj = safe . JSON . parse ( line ) ;
if ( ! obj ) return undefined ;
return JSON . stringify ( {
realtimeTimestamp : obj . _ _REALTIME _TIMESTAMP ,
monotonicTimestamp : obj . _ _MONOTONIC _TIMESTAMP ,
message : obj . MESSAGE ,
source : obj . SYSLOG _IDENTIFIER || ''
} ) + '\n' ;
} ) ;
transformStream . close = cp . kill . bind ( cp , 'SIGKILL' ) ; // closing stream kills the child process
cp . stdout . pipe ( transformStream ) ;
return callback ( null , transformStream ) ;
}