2018-01-29 15:47:26 -08:00
'use strict' ;
exports = module . exports = {
dnsSetup : dnsSetup ,
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
} ;
var assert = require ( 'assert' ) ,
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' ) ,
DomainError = domains . DomainError ,
eventlog = require ( './eventlog.js' ) ,
fs = require ( 'fs' ) ,
mail = require ( './mail.js' ) ,
path = require ( 'path' ) ,
paths = require ( './paths.js' ) ,
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' ) ,
SettingsError = settings . SettingsError ,
shell = require ( './shell.js' ) ,
superagent = require ( 'superagent' ) ,
sysinfo = require ( './sysinfo.js' ) ,
tokendb = require ( './tokendb.js' ) ,
user = require ( './user.js' ) ,
UserError = user . UserError ,
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 ,
restoring : false
} ;
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' ;
function autoprovision ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
const confJson = safe . fs . readFileSync ( paths . AUTO _PROVISION _FILE , 'utf8' ) ;
if ( ! confJson ) return callback ( ) ;
const conf = safe . JSON . parse ( confJson ) ;
if ( ! conf ) return callback ( ) ;
async . eachSeries ( Object . keys ( conf ) , function ( key , iteratorDone ) {
var name ;
switch ( key ) {
case 'appstoreConfig' : name = settings . APPSTORE _CONFIG _KEY ; break ;
case 'caasConfig' : name = settings . CAAS _CONFIG _KEY ; break ;
case 'backupConfig' : name = settings . BACKUP _CONFIG _KEY ; break ;
case 'tlsCert' :
debug ( ` autoprovision: ${ key } ` ) ;
2018-01-30 20:41:52 -08:00
return fs . writeFile ( path . join ( paths . NGINX _CERT _DIR , config . adminDomain ( ) + '.host.cert' ) , conf [ key ] , iteratorDone ) ;
2018-01-29 15:47:26 -08:00
case 'tlsKey' :
debug ( ` autoprovision: ${ key } ` ) ;
2018-01-30 20:41:52 -08:00
return fs . writeFile ( path . join ( paths . NGINX _CERT _DIR , config . adminDomain ( ) + '.host.key' ) , conf [ key ] , iteratorDone ) ;
2018-01-29 15:47:26 -08:00
default :
debug ( ` autoprovision: ${ key } ignored ` ) ;
return iteratorDone ( ) ;
}
debug ( ` autoprovision: ${ name } ` ) ;
settingsdb . set ( name , JSON . stringify ( conf [ key ] ) , iteratorDone ) ;
} , callback ) ;
}
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
function done ( error ) {
gWebadminStatus . configuring = false ;
debug ( 'configureWebadmin: done error: %j' , error || { } ) ;
callback ( error ) ;
}
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-01-29 15:47:26 -08:00
if ( error ) return done ( error ) ;
gWebadminStatus . tls = true ;
2018-01-30 13:54:13 -08:00
done ( ) ;
2018-01-29 15:47:26 -08:00
} ) ;
}
function addWebadminDnsRecord ( ip , domain , callback ) {
assert . strictEqual ( typeof ip , 'string' ) ;
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
if ( process . env . BOX _ENV === 'test' ) return callback ( ) ;
async . retry ( { times : 10 , interval : 20000 } , function ( retryCallback ) {
domains . upsertDNSRecords ( config . adminLocation ( ) , domain , 'A' , [ ip ] , retryCallback ) ;
} , function ( error ) {
if ( error ) debug ( 'addWebadminDnsRecord: done updating records with error:' , error ) ;
else debug ( 'addWebadminDnsRecord: done' ) ;
callback ( error ) ;
} ) ;
}
// 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
addWebadminDnsRecord ( ip , config . adminDomain ( ) , function ( error ) {
2018-01-30 13:54:13 -08:00
if ( error ) return configureReverseProxy ( error ) ;
2018-01-29 15:47:26 -08:00
domains . waitForDNSRecord ( config . adminFqdn ( ) , config . adminDomain ( ) , ip , 'A' , { 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-01-31 17:42:26 +01:00
function dnsSetup ( adminFqdn , domain , zoneName , provider , dnsConfig , tlsConfig , callback ) {
2018-01-29 15:47:26 -08:00
assert . strictEqual ( typeof adminFqdn , 'string' ) ;
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof zoneName , 'string' ) ;
assert . strictEqual ( typeof provider , 'string' ) ;
assert . strictEqual ( typeof dnsConfig , 'object' ) ;
2018-01-31 17:42:26 +01:00
assert . strictEqual ( typeof tlsConfig , '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-02-04 15:08:46 -08:00
if ( gWebadminStatus . configuring || gWebadminStatus . restoring ) return callback ( new SetupError ( SetupError . BAD _STATE , 'Already restoring or configuring' ) ) ;
2018-01-29 15:47:26 -08:00
if ( ! zoneName ) zoneName = tld . getDomain ( domain ) || domain ;
debug ( 'dnsSetup: Setting up Cloudron with domain %s and zone %s' , domain , zoneName ) ;
function done ( error ) {
if ( error && error . reason === DomainError . BAD _FIELD ) return callback ( new SetupError ( SetupError . BAD _FIELD , error . message ) ) ;
if ( error ) return callback ( new SetupError ( SetupError . INTERNAL _ERROR , error ) ) ;
2018-01-30 20:41:52 -08:00
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-01-29 15:47:26 -08:00
autoprovision ( function ( error ) {
if ( error ) return callback ( new SetupError ( SetupError . INTERNAL _ERROR , error ) ) ;
2018-01-31 20:10:47 -08:00
clients . addDefaultClients ( config . adminOrigin ( ) , callback ) ;
2018-01-29 15:47:26 -08:00
2018-01-31 20:10:47 -08:00
configureWebadmin ( NOOP _CALLBACK ) ;
2018-01-29 15:47:26 -08:00
} ) ;
}
domains . get ( domain , function ( error , result ) {
if ( error && error . reason !== DomainError . NOT _FOUND ) return callback ( new SettingsError ( SettingsError . INTERNAL _ERROR , error ) ) ;
if ( ! result ) {
async . series ( [
2018-01-31 17:42:26 +01:00
domains . add . bind ( null , domain , zoneName , provider , dnsConfig , null /* cert */ , tlsConfig ) ,
2018-01-29 15:47:26 -08:00
mail . add . bind ( null , domain )
] , done ) ;
} else {
2018-01-31 17:42:26 +01:00
domains . update ( domain , provider , dnsConfig , null /* cert */ , tlsConfig , done ) ;
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
user . createOwner ( username , password , email , displayName , auditSource , function ( error , userObject ) {
if ( error && error . reason === UserError . ALREADY _EXISTS ) return callback ( new SetupError ( SetupError . ALREADY _PROVISIONED ) ) ;
if ( error && error . reason === UserError . BAD _FIELD ) return callback ( new SetupError ( SetupError . BAD _FIELD , error . message ) ) ;
if ( error ) return callback ( new SetupError ( SetupError . INTERNAL _ERROR , error ) ) ;
clients . get ( 'cid-webadmin' , function ( error , result ) {
if ( error ) return callback ( new SetupError ( SetupError . INTERNAL _ERROR , error ) ) ;
// Also generate a token so the admin creation can also act as a login
var token = tokendb . generateToken ( ) ;
var expires = Date . now ( ) + constants . DEFAULT _TOKEN _EXPIRATION ;
tokendb . add ( token , userObject . id , result . id , expires , '*' , function ( error ) {
if ( error ) return callback ( new SetupError ( SetupError . INTERNAL _ERROR , error ) ) ;
eventlog . add ( eventlog . ACTION _ACTIVATE , auditSource , { } ) ;
callback ( null , { token : token , expires : expires } ) ;
2018-01-31 22:16:06 -08:00
setTimeout ( cloudron . onActivated , 3000 ) ; // hack for now to not block the above http response
2018-01-29 15:47:26 -08:00
} ) ;
} ) ;
} ) ;
}
function restore ( backupConfig , backupId , version , callback ) {
assert . strictEqual ( typeof backupConfig , 'object' ) ;
assert . strictEqual ( typeof backupId , 'string' ) ;
assert . strictEqual ( typeof version , 'string' ) ;
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-02-04 15:08:46 -08:00
if ( gWebadminStatus . configuring || gWebadminStatus . restoring ) return callback ( new SetupError ( SetupError . BAD _STATE , 'Already restoring or configuring' ) ) ;
2018-01-29 15:47:26 -08:00
user . count ( function ( error , count ) {
if ( error ) return callback ( new SetupError ( SetupError . INTERNAL _ERROR , error ) ) ;
if ( count ) return callback ( new SetupError ( SetupError . ALREADY _PROVISIONED , 'Already activated' ) ) ;
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 ) ) ;
debug ( ` restore: restoring from ${ backupId } from provider ${ backupConfig . provider } ` ) ;
gWebadminStatus . restoring = true ;
callback ( null ) ; // do no block
async . series ( [
backups . restore . bind ( null , backupConfig , backupId ) ,
autoprovision ,
shell . sudo . bind ( null , 'restart' , [ RESTART _CMD ] )
] , function ( error ) {
debug ( 'restore:' , error ) ;
gWebadminStatus . restoring = false ;
} ) ;
} ) ;
} ) ;
}
function getStatus ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
user . count ( function ( error , count ) {
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 ,
activated : count !== 0 ,
webadminStatus : gWebadminStatus // only valid when !activated
} ) ;
} ) ;
} ) ;
}