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 ,
activate : activate ,
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' ) ,
safe = require ( 'safetydance' ) ,
semver = require ( 'semver' ) ,
settingsdb = require ( './settingsdb.js' ) ,
settings = require ( './settings.js' ) ,
shell = require ( './shell.js' ) ,
superagent = require ( 'superagent' ) ,
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 ) ; } ;
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
}
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-11-11 09:29:11 -08:00
let webadminStatus = cloudron . getWebadminStatus ( ) ;
if ( webadminStatus . configuring || webadminStatus . 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 ) ,
2018-11-11 09:29:11 -08:00
cloudron . configureWebadmin
2018-11-02 17:24:38 -07:00
] , 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-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
} ) ;
} ) ;
}
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-11-11 09:29:11 -08:00
let webadminStatus = cloudron . getWebadminStatus ( ) ;
if ( webadminStatus . configuring || webadminStatus . 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-11-11 09:29:11 -08:00
webadminStatus . restore . active = true ;
webadminStatus . 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-11-25 14:57:17 -08:00
shell . sudo . bind ( null , 'restart' , [ RESTART _CMD ] , { } )
2018-01-29 15:47:26 -08:00
] , function ( error ) {
debug ( 'restore:' , error ) ;
2018-11-11 09:29:11 -08:00
if ( error ) webadminStatus . restore . error = error . message ;
webadminStatus . restore . active = false ;
2018-01-29 15:47:26 -08:00
} ) ;
} ) ;
} ) ;
}