2018-01-29 15:47:26 -08:00
'use strict' ;
exports = module . exports = {
2018-10-30 13:36:00 -07:00
provision : provision ,
2018-01-29 15:47:26 -08:00
restore : restore ,
getStatus : getStatus ,
activate : activate ,
2018-01-30 16:26:17 +01:00
configureWebadmin : configureWebadmin ,
2018-01-29 15:47:26 -08:00
SetupError : SetupError
} ;
2018-07-25 10:34:57 -07:00
var assert = require ( 'assert' ) ,
2018-01-29 15:47:26 -08:00
async = require ( 'async' ) ,
backups = require ( './backups.js' ) ,
BackupsError = require ( './backups.js' ) . BackupsError ,
config = require ( './config.js' ) ,
constants = require ( './constants.js' ) ,
clients = require ( './clients.js' ) ,
cloudron = require ( './cloudron.js' ) ,
debug = require ( 'debug' ) ( 'box:setup' ) ,
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' ) ,
mail = require ( './mail.js' ) ,
path = require ( 'path' ) ,
2018-01-30 12:23:27 -08:00
reverseProxy = require ( './reverseproxy.js' ) ,
2018-01-29 15:47:26 -08:00
safe = require ( 'safetydance' ) ,
semver = require ( 'semver' ) ,
settingsdb = require ( './settingsdb.js' ) ,
settings = require ( './settings.js' ) ,
shell = require ( './shell.js' ) ,
superagent = require ( 'superagent' ) ,
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' ) ,
util = require ( 'util' ) ;
var RESTART _CMD = path . join ( _ _dirname , 'scripts/restart.sh' ) ;
var NOOP _CALLBACK = function ( error ) { if ( error ) debug ( error ) ; } ;
var gWebadminStatus = {
dns : false ,
tls : false ,
configuring : false ,
2018-07-29 20:25:37 -07:00
restore : {
active : false ,
error : null
}
2018-01-29 15:47:26 -08:00
} ;
function SetupError ( 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 ( SetupError , Error ) ;
SetupError . BAD _FIELD = 'Field error' ;
2018-02-04 15:08:46 -08:00
SetupError . BAD _STATE = 'Bad State' ;
2018-01-29 15:47:26 -08:00
SetupError . ALREADY _SETUP = 'Already Setup' ;
SetupError . INTERNAL _ERROR = 'Internal Error' ;
SetupError . EXTERNAL _ERROR = 'External Error' ;
SetupError . ALREADY _PROVISIONED = 'Already Provisioned' ;
2018-10-30 18:43:52 -07:00
function autoprovision ( autoconf , callback ) {
assert . strictEqual ( typeof autoconf , 'object' ) ;
2018-01-29 15:47:26 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-10-30 18:43:52 -07:00
async . eachSeries ( Object . keys ( autoconf ) , function ( key , iteratorDone ) {
2018-11-02 17:10:16 -07:00
debug ( ` autoprovision: ${ key } ` ) ;
2018-01-29 15:47:26 -08:00
switch ( key ) {
2018-11-01 22:28:32 -07:00
case 'appstoreConfig' :
if ( config . provider ( ) === 'caas' ) { // skip registration
settingsdb . set ( settings . APPSTORE _CONFIG _KEY , JSON . stringify ( autoconf [ key ] ) , iteratorDone ) ;
} else { // register cloudron
settings . setAppstoreConfig ( autoconf [ key ] , iteratorDone ) ;
}
break ;
case 'caasConfig' :
settingsdb . set ( settings . CAAS _CONFIG _KEY , JSON . stringify ( autoconf [ key ] ) , iteratorDone ) ;
break ;
case 'backupConfig' :
settings . setBackupConfig ( autoconf [ key ] , iteratorDone ) ;
break ;
2018-01-29 15:47:26 -08:00
default :
debug ( ` autoprovision: ${ key } ignored ` ) ;
return iteratorDone ( ) ;
}
2018-10-30 18:43:52 -07:00
} , function ( error ) {
if ( error ) return callback ( new SetupError ( SetupError . INTERNAL _ERROR , error ) ) ;
callback ( null ) ;
} ) ;
2018-01-29 15:47:26 -08:00
}
function configureWebadmin ( callback ) {
callback = callback || NOOP _CALLBACK ;
debug ( 'configureWebadmin: adminDomain:%s status:%j' , config . adminDomain ( ) , gWebadminStatus ) ;
if ( process . env . BOX _ENV === 'test' || ! config . adminDomain ( ) || gWebadminStatus . configuring ) return callback ( ) ;
gWebadminStatus . configuring = true ; // re-entracy guard
2018-01-30 13:54:13 -08:00
function configureReverseProxy ( error ) {
2018-01-30 19:59:09 -08:00
debug ( 'configureReverseProxy: error %j' , error || null ) ;
2018-01-29 15:47:26 -08:00
2018-01-30 15:16:34 -08:00
reverseProxy . configureAdmin ( { userId : null , username : 'setup' } , function ( error ) {
2018-03-08 18:26:44 -08:00
debug ( 'configureWebadmin: done error: %j' , error || { } ) ;
gWebadminStatus . configuring = false ;
2018-01-29 15:47:26 -08:00
2018-03-08 18:26:44 -08:00
if ( error ) return callback ( error ) ;
2018-01-29 15:47:26 -08:00
2018-03-08 18:26:44 -08:00
gWebadminStatus . tls = true ;
2018-01-29 15:47:26 -08:00
2018-03-08 18:26:44 -08:00
callback ( ) ;
2018-01-29 15:47:26 -08:00
} ) ;
}
// update the DNS. configure nginx regardless of whether it succeeded so that
// box is accessible even if dns creds are invalid
sysinfo . getPublicIp ( function ( error , ip ) {
2018-01-30 13:54:13 -08:00
if ( error ) return configureReverseProxy ( error ) ;
2018-01-29 15:47:26 -08:00
2018-03-08 18:26:44 -08:00
domains . upsertDnsRecords ( config . adminLocation ( ) , config . adminDomain ( ) , 'A' , [ ip ] , function ( error ) {
debug ( 'addWebadminDnsRecord: updated records with error:' , error ) ;
2018-01-30 13:54:13 -08:00
if ( error ) return configureReverseProxy ( error ) ;
2018-01-29 15:47:26 -08:00
2018-09-22 11:25:58 -07:00
domains . waitForDnsRecord ( config . adminLocation ( ) , config . adminDomain ( ) , 'A' , ip , { interval : 30000 , times : 50000 } , function ( error ) {
2018-01-30 13:54:13 -08:00
if ( error ) return configureReverseProxy ( error ) ;
2018-01-29 15:47:26 -08:00
gWebadminStatus . dns = true ;
2018-01-30 13:54:13 -08:00
configureReverseProxy ( ) ;
2018-01-29 15:47:26 -08:00
} ) ;
} ) ;
} ) ;
}
2018-11-10 00:43:46 -08:00
function provision ( dnsConfig , autoconf , auditSource , callback ) {
2018-01-29 15:47:26 -08:00
assert . strictEqual ( typeof dnsConfig , 'object' ) ;
2018-10-30 18:43:52 -07:00
assert . strictEqual ( typeof autoconf , 'object' ) ;
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' ) ;
if ( config . adminDomain ( ) ) return callback ( new SetupError ( SetupError . ALREADY _SETUP ) ) ;
2018-07-29 20:25:37 -07:00
if ( gWebadminStatus . configuring || gWebadminStatus . restore . active ) return callback ( new SetupError ( SetupError . BAD _STATE , 'Already restoring or configuring' ) ) ;
2018-02-04 15:08:46 -08:00
2018-10-30 13:36:00 -07:00
const domain = dnsConfig . domain . toLowerCase ( ) ;
const zoneName = dnsConfig . zoneName ? dnsConfig . zoneName : ( tld . getDomain ( domain ) || domain ) ;
2018-02-09 12:43:03 +01:00
2018-10-30 13:36:00 -07:00
const adminFqdn = 'my' + ( dnsConfig . config . hyphenatedSubdomains ? '-' : '.' ) + domain ;
2018-01-29 15:47:26 -08:00
2018-10-30 13:36:00 -07:00
debug ( ` provision: Setting up Cloudron with domain ${ domain } and zone ${ zoneName } using admin fqdn ${ adminFqdn } ` ) ;
2018-01-29 15:47:26 -08:00
domains . get ( domain , function ( error , result ) {
2018-06-05 21:13:38 -07:00
if ( error && error . reason !== DomainsError . NOT _FOUND ) return callback ( new SetupError ( SetupError . INTERNAL _ERROR , error ) ) ;
2018-01-29 15:47:26 -08:00
2018-06-05 21:13:38 -07:00
if ( result ) return callback ( new SetupError ( SetupError . BAD _STATE , 'Domain already exists' ) ) ;
2018-03-08 15:10:38 -08:00
2018-11-10 00:43:46 -08:00
let data = {
zoneName : zoneName ,
provider : dnsConfig . provider ,
config : dnsConfig . config ,
fallbackCertificate : dnsConfig . fallbackCertificate || null ,
tlsConfig : dnsConfig . tlsConfig || { provider : 'letsencrypt-prod' }
} ;
2018-03-08 15:10:38 -08:00
async . series ( [
2018-11-10 00:43:46 -08:00
domains . add . bind ( null , domain , data , auditSource ) ,
2018-04-03 14:37:52 -07:00
mail . addDomain . bind ( null , domain )
2018-10-30 18:43:52 -07:00
] , function ( error ) {
if ( error && error . reason === DomainsError . BAD _FIELD ) return callback ( new SetupError ( SetupError . BAD _FIELD , error . message ) ) ;
if ( error && error . reason === DomainsError . ALREADY _EXISTS ) return callback ( new SetupError ( SetupError . BAD _FIELD , error . message ) ) ;
if ( error ) return callback ( new SetupError ( SetupError . INTERNAL _ERROR , error ) ) ;
config . setAdminDomain ( domain ) ; // set fqdn only after dns config is valid, otherwise cannot re-setup if we failed
config . setAdminFqdn ( adminFqdn ) ;
config . setAdminLocation ( 'my' ) ;
2018-11-10 00:43:46 -08:00
eventlog . add ( eventlog . ACTION _PROVISION , auditSource , { } ) ;
2018-11-02 17:24:38 -07:00
clients . addDefaultClients ( config . adminOrigin ( ) , callback ) ;
2018-10-30 18:43:52 -07:00
2018-11-02 17:24:38 -07:00
async . series ( [
autoprovision . bind ( null , autoconf ) ,
configureWebadmin
] , NOOP _CALLBACK ) ;
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-04-29 17:37:53 -07:00
if ( error && error . reason === UsersError . ALREADY _EXISTS ) return callback ( new SetupError ( SetupError . ALREADY _PROVISIONED ) ) ;
if ( error && error . reason === UsersError . BAD _FIELD ) return callback ( new SetupError ( SetupError . BAD _FIELD , error . message ) ) ;
2018-01-29 15:47:26 -08:00
if ( error ) return callback ( new SetupError ( SetupError . INTERNAL _ERROR , error ) ) ;
2018-08-27 14:50:41 -07:00
clients . addTokenByUserId ( 'cid-webadmin' , userObject . id , Date . now ( ) + constants . DEFAULT _TOKEN _EXPIRATION , { } , function ( error , result ) {
2018-01-29 15:47:26 -08:00
if ( error ) return callback ( new SetupError ( SetupError . INTERNAL _ERROR , error ) ) ;
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-06-27 23:17:04 -07:00
setTimeout ( cloudron . onActivated , 3000 ) ; // hack for now to not block the above http response
2018-01-29 15:47:26 -08:00
} ) ;
} ) ;
}
2018-11-10 00:43:46 -08:00
function restore ( backupConfig , backupId , version , autoconf , 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-10-30 18:43:52 -07:00
assert . strictEqual ( typeof autoconf , 'object' ) ;
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' ) ;
if ( ! semver . valid ( version ) ) return callback ( new SetupError ( SetupError . BAD _STATE , 'version is not a valid semver' ) ) ;
if ( semver . major ( config . version ( ) ) !== semver . major ( version ) || semver . minor ( config . version ( ) ) !== semver . minor ( version ) ) return callback ( new SetupError ( SetupError . BAD _STATE , ` Run cloudron-setup with --version ${ version } to restore from this backup ` ) ) ;
2018-07-29 20:25:37 -07:00
if ( gWebadminStatus . configuring || gWebadminStatus . restore . active ) return callback ( new SetupError ( SetupError . BAD _STATE , 'Already restoring or configuring' ) ) ;
2018-02-04 15:08:46 -08:00
2018-11-10 18:08:08 -08:00
users . isActivated ( function ( error , activated ) {
2018-01-29 15:47:26 -08:00
if ( error ) return callback ( new SetupError ( SetupError . INTERNAL _ERROR , error ) ) ;
2018-11-10 18:08:08 -08:00
if ( activated ) return callback ( new SetupError ( SetupError . ALREADY _PROVISIONED , 'Already activated' ) ) ;
2018-01-29 15:47:26 -08:00
backups . testConfig ( backupConfig , function ( error ) {
if ( error && error . reason === BackupsError . BAD _FIELD ) return callback ( new SetupError ( SetupError . BAD _FIELD , error . message ) ) ;
if ( error && error . reason === BackupsError . EXTERNAL _ERROR ) return callback ( new SetupError ( SetupError . EXTERNAL _ERROR , error . message ) ) ;
if ( error ) return callback ( new SetupError ( SetupError . INTERNAL _ERROR , error ) ) ;
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-07-29 20:25:37 -07:00
gWebadminStatus . restore . active = true ;
gWebadminStatus . restore . error = null ;
2018-01-29 15:47:26 -08:00
callback ( null ) ; // do no block
async . series ( [
backups . restore . bind ( null , backupConfig , backupId ) ,
2018-11-10 01:28:59 -08:00
eventlog . add . bind ( null , eventlog . ACTION _RESTORE , auditSource , { backupId } ) ,
2018-10-30 18:43:52 -07:00
autoprovision . bind ( null , autoconf ) ,
2018-09-26 12:39:33 -07:00
// currently, our suggested restore flow is after a dnsSetup. The dnSetup creates DKIM keys and updates the DNS
// for this reason, we have to re-setup DNS after a restore so it has DKIm from the backup
// Once we have a 100% IP based restore, we can skip this
2018-07-25 10:44:59 -07:00
mail . setDnsRecords . bind ( null , config . adminDomain ( ) ) ,
2018-01-29 15:47:26 -08:00
shell . sudo . bind ( null , 'restart' , [ RESTART _CMD ] )
] , function ( error ) {
debug ( 'restore:' , error ) ;
2018-07-29 20:25:37 -07:00
if ( error ) gWebadminStatus . restore . error = error . message ;
gWebadminStatus . restore . active = false ;
2018-01-29 15:47:26 -08:00
} ) ;
} ) ;
} ) ;
}
function getStatus ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2018-11-10 18:08:08 -08:00
users . isActivated ( function ( error , activated ) {
2018-01-29 15:47:26 -08:00
if ( error ) return callback ( new SetupError ( SetupError . INTERNAL _ERROR , error ) ) ;
settings . getCloudronName ( function ( error , cloudronName ) {
if ( error ) return callback ( new SetupError ( SetupError . INTERNAL _ERROR , error ) ) ;
callback ( null , {
version : config . version ( ) ,
apiServerOrigin : config . apiServerOrigin ( ) , // used by CaaS tool
provider : config . provider ( ) ,
cloudronName : cloudronName ,
adminFqdn : config . adminDomain ( ) ? config . adminFqdn ( ) : null ,
2018-11-10 18:08:08 -08:00
activated : activated ,
2018-08-28 21:43:25 -07:00
edition : config . edition ( ) ,
2018-01-29 15:47:26 -08:00
webadminStatus : gWebadminStatus // only valid when !activated
} ) ;
} ) ;
} ) ;
}