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
sendHeartbeat : sendHeartbeat ,
2016-01-14 11:13:00 -08:00
updateToLatest : updateToLatest ,
2015-07-20 00:09:47 -07:00
reboot : reboot ,
2016-01-25 20:18:50 -08:00
retire : retire ,
2016-06-27 22:24:30 -05:00
migrate : migrate ,
2015-10-27 16:00:31 -07:00
2016-01-22 17:37:41 -08:00
checkDiskSpace : checkDiskSpace ,
2016-12-09 09:19:23 +01:00
readDkimPublicKeySync : readDkimPublicKeySync ,
2017-04-25 15:18:09 -07:00
refreshDNS : refreshDNS ,
configureWebadmin : configureWebadmin
2015-09-28 23:10:09 -07:00
} ;
2015-07-20 00:09:47 -07:00
2017-04-14 19:44:14 -07:00
var appdb = require ( './appdb.js' ) ,
apps = require ( './apps.js' ) ,
2015-07-20 00:09:47 -07:00
assert = require ( 'assert' ) ,
async = require ( 'async' ) ,
backups = require ( './backups.js' ) ,
2017-01-05 17:13:31 -08:00
certificates = require ( './certificates.js' ) ,
2016-08-13 00:12:15 -07:00
child _process = require ( 'child_process' ) ,
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' ) ,
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' ) ,
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' ) ,
settings = require ( './settings.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-08-26 16:14:51 -07:00
subdomains = require ( './subdomains.js' ) ,
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-06-07 09:59:29 -07:00
user = require ( './user.js' ) ,
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' ) ,
2016-01-25 20:18:50 -08:00
RETIRE _CMD = path . join ( _ _dirname , 'scripts/retire.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
2016-07-02 10:41:10 -05: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 : ''
}
} ;
2017-04-25 15:18:09 -07:00
var gBoxAndUserDetails = null , // cached cloudron details like region,size...
gWebadminStatus = { dns : false , tls : false , configuring : 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-04-25 15:18:09 -07:00
gWebadminStatus = { dns : false , tls : false , configuring : false } ;
2017-02-06 21:53:29 -08:00
gBoxAndUserDetails = null ;
2017-01-09 18:24:39 -08:00
async . series ( [
2017-02-07 10:30:52 -08:00
certificates . initialize ,
settings . initialize ,
2017-01-09 19:06:32 -08:00
installAppBundle ,
2017-04-25 14:09:13 -07:00
configureDefaultServer ,
onDomainConfigured
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-01-09 09:24:32 -08:00
mailer . stop ,
2017-04-24 13:50:24 -07:00
platform . stop ,
2017-02-07 10:30:52 -08:00
certificates . uninitialize ,
settings . uninitialize
2017-01-09 09:24:32 -08:00
] , callback ) ;
2015-07-20 00:09:47 -07:00
}
2017-04-25 14:09:13 -07:00
function onDomainConfigured ( callback ) {
2017-01-07 23:31:29 -08:00
callback = callback || NOOP _CALLBACK ;
2017-04-25 15:18:09 -07:00
if ( ! config . fqdn ( ) ) return callback ( ) ;
2017-01-09 10:49:34 -08:00
2017-01-07 23:31:29 -08:00
async . series ( [
2017-01-09 15:25:45 -08:00
clients . addDefaultClients ,
2017-01-09 09:26:56 -08:00
certificates . ensureFallbackCertificate ,
2017-02-07 01:08:37 -08:00
ensureDkimKey ,
2017-04-25 15:18:09 -07:00
platform . start , // requires fallback certs for mail container
mailer . start , // this requires the "mail" container to be running
cron . initialize
2017-01-07 23:31:29 -08:00
] , callback ) ;
}
2017-06-11 22:32:05 -07:00
function dnsSetup ( dnsConfig , domain , zoneName , callback ) {
2017-01-12 11:00:46 -08:00
assert . strictEqual ( typeof dnsConfig , 'object' ) ;
assert . strictEqual ( typeof domain , 'string' ) ;
2017-06-11 22:32:05 -07:00
assert . strictEqual ( typeof zoneName , 'string' ) ;
2017-01-12 11:00:46 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
if ( config . fqdn ( ) ) return callback ( new CloudronError ( CloudronError . ALREADY _SETUP ) ) ;
2017-06-11 22:32:05 -07:00
if ( ! zoneName ) zoneName = tld . getDomain ( domain ) || '' ;
debug ( 'dnsSetup: Setting up Cloudron with domain %s and zone %s' , domain , zoneName ) ;
settings . setDnsConfig ( dnsConfig , domain , zoneName , function ( error ) {
2017-01-12 11:00:46 -08:00
if ( error && error . reason === SettingsError . BAD _FIELD ) return callback ( new CloudronError ( CloudronError . BAD _FIELD , error . message ) ) ;
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
2017-06-08 19:22:58 -07:00
config . setFqdn ( domain ) ; // set fqdn only after dns config is valid, otherwise cannot re-setup if we failed
2017-06-11 22:32:05 -07:00
config . setZoneName ( zoneName ) ;
2017-01-14 12:59:13 -08:00
2017-04-25 15:18:09 -07:00
async . series ( [ // do not block
onDomainConfigured ,
configureWebadmin
] , NOOP _CALLBACK ) ;
2017-01-12 11:08:34 -08:00
2017-01-12 11:00:46 -08:00
callback ( ) ;
} ) ;
}
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
debug ( 'configureDefaultServer: domain %s' , config . fqdn ( ) ) ;
2017-01-05 17:13:31 -08:00
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 ;
2017-04-25 15:18:09 -07:00
debug ( 'configureWebadmin: fqdn:%s status:%j' , config . fqdn ( ) , gWebadminStatus ) ;
2017-01-17 10:58:58 +01:00
2017-04-25 15:18:09 -07:00
if ( process . env . BOX _ENV === 'test' || ! config . fqdn ( ) || gWebadminStatus . configuring ) return callback ( ) ;
gWebadminStatus . configuring = true ; // re-entracy guard
function done ( error ) {
gWebadminStatus . configuring = false ;
debug ( 'configureWebadmin: done error:%j' , error ) ;
callback ( error ) ;
}
2017-01-06 12:42:21 +01:00
2017-08-28 21:00:29 -07:00
function configureNginx ( error ) {
debug ( 'configureNginx: dns update:%j' , error ) ;
certificates . ensureCertificate ( { location : constants . ADMIN _LOCATION } , function ( error , certFilePath , keyFilePath ) {
if ( error ) return done ( error ) ;
gWebadminStatus . tls = true ;
nginx . configureAdmin ( certFilePath , keyFilePath , constants . NGINX _ADMIN _CONFIG _FILE _NAME , config . adminFqdn ( ) , done ) ;
} ) ;
}
// 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
2017-04-25 14:06:08 -07:00
addDnsRecords ( ip , function ( error ) {
2017-08-28 21:00:29 -07:00
if ( error ) return configureNginx ( error ) ;
2017-01-05 17:13:31 -08:00
2017-04-25 14:04:25 -07:00
subdomains . waitForDns ( config . adminFqdn ( ) , 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-04-25 14:46:30 -07:00
platform . createMailConfig ( NOOP _CALLBACK ) ; // bounces can now be sent to the cloudron owner
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 ,
2017-01-10 22:19:41 +01:00
adminFqdn : config . fqdn ( ) ? config . adminFqdn ( ) : null ,
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 ) ) ;
} ) ;
}
2016-07-02 10:30:12 -05:00
function getBoxAndUserDetails ( callback ) {
2015-07-20 00:09:47 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
2016-07-02 10:41:10 -05:00
if ( gBoxAndUserDetails ) return callback ( null , gBoxAndUserDetails ) ;
2015-07-20 00:09:47 -07:00
2016-07-18 11:59:32 +02:00
// only supported for caas
if ( config . provider ( ) !== 'caas' ) return callback ( null , { } ) ;
2015-12-29 17:43:54 +01:00
2015-07-20 00:09:47 -07:00
superagent
. get ( config . apiServerOrigin ( ) + '/api/v1/boxes/' + config . fqdn ( ) )
. query ( { token : config . token ( ) } )
2016-09-12 12:53:51 -07:00
. timeout ( 30 * 1000 )
2015-07-20 00:09:47 -07:00
. end ( function ( error , result ) {
2016-07-18 11:59:32 +02:00
if ( error && ! error . response ) return callback ( new CloudronError ( CloudronError . EXTERNAL _ERROR , 'Cannot reach appstore' ) ) ;
if ( result . statusCode !== 200 ) return callback ( new CloudronError ( CloudronError . EXTERNAL _ERROR , util . format ( '%s %j' , result . statusCode , result . body ) ) ) ;
2015-07-20 00:09:47 -07:00
2016-07-02 10:41:10 -05:00
gBoxAndUserDetails = result . body ;
2015-07-20 00:09:47 -07:00
2016-07-02 10:41:10 -05:00
return callback ( null , gBoxAndUserDetails ) ;
2015-07-20 00:09:47 -07:00
} ) ;
}
function getConfig ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2016-07-02 10:30:12 -05:00
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 ) ) ;
2015-07-23 12:52:04 +02:00
settings . getDeveloperMode ( function ( error , developerMode ) {
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
2016-12-15 12:15:04 -08:00
callback ( null , {
apiServerOrigin : config . apiServerOrigin ( ) ,
webServerOrigin : config . webServerOrigin ( ) ,
fqdn : config . fqdn ( ) ,
version : config . version ( ) ,
update : updateChecker . getUpdateInfo ( ) ,
progress : progress . get ( ) ,
isCustomDomain : config . isCustomDomain ( ) ,
isDemo : config . isDemo ( ) ,
developerMode : developerMode ,
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-23 12:52:04 +02:00
} ) ;
2015-07-20 00:09:47 -07:00
} ) ;
} ) ;
} ) ;
}
function sendHeartbeat ( ) {
2017-01-23 21:38:22 +01:00
if ( config . provider ( ) !== 'caas' ) return ;
2015-07-20 00:09:47 -07:00
2015-12-29 17:43:54 +01:00
var url = config . apiServerOrigin ( ) + '/api/v1/boxes/' + config . fqdn ( ) + '/heartbeat' ;
2016-09-12 12:53:51 -07:00
superagent . post ( url ) . query ( { token : config . token ( ) , version : config . version ( ) } ) . timeout ( 30 * 1000 ) . end ( function ( error , result ) {
2015-12-15 09:12:52 -08:00
if ( error && ! error . response ) debug ( 'Network error sending heartbeat.' , error ) ;
2015-07-20 00:09:47 -07:00
else if ( result . statusCode !== 200 ) debug ( 'Server responded to heartbeat with %s %s' , result . statusCode , result . text ) ;
2015-07-28 09:32:14 -07:00
else debug ( 'Heartbeat sent to %s' , url ) ;
2015-07-20 00:09:47 -07:00
} ) ;
}
2017-02-07 01:08:37 -08:00
function ensureDkimKey ( callback ) {
2017-04-25 15:18:09 -07:00
assert ( config . fqdn ( ) , 'fqdn is not set' ) ;
2017-01-05 17:14:27 +01:00
var dkimPath = path . join ( paths . MAIL _DATA _DIR , 'dkim/' + config . fqdn ( ) ) ;
var dkimPrivateKeyFile = path . join ( dkimPath , 'private' ) ;
var dkimPublicKeyFile = path . join ( dkimPath , 'public' ) ;
2016-05-05 21:06:43 -07:00
2017-01-05 17:04:03 +01:00
if ( ! fs . existsSync ( dkimPrivateKeyFile ) || ! fs . existsSync ( dkimPublicKeyFile ) ) {
debug ( 'Generating new DKIM keys' ) ;
2017-01-05 17:14:27 +01:00
if ( ! safe . fs . mkdirSync ( dkimPath ) && safe . error . code !== 'EEXIST' ) {
debug ( 'Error creating dkim.' , safe . error ) ;
return null ;
}
2017-01-05 17:04:03 +01:00
child _process . execSync ( 'openssl genrsa -out ' + dkimPrivateKeyFile + ' 1024' ) ;
child _process . execSync ( 'openssl rsa -in ' + dkimPrivateKeyFile + ' -out ' + dkimPublicKeyFile + ' -pubout -outform PEM' ) ;
} else {
2016-05-05 21:06:43 -07:00
debug ( 'DKIM keys already present' ) ;
}
2017-02-07 01:08:37 -08:00
callback ( ) ;
}
function readDkimPublicKeySync ( ) {
if ( ! config . fqdn ( ) ) {
debug ( 'Cannot read dkim public key without a domain.' , safe . error ) ;
return null ;
}
var dkimPath = path . join ( paths . MAIL _DATA _DIR , 'dkim/' + config . fqdn ( ) ) ;
var dkimPublicKeyFile = path . join ( dkimPath , 'public' ) ;
2015-07-20 00:09:47 -07:00
var publicKey = safe . fs . readFileSync ( dkimPublicKeyFile , 'utf8' ) ;
2015-08-30 21:44:59 -07:00
if ( publicKey === null ) {
2015-10-29 10:54:28 -07:00
debug ( 'Error reading dkim public key.' , safe . error ) ;
return null ;
2015-08-30 21:44:59 -07:00
}
2015-07-20 00:09:47 -07:00
// remove header, footer and new lines
publicKey = publicKey . split ( '\n' ) . slice ( 1 , - 2 ) . join ( '' ) ;
2015-10-29 10:54:28 -07:00
return publicKey ;
}
2015-07-20 00:09:47 -07:00
2016-03-31 08:44:31 -07:00
// NOTE: if you change the SPF record here, be sure the wait check in mailer.js
2017-02-15 18:27:37 -08:00
// https://agari.zendesk.com/hc/en-us/articles/202952749-How-long-can-my-SPF-record-be-
2015-10-30 13:53:12 -07:00
function txtRecordsWithSpf ( callback ) {
2015-10-29 10:54:28 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
2015-07-20 00:09:47 -07:00
2015-10-29 15:35:07 -07:00
subdomains . get ( '' , 'TXT' , function ( error , txtRecords ) {
2015-10-29 10:54:28 -07:00
if ( error ) return callback ( error ) ;
2015-10-30 18:12:24 -07:00
debug ( 'txtRecordsWithSpf: current txt records - %j' , txtRecords ) ;
2017-02-15 18:27:37 -08:00
var i , matches , validSpf ;
2015-10-29 10:54:28 -07:00
2015-10-29 15:35:07 -07:00
for ( i = 0 ; i < txtRecords . length ; i ++ ) {
2017-02-15 18:27:37 -08:00
matches = txtRecords [ i ] . match ( /^("?v=spf1) / ) ; // DO backend may return without quotes
if ( matches === null ) continue ;
2015-10-29 10:54:28 -07:00
2017-02-15 18:27:37 -08:00
// this won't work if the entry is arbitrarily "split" across quoted strings
validSpf = txtRecords [ i ] . indexOf ( 'a:' + config . adminFqdn ( ) ) !== - 1 ;
break ; // there can only be one SPF record
2015-07-20 00:09:47 -07:00
}
2015-10-29 10:54:28 -07:00
if ( validSpf ) return callback ( null , null ) ;
2017-02-15 18:27:37 -08:00
if ( ! matches ) { // no spf record was found, create one
txtRecords . push ( '"v=spf1 a:' + config . adminFqdn ( ) + ' ~all"' ) ;
debug ( 'txtRecordsWithSpf: adding txt record' ) ;
} else { // just add ourself
2017-02-16 15:32:46 -08:00
txtRecords [ i ] = matches [ 1 ] + ' a:' + config . adminFqdn ( ) + txtRecords [ i ] . slice ( matches [ 1 ] . length ) ;
2017-02-15 18:27:37 -08:00
debug ( 'txtRecordsWithSpf: inserting txt record' ) ;
2015-10-29 10:54:28 -07:00
}
2015-10-30 13:45:07 -07:00
return callback ( null , txtRecords ) ;
2015-10-29 10:54:28 -07:00
} ) ;
}
2017-04-25 14:06:08 -07:00
function addDnsRecords ( ip , callback ) {
assert . strictEqual ( typeof ip , 'string' ) ;
2017-01-02 13:14:03 +01:00
callback = callback || NOOP _CALLBACK ;
2015-10-29 10:54:28 -07:00
2015-10-30 12:50:27 -07:00
if ( process . env . BOX _ENV === 'test' ) return callback ( ) ;
2015-10-29 10:54:28 -07:00
var dkimKey = readDkimPublicKeySync ( ) ;
2016-05-04 09:28:12 -07:00
if ( ! dkimKey ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , new Error ( 'Failed to read dkim public key' ) ) ) ;
2015-10-29 10:54:28 -07:00
2017-04-25 14:06:08 -07:00
var webadminRecord = { subdomain : constants . ADMIN _LOCATION , type : 'A' , values : [ ip ] } ;
// t=s limits the domainkey to this domain and not it's subdomains
var dkimRecord = { subdomain : constants . DKIM _SELECTOR + '._domainkey' , type : 'TXT' , values : [ '"v=DKIM1; t=s; p=' + dkimKey + '"' ] } ;
2015-10-29 10:54:28 -07:00
2017-04-25 14:06:08 -07:00
var records = [ ] ;
if ( config . isCustomDomain ( ) ) {
records . push ( webadminRecord ) ;
records . push ( dkimRecord ) ;
} else {
// for non-custom domains, we show a noapp.html page
var nakedDomainRecord = { subdomain : '' , type : 'A' , values : [ ip ] } ;
2015-10-29 10:54:28 -07:00
2017-04-25 14:06:08 -07:00
records . push ( nakedDomainRecord ) ;
records . push ( webadminRecord ) ;
records . push ( dkimRecord ) ;
}
2015-10-29 10:54:28 -07:00
2017-04-25 14:06:08 -07:00
debug ( 'addDnsRecords: %j' , records ) ;
2015-10-29 10:54:28 -07:00
2017-04-25 14:06:08 -07:00
async . retry ( { times : 10 , interval : 20000 } , function ( retryCallback ) {
txtRecordsWithSpf ( function ( error , txtRecords ) {
if ( error ) return retryCallback ( error ) ;
2015-10-30 13:48:46 -07:00
2017-04-25 14:06:08 -07:00
if ( txtRecords ) records . push ( { subdomain : '' , type : 'TXT' , values : txtRecords } ) ;
2015-11-04 14:28:22 -08:00
2017-04-25 14:06:08 -07:00
debug ( 'addDnsRecords: will update %j' , records ) ;
2016-01-05 12:16:48 +01:00
2017-04-25 14:06:08 -07:00
async . mapSeries ( records , function ( record , iteratorCallback ) {
subdomains . upsert ( record . subdomain , record . type , record . values , iteratorCallback ) ;
} , function ( error , changeIds ) {
if ( error ) debug ( 'addDnsRecords: failed to update : %s. will retry' , error ) ;
else debug ( 'addDnsRecords: records %j added with changeIds %j' , records , changeIds ) ;
retryCallback ( error ) ;
2015-11-04 14:28:22 -08:00
} ) ;
2017-04-25 14:06:08 -07:00
} ) ;
} , function ( error ) {
debug ( 'addDnsRecords: done updating records with error:' , error ) ;
2015-10-30 18:12:24 -07:00
2017-04-25 14:06:08 -07:00
callback ( error ) ;
2015-08-30 21:29:02 -07: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' ) ;
2015-07-20 00:09:47 -07:00
doUpgrade ( 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 ) ;
}
2017-06-13 22:58:07 +02:00
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 doUpgrade ( boxUpdateInfo , callback ) {
assert ( boxUpdateInfo !== null && typeof boxUpdateInfo === 'object' ) ;
2015-07-28 14:40:22 -07:00
function upgradeError ( 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 upgrade' ) ;
2015-07-20 00:09:47 -07:00
2016-05-03 18:36:50 -07:00
backups . backupBoxAndApps ( { userId : null , username : 'upgrader' } , function ( error ) {
2015-07-29 12:33:07 +02:00
if ( error ) return upgradeError ( error ) ;
2015-07-20 00:09:47 -07:00
superagent . post ( config . apiServerOrigin ( ) + '/api/v1/boxes/' + config . fqdn ( ) + '/upgrade' )
. query ( { token : config . token ( ) } )
. send ( { version : boxUpdateInfo . version } )
2016-09-12 12:53:51 -07:00
. timeout ( 30 * 1000 )
2015-07-20 00:09:47 -07:00
. end ( function ( error , result ) {
2015-12-15 09:12:52 -08:00
if ( error && ! error . response ) return upgradeError ( new Error ( 'Network error making upgrade request: ' + error ) ) ;
if ( result . statusCode !== 202 ) return upgradeError ( new Error ( util . format ( 'Server not ready to upgrade. statusCode: %s body: %j' , result . status , result . body ) ) ) ;
2015-07-20 00:09:47 -07:00
progress . set ( progress . UPDATE , 10 , 'Updating base system' ) ;
// no need to unlock since this is the last thing we ever do on this box
2016-01-26 10:29:32 -08:00
callback ( ) ;
2016-06-29 23:24:00 -05:00
retire ( 'upgrade' ) ;
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 ( ) ,
token : config . token ( ) ,
apiServerOrigin : config . apiServerOrigin ( ) ,
webServerOrigin : config . webServerOrigin ( ) ,
fqdn : config . fqdn ( ) ,
2017-01-05 14:38:36 -08:00
tlsCert : config . tlsCert ( ) ,
tlsKey : config . tlsKey ( ) ,
2016-10-31 15:10:39 +01:00
isCustomDomain : config . isCustomDomain ( ) ,
isDemo : config . isDemo ( ) ,
2017-07-16 10:55:19 -07:00
zoneName : config . zoneName ( ) ,
2016-10-31 15:10:39 +01:00
appstore : {
token : config . token ( ) ,
apiServerOrigin : config . apiServerOrigin ( )
} ,
caas : {
2016-01-12 16:50:34 +01:00
token : config . token ( ) ,
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-04-04 22:19:15 -07:00
function installAppBundle ( callback ) {
2017-01-09 18:24:39 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
if ( fs . existsSync ( paths . FIRST _RUN _FILE ) ) return callback ( ) ;
2016-04-04 22:19:15 -07:00
var bundle = config . get ( 'appBundle' ) ;
2017-01-09 18:24:39 -08:00
debug ( 'initialize: installing app bundle on first run: %j' , bundle ) ;
2016-04-04 22:19:15 -07:00
2017-01-09 18:24:39 -08:00
if ( ! bundle || bundle . length === 0 ) return callback ( ) ;
2016-04-04 22:19:15 -07:00
async . eachSeries ( bundle , function ( appInfo , iteratorCallback ) {
2016-06-04 01:07:43 -07:00
debug ( 'autoInstall: installing %s at %s' , appInfo . appstoreId , appInfo . location ) ;
2016-04-04 22:19:15 -07:00
2016-06-04 01:07:43 -07:00
var data = {
appStoreId : appInfo . appstoreId ,
location : appInfo . location ,
portBindings : appInfo . portBindings || null ,
accessRestriction : appInfo . accessRestriction || null ,
} ;
2016-06-03 23:22:38 -07:00
2016-06-04 13:40:43 -07:00
apps . install ( data , { userId : null , username : 'autoinstaller' } , iteratorCallback ) ;
2016-04-04 22:19:15 -07:00
} , function ( error ) {
if ( error ) debug ( 'autoInstallApps: ' , error ) ;
2017-01-09 18:24:39 -08:00
fs . writeFileSync ( paths . FIRST _RUN _FILE , 'been there, done that' , 'utf8' ) ;
2016-04-04 22:19:15 -07:00
callback ( ) ;
} ) ;
}
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
2016-07-05 22:04:24 -05:00
function retire ( reason , info , callback ) {
2016-06-29 23:24:00 -05:00
assert ( reason === 'migrate' || reason === 'upgrade' ) ;
2016-07-05 22:04:24 -05:00
info = info || { } ;
2016-01-26 10:29:32 -08:00
callback = callback || NOOP _CALLBACK ;
2016-01-25 17:54:02 -08:00
var data = {
2016-07-06 16:25:39 -05:00
apiServerOrigin : config . apiServerOrigin ( ) ,
2016-01-25 17:54:02 -08:00
isCustomDomain : config . isCustomDomain ( ) ,
fqdn : config . fqdn ( )
} ;
2016-07-05 22:04:24 -05:00
shell . sudo ( 'retire' , [ RETIRE _CMD , reason , JSON . stringify ( info ) , JSON . stringify ( data ) ] , callback ) ;
2016-01-25 20:18:50 -08:00
}
2016-01-25 17:54:02 -08:00
2016-07-04 22:30:25 -05:00
function doMigrate ( options , callback ) {
2016-07-02 16:03:21 -05:00
assert . strictEqual ( typeof options , 'object' ) ;
2016-06-27 22:24:30 -05:00
assert . strictEqual ( typeof callback , 'function' ) ;
var error = locker . lock ( locker . OP _MIGRATE ) ;
if ( error ) return callback ( new CloudronError ( CloudronError . BAD _STATE , error . message ) ) ;
function unlock ( error ) {
2016-06-28 16:09:44 -05:00
debug ( 'Failed to migrate' , error ) ;
locker . unlock ( locker . OP _MIGRATE ) ;
2016-06-28 16:21:22 -05:00
progress . set ( progress . MIGRATE , - 1 , 'Backup failed: ' + error . message ) ;
2016-06-27 22:24:30 -05:00
}
2016-06-28 16:16:15 -05:00
progress . set ( progress . MIGRATE , 10 , 'Backing up for migration' ) ;
2016-06-28 15:34:04 -05:00
2016-06-27 22:24:30 -05:00
// initiate the migration in the background
2017-04-22 18:28:58 -07:00
backups . backupBoxAndApps ( { userId : null , username : 'migrator' } , function ( error ) {
2016-06-27 22:24:30 -05:00
if ( error ) return unlock ( error ) ;
2016-07-02 16:03:21 -05:00
debug ( 'migrate: domain: %s size %s region %s' , options . domain , options . size , options . region ) ;
2016-06-27 22:24:30 -05:00
superagent
. post ( config . apiServerOrigin ( ) + '/api/v1/boxes/' + config . fqdn ( ) + '/migrate' )
. query ( { token : config . token ( ) } )
2016-07-02 16:03:21 -05:00
. send ( options )
2016-09-12 12:53:51 -07:00
. timeout ( 30 * 1000 )
2016-06-27 22:24:30 -05:00
. end ( function ( error , result ) {
if ( error && ! error . response ) return unlock ( error ) ; // network error
if ( result . statusCode === 409 ) return unlock ( new CloudronError ( CloudronError . BAD _STATE ) ) ;
if ( result . statusCode === 404 ) return unlock ( new CloudronError ( CloudronError . NOT _FOUND ) ) ;
if ( result . statusCode !== 202 ) return unlock ( new CloudronError ( CloudronError . EXTERNAL _ERROR , util . format ( '%s %j' , result . status , result . body ) ) ) ;
2016-06-28 16:09:44 -05:00
progress . set ( progress . MIGRATE , 10 , 'Migrating' ) ;
2016-06-28 15:43:34 -05:00
2016-07-05 22:04:24 -05:00
retire ( 'migrate' , _ . pick ( options , 'domain' , 'size' , 'region' ) ) ;
2016-06-27 22:24:30 -05:00
} ) ;
} ) ;
callback ( null ) ;
}
2016-07-04 22:30:25 -05:00
function migrate ( options , callback ) {
assert . strictEqual ( typeof options , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2016-08-31 21:20:48 -07:00
if ( config . isDemo ( ) ) return callback ( new CloudronError ( CloudronError . BAD _FIELD , 'Not allowed in demo mode' ) ) ;
2016-07-04 22:30:25 -05:00
if ( ! options . domain ) return doMigrate ( options , callback ) ;
2017-06-11 22:32:05 -07:00
var dnsConfig = _ . pick ( options , 'domain' , 'provider' , 'accessKeyId' , 'secretAccessKey' , 'region' , 'endpoint' , 'token' , 'zoneName' ) ;
2016-07-04 22:30:25 -05:00
2017-06-21 13:32:25 +02:00
settings . setDnsConfig ( dnsConfig , options . domain , options . zoneName || tld . getDomain ( options . domain ) , function ( error ) {
2016-07-04 22:30:25 -05:00
if ( error && error . reason === SettingsError . BAD _FIELD ) return callback ( new CloudronError ( CloudronError . BAD _FIELD , error . message ) ) ;
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
2017-01-10 16:02:11 -08:00
// TODO: should probably rollback dns config if migrate fails
2016-07-04 22:30:25 -05:00
doMigrate ( options , callback ) ;
} ) ;
}
2017-01-02 13:00:30 +01:00
2017-04-14 19:52:44 -07:00
// called for dynamic dns setups where we have to update the IP
2017-01-02 13:00:30 +01:00
function refreshDNS ( callback ) {
2017-01-02 13:14:03 +01:00
callback = callback || NOOP _CALLBACK ;
2017-01-02 13:00:30 +01:00
2017-02-23 22:03:44 -08:00
sysinfo . getPublicIp ( function ( error , ip ) {
2017-01-02 13:00:30 +01:00
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
debug ( 'refreshDNS: current ip %s' , ip ) ;
2017-04-25 14:06:08 -07:00
addDnsRecords ( ip , function ( error ) {
2017-01-02 13:00:30 +01:00
if ( error ) return callback ( error ) ;
2017-01-02 14:00:07 +01:00
debug ( 'refreshDNS: done for system records' ) ;
2017-01-02 13:00:30 +01:00
2017-01-02 14:00:07 +01:00
apps . getAll ( function ( error , result ) {
if ( error ) return callback ( error ) ;
async . each ( result , function ( app , callback ) {
2017-04-14 19:44:14 -07:00
// do not change state of installing apps since apptask will error if dns record already exists
if ( app . installationState !== appdb . ISTATE _INSTALLED ) return callback ( ) ;
2017-02-02 10:32:22 -08:00
subdomains . upsert ( app . location , 'A' , [ ip ] , callback ) ;
2017-01-02 14:00:07 +01:00
} , function ( error ) {
if ( error ) return callback ( error ) ;
debug ( 'refreshDNS: done for apps' ) ;
callback ( ) ;
} ) ;
} ) ;
2017-01-02 13:00:30 +01:00
} ) ;
} ) ;
}
2017-04-18 15:15:35 -07: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 ) ;
}