2015-07-20 00:09:47 -07:00
#!/usr/bin/env node
'use strict' ;
exports = module . exports = {
2019-08-26 15:55:57 -07:00
run : run ,
2015-07-20 00:09:47 -07:00
// exported for testing
2015-12-10 15:21:10 -08:00
_reserveHttpPort : reserveHttpPort ,
2018-01-30 12:23:27 -08:00
_configureReverseProxy : configureReverseProxy ,
_unconfigureReverseProxy : unconfigureReverseProxy ,
2018-09-14 10:18:43 -07:00
_createAppDir : createAppDir ,
_deleteAppDir : deleteAppDir ,
2015-07-20 00:09:47 -07:00
_verifyManifest : verifyManifest ,
2019-09-03 19:13:57 -07:00
_registerSubdomains : registerSubdomains ,
_unregisterSubdomains : unregisterSubdomains ,
2018-02-07 20:54:43 +01:00
_waitForDnsPropagation : waitForDnsPropagation
2015-07-20 00:09:47 -07:00
} ;
require ( 'supererror' ) ( { splatchError : true } ) ;
var addons = require ( './addons.js' ) ,
appdb = require ( './appdb.js' ) ,
apps = require ( './apps.js' ) ,
assert = require ( 'assert' ) ,
async = require ( 'async' ) ,
2019-10-30 11:02:21 -07:00
auditSource = require ( './auditsource.js' ) ,
2016-04-10 19:17:44 -07:00
backups = require ( './backups.js' ) ,
2019-09-05 17:13:07 -07:00
BoxError = require ( './boxerror.js' ) ,
2020-01-31 13:33:19 -08:00
collectd = require ( './collectd.js' ) ,
2019-07-26 10:10:14 -07:00
constants = require ( './constants.js' ) ,
2015-07-20 00:09:47 -07:00
debug = require ( 'debug' ) ( 'box:apptask' ) ,
2019-08-12 20:38:24 -07:00
df = require ( '@sindresorhus/df' ) ,
2015-10-19 14:09:20 -07:00
docker = require ( './docker.js' ) ,
2017-10-29 00:15:11 +02:00
domains = require ( './domains.js' ) ,
2015-07-20 00:09:47 -07:00
ejs = require ( 'ejs' ) ,
2019-05-07 12:04:43 +02:00
eventlog = require ( './eventlog.js' ) ,
2015-07-20 00:09:47 -07:00
fs = require ( 'fs' ) ,
manifestFormat = require ( 'cloudron-manifestformat' ) ,
2018-09-13 13:55:49 -07:00
mkdirp = require ( 'mkdirp' ) ,
2015-07-20 00:09:47 -07:00
net = require ( 'net' ) ,
2017-08-11 22:05:31 +02:00
os = require ( 'os' ) ,
2015-07-20 00:09:47 -07:00
path = require ( 'path' ) ,
paths = require ( './paths.js' ) ,
2018-01-30 12:23:27 -08:00
reverseProxy = require ( './reverseproxy.js' ) ,
2018-06-04 11:45:22 +02:00
rimraf = require ( 'rimraf' ) ,
2015-07-20 00:09:47 -07:00
safe = require ( 'safetydance' ) ,
2019-07-26 10:49:29 -07:00
settings = require ( './settings.js' ) ,
2015-07-20 00:09:47 -07:00
shell = require ( './shell.js' ) ,
superagent = require ( 'superagent' ) ,
sysinfo = require ( './sysinfo.js' ) ,
util = require ( 'util' ) ,
_ = require ( 'underscore' ) ;
2020-01-31 13:37:07 -08:00
const COLLECTD _CONFIG _EJS = fs . readFileSync ( _ _dirname + '/collectd/app.ejs' , { encoding : 'utf8' } ) ,
2018-12-20 14:33:29 -08:00
MV _VOLUME _CMD = path . join ( _ _dirname , 'scripts/mvvolume.sh' ) ,
2017-08-11 22:05:31 +02:00
LOGROTATE _CONFIG _EJS = fs . readFileSync ( _ _dirname + '/logrotate.ejs' , { encoding : 'utf8' } ) ,
2018-09-13 13:55:49 -07:00
CONFIGURE _LOGROTATE _CMD = path . join ( _ _dirname , 'scripts/configurelogrotate.sh' ) ;
2015-07-20 00:09:47 -07:00
2016-01-05 12:14:39 +01:00
function debugApp ( app ) {
assert . strictEqual ( typeof app , 'object' ) ;
2015-07-20 00:09:47 -07:00
2018-02-08 15:07:49 +01:00
debug ( app . fqdn + ' ' + util . format . apply ( util , Array . prototype . slice . call ( arguments , 1 ) ) ) ;
2015-07-20 00:09:47 -07:00
}
2019-09-24 10:28:50 -07:00
function makeTaskError ( error , app ) {
2019-12-05 09:32:45 -08:00
assert . strictEqual ( typeof error , 'object' ) ;
assert . strictEqual ( typeof app , 'object' ) ;
2019-09-24 10:28:50 -07:00
// track a few variables which helps 'repair' restart the task (see also scheduleTask in apps.js)
2019-11-22 14:27:41 -08:00
error . details . taskId = app . taskId ;
error . details . installationState = app . installationState ;
return error . toPlainObject ( ) ;
2019-09-21 19:45:55 -07:00
}
2017-12-21 01:04:38 -08:00
// updates the app object and the database
function updateApp ( app , values , callback ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof values , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
debugApp ( app , 'updating app with values: %j' , values ) ;
appdb . update ( app . id , values , function ( error ) {
2019-10-24 20:31:45 -07:00
if ( error ) return callback ( error ) ;
2017-12-21 01:04:38 -08:00
for ( var value in values ) {
app [ value ] = values [ value ] ;
}
2019-09-06 11:19:58 -07:00
callback ( null ) ;
2017-12-21 01:04:38 -08:00
} ) ;
}
2015-12-10 15:21:10 -08:00
function reserveHttpPort ( app , callback ) {
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-09-06 11:19:58 -07:00
let server = net . createServer ( ) ;
2015-07-20 00:09:47 -07:00
server . listen ( 0 , function ( ) {
2019-09-06 11:19:58 -07:00
let port = server . address ( ) . port ;
2015-12-10 15:21:10 -08:00
updateApp ( app , { httpPort : port } , function ( error ) {
2019-09-06 11:19:58 -07:00
server . close ( function ( /* closeError */ ) {
if ( error ) return callback ( new BoxError ( BoxError . NETWORK _ERROR , ` Failed to allocate http port ${ port } : ${ error . message } ` ) ) ;
2015-12-10 15:21:10 -08:00
2019-09-06 11:19:58 -07:00
callback ( null ) ;
} ) ;
2015-07-20 00:09:47 -07:00
} ) ;
} ) ;
}
2018-01-30 12:23:27 -08:00
function configureReverseProxy ( app , callback ) {
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-09-06 11:19:58 -07:00
reverseProxy . configureApp ( app , { userId : null , username : 'apptask' } , function ( error ) {
2019-10-24 10:31:56 -07:00
if ( error ) return callback ( error ) ;
2019-09-06 11:19:58 -07:00
callback ( null ) ;
} ) ;
2015-07-20 00:09:47 -07:00
}
2018-01-30 12:23:27 -08:00
function unconfigureReverseProxy ( app , callback ) {
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-09-06 11:19:58 -07:00
reverseProxy . unconfigureApp ( app , function ( error ) {
2019-10-24 10:31:56 -07:00
if ( error ) return callback ( error ) ;
2019-09-06 11:19:58 -07:00
callback ( null ) ;
} ) ;
2015-07-20 00:09:47 -07:00
}
function createContainer ( app , callback ) {
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2015-10-19 16:22:35 -07:00
assert ( ! app . containerId ) ; // otherwise, it will trigger volumeFrom
2015-10-19 18:48:56 -07:00
debugApp ( app , 'creating container' ) ;
2015-10-19 21:33:53 -07:00
docker . createContainer ( app , function ( error , container ) {
2019-10-23 09:24:51 -07:00
if ( error ) return callback ( error ) ;
2015-07-20 00:09:47 -07:00
2019-11-19 17:27:39 -08:00
updateApp ( app , { containerId : container . id } , function ( error ) {
if ( error ) return callback ( error ) ;
// re-generate configs that rely on container id
async . series ( [
addLogrotateConfig . bind ( null , app ) ,
addCollectdProfile . bind ( null , app )
] , callback ) ;
} ) ;
2015-07-20 00:09:47 -07:00
} ) ;
}
2019-01-17 23:32:24 -08:00
function deleteContainers ( app , options , callback ) {
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
2019-01-17 23:32:24 -08:00
assert . strictEqual ( typeof options , 'object' ) ;
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof callback , 'function' ) ;
2017-09-07 20:09:46 -07:00
debugApp ( app , 'deleting app containers (app, scheduler)' ) ;
2015-10-19 18:48:56 -07:00
2019-11-19 17:27:39 -08:00
async . series ( [
// remove configs that rely on container id
removeCollectdProfile . bind ( null , app ) ,
removeLogrotateConfig . bind ( null , app ) ,
docker . stopContainers . bind ( null , app . id ) ,
docker . deleteContainers . bind ( null , app . id , options ) ,
updateApp . bind ( null , app , { containerId : null } )
] , callback ) ;
2015-07-20 00:09:47 -07:00
}
2018-09-14 10:18:43 -07:00
function createAppDir ( app , callback ) {
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-09-06 11:19:58 -07:00
const appDir = path . join ( paths . APPS _DATA _DIR , app . id ) ;
mkdirp ( appDir , function ( error ) {
if ( error ) return callback ( new BoxError ( BoxError . FS _ERROR , ` Error creating directory: ${ error . message } ` , { appDir } ) ) ;
callback ( null ) ;
} ) ;
2015-07-20 00:09:47 -07:00
}
2018-09-14 10:18:43 -07:00
function deleteAppDir ( app , options , callback ) {
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
2017-09-07 15:43:57 -07:00
assert . strictEqual ( typeof options , 'object' ) ;
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-09-15 17:05:04 -07:00
const appDataDir = path . join ( paths . APPS _DATA _DIR , app . id ) ;
2018-09-13 13:55:49 -07:00
2018-10-18 19:47:40 -07:00
// resolve any symlinked data dir
const stat = safe . fs . lstatSync ( appDataDir ) ;
if ( ! stat ) return callback ( null ) ;
2018-09-14 14:37:20 +02:00
2018-10-18 19:47:40 -07:00
const resolvedAppDataDir = stat . isSymbolicLink ( ) ? safe . fs . readlinkSync ( appDataDir ) : appDataDir ;
2018-09-13 13:55:49 -07:00
2018-10-18 19:47:40 -07:00
if ( safe . fs . existsSync ( resolvedAppDataDir ) ) {
const entries = safe . fs . readdirSync ( resolvedAppDataDir ) ;
2019-09-06 11:19:58 -07:00
if ( ! entries ) return callback ( new BoxError ( BoxError . FS _ERROR , ` Error listing ${ resolvedAppDataDir } : ${ safe . error . message } ` ) ) ;
2018-10-18 19:47:40 -07:00
// remove only files. directories inside app dir are currently volumes managed by the addons
2019-01-18 14:48:31 -08:00
// we cannot delete those dirs anyway because of perms
2018-10-18 19:47:40 -07:00
entries . forEach ( function ( entry ) {
let stat = safe . fs . statSync ( path . join ( resolvedAppDataDir , entry ) ) ;
if ( stat && ! stat . isDirectory ( ) ) safe . fs . unlinkSync ( path . join ( resolvedAppDataDir , entry ) ) ;
} ) ;
}
2018-09-15 17:05:04 -07:00
// if this fails, it's probably because the localstorage/redis addons have not cleaned up properly
2018-10-18 19:47:40 -07:00
if ( options . removeDirectory ) {
if ( stat . isSymbolicLink ( ) ) {
2019-09-06 11:19:58 -07:00
if ( ! safe . fs . unlinkSync ( appDataDir ) ) {
if ( safe . error . code !== 'ENOENT' ) return callback ( new BoxError ( BoxError . FS _ERROR , ` Error unlinking dir ${ appDataDir } : ${ safe . error . message } ` ) ) ;
}
2018-10-18 19:47:40 -07:00
} else {
2019-09-06 11:19:58 -07:00
if ( ! safe . fs . rmdirSync ( appDataDir ) ) {
if ( safe . error . code !== 'ENOENT' ) return callback ( new BoxError ( BoxError . FS _ERROR , ` Error removing dir ${ appDataDir } : ${ safe . error . message } ` ) ) ;
}
2018-10-18 19:47:40 -07:00
}
}
2018-09-15 17:05:04 -07:00
callback ( null ) ;
2015-07-20 00:09:47 -07:00
}
function addCollectdProfile ( app , callback ) {
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-08-17 03:37:22 -07:00
var collectdConf = ejs . render ( COLLECTD _CONFIG _EJS , { appId : app . id , containerId : app . containerId , appDataDir : apps . getDataDir ( app , app . dataDir ) } ) ;
2020-01-31 13:33:19 -08:00
collectd . addProfile ( app . id , collectdConf , callback ) ;
2015-07-20 00:09:47 -07:00
}
function removeCollectdProfile ( app , callback ) {
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2020-01-31 13:33:19 -08:00
collectd . removeProfile ( app . id , callback ) ;
2015-07-20 00:09:47 -07:00
}
2017-08-11 22:05:31 +02:00
function addLogrotateConfig ( app , callback ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
docker . inspect ( app . containerId , function ( error , result ) {
2019-10-23 09:24:51 -07:00
if ( error ) return callback ( error ) ;
2017-08-11 22:05:31 +02:00
var runVolume = result . Mounts . find ( function ( mount ) { return mount . Destination === '/run' ; } ) ;
2019-09-06 11:19:58 -07:00
if ( ! runVolume ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , 'App does not have /run mounted' ) ) ;
2017-08-11 22:05:31 +02:00
2017-09-12 21:45:42 -07:00
// logrotate configs can have arbitrary commands, so the config files must be owned by root
2019-07-30 14:47:33 -07:00
var logrotateConf = ejs . render ( LOGROTATE _CONFIG _EJS , { volumePath : runVolume . Source , appId : app . id } ) ;
2017-08-11 22:05:31 +02:00
var tmpFilePath = path . join ( os . tmpdir ( ) , app . id + '.logrotate' ) ;
fs . writeFile ( tmpFilePath , logrotateConf , function ( error ) {
2019-09-06 11:19:58 -07:00
if ( error ) return callback ( new BoxError ( BoxError . FS _ERROR , ` Error writing logrotate config: ${ error . message } ` ) ) ;
shell . sudo ( 'addLogrotateConfig' , [ CONFIGURE _LOGROTATE _CMD , 'add' , app . id , tmpFilePath ] , { } , function ( error ) {
if ( error ) return callback ( new BoxError ( BoxError . LOGROTATE _ERROR , ` Error adding logrotate config: ${ error . message } ` ) ) ;
callback ( null ) ;
} ) ;
2017-08-11 22:05:31 +02:00
} ) ;
} ) ;
}
function removeLogrotateConfig ( app , callback ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-09-06 11:19:58 -07:00
shell . sudo ( 'removeLogrotateConfig' , [ CONFIGURE _LOGROTATE _CMD , 'remove' , app . id ] , { } , function ( error ) {
if ( error ) return callback ( new BoxError ( BoxError . LOGROTATE _ERROR , ` Error removing logrotate config: ${ error . message } ` ) ) ;
callback ( null ) ;
} ) ;
}
function cleanupLogs ( app , callback ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
// note that redis container logs are cleaned up by the addon
rimraf ( path . join ( paths . LOG _DIR , app . id ) , function ( error ) {
if ( error ) debugApp ( app , 'cannot cleanup logs: %s' , error ) ;
callback ( null ) ;
} ) ;
2017-08-11 22:05:31 +02:00
}
2017-10-12 17:46:15 -07:00
function verifyManifest ( manifest , callback ) {
assert . strictEqual ( typeof manifest , 'object' ) ;
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof callback , 'function' ) ;
2015-07-20 00:09:47 -07:00
var error = manifestFormat . parse ( manifest ) ;
2019-09-05 17:13:07 -07:00
if ( error ) return callback ( new BoxError ( BoxError . BAD _FIELD , ` Manifest error: ${ error . message } ` , { field : 'manifest' } ) ) ;
2015-07-20 00:09:47 -07:00
error = apps . checkManifestConstraints ( manifest ) ;
2019-09-05 17:13:07 -07:00
if ( error ) return callback ( new BoxError ( BoxError . CONFLICT , ` Manifest constraint check failed: ${ error . message } ` , { field : 'manifest' } ) ) ;
2015-07-20 00:09:47 -07:00
2019-09-05 17:13:07 -07:00
callback ( null ) ;
2015-07-20 00:09:47 -07:00
}
function downloadIcon ( app , callback ) {
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2017-01-19 12:38:41 +01:00
// nothing to download if we dont have an appStoreId
if ( ! app . appStoreId ) return callback ( null ) ;
2015-07-20 00:09:47 -07:00
debugApp ( app , 'Downloading icon of %s@%s' , app . appStoreId , app . manifest . version ) ;
2019-07-26 10:49:29 -07:00
var iconUrl = settings . apiServerOrigin ( ) + '/api/v1/apps/' + app . appStoreId + '/versions/' + app . manifest . version + '/icon' ;
2015-07-20 00:09:47 -07:00
2016-04-20 00:40:20 -07:00
async . retry ( { times : 10 , interval : 5000 } , function ( retryCallback ) {
superagent
. get ( iconUrl )
. buffer ( true )
2016-09-12 12:53:51 -07:00
. timeout ( 30 * 1000 )
2016-04-20 00:40:20 -07:00
. end ( function ( error , res ) {
2019-09-06 11:19:58 -07:00
if ( error && ! error . response ) return retryCallback ( new BoxError ( BoxError . NETWORK _ERROR , ` Network error downloading icon : ${ error . message } ` ) ) ;
2016-04-20 00:40:20 -07:00
if ( res . statusCode !== 200 ) return retryCallback ( null ) ; // ignore error. this can also happen for apps installed with cloudron-cli
2015-07-20 00:09:47 -07:00
2019-09-06 11:19:58 -07:00
const iconPath = path . join ( paths . APP _ICONS _DIR , app . id + '.png' ) ;
if ( ! safe . fs . writeFileSync ( iconPath , res . body ) ) return retryCallback ( new BoxError ( BoxError . FS _ERROR , ` Error saving icon to ${ iconPath } : ${ safe . error . message } ` ) ) ;
2015-07-20 00:09:47 -07:00
2016-04-20 00:40:20 -07:00
retryCallback ( null ) ;
2017-09-17 21:25:07 -07:00
} ) ;
2016-04-20 00:40:20 -07:00
} , callback ) ;
2015-07-20 00:09:47 -07:00
}
2019-09-06 11:19:58 -07:00
function removeIcon ( app , callback ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
if ( ! safe . fs . unlinkSync ( path . join ( paths . APP _ICONS _DIR , app . id + '.png' ) ) ) {
if ( safe . error . code !== 'ENOENT' ) debugApp ( app , 'cannot remove icon : %s' , safe . error ) ;
}
if ( ! safe . fs . unlinkSync ( path . join ( paths . APP _ICONS _DIR , app . id + '.user.png' ) ) ) {
if ( safe . error . code !== 'ENOENT' ) debugApp ( app , 'cannot remove user icon : %s' , safe . error ) ;
}
callback ( null ) ;
}
2019-09-03 19:13:57 -07:00
function registerSubdomains ( app , overwrite , callback ) {
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
2017-02-02 10:40:10 -08:00
assert . strictEqual ( typeof overwrite , 'boolean' ) ;
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-10-29 15:46:33 -07:00
sysinfo . getServerIp ( function ( error , ip ) {
2016-01-05 12:16:39 +01:00
if ( error ) return callback ( error ) ;
2015-09-08 12:51:25 -07:00
2019-09-03 19:13:57 -07:00
const allDomains = [ { subdomain : app . location , domain : app . domain } ] . concat ( app . alternateDomains ) ;
2015-07-20 00:09:47 -07:00
2019-09-16 09:22:43 -07:00
debug ( ` registerSubdomain: Will register ${ JSON . stringify ( allDomains ) } ` ) ;
2019-09-03 19:13:57 -07:00
async . eachSeries ( allDomains , function ( domain , iteratorDone ) {
2018-06-29 15:07:20 +02:00
async . retry ( { times : 200 , interval : 5000 } , function ( retryCallback ) {
2019-09-03 19:13:57 -07:00
debugApp ( app , 'Registering subdomain: %s%s' , domain . subdomain ? ( domain . subdomain + '.' ) : '' , domain . domain ) ;
2018-06-29 15:07:20 +02:00
// get the current record before updating it
domains . getDnsRecords ( domain . subdomain , domain . domain , 'A' , function ( error , values ) {
2019-10-23 10:02:04 -07:00
if ( error && error . reason === BoxError . EXTERNAL _ERROR ) return retryCallback ( new BoxError ( BoxError . EXTERNAL _ERROR , error . message , { domain } ) ) ; // try again
if ( error && error . reason === BoxError . ACCESS _DENIED ) return retryCallback ( null , new BoxError ( BoxError . ACCESS _DENIED , error . message , { domain } ) ) ;
if ( error && error . reason === BoxError . NOT _FOUND ) return retryCallback ( null , new BoxError ( BoxError . NOT _FOUND , error . message , { domain } ) ) ;
2019-09-23 10:49:50 -07:00
if ( error ) return retryCallback ( null , new BoxError ( BoxError . EXTERNAL _ERROR , error . message , domain ) ) ; // give up for other errors
2018-06-29 15:07:20 +02:00
2019-09-10 15:23:47 -07:00
if ( values . length !== 0 && values [ 0 ] === ip ) return retryCallback ( null ) ; // up-to-date
2018-06-29 15:07:20 +02:00
// refuse to update any existing DNS record for custom domains that we did not create
2019-09-19 22:45:44 -07:00
if ( values . length !== 0 && ! overwrite ) return retryCallback ( null , new BoxError ( BoxError . ALREADY _EXISTS , 'DNS Record already exists' , { domain } ) ) ;
2018-06-29 15:07:20 +02:00
2018-06-29 22:25:34 +02:00
domains . upsertDnsRecords ( domain . subdomain , domain . domain , 'A' , [ ip ] , function ( error ) {
2019-10-23 10:02:04 -07:00
if ( error && ( error . reason === BoxError . BUSY || error . reason === BoxError . EXTERNAL _ERROR ) ) {
2019-09-05 17:13:07 -07:00
debug ( 'registerSubdomains: Upsert error. Will retry.' , error . message ) ;
2019-09-19 22:45:44 -07:00
return retryCallback ( new BoxError ( BoxError . EXTERNAL _ERROR , error . message , { domain } ) ) ; // try again
2018-10-05 17:07:08 +02:00
}
2019-09-03 19:13:57 -07:00
2019-09-05 17:13:07 -07:00
retryCallback ( null , error ? new BoxError ( BoxError . EXTERNAL _ERROR , error . message , domain ) : null ) ;
2018-06-29 15:07:20 +02:00
} ) ;
} ) ;
} , function ( error , result ) {
2019-09-05 17:13:07 -07:00
if ( error || result ) return iteratorDone ( error || result ) ;
2019-09-03 19:13:57 -07:00
iteratorDone ( null ) ;
2018-06-29 15:07:20 +02:00
} ) ;
} , callback ) ;
} ) ;
}
2019-09-03 19:13:57 -07:00
function unregisterSubdomains ( app , allDomains , callback ) {
2018-06-29 19:04:48 +02:00
assert . strictEqual ( typeof app , 'object' ) ;
2019-09-03 19:13:57 -07:00
assert ( Array . isArray ( allDomains ) ) ;
2018-06-29 19:04:48 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-10-29 15:46:33 -07:00
sysinfo . getServerIp ( function ( error , ip ) {
2018-06-29 19:04:48 +02:00
if ( error ) return callback ( error ) ;
2019-09-03 19:13:57 -07:00
async . eachSeries ( allDomains , function ( domain , iteratorDone ) {
2018-06-29 19:04:48 +02:00
async . retry ( { times : 30 , interval : 5000 } , function ( retryCallback ) {
debugApp ( app , 'Unregistering subdomain: %s%s' , domain . subdomain ? ( domain . subdomain + '.' ) : '' , domain . domain ) ;
domains . removeDnsRecords ( domain . subdomain , domain . domain , 'A' , [ ip ] , function ( error ) {
2019-10-23 10:02:04 -07:00
if ( error && error . reason === BoxError . NOT _FOUND ) return retryCallback ( null , null ) ;
if ( error && ( error . reason === BoxError . SBUSY || error . reason === BoxError . EXTERNAL _ERROR ) ) {
2019-09-05 17:13:07 -07:00
debug ( 'registerSubdomains: Remove error. Will retry.' , error . message ) ;
2019-09-19 22:45:44 -07:00
return retryCallback ( new BoxError ( BoxError . EXTERNAL _ERROR , error . message , { domain } ) ) ; // try again
2019-09-05 17:13:07 -07:00
}
2018-06-29 19:04:48 +02:00
2019-09-19 22:45:44 -07:00
retryCallback ( null , error ? new BoxError ( BoxError . EXTERNAL _ERROR , error . message , { domain } ) : null ) ;
2018-06-29 19:04:48 +02:00
} ) ;
} , function ( error , result ) {
2019-09-05 17:13:07 -07:00
if ( error || result ) return iteratorDone ( error || result ) ;
2019-09-03 19:13:57 -07:00
iteratorDone ( ) ;
2018-06-29 19:04:48 +02:00
} ) ;
} , callback ) ;
} ) ;
}
2015-07-20 00:09:47 -07:00
function waitForDnsPropagation ( app , callback ) {
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-07-26 10:10:14 -07:00
if ( ! constants . CLOUDRON ) {
2015-07-20 00:09:47 -07:00
debugApp ( app , 'Skipping dns propagation check for development' ) ;
return callback ( null ) ;
}
2019-10-29 15:46:33 -07:00
sysinfo . getServerIp ( function ( error , ip ) {
2019-09-06 11:19:58 -07:00
if ( error ) return callback ( new BoxError ( BoxError . NETWORK _ERROR , ` Error getting public IP: ${ error . message } ` ) ) ;
2016-11-30 18:34:11 +01:00
2020-03-12 17:13:13 -07:00
domains . waitForDnsRecord ( app . location , app . domain , 'A' , ip , { times : 240 } , function ( error ) {
2019-09-06 11:19:58 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DNS _ERROR , ` DNS Record is not synced yet: ${ error . message } ` , { ip : ip , subdomain : app . location , domain : app . domain } ) ) ;
2018-06-29 22:32:47 +02:00
// now wait for alternateDomains, if any
2018-09-22 11:25:58 -07:00
async . eachSeries ( app . alternateDomains , function ( domain , iteratorCallback ) {
2020-03-12 17:13:13 -07:00
domains . waitForDnsRecord ( domain . subdomain , domain . domain , 'A' , ip , { times : 240 } , function ( error ) {
2019-09-06 11:19:58 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DNS _ERROR , ` DNS Record is not synced yet: ${ error . message } ` , { ip : ip , subdomain : domain . subdomain , domain : domain . domain } ) ) ;
iteratorCallback ( ) ;
} ) ;
2018-06-29 22:32:47 +02:00
} , callback ) ;
} ) ;
2016-11-30 18:34:11 +01:00
} ) ;
2015-07-20 00:09:47 -07:00
}
2019-11-25 14:22:27 -08:00
function moveDataDir ( app , targetDir , callback ) {
2018-12-20 14:33:29 -08:00
assert . strictEqual ( typeof app , 'object' ) ;
2019-11-25 14:22:27 -08:00
assert ( targetDir === null || typeof targetDir === 'string' ) ;
2018-12-20 14:33:29 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-11-25 14:22:27 -08:00
let resolvedSourceDir = apps . getDataDir ( app , app . dataDir ) ;
let resolvedTargetDir = apps . getDataDir ( app , targetDir ) ;
2018-12-20 14:33:29 -08:00
2019-09-08 16:57:08 -07:00
debug ( ` moveDataDir: migrating data from ${ resolvedSourceDir } to ${ resolvedTargetDir } ` ) ;
2018-12-20 14:33:29 -08:00
2019-12-06 08:09:43 -08:00
if ( resolvedSourceDir === resolvedTargetDir ) return callback ( ) ;
2019-09-08 16:57:08 -07:00
shell . sudo ( 'moveDataDir' , [ MV _VOLUME _CMD , resolvedSourceDir , resolvedTargetDir ] , { } , function ( error ) {
2019-09-06 11:19:58 -07:00
if ( error ) return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , ` Error migrating data directory: ${ error . message } ` ) ) ;
callback ( null ) ;
} ) ;
2018-12-20 14:33:29 -08:00
}
2019-08-12 20:38:24 -07:00
function downloadImage ( manifest , callback ) {
assert . strictEqual ( typeof manifest , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
docker . info ( function ( error , info ) {
2019-10-23 09:24:51 -07:00
if ( error ) return callback ( error ) ;
2019-08-12 20:38:24 -07:00
const dfAsync = util . callbackify ( df . file ) ;
dfAsync ( info . DockerRootDir , function ( error , diskUsage ) {
2019-09-06 11:19:58 -07:00
if ( error ) return callback ( new BoxError ( BoxError . FS _ERROR , ` Error getting file system info: ${ error . message } ` ) ) ;
if ( diskUsage . available < ( 1024 * 1024 * 1024 ) ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , 'Not enough disk space to pull docker image' , { diskUsage : diskUsage , dockerRootDir : info . DockerRootDir } ) ) ;
2019-08-12 20:38:24 -07:00
2019-09-06 11:19:58 -07:00
docker . downloadImage ( manifest , function ( error ) {
2019-10-23 09:24:51 -07:00
if ( error ) return callback ( error ) ;
2019-09-06 11:19:58 -07:00
callback ( null ) ;
} ) ;
2019-08-12 20:38:24 -07:00
} ) ;
} ) ;
}
2019-09-22 00:20:12 -07:00
function startApp ( app , callback ) {
if ( app . runState === apps . RSTATE _STOPPED ) return callback ( ) ;
docker . startContainer ( app . id , callback ) ;
}
2019-09-16 09:31:34 -07:00
function install ( app , args , progressCallback , callback ) {
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
2019-09-16 09:31:34 -07:00
assert . strictEqual ( typeof args , 'object' ) ;
2019-08-26 20:43:15 -07:00
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-09-30 09:30:49 -07:00
const restoreConfig = args . restoreConfig ; // has to be set when restoring
2019-09-16 09:31:34 -07:00
const overwriteDns = args . overwriteDns ;
2019-12-06 09:42:05 -08:00
const oldManifest = args . oldManifest ;
2017-04-11 12:49:21 -07:00
2015-07-20 00:09:47 -07:00
async . series ( [
2017-10-12 17:46:15 -07:00
// this protects against the theoretical possibility of an app being marked for install/restore from
// a previous version of box code
verifyManifest . bind ( null , app . manifest ) ,
2015-07-20 00:09:47 -07:00
// teardown for re-installs
2019-08-26 20:43:15 -07:00
progressCallback . bind ( null , { percent : 10 , message : 'Cleaning up old install' } ) ,
2019-11-20 15:38:55 -08:00
unconfigureReverseProxy . bind ( null , app ) ,
2019-01-17 23:32:24 -08:00
deleteContainers . bind ( null , app , { managedOnly : true } ) ,
2018-05-22 12:05:55 -07:00
function teardownAddons ( next ) {
// when restoring, app does not require these addons anymore. remove carefully to preserve the db passwords
2019-09-30 09:58:13 -07:00
let addonsToRemove ;
2019-12-06 09:42:05 -08:00
if ( oldManifest ) {
addonsToRemove = _ . omit ( oldManifest . addons , Object . keys ( app . manifest . addons ) ) ;
2019-09-30 09:58:13 -07:00
} else {
addonsToRemove = app . manifest . addons ;
}
2018-05-22 12:05:55 -07:00
addons . teardownAddons ( app , addonsToRemove , next ) ;
} ,
2019-12-05 10:40:32 -08:00
function deleteAppDirIfNeeded ( done ) {
if ( restoreConfig && ! restoreConfig . backupId ) return done ( ) ; // in-place import should not delete data dir
deleteAppDir ( app , { removeDirectory : false } , done ) ; // do not remove any symlinked appdata dir
} ,
2017-04-11 15:16:42 -07:00
function deleteImageIfChanged ( done ) {
2019-12-06 09:42:05 -08:00
if ( ! oldManifest || oldManifest . dockerImage === app . manifest . dockerImage ) return done ( ) ;
2017-04-11 15:16:42 -07:00
2019-12-06 09:42:05 -08:00
docker . deleteImage ( oldManifest , done ) ;
2017-04-11 15:16:42 -07:00
} ,
2015-07-20 00:09:47 -07:00
2015-12-10 15:21:10 -08:00
reserveHttpPort . bind ( null , app ) ,
2015-07-20 00:09:47 -07:00
2019-08-26 20:43:15 -07:00
progressCallback . bind ( null , { percent : 20 , message : 'Downloading icon' } ) ,
2015-07-20 00:09:47 -07:00
downloadIcon . bind ( null , app ) ,
2019-09-03 19:13:57 -07:00
progressCallback . bind ( null , { percent : 30 , message : 'Registering subdomains' } ) ,
2019-09-16 09:31:34 -07:00
registerSubdomains . bind ( null , app , overwriteDns ) ,
2018-09-22 10:09:46 -07:00
2019-08-26 20:43:15 -07:00
progressCallback . bind ( null , { percent : 40 , message : 'Downloading image' } ) ,
2019-08-12 20:38:24 -07:00
downloadImage . bind ( null , app . manifest ) ,
2015-07-20 00:09:47 -07:00
2019-08-26 20:43:15 -07:00
progressCallback . bind ( null , { percent : 50 , message : 'Creating app data directory' } ) ,
2018-09-14 10:18:43 -07:00
createAppDir . bind ( null , app ) ,
2015-07-20 00:09:47 -07:00
2017-04-11 12:49:21 -07:00
function restoreFromBackup ( next ) {
2019-09-30 09:58:13 -07:00
if ( ! restoreConfig ) {
2017-04-20 17:55:47 -07:00
async . series ( [
2019-08-26 20:43:15 -07:00
progressCallback . bind ( null , { percent : 60 , message : 'Setting up addons' } ) ,
2017-04-20 17:55:47 -07:00
addons . setupAddons . bind ( null , app , app . manifest . addons ) ,
] , next ) ;
2019-12-05 10:40:32 -08:00
} else if ( ! restoreConfig . backupId ) { // in-place import
async . series ( [
progressCallback . bind ( null , { percent : 60 , message : 'Importing addons in-place' } ) ,
2019-12-05 11:12:40 -08:00
addons . setupAddons . bind ( null , app , app . manifest . addons ) ,
2019-12-05 12:10:47 -08:00
addons . clearAddons . bind ( null , app , _ . omit ( app . manifest . addons , 'localstorage' ) ) ,
2019-12-05 10:40:32 -08:00
addons . restoreAddons . bind ( null , app , app . manifest . addons ) ,
] , next ) ;
2017-04-20 17:55:47 -07:00
} else {
async . series ( [
2019-08-26 20:43:15 -07:00
progressCallback . bind ( null , { percent : 65 , message : 'Download backup and restoring addons' } ) ,
2018-09-15 17:05:04 -07:00
addons . setupAddons . bind ( null , app , app . manifest . addons ) ,
addons . clearAddons . bind ( null , app , app . manifest . addons ) ,
2019-12-05 10:40:32 -08:00
backups . downloadApp . bind ( null , app , restoreConfig , ( progress ) => {
progressCallback ( { percent : 65 , message : progress . message } ) ;
} ) ,
addons . restoreAddons . bind ( null , app , app . manifest . addons )
2017-04-20 17:55:47 -07:00
] , next ) ;
}
2017-04-11 12:49:21 -07:00
} ,
2019-08-26 20:43:15 -07:00
progressCallback . bind ( null , { percent : 70 , message : 'Creating container' } ) ,
2015-07-20 00:09:47 -07:00
createContainer . bind ( null , app ) ,
2019-09-22 00:20:12 -07:00
startApp . bind ( null , app ) ,
2015-07-20 00:09:47 -07:00
2019-08-26 20:43:15 -07:00
progressCallback . bind ( null , { percent : 85 , message : 'Waiting for DNS propagation' } ) ,
2015-07-20 00:09:47 -07:00
exports . _waitForDnsPropagation . bind ( null , app ) ,
2019-08-26 20:43:15 -07:00
progressCallback . bind ( null , { percent : 95 , message : 'Configuring reverse proxy' } ) ,
2018-01-30 12:23:27 -08:00
configureReverseProxy . bind ( null , app ) ,
2015-12-07 22:03:16 -08:00
2019-08-26 20:43:15 -07:00
progressCallback . bind ( null , { percent : 100 , message : 'Done' } ) ,
2019-09-21 19:45:55 -07:00
updateApp . bind ( null , app , { installationState : apps . ISTATE _INSTALLED , error : null , health : null } )
2015-07-20 00:09:47 -07:00
] , function seriesDone ( error ) {
if ( error ) {
debugApp ( app , 'error installing app: %s' , error ) ;
2019-09-24 10:28:50 -07:00
return updateApp ( app , { installationState : apps . ISTATE _ERROR , error : makeTaskError ( error , app ) } , callback . bind ( null , error ) ) ;
2015-07-20 00:09:47 -07:00
}
callback ( null ) ;
} ) ;
}
2019-09-21 19:45:55 -07:00
function backup ( app , args , progressCallback , callback ) {
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
2019-09-21 19:45:55 -07:00
assert . strictEqual ( typeof args , 'object' ) ;
2019-08-26 22:11:27 -07:00
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof callback , 'function' ) ;
2015-07-20 00:09:47 -07:00
async . series ( [
2019-08-26 22:11:27 -07:00
progressCallback . bind ( null , { percent : 10 , message : 'Backing up' } ) ,
backups . backupApp . bind ( null , app , { /* options */ } , ( progress ) => {
progressCallback ( { percent : 30 , message : progress . message } ) ;
} ) ,
2015-07-20 00:09:47 -07:00
2019-08-26 22:11:27 -07:00
progressCallback . bind ( null , { percent : 100 , message : 'Done' } ) ,
2019-08-30 13:12:49 -07:00
updateApp . bind ( null , app , { installationState : apps . ISTATE _INSTALLED , error : null } )
2015-07-20 00:09:47 -07:00
] , function seriesDone ( error ) {
if ( error ) {
debugApp ( app , 'error backing up app: %s' , error ) ;
2019-12-05 16:31:11 -08:00
// return to installed state intentionally. the error is stashed only in the task and not the app (the UI shows error state otherwise)
return updateApp ( app , { installationState : apps . ISTATE _INSTALLED , error : null } , callback . bind ( null , makeTaskError ( error , app ) ) ) ;
2015-07-20 00:09:47 -07:00
}
callback ( null ) ;
} ) ;
}
2019-09-21 19:45:55 -07:00
function create ( app , args , progressCallback , callback ) {
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
2019-09-21 19:45:55 -07:00
assert . strictEqual ( typeof args , 'object' ) ;
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof progressCallback , 'function' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
async . series ( [
progressCallback . bind ( null , { percent : 10 , message : 'Cleaning up old install' } ) ,
deleteContainers . bind ( null , app , { managedOnly : true } ) ,
2019-09-09 15:58:58 -07:00
// FIXME: re-setup addons only because sendmail addon to re-inject env vars on mailboxName change
progressCallback . bind ( null , { percent : 30 , message : 'Setting up addons' } ) ,
addons . setupAddons . bind ( null , app , app . manifest . addons ) ,
2019-09-08 16:57:08 -07:00
progressCallback . bind ( null , { percent : 60 , message : 'Creating container' } ) ,
createContainer . bind ( null , app ) ,
2019-09-22 00:20:12 -07:00
startApp . bind ( null , app ) ,
2019-09-08 16:57:08 -07:00
progressCallback . bind ( null , { percent : 100 , message : 'Done' } ) ,
updateApp . bind ( null , app , { installationState : apps . ISTATE _INSTALLED , error : null , health : null } )
] , function seriesDone ( error ) {
if ( error ) {
debugApp ( app , 'error creating : %s' , error ) ;
2019-09-24 10:28:50 -07:00
return updateApp ( app , { installationState : apps . ISTATE _ERROR , error : makeTaskError ( error , app ) } , callback . bind ( null , error ) ) ;
2019-09-08 16:57:08 -07:00
}
callback ( null ) ;
} ) ;
}
2019-09-10 15:23:47 -07:00
function changeLocation ( app , args , progressCallback , callback ) {
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
2019-09-10 15:23:47 -07:00
assert . strictEqual ( typeof args , 'object' ) ;
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof progressCallback , 'function' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-09-10 15:23:47 -07:00
const oldConfig = args . oldConfig ;
2019-09-08 16:57:08 -07:00
const locationChanged = oldConfig . fqdn !== app . fqdn ;
2019-09-26 22:30:58 -07:00
const overwriteDns = args . overwriteDns ;
2019-09-08 16:57:08 -07:00
async . series ( [
progressCallback . bind ( null , { percent : 10 , message : 'Cleaning up old install' } ) ,
2019-11-20 15:38:55 -08:00
unconfigureReverseProxy . bind ( null , app ) ,
2019-09-08 16:57:08 -07:00
deleteContainers . bind ( null , app , { managedOnly : true } ) ,
function ( next ) {
let obsoleteDomains = oldConfig . alternateDomains . filter ( function ( o ) {
return ! app . alternateDomains . some ( function ( n ) { return n . subdomain === o . subdomain && n . domain === o . domain ; } ) ;
} ) ;
if ( locationChanged ) obsoleteDomains . push ( { subdomain : oldConfig . location , domain : oldConfig . domain } ) ;
if ( obsoleteDomains . length === 0 ) return next ( ) ;
unregisterSubdomains ( app , obsoleteDomains , next ) ;
} ,
progressCallback . bind ( null , { percent : 30 , message : 'Registering subdomains' } ) ,
2019-09-26 22:30:58 -07:00
registerSubdomains . bind ( null , app , overwriteDns ) ,
2019-09-08 16:57:08 -07:00
// re-setup addons since they rely on the app's fqdn (e.g oauth)
progressCallback . bind ( null , { percent : 50 , message : 'Setting up addons' } ) ,
addons . setupAddons . bind ( null , app , app . manifest . addons ) ,
progressCallback . bind ( null , { percent : 60 , message : 'Creating container' } ) ,
createContainer . bind ( null , app ) ,
2019-09-22 00:20:12 -07:00
startApp . bind ( null , app ) ,
2019-09-08 16:57:08 -07:00
progressCallback . bind ( null , { percent : 80 , message : 'Waiting for DNS propagation' } ) ,
exports . _waitForDnsPropagation . bind ( null , app ) ,
progressCallback . bind ( null , { percent : 90 , message : 'Configuring reverse proxy' } ) ,
configureReverseProxy . bind ( null , app ) ,
progressCallback . bind ( null , { percent : 100 , message : 'Done' } ) ,
updateApp . bind ( null , app , { installationState : apps . ISTATE _INSTALLED , error : null , health : null } )
] , function seriesDone ( error ) {
if ( error ) {
2019-12-05 16:27:00 -08:00
debugApp ( app , 'error changing location : %s' , error ) ;
2019-09-24 10:28:50 -07:00
return updateApp ( app , { installationState : apps . ISTATE _ERROR , error : makeTaskError ( error , app ) } , callback . bind ( null , error ) ) ;
2019-09-08 16:57:08 -07:00
}
callback ( null ) ;
} ) ;
}
2019-09-21 19:45:55 -07:00
function migrateDataDir ( app , args , progressCallback , callback ) {
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
2019-09-21 19:45:55 -07:00
assert . strictEqual ( typeof args , 'object' ) ;
2019-09-08 16:57:08 -07:00
assert . strictEqual ( typeof progressCallback , 'function' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-11-25 14:22:27 -08:00
let newDataDir = args . newDataDir ;
assert ( newDataDir === null || typeof newDataDir === 'string' ) ;
2019-09-21 19:45:55 -07:00
2019-09-08 16:57:08 -07:00
async . series ( [
progressCallback . bind ( null , { percent : 10 , message : 'Cleaning up old install' } ) ,
deleteContainers . bind ( null , app , { managedOnly : true } ) ,
progressCallback . bind ( null , { percent : 45 , message : 'Ensuring app data directory' } ) ,
createAppDir . bind ( null , app ) ,
2019-09-09 16:37:59 -07:00
// re-setup addons since this creates the localStorage volume
progressCallback . bind ( null , { percent : 50 , message : 'Setting up addons' } ) ,
2019-11-25 14:22:27 -08:00
addons . setupAddons . bind ( null , _ . extend ( { } , app , { dataDir : newDataDir } ) , app . manifest . addons ) ,
2019-09-09 16:37:59 -07:00
2019-12-06 08:09:43 -08:00
progressCallback . bind ( null , { percent : 60 , message : 'Moving data dir' } ) ,
moveDataDir . bind ( null , app , newDataDir ) ,
2019-09-08 16:57:08 -07:00
2019-12-06 08:09:43 -08:00
progressCallback . bind ( null , { percent : 90 , message : 'Creating container' } ) ,
2019-09-08 16:57:08 -07:00
createContainer . bind ( null , app ) ,
2019-09-22 00:20:12 -07:00
startApp . bind ( null , app ) ,
2019-09-08 16:57:08 -07:00
progressCallback . bind ( null , { percent : 100 , message : 'Done' } ) ,
2019-11-25 14:22:27 -08:00
updateApp . bind ( null , app , { installationState : apps . ISTATE _INSTALLED , error : null , health : null , dataDir : newDataDir } )
2019-09-08 16:57:08 -07:00
] , function seriesDone ( error ) {
if ( error ) {
2019-12-05 16:27:00 -08:00
debugApp ( app , 'error migrating data dir : %s' , error ) ;
2019-09-24 10:28:50 -07:00
return updateApp ( app , { installationState : apps . ISTATE _ERROR , error : makeTaskError ( error , app ) } , callback . bind ( null , error ) ) ;
2019-09-08 16:57:08 -07:00
}
callback ( null ) ;
} ) ;
}
2019-12-06 09:23:58 -08:00
// configure is called for an infra update and repair to re-create container, reverseproxy config. it's all "local"
2019-09-21 19:45:55 -07:00
function configure ( app , args , progressCallback , callback ) {
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
2019-09-21 19:45:55 -07:00
assert . strictEqual ( typeof args , 'object' ) ;
2019-08-26 21:36:18 -07:00
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof callback , 'function' ) ;
2015-07-20 00:09:47 -07:00
async . series ( [
2019-08-26 21:36:18 -07:00
progressCallback . bind ( null , { percent : 10 , message : 'Cleaning up old install' } ) ,
2019-11-20 15:38:55 -08:00
unconfigureReverseProxy . bind ( null , app ) ,
2019-01-17 23:32:24 -08:00
deleteContainers . bind ( null , app , { managedOnly : true } ) ,
2015-12-10 15:21:10 -08:00
reserveHttpPort . bind ( null , app ) ,
2015-07-20 00:09:47 -07:00
2019-08-26 21:36:18 -07:00
progressCallback . bind ( null , { percent : 20 , message : 'Downloading icon' } ) ,
2017-03-15 20:24:03 -07:00
downloadIcon . bind ( null , app ) ,
2019-08-26 21:36:18 -07:00
progressCallback . bind ( null , { percent : 40 , message : 'Downloading image' } ) ,
2019-08-12 20:38:24 -07:00
downloadImage . bind ( null , app . manifest ) ,
2017-02-17 15:00:55 +01:00
2019-08-26 21:36:18 -07:00
progressCallback . bind ( null , { percent : 45 , message : 'Ensuring app data directory' } ) ,
2018-09-14 10:18:43 -07:00
createAppDir . bind ( null , app ) ,
2017-02-17 15:00:55 +01:00
2015-07-20 00:09:47 -07:00
// re-setup addons since they rely on the app's fqdn (e.g oauth)
2019-08-26 21:36:18 -07:00
progressCallback . bind ( null , { percent : 50 , message : 'Setting up addons' } ) ,
2015-07-20 00:09:47 -07:00
addons . setupAddons . bind ( null , app , app . manifest . addons ) ,
2019-08-26 21:36:18 -07:00
progressCallback . bind ( null , { percent : 60 , message : 'Creating container' } ) ,
2015-07-20 00:09:47 -07:00
createContainer . bind ( null , app ) ,
2019-09-22 00:20:12 -07:00
startApp . bind ( null , app ) ,
2015-07-20 00:09:47 -07:00
2019-08-26 21:36:18 -07:00
progressCallback . bind ( null , { percent : 90 , message : 'Configuring reverse proxy' } ) ,
2018-01-30 12:23:27 -08:00
configureReverseProxy . bind ( null , app ) ,
2015-12-07 22:03:16 -08:00
2019-08-26 21:36:18 -07:00
progressCallback . bind ( null , { percent : 100 , message : 'Done' } ) ,
2019-08-30 13:12:49 -07:00
updateApp . bind ( null , app , { installationState : apps . ISTATE _INSTALLED , error : null , health : null } )
2015-07-20 00:09:47 -07:00
] , function seriesDone ( error ) {
if ( error ) {
debugApp ( app , 'error reconfiguring : %s' , error ) ;
2019-09-24 10:28:50 -07:00
return updateApp ( app , { installationState : apps . ISTATE _ERROR , error : makeTaskError ( error , app ) } , callback . bind ( null , error ) ) ;
2015-07-20 00:09:47 -07:00
}
callback ( null ) ;
} ) ;
}
// nginx configuration is skipped because app.httpPort is expected to be available
2019-09-21 19:45:55 -07:00
function update ( app , args , progressCallback , callback ) {
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
2019-09-21 19:45:55 -07:00
assert . strictEqual ( typeof args , 'object' ) ;
2019-08-26 22:08:40 -07:00
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-09-21 19:45:55 -07:00
const updateConfig = args . updateConfig ;
2019-08-27 15:18:16 -07:00
debugApp ( app , ` Updating to ${ updateConfig . manifest . version } ` ) ;
2015-07-20 00:09:47 -07:00
// app does not want these addons anymore
2016-02-02 08:37:48 -08:00
// FIXME: this does not handle option changes (like multipleDatabases)
2019-08-27 15:18:16 -07:00
var unusedAddons = _ . omit ( app . manifest . addons , Object . keys ( updateConfig . manifest . addons ) ) ;
2015-07-20 00:09:47 -07:00
async . series ( [
2017-10-12 17:46:15 -07:00
// this protects against the theoretical possibility of an app being marked for update from
// a previous version of box code
2019-12-18 09:33:44 -08:00
progressCallback . bind ( null , { percent : 5 , message : 'Verify manifest' } ) ,
2019-08-27 15:18:16 -07:00
verifyManifest . bind ( null , updateConfig . manifest ) ,
2017-10-12 17:46:15 -07:00
function ( next ) {
2019-08-29 10:59:05 -07:00
if ( updateConfig . skipBackup ) return next ( null ) ;
2017-10-12 17:46:15 -07:00
async . series ( [
2019-08-26 22:08:40 -07:00
progressCallback . bind ( null , { percent : 15 , message : 'Backing up app' } ) ,
2019-04-13 17:09:15 -07:00
// preserve update backups for 3 weeks
2019-08-26 22:11:27 -07:00
backups . backupApp . bind ( null , app , { preserveSecs : 3 * 7 * 24 * 60 * 60 } , ( progress ) => {
progressCallback ( { percent : 15 , message : ` Backup - ${ progress . message } ` } ) ;
} )
2017-11-21 15:26:55 -08:00
] , function ( error ) {
if ( error ) error . backupError = true ;
next ( error ) ;
} ) ;
2017-10-12 17:46:15 -07:00
} ,
2015-07-20 00:09:47 -07:00
2016-02-04 19:53:39 -08:00
// download new image before app is stopped. this is so we can reduce downtime
// and also not remove the 'common' layers when the old image is deleted
2019-08-26 22:08:40 -07:00
progressCallback . bind ( null , { percent : 25 , message : 'Downloading image' } ) ,
2019-08-27 15:18:16 -07:00
downloadImage . bind ( null , updateConfig . manifest ) ,
2016-02-04 19:53:39 -08:00
2015-07-20 00:55:27 -07:00
// note: we cleanup first and then backup. this is done so that the app is not running should backup fail
// we cannot easily 'recover' from backup failures because we have to revert manfest and portBindings
2019-08-26 22:08:40 -07:00
progressCallback . bind ( null , { percent : 35 , message : 'Cleaning up old install' } ) ,
2019-01-17 23:32:24 -08:00
deleteContainers . bind ( null , app , { managedOnly : true } ) ,
2015-08-19 14:16:01 -07:00
function deleteImageIfChanged ( done ) {
2019-08-27 15:18:16 -07:00
if ( app . manifest . dockerImage === updateConfig . manifest . dockerImage ) return done ( ) ;
2015-08-19 14:16:01 -07:00
2017-10-12 17:46:15 -07:00
docker . deleteImage ( app . manifest , done ) ;
2015-07-20 10:09:02 -07:00
} ,
2015-07-20 00:55:27 -07:00
2016-06-13 23:07:41 -07:00
// only delete unused addons after backup
addons . teardownAddons . bind ( null , app , unusedAddons ) ,
2017-10-23 22:11:33 +02:00
// free unused ports
function ( next ) {
2018-08-12 22:47:59 -07:00
const currentPorts = app . portBindings || { } ;
2019-08-27 15:18:16 -07:00
const newTcpPorts = updateConfig . manifest . tcpPorts || { } ;
const newUdpPorts = updateConfig . manifest . udpPorts || { } ;
2017-10-23 22:11:33 +02:00
async . each ( Object . keys ( currentPorts ) , function ( portName , callback ) {
2019-09-06 11:19:58 -07:00
if ( newTcpPorts [ portName ] || newUdpPorts [ portName ] ) return callback ( null ) ; // port still in use
2017-10-23 22:11:33 +02:00
2018-08-12 22:08:19 -07:00
appdb . delPortBinding ( currentPorts [ portName ] , apps . PORT _TYPE _TCP , function ( error ) {
2019-10-24 11:13:48 -07:00
if ( error && error . reason === BoxError . NOT _FOUND ) console . error ( 'Portbinding does not exist in database.' ) ;
2017-10-23 22:11:33 +02:00
else if ( error ) return next ( error ) ;
// also delete from app object for further processing (the db is updated in the next step)
delete app . portBindings [ portName ] ;
2019-09-06 11:19:58 -07:00
callback ( null ) ;
2017-10-23 22:11:33 +02:00
} ) ;
} , next ) ;
} ,
2019-08-29 20:47:38 -07:00
updateApp . bind ( null , app , _ . pick ( updateConfig , 'manifest' , 'appStoreId' , 'memoryLimit' ) ) , // switch over to the new config
2017-10-12 17:46:15 -07:00
2019-08-26 22:08:40 -07:00
progressCallback . bind ( null , { percent : 45 , message : 'Downloading icon' } ) ,
2015-07-20 00:09:47 -07:00
downloadIcon . bind ( null , app ) ,
2019-08-26 22:08:40 -07:00
progressCallback . bind ( null , { percent : 70 , message : 'Updating addons' } ) ,
2019-08-27 15:18:16 -07:00
addons . setupAddons . bind ( null , app , updateConfig . manifest . addons ) ,
2015-07-20 00:09:47 -07:00
2019-08-26 22:08:40 -07:00
progressCallback . bind ( null , { percent : 80 , message : 'Creating container' } ) ,
2015-07-20 00:09:47 -07:00
createContainer . bind ( null , app ) ,
2019-09-22 00:20:12 -07:00
startApp . bind ( null , app ) ,
2015-07-20 00:09:47 -07:00
2019-08-26 22:08:40 -07:00
progressCallback . bind ( null , { percent : 100 , message : 'Done' } ) ,
2019-08-30 13:12:49 -07:00
updateApp . bind ( null , app , { installationState : apps . ISTATE _INSTALLED , error : null , health : null , updateTime : new Date ( ) } )
2015-07-20 00:09:47 -07:00
] , function seriesDone ( error ) {
2017-11-21 15:26:55 -08:00
if ( error && error . backupError ) {
debugApp ( app , 'update aborted because backup failed' , error ) ;
2019-08-30 13:12:49 -07:00
updateApp ( app , { installationState : apps . ISTATE _INSTALLED , error : null , health : null } , callback . bind ( null , error ) ) ;
2017-11-21 15:26:55 -08:00
} else if ( error ) {
2015-07-20 00:09:47 -07:00
debugApp ( app , 'Error updating app: %s' , error ) ;
2019-09-24 10:28:50 -07:00
updateApp ( app , { installationState : apps . ISTATE _ERROR , error : makeTaskError ( error , app ) } , callback . bind ( null , error ) ) ;
2017-11-21 15:26:55 -08:00
} else {
2019-10-30 11:02:21 -07:00
eventlog . add ( eventlog . ACTION _APP _UPDATE _FINISH , auditSource . APP _TASK , { app : app , success : true } , ( ) => callback ( ) ) ; // ignore error
2015-07-20 00:09:47 -07:00
}
} ) ;
}
2019-09-22 00:20:12 -07:00
function start ( app , args , progressCallback , callback ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof args , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
async . series ( [
progressCallback . bind ( null , { percent : 20 , message : 'Starting container' } ) ,
docker . startContainer . bind ( null , app . id ) ,
2020-01-26 16:05:23 -08:00
// stopped apps do not renew certs. currently, we don't do DNS to not overwrite existing user settings
progressCallback . bind ( null , { percent : 60 , message : 'Configuring reverse proxy' } ) ,
configureReverseProxy . bind ( null , app ) ,
2019-09-22 00:20:12 -07:00
progressCallback . bind ( null , { percent : 100 , message : 'Done' } ) ,
updateApp . bind ( null , app , { installationState : apps . ISTATE _INSTALLED , error : null , health : null } )
] , function seriesDone ( error ) {
if ( error ) {
debugApp ( app , 'error starting app: %s' , error ) ;
2019-09-24 10:28:50 -07:00
return updateApp ( app , { installationState : apps . ISTATE _ERROR , error : makeTaskError ( error , app ) } , callback . bind ( null , error ) ) ;
2019-09-22 00:20:12 -07:00
}
callback ( null ) ;
} ) ;
}
function stop ( app , args , progressCallback , callback ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof args , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
async . series ( [
progressCallback . bind ( null , { percent : 20 , message : 'Stopping container' } ) ,
docker . stopContainers . bind ( null , app . id ) ,
progressCallback . bind ( null , { percent : 100 , message : 'Done' } ) ,
updateApp . bind ( null , app , { installationState : apps . ISTATE _INSTALLED , error : null , health : null } )
] , function seriesDone ( error ) {
if ( error ) {
debugApp ( app , 'error starting app: %s' , error ) ;
2019-09-24 10:28:50 -07:00
return updateApp ( app , { installationState : apps . ISTATE _ERROR , error : makeTaskError ( error , app ) } , callback . bind ( null , error ) ) ;
2019-09-22 00:20:12 -07:00
}
callback ( null ) ;
} ) ;
}
2019-12-20 10:29:29 -08:00
function restart ( app , args , progressCallback , callback ) {
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof args , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
async . series ( [
progressCallback . bind ( null , { percent : 20 , message : 'Restarting container' } ) ,
docker . restartContainer . bind ( null , app . id ) ,
progressCallback . bind ( null , { percent : 100 , message : 'Done' } ) ,
updateApp . bind ( null , app , { installationState : apps . ISTATE _INSTALLED , error : null , health : null } )
] , function seriesDone ( error ) {
if ( error ) {
debugApp ( app , 'error starting app: %s' , error ) ;
return updateApp ( app , { installationState : apps . ISTATE _ERROR , error : makeTaskError ( error , app ) } , callback . bind ( null , error ) ) ;
}
callback ( null ) ;
} ) ;
}
2019-09-21 19:45:55 -07:00
function uninstall ( app , args , progressCallback , callback ) {
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof app , 'object' ) ;
2019-09-21 19:45:55 -07:00
assert . strictEqual ( typeof args , 'object' ) ;
2019-08-26 21:56:55 -07:00
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2016-01-05 12:14:39 +01:00
assert . strictEqual ( typeof callback , 'function' ) ;
2015-07-20 00:09:47 -07:00
async . series ( [
2019-08-26 21:56:55 -07:00
progressCallback . bind ( null , { percent : 20 , message : 'Deleting container' } ) ,
2019-11-20 15:38:55 -08:00
unconfigureReverseProxy . bind ( null , app ) ,
2019-01-17 23:32:24 -08:00
deleteContainers . bind ( null , app , { } ) ,
2015-07-20 00:09:47 -07:00
2019-08-26 21:56:55 -07:00
progressCallback . bind ( null , { percent : 30 , message : 'Teardown addons' } ) ,
2015-07-20 00:09:47 -07:00
addons . teardownAddons . bind ( null , app , app . manifest . addons ) ,
2019-08-26 21:56:55 -07:00
progressCallback . bind ( null , { percent : 40 , message : 'Deleting app data directory' } ) ,
2018-09-14 10:18:43 -07:00
deleteAppDir . bind ( null , app , { removeDirectory : true } ) ,
2015-07-20 00:09:47 -07:00
2019-08-26 21:56:55 -07:00
progressCallback . bind ( null , { percent : 50 , message : 'Deleting image' } ) ,
2015-10-19 15:39:26 -07:00
docker . deleteImage . bind ( null , app . manifest ) ,
2015-07-20 00:09:47 -07:00
2019-08-26 21:56:55 -07:00
progressCallback . bind ( null , { percent : 60 , message : 'Unregistering domains' } ) ,
2019-09-03 19:13:57 -07:00
unregisterSubdomains . bind ( null , app , [ { subdomain : app . location , domain : app . domain } ] . concat ( app . alternateDomains ) ) ,
2015-07-20 00:09:47 -07:00
2019-08-26 21:56:55 -07:00
progressCallback . bind ( null , { percent : 70 , message : 'Cleanup icon' } ) ,
2015-07-20 00:09:47 -07:00
removeIcon . bind ( null , app ) ,
2019-08-26 21:56:55 -07:00
progressCallback . bind ( null , { percent : 90 , message : 'Cleanup logs' } ) ,
2018-06-04 11:45:22 +02:00
cleanupLogs . bind ( null , app ) ,
2019-08-26 21:56:55 -07:00
progressCallback . bind ( null , { percent : 95 , message : 'Remove app from database' } ) ,
2015-07-20 00:09:47 -07:00
appdb . del . bind ( null , app . id )
2016-02-24 17:53:21 +01:00
] , function seriesDone ( error ) {
if ( error ) {
debugApp ( app , 'error uninstalling app: %s' , error ) ;
2019-09-24 10:28:50 -07:00
return updateApp ( app , { installationState : apps . ISTATE _ERROR , error : makeTaskError ( error , app ) } , callback . bind ( null , error ) ) ;
2016-02-24 17:53:21 +01:00
}
callback ( null ) ;
} ) ;
2015-07-20 00:09:47 -07:00
}
2019-08-27 15:18:16 -07:00
function run ( appId , args , progressCallback , callback ) {
2019-08-26 15:55:57 -07:00
assert . strictEqual ( typeof appId , 'string' ) ;
2019-08-27 15:18:16 -07:00
assert . strictEqual ( typeof args , 'object' ) ;
2019-08-26 15:55:57 -07:00
assert . strictEqual ( typeof progressCallback , 'function' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
// determine what to do
apps . get ( appId , function ( error , app ) {
if ( error ) return callback ( error ) ;
debugApp ( app , 'startTask installationState: %s runState: %s' , app . installationState , app . runState ) ;
2015-07-20 00:09:47 -07:00
2015-07-20 10:03:55 -07:00
switch ( app . installationState ) {
2019-09-21 19:45:55 -07:00
case apps . ISTATE _PENDING _INSTALL :
case apps . ISTATE _PENDING _CLONE :
case apps . ISTATE _PENDING _RESTORE :
return install ( app , args , progressCallback , callback ) ;
case apps . ISTATE _PENDING _CONFIGURE :
return configure ( app , args , progressCallback , callback ) ;
2019-09-10 14:25:12 -07:00
case apps . ISTATE _PENDING _RECREATE _CONTAINER :
case apps . ISTATE _PENDING _RESIZE :
case apps . ISTATE _PENDING _DEBUG :
2019-09-21 19:45:55 -07:00
return create ( app , args , progressCallback , callback ) ;
case apps . ISTATE _PENDING _LOCATION _CHANGE :
return changeLocation ( app , args , progressCallback , callback ) ;
case apps . ISTATE _PENDING _DATA _DIR _MIGRATION :
return migrateDataDir ( app , args , progressCallback , callback ) ;
case apps . ISTATE _PENDING _UNINSTALL :
return uninstall ( app , args , progressCallback , callback ) ;
case apps . ISTATE _PENDING _UPDATE :
return update ( app , args , progressCallback , callback ) ;
case apps . ISTATE _PENDING _BACKUP :
return backup ( app , args , progressCallback , callback ) ;
2019-09-22 00:20:12 -07:00
case apps . ISTATE _PENDING _START :
return start ( app , args , progressCallback , callback ) ;
case apps . ISTATE _PENDING _STOP :
return stop ( app , args , progressCallback , callback ) ;
2019-12-20 10:29:29 -08:00
case apps . ISTATE _PENDING _RESTART :
return restart ( app , args , progressCallback , callback ) ;
2015-07-20 10:03:55 -07:00
default :
debugApp ( app , 'apptask launched with invalid command' ) ;
2019-12-04 10:29:06 -08:00
return callback ( new BoxError ( BoxError . INTERNAL _ERROR , 'Unknown install command in apptask:' + app . installationState ) ) ;
2015-07-20 00:09:47 -07:00
}
} ) ;
}