2018-01-29 15:47:26 -08:00
'use strict' ;
exports = module . exports = {
2021-05-01 11:21:09 -07:00
setup ,
restore ,
activate ,
2023-08-04 11:01:45 +05:30
getStatus ,
2018-01-29 15:47:26 -08:00
} ;
2023-08-10 16:50:29 +05:30
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' ) ,
2023-08-11 19:41:05 +05:30
dashboard = require ( './dashboard.js' ) ,
2018-01-29 15:47:26 -08:00
constants = require ( './constants.js' ) ,
2018-12-11 15:29:47 -08:00
debug = require ( 'debug' ) ( 'box:provision' ) ,
2023-08-14 09:40:31 +05:30
dns = require ( './dns.js' ) ,
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' ) ,
2023-08-04 21:37:38 +05:30
mailServer = require ( './mailserver.js' ) ,
2023-08-03 06:05:29 +05:30
network = require ( './network.js' ) ,
2023-08-12 19:28:07 +05:30
platform = require ( './platform.js' ) ,
2021-05-04 21:40:11 -07:00
reverseProxy = require ( './reverseproxy.js' ) ,
2021-05-26 23:01:05 -07:00
safe = require ( 'safetydance' ) ,
2024-10-14 19:10:31 +02:00
shell = require ( './shell.js' ) ( 'provision' ) ,
2018-01-29 15:47:26 -08:00
semver = require ( 'semver' ) ,
2021-11-17 11:48:06 -08:00
paths = require ( './paths.js' ) ,
2023-08-10 18:45:27 +05:30
system = require ( './system.js' ) ,
2018-04-29 10:58:45 -07:00
users = require ( './users.js' ) ,
2018-01-29 15:47:26 -08:00
tld = require ( 'tldjs' ) ,
2023-05-25 11:27:23 +02:00
tokens = require ( './tokens.js' ) ;
2018-12-15 15:27:16 -08:00
// we cannot use tasks since the tasks table gets overwritten when db is imported
2023-08-10 18:45:27 +05:30
const gStatus = {
2018-12-15 15:27:16 -08:00
setup : {
active : false ,
message : '' ,
2024-07-15 18:35:04 +02:00
errorMessage : null ,
startTime : null
2018-12-15 15:27:16 -08:00
} ,
restore : {
active : false ,
message : '' ,
2024-07-15 18:35:04 +02:00
errorMessage : null ,
startTime : null
2023-08-10 18:45:27 +05:30
} ,
activated : false ,
adminFqdn : null ,
provider : null ,
version : constants . VERSION // for reloading dashboard
2018-12-15 15:27:16 -08:00
} ;
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 } ` ) ;
2023-08-10 18:45:27 +05:30
gStatus [ task ] . message = message ;
2018-12-15 15:27:16 -08:00
}
2021-11-17 11:48:06 -08:00
async function ensureDhparams ( ) {
if ( fs . existsSync ( paths . DHPARAMS _FILE ) ) return ;
debug ( 'ensureDhparams: generating dhparams' ) ;
2024-10-15 10:10:15 +02:00
const dhparams = await shell . spawn ( 'openssl' , [ 'dhparam' , '-dsaparam' , '2048' ] , { encoding : 'utf8' } ) ;
2021-11-17 11:48:06 -08:00
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
2023-08-13 10:29:24 +05:30
await dashboard . clearLocation ( ) ;
2021-08-27 09:52:24 -07:00
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' ) ;
2023-08-14 09:40:31 +05:30
const location = { subdomain : constants . DASHBOARD _SUBDOMAIN , domain } ;
2021-08-27 09:52:24 -07:00
try {
2024-10-30 20:58:31 +01:00
debug ( ` setupTask: subdomain ${ location . subdomain } and domain ${ location . domain } ` ) ;
2023-08-14 09:40:31 +05:30
await dns . registerLocations ( [ location ] , { overwriteDns : true } , ( progress ) => setProgress ( 'setup' , progress . message ) ) ;
await dns . waitForLocations ( [ location ] , ( progress ) => setProgress ( 'setup' , progress . message ) ) ;
await reverseProxy . ensureCertificate ( location , { } , auditSource ) ;
2021-11-17 11:48:06 -08:00
await ensureDhparams ( ) ;
2023-08-16 19:28:04 +05:30
await dashboard . setupLocation ( constants . DASHBOARD _SUBDOMAIN , domain , auditSource ) ;
2021-08-27 09:52:24 -07:00
setProgress ( 'setup' , 'Done' ) ,
await eventlog . add ( eventlog . ACTION _PROVISION , auditSource , { } ) ;
} catch ( error ) {
2023-08-14 09:40:31 +05:30
debug ( 'setupTask: error. %o' , error ) ;
2023-08-13 21:36:56 +05:30
gStatus . setup . errorMessage = error . message ;
2021-08-27 09:52:24 -07:00
}
2023-08-10 18:45:27 +05:30
gStatus . setup . active = false ;
2021-08-27 09:52:24 -07:00
}
2019-02-26 12:32:35 -08:00
2024-04-25 19:33:04 +02:00
async function setup ( domainConfig , ipv4Config , ipv6Config , auditSource ) {
2022-01-05 22:41:41 -08:00
assert . strictEqual ( typeof domainConfig , 'object' ) ;
2023-08-03 06:05:29 +05:30
assert . strictEqual ( typeof ipv4Config , 'object' ) ;
2024-04-25 19:33:04 +02:00
assert . strictEqual ( typeof ipv6Config , 'object' ) ;
2018-12-07 14:35:04 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2018-01-29 15:47:26 -08:00
2023-08-10 18:45:27 +05:30
if ( gStatus . setup . active || gStatus . restore . active ) throw new BoxError ( BoxError . BAD _STATE , 'Already setting up or restoring' ) ;
2018-01-29 15:47:26 -08:00
2024-07-15 18:35:04 +02:00
gStatus . setup = { active : true , errorMessage : '' , message : 'Adding domain' , startTime : ( new Date ( ) ) . toISOString ( ) } ;
2018-01-29 15:47:26 -08:00
2021-08-27 09:52:24 -07:00
try {
const activated = await users . isActivated ( ) ;
2023-08-16 11:34:41 +05:30
if ( activated ) throw new BoxError ( BoxError . CONFLICT , 'Already activated' ) ;
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
2023-08-13 21:36:56 +05:30
debug ( ` setup: 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
2023-08-16 19:28:04 +05:30
await mailServer . setLocation ( constants . DASHBOARD _SUBDOMAIN , domain ) ; // default mail location. do this before we add the domain for upserting mail DNS
2021-08-27 09:52:24 -07:00
await domains . add ( domain , data , auditSource ) ;
2023-08-03 06:05:29 +05:30
await network . setIPv4Config ( ipv4Config ) ;
2024-04-25 19:33:04 +02:00
await network . setIPv6Config ( ipv6Config ) ;
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 ) {
2023-04-16 10:49:59 +02:00
debug ( 'setup: error. %o' , error ) ;
2023-08-10 18:45:27 +05:30
gStatus . setup . active = false ;
gStatus . setup . errorMessage = error . message ;
2021-08-27 09:52:24 -07:00
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
2023-08-12 19:28:07 +05:30
setImmediate ( ( ) => safe ( platform . onActivated ( { skipDnsSetup : false } ) , { 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
}
2024-04-25 19:33:04 +02:00
async function restoreTask ( backupConfig , remotePath , ipv4Config , ipv6Config , 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' ) ;
2023-08-03 06:05:29 +05:30
assert . strictEqual ( typeof ipv4Config , 'object' ) ;
2024-04-25 19:33:04 +02:00
assert . strictEqual ( typeof ipv6Config , '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' ) ;
2024-04-09 13:53:15 +02:00
backupConfig . rootPath = backups . getRootPath ( backupConfig , paths . MANAGED _BACKUP _MOUNT _DIR ) ;
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 ( ) ;
2023-08-03 06:05:29 +05:30
await network . setIPv4Config ( ipv4Config ) ;
2024-04-25 19:33:04 +02:00
await network . setIPv6Config ( ipv6Config ) ;
2021-08-27 09:52:24 -07:00
await reverseProxy . restoreFallbackCertificates ( ) ;
2018-01-29 15:47:26 -08:00
2023-08-14 09:40:31 +05:30
const location = await dashboard . getLocation ( ) ; // load this fresh from after the backup.restore
if ( ! options . skipDnsSetup ) {
await dns . registerLocations ( [ location ] , { overwriteDns : true } , ( progress ) => setProgress ( 'restore' , progress . message ) ) ;
await dns . waitForLocations ( [ location ] , ( progress ) => setProgress ( 'setup' , progress . message ) ) ;
await reverseProxy . ensureCertificate ( location , { } , auditSource ) ;
}
2024-04-27 11:02:50 +02:00
await dashboard . setupLocation ( location . subdomain , location . domain , auditSource ) ;
2023-07-13 11:50:57 +05:30
2023-08-15 20:24:54 +05:30
delete backupConfig . rootPath ;
2023-08-04 11:24:28 +05:30
await backups . setConfig ( backupConfig ) ;
2022-04-05 09:28:30 -07:00
await eventlog . add ( eventlog . ACTION _RESTORE , auditSource , { remotePath } ) ;
2018-11-11 09:29:11 -08:00
2023-08-12 19:28:07 +05:30
setImmediate ( ( ) => safe ( platform . onActivated ( { skipDnsSetup : options . skipDnsSetup } ) , { debug } ) ) ;
2021-08-27 09:52:24 -07:00
} catch ( error ) {
2023-08-13 21:36:56 +05:30
debug ( 'restoreTask: error. %o' , error ) ;
2023-08-10 18:45:27 +05:30
gStatus . restore . errorMessage = error ? error . message : '' ;
2018-12-15 15:27:16 -08:00
}
2023-08-10 18:45:27 +05:30
gStatus . restore . active = false ;
2021-08-27 09:52:24 -07:00
}
2018-02-04 15:08:46 -08:00
2024-04-27 12:46:37 +02:00
async function restore ( backupConfig , remotePath , version , ipv4Config , ipv6Config , options , auditSource ) {
2023-08-15 20:24:54 +05:30
assert . strictEqual ( typeof backupConfig , 'object' ) ; // format, storage, password
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' ) ;
2023-08-03 06:05:29 +05:30
assert . strictEqual ( typeof ipv4Config , 'object' ) ;
2024-04-27 12:46:37 +02:00
assert . strictEqual ( typeof ipv6Config , 'object' ) ;
2021-08-27 09:52:24 -07:00
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 ` ) ;
2023-08-10 18:45:27 +05:30
if ( gStatus . setup . active || gStatus . restore . active ) throw new BoxError ( BoxError . BAD _STATE , 'Already setting up or restoring' ) ;
2021-08-27 09:52:24 -07:00
2024-07-15 18:35:04 +02:00
gStatus . restore = { active : true , errorMessage : '' , message : 'Testing backup config' , startTime : ( new Date ( ) ) . toISOString ( ) } ;
2021-08-27 09:52:24 -07:00
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
2023-08-15 20:24:54 +05:30
let error = backups . validateFormat ( backupConfig . format ) ;
if ( error ) throw error ;
2021-08-27 09:52:24 -07:00
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
}
2024-06-25 12:38:27 +02:00
await backups . setupManagedStorage ( backupConfig , paths . MANAGED _BACKUP _MOUNT _DIR ) ; // this validates mountOptions
2024-04-27 12:46:37 +02:00
error = await backups . testStorage ( Object . assign ( { mountPath : paths . MANAGED _BACKUP _MOUNT _DIR } , backupConfig ) ) ; // this validates provider and it's api options. requires mountPath
2024-04-09 13:20:01 +02:00
if ( error ) throw error ;
2023-08-03 13:38:42 +05:30
error = await network . testIPv4Config ( ipv4Config ) ;
2021-08-27 09:52:24 -07:00
if ( error ) throw error ;
2024-04-27 12:46:37 +02:00
safe ( restoreTask ( backupConfig , remotePath , ipv4Config , ipv6Config , options , auditSource ) , { debug } ) ; // now that args are validated run the task in the background
2021-08-27 09:52:24 -07:00
} catch ( error ) {
2023-08-13 21:36:56 +05:30
debug ( 'restore: error. %o' , error ) ;
2023-08-10 18:45:27 +05:30
gStatus . restore . active = false ;
2023-08-13 21:36:56 +05:30
gStatus . restore . errorMessage = error . message ;
2021-08-27 09:52:24 -07:00
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 ( ) {
2023-08-11 19:41:05 +05:30
const { fqdn : dashboardFqdn } = await dashboard . getLocation ( ) ;
gStatus . adminFqdn = dashboardFqdn ; // indicator for main.js if dnssetup was already done but not activated
2023-08-10 18:45:27 +05:30
gStatus . activated = await users . isActivated ( ) ; // indicator for admin setup
gStatus . provider = await system . getProvider ( ) ; // preselect dns provider and ami token
return gStatus ;
2018-12-15 15:27:16 -08:00
}