2018-01-29 15:47:26 -08:00
'use strict' ;
exports = module . exports = {
2021-05-01 11:21:09 -07:00
setup ,
restore ,
activate ,
getStatus
2018-01-29 15:47:26 -08:00
} ;
2021-05-04 21:40:11 -07:00
const assert = require ( 'assert' ) ,
2018-01-29 15:47:26 -08:00
backups = require ( './backups.js' ) ,
2021-07-14 11:07:19 -07:00
backuptask = require ( './backuptask.js' ) ,
2019-10-22 20:36:20 -07:00
BoxError = require ( './boxerror.js' ) ,
2020-10-18 10:15:36 -07:00
branding = require ( './branding.js' ) ,
2018-01-29 15:47:26 -08:00
constants = require ( './constants.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' ) ,
eventlog = require ( './eventlog.js' ) ,
2021-11-17 11:48:06 -08:00
fs = require ( 'fs' ) ,
2018-01-29 15:47:26 -08:00
mail = require ( './mail.js' ) ,
2021-05-26 23:01:05 -07:00
mounts = require ( './mounts.js' ) ,
2021-05-04 21:40:11 -07:00
reverseProxy = require ( './reverseproxy.js' ) ,
2021-05-26 23:01:05 -07:00
safe = require ( 'safetydance' ) ,
2018-01-29 15:47:26 -08:00
semver = require ( 'semver' ) ,
settings = require ( './settings.js' ) ,
2019-11-11 16:05:53 -08:00
sysinfo = require ( './sysinfo.js' ) ,
2021-11-17 11:48:06 -08:00
paths = require ( './paths.js' ) ,
2018-04-29 10:58:45 -07:00
users = require ( './users.js' ) ,
2018-01-29 15:47:26 -08:00
tld = require ( 'tldjs' ) ,
2020-02-06 16:57:33 +01:00
tokens = require ( './tokens.js' ) ,
2018-12-15 15:27:16 -08:00
_ = require ( 'underscore' ) ;
// we cannot use tasks since the tasks table gets overwritten when db is imported
2021-09-17 09:22:46 -07:00
const gProvisionStatus = {
2018-12-15 15:27:16 -08:00
setup : {
active : false ,
message : '' ,
errorMessage : null
} ,
restore : {
active : false ,
message : '' ,
errorMessage : null
}
} ;
2018-01-29 15:47:26 -08:00
2022-04-15 17:29:15 -05:00
function setProgress ( task , message ) {
2018-12-22 20:48:03 -08:00
debug ( ` setProgress: ${ task } - ${ message } ` ) ;
2018-12-15 15:27:16 -08:00
gProvisionStatus [ task ] . message = message ;
}
2021-11-17 11:48:06 -08:00
async function ensureDhparams ( ) {
if ( fs . existsSync ( paths . DHPARAMS _FILE ) ) return ;
debug ( 'ensureDhparams: generating dhparams' ) ;
const dhparams = safe . child _process . execSync ( 'openssl dhparam -dsaparam 2048' ) ;
if ( ! dhparams ) throw new BoxError ( BoxError . OPENSSL _ERROR , safe . error ) ;
if ( ! safe . fs . writeFileSync ( paths . DHPARAMS _FILE , dhparams ) ) throw new BoxError ( BoxError . FS _ERROR , ` Could not save dhparams.pem: ${ safe . error . message } ` ) ;
}
2021-08-27 09:52:24 -07:00
async function unprovision ( ) {
2018-12-07 14:35:04 -08:00
// TODO: also cancel any existing configureWebadmin task
2021-08-27 09:52:24 -07:00
await settings . setDashboardLocation ( '' , '' ) ;
await mail . clearDomains ( ) ;
await domains . clear ( ) ;
2018-12-07 14:35:04 -08:00
}
2018-02-09 12:43:03 +01:00
2021-08-27 09:52:24 -07:00
async function setupTask ( domain , auditSource ) {
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof auditSource , 'object' ) ;
try {
await cloudron . setupDnsAndCert ( constants . DASHBOARD _LOCATION , domain , auditSource , ( progress ) => setProgress ( 'setup' , progress . message ) ) ;
2021-11-17 11:48:06 -08:00
await ensureDhparams ( ) ;
2021-08-27 09:52:24 -07:00
await cloudron . setDashboardDomain ( domain , auditSource ) ;
setProgress ( 'setup' , 'Done' ) ,
await eventlog . add ( eventlog . ACTION _PROVISION , auditSource , { } ) ;
} catch ( error ) {
gProvisionStatus . setup . errorMessage = error ? error . message : '' ;
}
gProvisionStatus . setup . active = false ;
}
2019-02-26 12:32:35 -08:00
2022-01-05 22:41:41 -08:00
async function setup ( domainConfig , sysinfoConfig , auditSource ) {
assert . strictEqual ( typeof domainConfig , 'object' ) ;
2019-10-29 20:12:37 -07:00
assert . strictEqual ( typeof sysinfoConfig , 'object' ) ;
2018-12-07 14:35:04 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2018-01-29 15:47:26 -08:00
2021-08-27 09:52:24 -07:00
if ( gProvisionStatus . setup . active || gProvisionStatus . restore . active ) throw new BoxError ( BoxError . 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
2021-08-27 09:52:24 -07:00
try {
const activated = await users . isActivated ( ) ;
if ( activated ) throw new BoxError ( BoxError . CONFLICT , 'Already activated' , { activate : true } ) ;
2018-03-08 15:10:38 -08:00
2021-08-27 09:52:24 -07:00
await unprovision ( ) ;
2018-12-15 15:27:16 -08:00
2022-01-05 22:41:41 -08:00
const domain = domainConfig . domain . toLowerCase ( ) ;
const zoneName = domainConfig . zoneName ? domainConfig . zoneName : ( tld . getDomain ( domain ) || domain ) ;
2018-11-10 00:43:46 -08:00
2021-08-27 09:52:24 -07:00
debug ( ` setup: Setting up Cloudron with domain ${ domain } and zone ${ zoneName } ` ) ;
2018-10-30 18:43:52 -07:00
2021-08-27 09:52:24 -07:00
const data = {
zoneName : zoneName ,
2022-01-05 22:41:41 -08:00
provider : domainConfig . provider ,
config : domainConfig . config ,
fallbackCertificate : domainConfig . fallbackCertificate || null ,
tlsConfig : domainConfig . tlsConfig || { provider : 'letsencrypt-prod' } ,
2021-08-27 09:52:24 -07:00
dkimSelector : 'cloudron'
} ;
2018-11-10 00:43:46 -08:00
2021-08-27 09:52:24 -07:00
await settings . setMailLocation ( domain , ` ${ constants . DASHBOARD _LOCATION } . ${ domain } ` ) ; // default mail location. do this before we add the domain for upserting mail DNS
await domains . add ( domain , data , auditSource ) ;
await settings . setSysinfoConfig ( sysinfoConfig ) ;
2018-10-30 18:43:52 -07:00
2021-09-26 21:24:31 -07:00
safe ( setupTask ( domain , auditSource ) , { debug } ) ; // now that args are validated run the task in the background
2021-08-27 09:52:24 -07:00
} catch ( error ) {
debug ( 'setup: error' , error ) ;
gProvisionStatus . setup . active = false ;
gProvisionStatus . setup . errorMessage = error . message ;
throw error ;
}
2018-01-29 15:47:26 -08:00
}
2021-07-15 09:50:11 -07:00
async function activate ( username , password , email , displayName , ip , auditSource ) {
2018-01-29 15:47:26 -08:00
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' ) ;
2021-07-15 09:50:11 -07:00
debug ( ` activate: user: ${ username } email: ${ email } ` ) ;
2018-01-29 15:47:26 -08:00
2021-07-19 12:43:30 -07:00
const [ error , ownerId ] = await safe ( users . createOwner ( email , username , password , displayName , auditSource ) ) ;
2021-07-15 09:50:11 -07:00
if ( error && error . reason === BoxError . ALREADY _EXISTS ) throw new BoxError ( BoxError . CONFLICT , 'Already activated' ) ;
if ( error ) throw error ;
2018-01-29 15:47:26 -08:00
2021-07-19 12:43:30 -07:00
const token = { clientId : tokens . ID _WEBADMIN , identifier : ownerId , expires : Date . now ( ) + constants . DEFAULT _TOKEN _EXPIRATION _MSECS } ;
2021-07-15 09:50:11 -07:00
const result = await tokens . add ( token ) ;
2018-01-29 15:47:26 -08:00
2022-02-24 20:04:46 -08:00
await eventlog . add ( eventlog . ACTION _ACTIVATE , auditSource , { } ) ;
2018-01-29 15:47:26 -08:00
2021-09-17 09:22:46 -07:00
setImmediate ( ( ) => safe ( cloudron . onActivated ( { } ) , { debug } ) ) ;
2021-06-04 09:28:40 -07:00
2021-07-15 09:50:11 -07:00
return {
2021-07-19 12:43:30 -07:00
userId : ownerId ,
2021-07-15 09:50:11 -07:00
token : result . accessToken ,
expires : result . expires
} ;
2018-01-29 15:47:26 -08:00
}
2022-04-05 09:28:30 -07:00
async function restoreTask ( backupConfig , remotePath , sysinfoConfig , options , auditSource ) {
2018-01-29 15:47:26 -08:00
assert . strictEqual ( typeof backupConfig , 'object' ) ;
2022-04-05 09:28:30 -07:00
assert . strictEqual ( typeof remotePath , 'string' ) ;
2019-11-11 09:49:18 -08:00
assert . strictEqual ( typeof sysinfoConfig , 'object' ) ;
2021-02-24 14:56:09 -08:00
assert . strictEqual ( typeof options , 'object' ) ;
2018-11-10 00:43:46 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2018-01-29 15:47:26 -08:00
2021-08-27 09:52:24 -07:00
try {
2021-11-03 12:10:37 -07:00
setProgress ( 'restore' , 'Downloading box backup' ) ;
2022-04-05 09:28:30 -07:00
await backuptask . restore ( backupConfig , remotePath , ( progress ) => setProgress ( 'restore' , progress . message ) ) ;
2021-11-03 12:10:37 -07:00
setProgress ( 'restore' , 'Downloading mail backup' ) ;
const mailBackups = await backups . getByIdentifierAndStatePaged ( backups . BACKUP _IDENTIFIER _MAIL , backups . BACKUP _STATE _NORMAL , 1 , 1 ) ;
if ( mailBackups . length === 0 ) throw new BoxError ( BoxError . NOT _FOUND , 'mail backup not found' ) ;
2022-04-13 21:23:12 -05:00
const mailRestoreConfig = { backupConfig , remotePath : mailBackups [ 0 ] . remotePath , backupFormat : mailBackups [ 0 ] . format } ;
2021-11-03 12:10:37 -07:00
await backuptask . downloadMail ( mailRestoreConfig , ( progress ) => setProgress ( 'restore' , progress . message ) ) ;
2021-11-17 11:48:06 -08:00
await ensureDhparams ( ) ;
2021-08-27 09:52:24 -07:00
await settings . setSysinfoConfig ( sysinfoConfig ) ;
await reverseProxy . restoreFallbackCertificates ( ) ;
2018-01-29 15:47:26 -08:00
2021-08-27 09:52:24 -07:00
const dashboardDomain = settings . dashboardDomain ( ) ; // load this fresh from after the backup.restore
if ( ! options . skipDnsSetup ) await cloudron . setupDnsAndCert ( constants . DASHBOARD _LOCATION , dashboardDomain , auditSource , ( progress ) => setProgress ( 'restore' , progress . message ) ) ;
await cloudron . setDashboardDomain ( dashboardDomain , auditSource ) ;
await settings . setBackupCredentials ( backupConfig ) ; // update just the credentials and not the policy and flags
2022-04-05 09:28:30 -07:00
await eventlog . add ( eventlog . ACTION _RESTORE , auditSource , { remotePath } ) ;
2018-11-11 09:29:11 -08:00
2021-11-03 12:10:37 -07:00
setImmediate ( ( ) => safe ( cloudron . onActivated ( options ) , { debug } ) ) ;
2021-08-27 09:52:24 -07:00
} catch ( error ) {
2018-12-15 15:27:16 -08:00
gProvisionStatus . restore . errorMessage = error ? error . message : '' ;
}
2021-08-27 09:52:24 -07:00
gProvisionStatus . restore . active = false ;
}
2018-02-04 15:08:46 -08:00
2022-04-13 21:23:12 -05:00
async function restore ( backupConfig , remotePath , version , sysinfoConfig , options , auditSource ) {
2021-08-27 09:52:24 -07:00
assert . strictEqual ( typeof backupConfig , 'object' ) ;
2022-04-13 21:23:12 -05:00
assert . strictEqual ( typeof remotePath , 'string' ) ;
2021-08-27 09:52:24 -07:00
assert . strictEqual ( typeof version , 'string' ) ;
assert . strictEqual ( typeof sysinfoConfig , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
assert . strictEqual ( typeof auditSource , 'object' ) ;
2022-02-07 13:19:59 -08:00
if ( ! semver . valid ( version ) ) throw new BoxError ( BoxError . BAD _FIELD , 'version is not a valid semver' ) ;
2021-08-27 09:52:24 -07:00
if ( constants . VERSION !== version ) throw new BoxError ( BoxError . BAD _STATE , ` Run "cloudron-setup --version ${ version } " on a fresh Ubuntu installation to restore from this backup ` ) ;
if ( gProvisionStatus . setup . active || gProvisionStatus . restore . active ) throw new BoxError ( BoxError . BAD _STATE , 'Already setting up or restoring' ) ;
gProvisionStatus . restore = { active : true , errorMessage : '' , message : 'Testing backup config' } ;
try {
const activated = await users . isActivated ( ) ;
if ( activated ) throw new BoxError ( BoxError . CONFLICT , 'Already activated. Restore with a fresh Cloudron installation.' ) ;
2018-01-29 15:47:26 -08:00
2022-01-26 12:40:28 -08:00
if ( mounts . isManagedProvider ( backupConfig . provider ) ) {
2022-01-26 12:17:46 -08:00
const error = mounts . validateMountOptions ( backupConfig . provider , backupConfig . mountOptions ) ;
2021-08-27 09:52:24 -07:00
if ( error ) throw error ;
2021-05-26 23:01:05 -07:00
const newMount = {
name : 'backup' ,
2022-01-26 12:59:34 -08:00
hostPath : paths . MANAGED _BACKUP _MOUNT _DIR ,
2021-05-26 23:01:05 -07:00
mountType : backupConfig . provider ,
mountOptions : backupConfig . mountOptions
} ;
2021-08-27 09:52:24 -07:00
await safe ( mounts . tryAddMount ( newMount , { timeout : 10 } ) ) ; // 10 seconds
}
2021-09-17 10:11:28 -07:00
let error = await backups . testProviderConfig ( backupConfig ) ;
2021-08-27 09:52:24 -07:00
if ( error ) throw error ;
if ( 'password' in backupConfig ) {
backupConfig . encryption = backups . generateEncryptionKeysSync ( backupConfig . password ) ;
delete backupConfig . password ;
} else {
backupConfig . encryption = null ;
2021-05-26 23:01:05 -07:00
}
2022-02-15 12:31:55 -08:00
error = await sysinfo . testIPv4Config ( sysinfoConfig ) ;
2021-08-27 09:52:24 -07:00
if ( error ) throw error ;
2022-04-13 21:23:12 -05:00
safe ( restoreTask ( backupConfig , remotePath , sysinfoConfig , options , auditSource ) , { debug } ) ; // now that args are validated run the task in the background
2021-08-27 09:52:24 -07:00
} catch ( error ) {
gProvisionStatus . restore . active = false ;
gProvisionStatus . restore . errorMessage = error ? error . message : '' ;
throw error ;
}
2018-01-29 15:47:26 -08:00
}
2018-12-15 15:27:16 -08:00
2021-08-18 13:25:42 -07:00
async function getStatus ( ) {
const activated = await users . isActivated ( ) ;
const allSettings = await settings . list ( ) ;
return _ . extend ( {
version : constants . VERSION ,
apiServerOrigin : settings . apiServerOrigin ( ) , // used by CaaS tool
webServerOrigin : settings . webServerOrigin ( ) , // used by CaaS tool
cloudronName : allSettings [ settings . CLOUDRON _NAME _KEY ] ,
footer : branding . renderFooter ( allSettings [ settings . FOOTER _KEY ] || constants . FOOTER ) ,
adminFqdn : settings . dashboardDomain ( ) ? settings . dashboardFqdn ( ) : null ,
language : allSettings [ settings . LANGUAGE _KEY ] ,
activated : activated ,
provider : settings . provider ( ) // used by setup wizard of marketplace images
} , gProvisionStatus ) ;
2018-12-15 15:27:16 -08:00
}