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 ,
sendHeartbeat : sendHeartbeat ,
2016-01-14 11:13:00 -08:00
updateToLatest : updateToLatest ,
2015-07-20 00:09:47 -07:00
update : update ,
reboot : reboot ,
2016-01-25 20:18:50 -08:00
retire : retire ,
2015-10-27 16:00:31 -07:00
2015-11-03 15:50:02 -08:00
isConfiguredSync : isConfiguredSync ,
2015-10-27 16:11:33 -07:00
2016-01-22 17:37:41 -08:00
checkDiskSpace : checkDiskSpace ,
2015-10-27 16:00:31 -07:00
events : new ( require ( 'events' ) . EventEmitter ) ( ) ,
2015-11-03 15:50:02 -08:00
EVENT _ACTIVATED : 'activated' ,
2016-04-04 22:19:15 -07:00
EVENT _CONFIGURED : 'configured' ,
EVENT _FIRST _RUN : 'firstrun'
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' ) ,
clientdb = require ( './clientdb.js' ) ,
config = require ( './config.js' ) ,
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' ) ,
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' ) ,
progress = require ( './progress.js' ) ,
safe = require ( 'safetydance' ) ,
settings = require ( './settings.js' ) ,
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' ) ,
tokendb = require ( './tokendb.js' ) ,
updateChecker = require ( './updatechecker.js' ) ,
user = require ( './user.js' ) ,
UserError = user . UserError ,
userdb = require ( './userdb.js' ) ,
2015-08-24 12:19:31 -07:00
util = require ( 'util' ) ,
2016-04-10 19:17:44 -07:00
uuid = require ( 'node-uuid' ) ;
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-01-25 20:18:50 -08:00
INSTALLER _UPDATE _URL = 'http://127.0.0.1:2020/api/v1/installer/update' ,
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
2015-10-29 10:54:28 -07:00
var gUpdatingDns = false , // flag for dns update reentrancy
2015-10-27 16:11:33 -07:00
gCloudronDetails = null , // cached cloudron details like region,size...
2016-04-08 17:01:07 +02:00
gAppstoreUserDetails = { } ,
2015-11-03 15:50:02 -08:00
gIsConfigured = null ; // cached configured state so that return value is synchronous. null means we are not initialized yet
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' ;
CloudronError . BAD _USERNAME = 'Bad username' ;
CloudronError . BAD _EMAIL = 'Bad email' ;
CloudronError . BAD _PASSWORD = 'Bad password' ;
CloudronError . BAD _NAME = 'Bad name' ;
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' ;
function initialize ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2015-11-03 15:50:02 -08:00
exports . events . on ( exports . EVENT _CONFIGURED , addDnsRecords ) ;
2016-04-19 13:56:09 -07:00
exports . events . on ( exports . EVENT _FIRST _RUN , installAppBundle ) ;
2016-04-04 22:19:15 -07:00
2016-04-05 10:56:27 -07:00
// check activation state for existing cloudrons that do not have first run file
// can be removed once cloudrons have been updated
isActivated ( function ( error , activated ) {
if ( error ) return callback ( error ) ;
2016-04-05 12:07:37 -07:00
debug ( 'initialize: cloudron %s activated' , activated ? '' : 'not' ) ;
2016-04-05 10:56:27 -07:00
if ( activated ) fs . writeFileSync ( paths . FIRST _RUN _FILE , 'been there, done that' , 'utf8' ) ;
2015-07-20 00:09:47 -07:00
2016-04-05 10:56:27 -07:00
if ( ! fs . existsSync ( paths . FIRST _RUN _FILE ) ) {
// EE API is sync. do not keep the server waiting
2016-04-05 12:07:37 -07:00
debug ( 'initialize: emitting first run event' ) ;
2016-04-05 10:56:27 -07:00
process . nextTick ( function ( ) { exports . events . emit ( exports . EVENT _FIRST _RUN ) ; } ) ;
fs . writeFileSync ( paths . FIRST _RUN _FILE , 'been there, done that' , 'utf8' ) ;
}
syncConfigState ( callback ) ;
} ) ;
2015-07-20 00:09:47 -07:00
}
function uninitialize ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2015-11-03 15:50:02 -08:00
exports . events . removeListener ( exports . EVENT _CONFIGURED , addDnsRecords ) ;
2016-04-04 22:19:15 -07:00
exports . events . removeListener ( exports . EVENT _FIRST _RUN , installAppBundle ) ;
2015-11-03 15:22:02 -08:00
2015-07-20 00:09:47 -07:00
callback ( null ) ;
}
2015-11-03 15:50:02 -08:00
function isConfiguredSync ( ) {
return gIsConfigured === true ;
}
2016-04-05 10:56:27 -07:00
function isActivated ( callback ) {
user . getOwner ( function ( error ) {
if ( error && error . reason === UserError . NOT _FOUND ) return callback ( null , false ) ;
if ( error ) return callback ( error ) ;
callback ( null , true ) ;
} ) ;
}
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
settings . getDnsConfig ( function ( error , dnsConfig ) {
if ( error ) return callback ( error ) ;
if ( ! dnsConfig ) return callback ( null , false ) ;
var isConfigured = ( config . isCustomDomain ( ) && dnsConfig . provider === 'route53' ) ||
( ! 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 ) {
assert ( ! gIsConfigured ) ;
callback = callback || NOOP _CALLBACK ;
isConfigured ( function ( error , configured ) {
if ( error ) return callback ( error ) ;
2015-11-03 16:11:24 -08:00
debug ( 'syncConfigState: configured = %s' , configured ) ;
2015-11-03 15:50:02 -08:00
if ( configured ) {
exports . events . emit ( exports . EVENT _CONFIGURED ) ;
} else {
settings . events . once ( settings . DNS _CONFIG _KEY , function ( ) { syncConfigState ( ) ; } ) ; // check again later
}
gIsConfigured = configured ;
callback ( ) ;
} ) ;
2015-10-27 16:11:33 -07: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 ) ;
superagent . get ( 'http://www.telize.com/geoip/' + ip ) . 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 ) ;
}
if ( ! result . body . timezone ) {
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 ) ;
}
debug ( 'Setting timezone to ' , result . body . timezone ) ;
settings . setTimeZone ( result . body . timezone , callback ) ;
} ) ;
}
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 ) ) ;
if ( error && error . reason === UserError . BAD _USERNAME ) return callback ( new CloudronError ( CloudronError . BAD _USERNAME ) ) ;
if ( error && error . reason === UserError . BAD _PASSWORD ) return callback ( new CloudronError ( CloudronError . BAD _PASSWORD ) ) ;
if ( error && error . reason === UserError . BAD _EMAIL ) return callback ( new CloudronError ( CloudronError . BAD _EMAIL ) ) ;
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
clientdb . getByAppIdAndType ( 'webadmin' , clientdb . TYPE _ADMIN , 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 ( ) ;
var expires = Date . now ( ) + 24 * 60 * 60 * 1000 ; // 1 day
2015-07-20 00:09:47 -07:00
2015-10-20 13:12:37 +02:00
tokendb . add ( token , tokendb . PREFIX _USER + userObject . id , result . id , expires , '*' , function ( error ) {
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
2015-07-20 00:09:47 -07:00
2015-10-27 22:18:02 -07:00
// EE API is sync. do not keep the REST API reponse waiting
process . nextTick ( function ( ) { exports . events . emit ( exports . EVENT _ACTIVATED ) ; } ) ;
2015-10-27 16:00:31 -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' ) ;
userdb . count ( function ( error , count ) {
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 ( ) ,
2015-07-20 00:09:47 -07:00
cloudronName : cloudronName
} ) ;
} ) ;
} ) ;
}
function getCloudronDetails ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
if ( gCloudronDetails ) return callback ( null , gCloudronDetails ) ;
2015-12-29 17:43:54 +01:00
if ( ! config . token ( ) ) {
gCloudronDetails = {
region : null ,
size : null
} ;
return callback ( null , gCloudronDetails ) ;
}
2015-07-20 00:09:47 -07:00
superagent
. get ( config . apiServerOrigin ( ) + '/api/v1/boxes/' + config . fqdn ( ) )
. query ( { token : config . token ( ) } )
. end ( function ( error , result ) {
2015-12-15 09:12:52 -08:00
if ( error && ! error . response ) return callback ( error ) ;
if ( result . statusCode !== 200 ) return callback ( new CloudronError ( CloudronError . EXTERNAL _ERROR , util . format ( '%s %j' , result . status , result . body ) ) ) ;
2015-07-20 00:09:47 -07:00
gCloudronDetails = result . body . box ;
2016-04-08 17:01:07 +02:00
gAppstoreUserDetails = result . body . user ;
2015-07-20 00:09:47 -07:00
return callback ( null , gCloudronDetails ) ;
} ) ;
}
function getConfig ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
getCloudronDetails ( function ( error , result ) {
if ( error ) {
2015-11-09 19:10:33 -08:00
debug ( 'Failed to fetch cloudron details.' , error ) ;
2015-07-20 00:09:47 -07:00
// set fallback values to avoid dependency on appstore
result = {
region : result ? result . region : null ,
size : result ? result . size : null
} ;
}
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-01-05 12:16:48 +01:00
sysinfo . getIp ( function ( error , ip ) {
if ( error ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , error ) ) ;
callback ( null , {
apiServerOrigin : config . apiServerOrigin ( ) ,
webServerOrigin : config . webServerOrigin ( ) ,
isDev : config . isDev ( ) ,
fqdn : config . fqdn ( ) ,
ip : ip ,
version : config . version ( ) ,
update : updateChecker . getUpdateInfo ( ) ,
progress : progress . get ( ) ,
isCustomDomain : config . isCustomDomain ( ) ,
developerMode : developerMode ,
region : result . region ,
size : result . size ,
2016-04-08 17:01:07 +02:00
billing : ! ! gAppstoreUserDetails . billing ,
2016-01-05 12:16:48 +01:00
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 ( ) {
2015-12-29 17:43:54 +01:00
if ( ! config . token ( ) ) 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' ;
2015-08-22 16:51:56 -07:00
superagent . post ( url ) . query ( { token : config . token ( ) , version : config . version ( ) } ) . timeout ( 10000 ) . 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
} ) ;
}
2015-10-29 10:54:28 -07:00
function readDkimPublicKeySync ( ) {
2015-07-20 00:09:47 -07:00
var dkimPublicKeyFile = path . join ( paths . MAIL _DATA _DIR , 'dkim/' + config . fqdn ( ) + '/public' ) ;
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
} ) ;
}
2015-11-03 15:22:02 -08:00
function addDnsRecords ( ) {
var 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 DKIM _SELECTOR = 'cloudron' ;
var DMARC _REPORT _EMAIL = 'dmarc-report@cloudron.io' ;
var dkimKey = readDkimPublicKeySync ( ) ;
if ( ! dkimKey ) return callback ( new CloudronError ( CloudronError . INTERNAL _ERROR , new Error ( 'internal error failed to read dkim public key' ) ) ) ;
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-01-05 12:16:48 +01:00
var webadminRecord = { subdomain : 'my' , type : 'A' , values : [ ip ] } ;
// t=s limits the domainkey to this domain and not it's subdomains
var dkimRecord = { subdomain : DKIM _SELECTOR + '._domainkey' , type : 'TXT' , values : [ '"v=DKIM1; t=s; p=' + dkimKey + '"' ] } ;
// DMARC requires special setup if report email id is in different domain
var dmarcRecord = { subdomain : '_dmarc' , type : 'TXT' , values : [ '"v=DMARC1; p=none; pct=100; rua=mailto:' + DMARC _REPORT _EMAIL + '; ruf=' + DMARC _REPORT _EMAIL + '"' ] } ;
var records = [ ] ;
if ( config . isCustomDomain ( ) ) {
records . push ( webadminRecord ) ;
records . push ( dkimRecord ) ;
} else {
2016-03-21 13:50:26 -07:00
// for custom domains, we show a nakeddomain.html page
var nakedDomainRecord = { subdomain : '' , type : 'A' , values : [ ip ] } ;
2016-01-05 12:16:48 +01:00
records . push ( nakedDomainRecord ) ;
records . push ( webadminRecord ) ;
records . push ( dkimRecord ) ;
records . push ( dmarcRecord ) ;
}
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 ) {
subdomains . update ( 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
} ) ;
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 ) ;
}
function update ( boxUpdateInfo , callback ) {
assert . strictEqual ( typeof boxUpdateInfo , 'object' ) ;
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-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-01-25 16:23:50 -08:00
if ( config . version ( ) . match ( /[-+]/ ) !== null && config . version ( ) . replace ( /[-+].*/ , '' ) === boxUpdateInfo . version ) {
2016-01-25 16:46:54 -08:00
doShortCircuitUpdate ( boxUpdateInfo , function ( error ) {
if ( error ) debug ( 'Short-circuit update failed' , error ) ;
locker . unlock ( locker . OP _BOX _UPDATE ) ;
} ) ;
2016-01-25 14:48:09 -08:00
} else 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-05-01 13:15:30 -07:00
eventlog . add ( eventlog . ACTION _UPDATE , auditSource , { boxUpdateInfo : boxUpdateInfo } ) ;
2016-01-14 11:13:00 -08:00
update ( boxUpdateInfo , callback ) ;
}
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-04-10 19:17:44 -07:00
backups . backupBoxAndApps ( 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 } )
. 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 ( ) ;
retire ( ) ;
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-04-10 19:17:44 -07:00
backups . backupBoxAndApps ( function ( error ) {
2015-07-29 13:52:59 +02:00
if ( error ) return updateError ( error ) ;
2015-07-20 00:09:47 -07:00
2016-01-12 16:50:34 +01:00
// NOTE: the args here are tied to the installer revision, box code and appstore provisioning logic
var args = {
sourceTarballUrl : boxUpdateInfo . sourceTarballUrl ,
// this data is opaque to the installer
data : {
token : config . token ( ) ,
apiServerOrigin : config . apiServerOrigin ( ) ,
webServerOrigin : config . webServerOrigin ( ) ,
fqdn : config . fqdn ( ) ,
tlsCert : fs . readFileSync ( path . join ( paths . NGINX _CERT _DIR , 'host.cert' ) , 'utf8' ) ,
tlsKey : fs . readFileSync ( path . join ( paths . NGINX _CERT _DIR , 'host.key' ) , 'utf8' ) ,
isCustomDomain : config . isCustomDomain ( ) ,
appstore : {
token : config . token ( ) ,
apiServerOrigin : config . apiServerOrigin ( )
} ,
caas : {
2015-11-07 00:26:12 -08:00
token : config . token ( ) ,
2015-08-26 10:59:17 -07:00
apiServerOrigin : config . apiServerOrigin ( ) ,
2016-01-12 16:50:34 +01:00
webServerOrigin : config . webServerOrigin ( )
} ,
2015-11-12 14:22:43 -08:00
2016-01-12 16:50:34 +01:00
version : boxUpdateInfo . version ,
boxVersionsUrl : config . get ( 'boxVersionsUrl' )
}
} ;
2015-07-20 00:09:47 -07:00
2016-01-12 16:50:34 +01:00
debug ( 'updating box %j' , args ) ;
2015-07-20 00:09:47 -07:00
2016-01-12 16:50:34 +01:00
superagent . post ( INSTALLER _UPDATE _URL ) . send ( args ) . end ( function ( error , result ) {
if ( error && ! error . response ) return updateError ( error ) ;
if ( result . statusCode !== 202 ) return updateError ( new Error ( 'Error initiating update: ' + JSON . stringify ( result . body ) ) ) ;
2015-07-20 00:09:47 -07:00
2016-01-12 16:50:34 +01:00
progress . set ( progress . UPDATE , 10 , 'Updating cloudron software' ) ;
2015-07-20 00:09:47 -07:00
2016-01-12 16:50:34 +01:00
callback ( null ) ;
2015-07-20 00:09:47 -07:00
} ) ;
// Do not add any code here. The installer script will stop the box code any instant
} ) ;
}
2016-04-04 22:19:15 -07:00
function installAppBundle ( callback ) {
callback = callback || NOOP _CALLBACK ;
var bundle = config . get ( 'appBundle' ) ;
if ( ! bundle || bundle . length === 0 ) {
debug ( 'installAppBundle: no bundle set' ) ;
return callback ( ) ;
}
async . eachSeries ( bundle , function ( appInfo , iteratorCallback ) {
var appstoreId = appInfo . appstoreId ;
var parts = appstoreId . split ( '@' ) ;
var url = config . apiServerOrigin ( ) + '/api/v1/apps/' + parts [ 0 ] + ( parts [ 1 ] ? '/versions/' + parts [ 1 ] : '' ) ;
superagent . get ( url ) . end ( function ( error , result ) {
if ( error && ! error . response ) return iteratorCallback ( new Error ( 'Network error: ' + error . message ) ) ;
if ( result . statusCode !== 200 ) return iteratorCallback ( util . format ( 'Failed to get app info from store.' , result . statusCode , result . text ) ) ;
debug ( 'autoInstall: installing %s at %s' , appstoreId , appInfo . location ) ;
apps . install ( uuid . v4 ( ) , appstoreId , result . body . manifest , appInfo . location ,
appInfo . portBindings || null , appInfo . accessRestriction || null ,
null /* icon */ , null /* cert */ , null /* key */ , 0 /* default mem limit */ ,
2016-04-26 16:21:26 -07:00
null /* altDomain */ , iteratorCallback ) ;
2016-04-04 22:19:15 -07:00
} ) ;
} , function ( error ) {
if ( error ) debug ( 'autoInstallApps: ' , error ) ;
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-01-25 15:56:46 -08:00
( entry . mount === '/' && entry . used <= ( 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-01-25 20:18:50 -08:00
function retire ( callback ) {
2016-01-26 10:29:32 -08:00
callback = callback || NOOP _CALLBACK ;
2016-01-25 17:54:02 -08:00
var data = {
isCustomDomain : config . isCustomDomain ( ) ,
fqdn : config . fqdn ( )
} ;
shell . sudo ( 'retire' , [ RETIRE _CMD , JSON . stringify ( data ) ] , callback ) ;
2016-01-25 20:18:50 -08:00
}
2016-01-25 17:54:02 -08:00