2018-01-29 15:47:26 -08:00
'use strict' ;
exports = module . exports = {
2018-12-11 15:29:47 -08:00
setup : setup ,
2018-01-29 15:47:26 -08:00
restore : restore ,
activate : activate ,
2018-12-15 15:27:16 -08:00
getStatus : getStatus ,
2018-01-29 15:47:26 -08:00
2019-05-07 14:15:35 -07:00
autoRegister : autoRegister ,
2018-12-11 15:29:47 -08:00
ProvisionError : ProvisionError
2018-01-29 15:47:26 -08:00
} ;
2019-05-07 14:15:35 -07:00
var appstore = require ( './appstore.js' ) ,
AppstoreError = require ( './appstore.js' ) . AppstoreError ,
assert = require ( 'assert' ) ,
2018-01-29 15:47:26 -08:00
async = require ( 'async' ) ,
backups = require ( './backups.js' ) ,
BackupsError = require ( './backups.js' ) . BackupsError ,
constants = require ( './constants.js' ) ,
clients = require ( './clients.js' ) ,
cloudron = require ( './cloudron.js' ) ,
2018-12-11 15:29:47 -08:00
debug = require ( 'debug' ) ( 'box:provision' ) ,
2018-01-29 15:47:26 -08:00
domains = require ( './domains.js' ) ,
2018-04-29 11:20:12 -07:00
DomainsError = domains . DomainsError ,
2018-01-29 15:47:26 -08:00
eventlog = require ( './eventlog.js' ) ,
2019-05-07 14:15:35 -07:00
fs = require ( 'fs' ) ,
2018-01-29 15:47:26 -08:00
mail = require ( './mail.js' ) ,
2019-05-07 14:15:35 -07:00
paths = require ( './paths.js' ) ,
2018-01-29 15:47:26 -08:00
safe = require ( 'safetydance' ) ,
semver = require ( 'semver' ) ,
settings = require ( './settings.js' ) ,
superagent = require ( 'superagent' ) ,
2019-07-26 07:25:40 -07:00
sysinfo = require ( './sysinfo.js' ) ,
2018-04-29 10:58:45 -07:00
users = require ( './users.js' ) ,
2018-04-29 17:37:53 -07:00
UsersError = users . UsersError ,
2018-01-29 15:47:26 -08:00
tld = require ( 'tldjs' ) ,
2018-12-15 15:27:16 -08:00
util = require ( 'util' ) ,
_ = require ( 'underscore' ) ;
const NOOP _CALLBACK = function ( error ) { if ( error ) debug ( error ) ; } ;
// we cannot use tasks since the tasks table gets overwritten when db is imported
let gProvisionStatus = {
setup : {
active : false ,
message : '' ,
errorMessage : null
} ,
restore : {
active : false ,
message : '' ,
errorMessage : null
}
} ;
2018-01-29 15:47:26 -08:00
2018-12-11 15:29:47 -08:00
function ProvisionError ( reason , errorOrMessage ) {
2018-01-29 15:47:26 -08:00
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 ;
}
}
2018-12-11 15:29:47 -08:00
util . inherits ( ProvisionError , Error ) ;
ProvisionError . BAD _FIELD = 'Field error' ;
ProvisionError . BAD _STATE = 'Bad State' ;
ProvisionError . ALREADY _SETUP = 'Already Setup' ;
ProvisionError . INTERNAL _ERROR = 'Internal Error' ;
ProvisionError . EXTERNAL _ERROR = 'External Error' ;
2019-05-22 10:41:25 -07:00
ProvisionError . LICENSE _ERROR = 'License Error' ;
2018-12-11 15:29:47 -08:00
ProvisionError . ALREADY _PROVISIONED = 'Already Provisioned' ;
2018-01-29 15:47:26 -08:00
2018-12-15 15:27:16 -08:00
function setProgress ( task , message , callback ) {
2018-12-22 20:48:03 -08:00
debug ( ` setProgress: ${ task } - ${ message } ` ) ;
2018-12-15 15:27:16 -08:00
gProvisionStatus [ task ] . message = message ;
callback ( ) ;
}
2019-05-15 09:53:07 -07:00
function autoRegister ( domain , callback ) {
assert . strictEqual ( typeof domain , 'string' ) ;
2019-05-07 17:38:20 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
if ( ! fs . existsSync ( paths . LICENSE _FILE ) ) return callback ( ) ;
const license = safe . fs . readFileSync ( paths . LICENSE _FILE , 'utf8' ) ;
if ( ! license ) return callback ( new ProvisionError ( ProvisionError . EXTERNAL _ERROR , 'Cannot read license' ) ) ;
2019-05-07 18:41:13 -07:00
debug ( 'Auto-registering cloudron' ) ;
2019-05-15 09:53:07 -07:00
appstore . registerWithLicense ( license . trim ( ) , domain , function ( error ) {
2019-05-07 17:38:20 -07:00
if ( error && error . reason !== AppstoreError . ALREADY _REGISTERED ) {
debug ( 'Failed to auto-register cloudron' , error ) ;
return callback ( new ProvisionError ( ProvisionError . LICENSE _ERROR , 'Failed to auto-register Cloudron with license. Please contact support@cloudron.io' ) ) ;
}
callback ( ) ;
} ) ;
}
2018-12-07 14:35:04 -08:00
function unprovision ( callback ) {
2018-01-29 15:47:26 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-12-07 14:35:04 -08:00
debug ( 'unprovision' ) ;
2018-01-29 15:47:26 -08:00
2018-12-07 14:35:04 -08:00
// TODO: also cancel any existing configureWebadmin task
async . series ( [
2019-07-26 10:49:29 -07:00
settings . setAdmin . bind ( null , '' , '' ) ,
2018-12-07 14:35:04 -08:00
mail . clearDomains ,
domains . clear
] , callback ) ;
}
2018-02-09 12:43:03 +01:00
2019-02-26 12:32:35 -08:00
2019-05-24 15:08:15 -07:00
function setup ( dnsConfig , backupConfig , auditSource , callback ) {
2018-12-07 14:35:04 -08:00
assert . strictEqual ( typeof dnsConfig , 'object' ) ;
2019-05-24 15:08:15 -07:00
assert . strictEqual ( typeof backupConfig , 'object' ) ;
2018-12-07 14:35:04 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2018-01-29 15:47:26 -08:00
2018-12-15 15:27:16 -08:00
if ( gProvisionStatus . setup . active || gProvisionStatus . restore . active ) return callback ( new ProvisionError ( ProvisionError . BAD _STATE , 'Already setting up or restoring' ) ) ;
2018-01-29 15:47:26 -08:00
2018-12-15 15:27:16 -08:00
gProvisionStatus . setup = { active : true , errorMessage : '' , message : 'Adding domain' } ;
2018-01-29 15:47:26 -08:00
2018-12-15 15:27:16 -08:00
function done ( error ) {
gProvisionStatus . setup . active = false ;
gProvisionStatus . setup . errorMessage = error ? error . message : '' ;
callback ( error ) ;
}
2018-03-08 15:10:38 -08:00
2018-12-15 15:27:16 -08:00
users . isActivated ( function ( error , activated ) {
if ( error ) return done ( new ProvisionError ( ProvisionError . INTERNAL _ERROR , error ) ) ;
if ( activated ) return done ( new ProvisionError ( ProvisionError . ALREADY _SETUP ) ) ;
unprovision ( function ( error ) {
if ( error ) return done ( new ProvisionError ( ProvisionError . INTERNAL _ERROR , error ) ) ;
2018-11-10 00:43:46 -08:00
2018-12-07 14:35:04 -08:00
const domain = dnsConfig . domain . toLowerCase ( ) ;
const zoneName = dnsConfig . zoneName ? dnsConfig . zoneName : ( tld . getDomain ( domain ) || domain ) ;
2018-10-30 18:43:52 -07:00
2018-12-15 15:27:16 -08:00
debug ( ` provision: Setting up Cloudron with domain ${ domain } and zone ${ zoneName } ` ) ;
2018-11-10 00:43:46 -08:00
2018-12-07 14:35:04 -08:00
let data = {
zoneName : zoneName ,
provider : dnsConfig . provider ,
config : dnsConfig . config ,
fallbackCertificate : dnsConfig . fallbackCertificate || null ,
tlsConfig : dnsConfig . tlsConfig || { provider : 'letsencrypt-prod' }
} ;
2018-10-30 18:43:52 -07:00
2018-12-07 16:15:21 -08:00
domains . add ( domain , data , auditSource , function ( error ) {
2018-12-15 15:27:16 -08:00
if ( error && error . reason === DomainsError . BAD _FIELD ) return done ( new ProvisionError ( ProvisionError . BAD _FIELD , error . message ) ) ;
if ( error && error . reason === DomainsError . ALREADY _EXISTS ) return done ( new ProvisionError ( ProvisionError . BAD _FIELD , error . message ) ) ;
if ( error ) return done ( new ProvisionError ( ProvisionError . INTERNAL _ERROR , error ) ) ;
callback ( ) ; // now that args are validated run the task in the background
2018-12-07 14:35:04 -08:00
async . series ( [
2019-05-15 09:53:07 -07:00
autoRegister . bind ( null , domain ) ,
2018-12-15 15:27:16 -08:00
domains . prepareDashboardDomain . bind ( null , domain , auditSource , ( progress ) => setProgress ( 'setup' , progress . message , NOOP _CALLBACK ) ) ,
2019-02-04 20:24:28 -08:00
cloudron . setDashboardDomain . bind ( null , domain , auditSource ) , // this sets up the config.fqdn()
2019-06-10 12:23:29 -07:00
mail . addDomain . bind ( null , domain ) , // this relies on config.mailFqdn() and config.adminDomain()
2018-12-15 15:27:16 -08:00
setProgress . bind ( null , 'setup' , 'Applying auto-configuration' ) ,
2019-05-24 15:08:15 -07:00
( next ) => { if ( ! backupConfig ) return next ( ) ; settings . setBackupConfig ( backupConfig , next ) ; } ,
2018-12-15 15:27:16 -08:00
setProgress . bind ( null , 'setup' , 'Done' ) ,
2018-12-07 16:15:21 -08:00
eventlog . add . bind ( null , eventlog . ACTION _PROVISION , auditSource , { } )
2018-12-15 15:27:16 -08:00
] , function ( error ) {
gProvisionStatus . setup . active = false ;
gProvisionStatus . setup . errorMessage = error ? error . message : '' ;
} ) ;
2018-12-07 14:35:04 -08:00
} ) ;
2018-10-30 18:43:52 -07:00
} ) ;
2018-01-29 15:47:26 -08:00
} ) ;
}
function setTimeZone ( ip , callback ) {
assert . strictEqual ( typeof ip , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
debug ( 'setTimeZone ip:%s' , ip ) ;
superagent . get ( 'https://geolocation.cloudron.io/json' ) . query ( { ip : ip } ) . timeout ( 10 * 1000 ) . end ( function ( error , result ) {
if ( ( error && ! error . response ) || result . statusCode !== 200 ) {
debug ( 'Failed to get geo location: %s' , error . message ) ;
return callback ( null ) ;
}
var timezone = safe . query ( result . body , 'location.time_zone' ) ;
if ( ! timezone || typeof timezone !== 'string' ) {
debug ( 'No timezone in geoip response : %j' , result . body ) ;
return callback ( null ) ;
}
debug ( 'Setting timezone to ' , timezone ) ;
settings . setTimeZone ( timezone , callback ) ;
} ) ;
}
function activate ( username , password , email , displayName , ip , auditSource , callback ) {
assert . strictEqual ( typeof username , 'string' ) ;
assert . strictEqual ( typeof password , 'string' ) ;
assert . strictEqual ( typeof email , 'string' ) ;
assert . strictEqual ( typeof displayName , 'string' ) ;
assert . strictEqual ( typeof ip , 'string' ) ;
assert . strictEqual ( typeof auditSource , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
debug ( 'activating user:%s email:%s' , username , email ) ;
setTimeZone ( ip , function ( ) { } ) ; // TODO: get this from user. note that timezone is detected based on the browser location and not the cloudron region
2018-04-29 10:58:45 -07:00
users . createOwner ( username , password , email , displayName , auditSource , function ( error , userObject ) {
2018-12-11 15:29:47 -08:00
if ( error && error . reason === UsersError . ALREADY _EXISTS ) return callback ( new ProvisionError ( ProvisionError . ALREADY _PROVISIONED , 'Already activated' ) ) ;
if ( error && error . reason === UsersError . BAD _FIELD ) return callback ( new ProvisionError ( ProvisionError . BAD _FIELD , error . message ) ) ;
if ( error ) return callback ( new ProvisionError ( ProvisionError . INTERNAL _ERROR , error ) ) ;
2018-01-29 15:47:26 -08:00
2018-08-27 14:50:41 -07:00
clients . addTokenByUserId ( 'cid-webadmin' , userObject . id , Date . now ( ) + constants . DEFAULT _TOKEN _EXPIRATION , { } , function ( error , result ) {
2018-12-11 15:29:47 -08:00
if ( error ) return callback ( new ProvisionError ( ProvisionError . INTERNAL _ERROR , error ) ) ;
2018-01-29 15:47:26 -08:00
2018-06-27 23:17:04 -07:00
eventlog . add ( eventlog . ACTION _ACTIVATE , auditSource , { } ) ;
2018-01-29 15:47:26 -08:00
2018-06-29 08:27:33 -07:00
callback ( null , {
userId : userObject . id ,
token : result . accessToken ,
expires : result . expires
} ) ;
2018-01-29 15:47:26 -08:00
2018-11-10 18:21:15 -08:00
setImmediate ( cloudron . onActivated . bind ( null , NOOP _CALLBACK ) ) ; // hack for now to not block the above http response
2018-01-29 15:47:26 -08:00
} ) ;
} ) ;
}
2019-05-24 15:08:15 -07:00
function restore ( backupConfig , backupId , version , auditSource , callback ) {
2018-01-29 15:47:26 -08:00
assert . strictEqual ( typeof backupConfig , 'object' ) ;
assert . strictEqual ( typeof backupId , 'string' ) ;
assert . strictEqual ( typeof version , 'string' ) ;
2018-11-10 00:43:46 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2018-01-29 15:47:26 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-12-11 15:29:47 -08:00
if ( ! semver . valid ( version ) ) return callback ( new ProvisionError ( ProvisionError . BAD _STATE , 'version is not a valid semver' ) ) ;
2019-07-25 14:40:52 -07:00
if ( semver . major ( constants . VERSION ) !== semver . major ( version ) || semver . minor ( constants . VERSION ) !== semver . minor ( version ) ) return callback ( new ProvisionError ( ProvisionError . BAD _STATE , ` Run cloudron-setup with --version ${ version } to restore from this backup ` ) ) ;
2018-01-29 15:47:26 -08:00
2018-12-15 15:27:16 -08:00
if ( gProvisionStatus . setup . active || gProvisionStatus . restore . active ) return callback ( new ProvisionError ( ProvisionError . BAD _STATE , 'Already setting up or restoring' ) ) ;
2018-11-11 09:29:11 -08:00
2018-12-15 15:27:16 -08:00
gProvisionStatus . restore = { active : true , errorMessage : '' , message : 'Testing backup config' } ;
function done ( error ) {
gProvisionStatus . restore . active = false ;
gProvisionStatus . restore . errorMessage = error ? error . message : '' ;
callback ( error ) ;
}
2018-02-04 15:08:46 -08:00
2018-11-10 18:08:08 -08:00
users . isActivated ( function ( error , activated ) {
2018-12-15 15:27:16 -08:00
if ( error ) return done ( new ProvisionError ( ProvisionError . INTERNAL _ERROR , error ) ) ;
2019-07-24 22:11:22 -07:00
if ( activated ) return done ( new ProvisionError ( ProvisionError . ALREADY _PROVISIONED , 'Already activated. Restore with a fresh Cloudron installation.' ) ) ;
2018-01-29 15:47:26 -08:00
backups . testConfig ( backupConfig , function ( error ) {
2018-12-15 15:27:16 -08:00
if ( error && error . reason === BackupsError . BAD _FIELD ) return done ( new ProvisionError ( ProvisionError . BAD _FIELD , error . message ) ) ;
if ( error && error . reason === BackupsError . EXTERNAL _ERROR ) return done ( new ProvisionError ( ProvisionError . EXTERNAL _ERROR , error . message ) ) ;
if ( error ) return done ( new ProvisionError ( ProvisionError . INTERNAL _ERROR , error ) ) ;
2018-01-29 15:47:26 -08:00
2018-03-07 13:59:17 -08:00
debug ( ` restore: restoring from ${ backupId } from provider ${ backupConfig . provider } with format ${ backupConfig . format } ` ) ;
2018-01-29 15:47:26 -08:00
2018-12-15 15:27:16 -08:00
callback ( ) ; // now that the fields are validated, continue task in the background
2018-01-29 15:47:26 -08:00
async . series ( [
2018-12-15 15:27:16 -08:00
setProgress . bind ( null , 'restore' , 'Downloading backup' ) ,
backups . restore . bind ( null , backupConfig , backupId , ( progress ) => setProgress ( 'restore' , progress . message , NOOP _CALLBACK ) ) ,
setProgress . bind ( null , 'restore' , 'Applying auto-configuration' ) ,
eventlog . add . bind ( null , eventlog . ACTION _RESTORE , auditSource , { backupId } ) ,
2018-01-29 15:47:26 -08:00
] , function ( error ) {
2018-12-15 15:27:16 -08:00
gProvisionStatus . restore . active = false ;
gProvisionStatus . restore . errorMessage = error ? error . message : '' ;
if ( ! error ) cloudron . onActivated ( NOOP _CALLBACK ) ;
2018-01-29 15:47:26 -08:00
} ) ;
} ) ;
} ) ;
}
2018-12-15 15:27:16 -08:00
function getStatus ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
users . isActivated ( function ( error , activated ) {
if ( error ) return callback ( new ProvisionError ( ProvisionError . INTERNAL _ERROR , error ) ) ;
settings . getCloudronName ( function ( error , cloudronName ) {
if ( error ) return callback ( new ProvisionError ( ProvisionError . INTERNAL _ERROR , error ) ) ;
callback ( null , _ . extend ( {
2019-07-25 14:40:52 -07:00
version : constants . VERSION ,
2019-07-26 10:49:29 -07:00
apiServerOrigin : settings . apiServerOrigin ( ) , // used by CaaS tool
2019-07-26 07:25:40 -07:00
provider : sysinfo . provider ( ) ,
2018-12-15 15:27:16 -08:00
cloudronName : cloudronName ,
2019-07-26 10:49:29 -07:00
adminFqdn : settings . adminDomain ( ) ? settings . adminFqdn ( ) : null ,
2018-12-15 15:27:16 -08:00
activated : activated ,
} , gProvisionStatus ) ) ;
} ) ;
} ) ;
}