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-01-12 11:00:46 -08:00
dnsSetup : dnsSetup ,
2015-07-20 00:09:47 -07:00
sendHeartbeat : sendHeartbeat ,
2016-11-15 15:24:40 +01:00
sendAliveStatus : sendAliveStatus ,
2015-07-20 00:09:47 -07:00
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
2017-01-05 19:01:44 -08:00
getConfigStateSync : getConfigStateSync ,
2015-10-27 16:11:33 -07:00
2016-01-22 17:37:41 -08:00
checkDiskSpace : checkDiskSpace ,
2016-12-09 09:19:23 +01:00
readDkimPublicKeySync : readDkimPublicKeySync ,
2017-01-02 13:00:30 +01:00
refreshDNS : refreshDNS ,
2016-12-09 09:19:23 +01:00
2017-01-09 11:02:33 -08:00
configureAdmin : configureAdmin
2015-09-28 23:10:09 -07:00
} ;
2015-07-20 00:09:47 -07:00
var apps = require ( './apps.js' ) ,
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' ) ,
2016-01-22 17:37:41 -08:00
df = require ( 'node-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' ) ,
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-01-09 10:49:34 -08:00
taskmanager = require ( './taskmanager.js' ) ,
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
2017-01-06 12:42:21 +01:00
var IP _BASED _SETUP _NAME = 'ip_based_setup' ; // This will be used for cert and nginx config file names
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 : ''
}
} ;
2015-10-29 10:54:28 -07:00
var gUpdatingDns = false , // flag for dns update reentrancy
2016-07-02 10:41:10 -05:00
gBoxAndUserDetails = null , // cached cloudron details like region,size...
2017-01-10 22:19:41 +01:00
gConfigState = { dns : false , tls : false , configured : 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-01-09 18:24:39 -08:00
async . series ( [
2017-01-09 19:06:32 -08:00
installAppBundle ,
syncConfigState
2017-01-09 18:24:39 -08:00
] , callback ) ;
2015-07-20 00:09:47 -07:00
}
function uninitialize ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2017-01-09 10:49:34 -08:00
platform . events . removeListener ( platform . EVENT _READY , onPlatformReady ) ;
2017-01-09 09:24:32 -08:00
async . series ( [
2017-01-09 11:00:09 -08:00
cron . uninitialize ,
2017-01-09 10:49:34 -08:00
taskmanager . pauseTasks ,
2017-01-09 09:24:32 -08:00
mailer . stop ,
platform . uninitialize
] , callback ) ;
2015-07-20 00:09:47 -07:00
}
2017-01-07 23:31:29 -08:00
function onConfigured ( callback ) {
callback = callback || NOOP _CALLBACK ;
2017-01-09 10:49:34 -08:00
debug ( 'onConfigured' ) ;
platform . events . on ( platform . EVENT _READY , onPlatformReady ) ;
2017-01-07 23:31:29 -08:00
async . series ( [
2017-01-09 15:25:45 -08:00
clients . addDefaultClients ,
2017-01-09 11:00:09 -08:00
cron . initialize ,
2017-01-09 09:26:56 -08:00
certificates . ensureFallbackCertificate ,
platform . initialize , // requires fallback certs in mail container
2017-01-07 23:31:29 -08:00
addDnsRecords ,
2017-01-07 23:33:20 -08:00
configureAdmin ,
2017-01-07 23:37:38 -08:00
mailer . start
2017-01-07 23:31:29 -08:00
] , callback ) ;
}
2017-01-09 10:49:34 -08:00
function onPlatformReady ( callback ) {
callback = callback || NOOP _CALLBACK ;
debug ( 'onPlatformReady' ) ;
async . series ( [
taskmanager . resumeTasks
] , callback ) ;
}
2017-01-05 19:01:44 -08:00
function getConfigStateSync ( ) {
return gConfigState ;
}
2015-11-03 15:50:02 -08:00
function isConfigured ( callback ) {
// set of rules to see if we have the configs required for cloudron to function
// note this checks for missing configs and not invalid configs
2017-01-05 13:05:36 +01:00
if ( ! config . fqdn ( ) ) return callback ( null , false ) ;
2015-11-03 15:50:02 -08:00
settings . getDnsConfig ( function ( error , dnsConfig ) {
if ( error ) return callback ( error ) ;
if ( ! dnsConfig ) return callback ( null , false ) ;
2016-12-21 15:10:56 -08:00
var isConfigured = ( config . isCustomDomain ( ) && ( dnsConfig . provider === 'route53' || dnsConfig . provider === 'digitalocean' || dnsConfig . provider === 'noop' || dnsConfig . provider === 'manual' ) ) ||
2015-11-03 15:50:02 -08:00
( ! config . isCustomDomain ( ) && dnsConfig . provider === 'caas' ) ;
2015-11-04 08:28:21 -08:00
callback ( null , isConfigured ) ;
2015-11-03 15:50:02 -08:00
} ) ;
}
function syncConfigState ( callback ) {
callback = callback || NOOP _CALLBACK ;
isConfigured ( function ( error , configured ) {
if ( error ) return callback ( error ) ;
2017-01-12 11:08:34 -08:00
debug ( 'syncConfigState: configured = %s already configured = %s' , configured , gConfigState . configured ) ;
if ( gConfigState . configured ) return callback ( ) ; // required because we call syncConfigState directly from dnsSetup
2015-11-03 16:11:24 -08:00
2015-11-03 15:50:02 -08:00
if ( configured ) {
2017-01-05 19:06:10 -08:00
gConfigState . configured = true ;
2017-01-09 19:06:32 -08:00
return onConfigured ( callback ) ;
2015-11-03 15:50:02 -08:00
}
2017-01-09 19:06:32 -08:00
settings . events . once ( settings . DNS _CONFIG _KEY , function ( ) { syncConfigState ( ) ; } ) ; // check again later
2015-11-03 15:50:02 -08:00
callback ( ) ;
} ) ;
2015-10-27 16:11:33 -07:00
}
2017-01-12 11:00:46 -08:00
function dnsSetup ( dnsConfig , domain , callback ) {
assert . strictEqual ( typeof dnsConfig , 'object' ) ;
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
if ( config . fqdn ( ) ) return callback ( new CloudronError ( CloudronError . ALREADY _SETUP ) ) ;
settings . setDnsConfig ( dnsConfig , domain , function ( error ) {
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-12 11:08:34 -08:00
config . set ( 'fqdn' , domain ) ; // set fqdn only after dns config is valid, otherwise cannot re-setup if we failed
syncConfigState ( ) ;
2017-01-12 11:00:46 -08:00
callback ( ) ;
} ) ;
}
2017-01-05 17:13:31 -08:00
function configureAdmin ( callback ) {
callback = callback || NOOP _CALLBACK ;
if ( process . env . BOX _ENV === 'test' ) return callback ( ) ;
debug ( 'configureAdmin' ) ;
sysinfo . getIp ( function ( error , ip ) {
if ( error ) return callback ( error ) ;
2017-01-06 14:19:38 +01:00
var certFilePath = path . join ( paths . NGINX _CERT _DIR , IP _BASED _SETUP _NAME + '-' + ip + '.cert' ) ;
var keyFilePath = path . join ( paths . NGINX _CERT _DIR , IP _BASED _SETUP _NAME + '-' + ip + '.key' ) ;
2017-01-05 17:13:31 -08:00
2017-01-06 14:19:38 +01:00
// check if we already have a cert for this IP, otherwise create one, this is mostly useful for servers with changing IPs
if ( ! fs . existsSync ( certFilePath ) || ! fs . existsSync ( keyFilePath ) ) {
debug ( 'configureAdmin: create new cert for %s' , ip ) ;
2017-01-05 17:13:31 -08:00
2017-01-06 14:19:38 +01:00
var certCommand = util . format ( 'openssl req -x509 -newkey rsa:2048 -keyout %s -out %s -days 3650 -subj /CN=%s -nodes' , keyFilePath , certFilePath , ip ) ;
safe . child _process . execSync ( certCommand ) ;
}
// always create a configuration for the ip
2017-01-06 21:59:46 +01:00
nginx . configureAdmin ( certFilePath , keyFilePath , IP _BASED _SETUP _NAME + '.conf' , '' , function ( error ) {
2017-01-06 14:19:38 +01:00
if ( error ) return callback ( error ) ;
// skip my.domain.com setup if we don't have a domain
if ( ! config . fqdn ( ) ) return callback ( null ) ;
2017-01-06 12:42:21 +01:00
2017-01-05 17:13:31 -08:00
subdomains . waitForDns ( config . adminFqdn ( ) , ip , 'A' , { interval : 30000 , times : 50000 } , function ( error ) {
if ( error ) return callback ( error ) ;
2017-01-05 19:01:44 -08:00
gConfigState . dns = true ;
2017-01-05 17:13:31 -08:00
certificates . ensureCertificate ( { location : constants . ADMIN _LOCATION } , function ( error , certFilePath , keyFilePath ) {
if ( error ) { // currently, this can never happen
debug ( 'Error obtaining certificate. Proceed anyway' , error ) ;
return callback ( ) ;
}
2017-01-05 19:01:44 -08:00
gConfigState . tls = true ;
2017-01-06 14:19:38 +01:00
nginx . configureAdmin ( certFilePath , keyFilePath , constants . NGINX _ADMIN _CONFIG _FILE _NAME , config . adminFqdn ( ) , callback ) ;
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 ) ;
2016-05-03 11:33:04 -07:00
// https://github.com/bluesmoon/node-geoip
// https://github.com/runk/node-maxmind
// { url: 'http://freegeoip.net/json/%s', jpath: 'time_zone' },
// { url: 'http://ip-api.com/json/%s', jpath: 'timezone' },
// { url: 'http://geoip.nekudo.com/api/%s', jpath: 'time_zone }
2016-09-12 12:53:51 -07:00
superagent . get ( 'http://ip-api.com/json/' + 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 ) ;
}
2016-09-07 20:00:44 -07:00
if ( ! result . body . timezone || typeof result . body . 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 ) ;
}
2016-09-07 20:00:44 -07:00
debug ( 'Setting timezone to ' , result . body . timezone ) ;
2015-07-20 00:09:47 -07:00
2016-09-07 20:00:44 -07:00
settings . setTimeZone ( result . body . 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 , { } ) ;
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
boxVersionsUrl : config . get ( 'boxVersionsUrl' ) ,
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-01-05 20:47:47 -08:00
configState : gConfigState
2015-07-20 00:09:47 -07:00
} ) ;
} ) ;
} ) ;
}
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 ( ) ,
isDev : config . isDev ( ) ,
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-06 09:42:14 -08:00
if ( ! config . token ( ) || ! config . fqdn ( ) ) 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
} ) ;
}
2016-11-15 15:24:40 +01:00
function sendAliveStatus ( callback ) {
if ( typeof callback !== 'function' ) {
callback = function ( error ) {
2016-11-15 16:59:45 +01:00
if ( error && error . reason !== CloudronError . INTERNAL _ERROR ) console . error ( error ) ;
2016-11-15 15:24:40 +01:00
else if ( error ) debug ( error ) ;
} ;
}
2017-01-10 10:22:47 +01:00
function sendAliveStatusWithAppstoreConfig ( backendSettings , appstoreConfig ) {
assert . strictEqual ( typeof backendSettings , 'object' ) ;
2016-11-15 15:24:40 +01:00
assert . strictEqual ( typeof appstoreConfig . userId , 'string' ) ;
assert . strictEqual ( typeof appstoreConfig . cloudronId , 'string' ) ;
assert . strictEqual ( typeof appstoreConfig . token , 'string' ) ;
var url = config . apiServerOrigin ( ) + '/api/v1/users/' + appstoreConfig . userId + '/cloudrons/' + appstoreConfig . cloudronId ;
var data = {
domain : config . fqdn ( ) ,
2016-11-16 14:45:27 +01:00
version : config . version ( ) ,
2017-01-10 10:22:47 +01:00
provider : config . provider ( ) ,
backendSettings : backendSettings
2016-11-15 15:24:40 +01:00
} ;
superagent . post ( url ) . send ( data ) . query ( { accessToken : appstoreConfig . token } ) . timeout ( 30 * 1000 ) . end ( function ( error , result ) {
if ( error && ! error . response ) return callback ( new CloudronError ( CloudronError . EXTERNAL _ERROR , error ) ) ;
if ( result . statusCode === 404 ) return callback ( new CloudronError ( CloudronError . NOT _FOUND ) ) ;
if ( result . statusCode !== 201 ) return callback ( new CloudronError ( CloudronError . EXTERNAL _ERROR , util . format ( 'Sending alive status failed. %s %j' , result . status , result . body ) ) ) ;
callback ( null ) ;
} ) ;
}
2017-01-10 10:22:47 +01:00
settings . getAll ( function ( error , result ) {
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
2016-11-15 15:24:40 +01:00
2017-01-10 10:22:47 +01:00
var backendSettings = {
dnsConfig : {
provider : result [ settings . DNS _CONFIG _KEY ] . provider ,
wildcard : result [ settings . DNS _CONFIG _KEY ] . provider === 'manual' ? result [ settings . DNS _CONFIG _KEY ] . wildcard : undefined
} ,
tlsConfig : {
provider : result [ settings . TLS _CONFIG _KEY ] . provider
} ,
backupConfig : {
provider : result [ settings . BACKUP _CONFIG _KEY ] . provider
}
} ;
2016-11-15 15:24:40 +01:00
2017-01-10 10:22:47 +01:00
// Caas Cloudrons do not store appstore credentials in their local database
if ( config . provider ( ) === 'caas' ) {
if ( ! config . token ( ) ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , 'no token set' ) ) ;
2016-11-15 15:24:40 +01:00
2017-01-10 10:22:47 +01:00
var url = config . apiServerOrigin ( ) + '/api/v1/exchangeBoxTokenWithUserToken' ;
superagent . post ( url ) . query ( { token : config . token ( ) } ) . timeout ( 30 * 1000 ) . end ( function ( error , result ) {
if ( error && ! error . response ) return callback ( new CloudronError ( CloudronError . EXTERNAL _ERROR , error ) ) ;
if ( result . statusCode !== 201 ) return callback ( new CloudronError ( CloudronError . EXTERNAL _ERROR , util . format ( 'App purchase failed. %s %j' , result . status , result . body ) ) ) ;
sendAliveStatusWithAppstoreConfig ( backendSettings , result . body ) ;
} ) ;
} else {
settings . getAppstoreConfig ( function ( error , result ) {
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
if ( ! result . token ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , 'not registered yet' ) ) ;
sendAliveStatusWithAppstoreConfig ( backendSettings , result ) ;
} ) ;
}
} ) ;
2016-11-15 15:24:40 +01:00
}
2017-01-05 17:04:03 +01:00
function readDkimPublicKeySync ( ) {
if ( ! config . fqdn ( ) ) {
debug ( 'Cannot read dkim public key without a domain.' , safe . error ) ;
return null ;
}
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' ) ;
}
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
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 ) ;
2015-10-30 13:53:12 -07:00
var i , validSpf ;
2015-10-29 10:54:28 -07:00
2015-10-29 15:35:07 -07:00
for ( i = 0 ; i < txtRecords . length ; i ++ ) {
2015-10-30 16:04:09 -07:00
if ( txtRecords [ i ] . indexOf ( '"v=spf1 ' ) !== 0 ) continue ; // not SPF
2015-10-29 10:54:28 -07:00
2016-03-27 23:05:29 -07:00
validSpf = txtRecords [ i ] . indexOf ( ' a:' + config . adminFqdn ( ) + ' ' ) !== - 1 ;
2015-10-29 10:54:28 -07:00
break ;
2015-07-20 00:09:47 -07:00
}
2015-10-29 10:54:28 -07:00
if ( validSpf ) return callback ( null , null ) ;
2015-10-29 15:35:07 -07:00
if ( i == txtRecords . length ) {
2016-03-27 23:05:29 -07:00
txtRecords [ i ] = '"v=spf1 a:' + config . adminFqdn ( ) + ' ~all"' ;
2015-10-29 10:54:28 -07:00
} else {
2016-03-27 23:05:29 -07:00
txtRecords [ i ] = '"v=spf1 a:' + config . adminFqdn ( ) + ' ' + txtRecords [ i ] . slice ( '"v=spf1 ' . length ) ;
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-01-02 13:00:30 +01:00
function addDnsRecords ( callback ) {
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 15:34:30 -07:00
if ( gUpdatingDns ) {
debug ( 'addDnsRecords: dns update already in progress' ) ;
return callback ( ) ;
}
2015-10-29 10:54:28 -07:00
gUpdatingDns = true ;
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
2016-01-05 12:16:48 +01:00
sysinfo . getIp ( function ( error , ip ) {
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
2015-10-29 10:54:28 -07:00
2016-05-11 09:54:51 -07:00
var webadminRecord = { subdomain : constants . ADMIN _LOCATION , type : 'A' , values : [ ip ] } ;
2016-01-05 12:16:48 +01:00
// t=s limits the domainkey to this domain and not it's subdomains
2016-12-14 14:54:17 +01:00
var dkimRecord = { subdomain : constants . DKIM _SELECTOR + '._domainkey' , type : 'TXT' , values : [ '"v=DKIM1; t=s; p=' + dkimKey + '"' ] } ;
2016-01-05 12:16:48 +01:00
var records = [ ] ;
if ( config . isCustomDomain ( ) ) {
records . push ( webadminRecord ) ;
records . push ( dkimRecord ) ;
} else {
2017-01-11 00:08:13 +01:00
// for non-custom domains, we show a noapp.html page
2016-03-21 13:50:26 -07:00
var nakedDomainRecord = { subdomain : '' , type : 'A' , values : [ ip ] } ;
2016-01-05 12:16:48 +01:00
records . push ( nakedDomainRecord ) ;
records . push ( webadminRecord ) ;
records . push ( dkimRecord ) ;
}
2015-10-29 10:54:28 -07:00
2016-01-05 12:16:48 +01:00
debug ( 'addDnsRecords: %j' , records ) ;
2015-10-29 10:54:28 -07:00
2016-01-05 12:16:48 +01:00
async . retry ( { times : 10 , interval : 20000 } , function ( retryCallback ) {
txtRecordsWithSpf ( function ( error , txtRecords ) {
if ( error ) return retryCallback ( error ) ;
2015-10-29 10:54:28 -07:00
2016-01-05 12:16:48 +01:00
if ( txtRecords ) records . push ( { subdomain : '' , type : 'TXT' , values : txtRecords } ) ;
2015-10-30 13:48:46 -07:00
2016-01-05 12:16:48 +01:00
debug ( 'addDnsRecords: will update %j' , records ) ;
2015-11-04 14:28:22 -08:00
2016-01-05 12:16:48 +01:00
async . mapSeries ( records , function ( record , iteratorCallback ) {
2016-09-05 16:41:04 -07:00
subdomains . upsert ( record . subdomain , record . type , record . values , iteratorCallback ) ;
2016-01-05 12:16:48 +01:00
} , 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
} ) ;
2016-01-05 12:16:48 +01:00
} , function ( error ) {
gUpdatingDns = false ;
2015-10-30 18:12:24 -07:00
2016-01-05 12:16:48 +01:00
debug ( 'addDnsRecords: done updating records with error:' , error ) ;
2015-10-30 18:12:24 -07:00
2016-01-05 12:16:48 +01: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 ) {
2015-11-26 12:04:39 +01:00
console . error ( '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 ) {
2015-11-26 12:04:39 +01:00
console . error ( '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-01-14 11:13:00 -08: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' ) ;
var boxUpdateInfo = updateChecker . getUpdateInfo ( ) . box ;
if ( ! boxUpdateInfo ) return callback ( new CloudronError ( CloudronError . ALREADY _UPTODATE , 'No update available' ) ) ;
2016-11-09 12:25:36 +01: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 ) ;
} ) ;
return callback ( null ) ;
}
2016-06-20 14:06:46 +02:00
if ( boxUpdateInfo . upgrade && config . provider ( ) !== 'caas' ) return callback ( new CloudronError ( CloudronError . SELF _UPGRADE _NOT _SUPPORTED ) ) ;
2016-06-02 19:26:42 -07: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 ( ) ,
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
2016-10-31 15:10:39 +01:00
version : boxUpdateInfo . version ,
boxVersionsUrl : config . get ( 'boxVersionsUrl' )
} ;
2015-07-20 00:09:47 -07:00
2016-10-31 15:10:39 +01:00
debug ( 'updating box %s %j' , boxUpdateInfo . sourceTarballUrl , data ) ;
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' ) ;
df ( function ( error , entries ) {
if ( error ) {
debug ( 'df error %s' , error . message ) ;
mailer . outOfDiskSpace ( error . message ) ;
return callback ( ) ;
}
var oos = entries . some ( function ( entry ) {
return ( entry . mount === paths . DATA _DIR && entry . capacity >= 0.90 ) ||
2016-07-26 17:10:22 -07:00
( entry . mount === '/' && entry . available <= ( 1.25 * 1024 * 1024 ) ) ; // 1.5G
2016-01-22 17:37:41 -08:00
} ) ;
debug ( 'Disk space checked. ok: %s' , ! oos ) ;
if ( oos ) mailer . outOfDiskSpace ( JSON . stringify ( entries , null , 4 ) ) ;
callback ( ) ;
} ) ;
}
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
backups . backupBoxAndApps ( { userId : null , username : 'migrator' } , function ( error , backupId ) {
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 ) ;
options . restoreKey = backupId ;
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 ) ;
2016-07-04 23:21:14 -05:00
var dnsConfig = _ . pick ( options , 'domain' , 'provider' , 'accessKeyId' , 'secretAccessKey' , 'region' , 'endpoint' ) ;
2016-07-04 22:30:25 -05:00
2017-01-10 16:23:01 -08:00
settings . setDnsConfig ( dnsConfig , 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
function refreshDNS ( callback ) {
2017-01-02 13:14:03 +01:00
callback = callback || NOOP _CALLBACK ;
2017-01-02 13:00:30 +01:00
sysinfo . getIp ( function ( error , ip ) {
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
debug ( 'refreshDNS: current ip %s' , ip ) ;
addDnsRecords ( function ( error ) {
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 ) {
// get the current record before updating it
subdomains . get ( app . location , 'A' , function ( error , values ) {
if ( error ) return callback ( error ) ;
// refuse to update any existing DNS record for custom domains that we did not create
if ( values . length !== 0 && ! app . dnsRecordId ) return callback ( null , new Error ( 'DNS Record already exists' ) ) ;
subdomains . upsert ( app . location , 'A' , [ ip ] , callback ) ;
} ) ;
} , function ( error ) {
if ( error ) return callback ( error ) ;
debug ( 'refreshDNS: done for apps' ) ;
callback ( ) ;
} ) ;
} ) ;
2017-01-02 13:00:30 +01:00
} ) ;
} ) ;
}