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
} ;
2025-06-06 09:31:31 +02:00
const appstore = require ( './appstore.js' ) ,
2025-08-14 11:17:38 +05:30
assert = require ( 'node:assert' ) ,
2025-09-12 09:48:37 +02:00
backupSites = require ( './backupsites.js' ) ,
2025-07-25 01:34:29 +02: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' ) ,
2025-08-14 11:17:38 +05:30
fs = require ( 'node: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' ) ,
2025-06-11 22:53:29 +02:00
oidcClients = require ( './oidcclients.js' ) ,
2026-01-17 13:38:17 +01:00
openssl = require ( './openssl.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' ) ,
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' ) ,
2025-10-06 19:50:58 +02:00
tasks = require ( './tasks.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' ) ;
2026-01-17 13:38:17 +01:00
const dhparams = await openssl . generateDhparam ( ) ;
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 ) ;
2025-09-12 09:48:37 +02:00
await backupSites . addDefault ( 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
2025-09-24 21:25:31 +02:00
await appstore . registerCloudron3 ( ) ;
2025-06-06 09:31:31 +02: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
2025-06-11 22:53:29 +02:00
const token = { clientId : oidcClients . ID _WEBADMIN , identifier : ownerId , allowedIpRanges : '' , 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
2025-12-05 16:12:59 +01:00
safe ( platform . onActivated ( { skipDnsSetup : false } ) , { debug } ) ; // background
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
}
2025-09-12 09:48:37 +02:00
async function restoreTask ( backupSite , remotePath , ipv4Config , ipv6Config , options , auditSource ) {
assert . strictEqual ( typeof backupSite , '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' ) ;
2025-08-05 14:13:39 +02:00
assert . strictEqual ( typeof options , 'object' ) ; // { skipDnsSetup }
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 {
2025-09-12 09:48:37 +02:00
setProgress ( 'restore' , 'Preparing backup site' ) ;
await backupSites . storageApi ( backupSite ) . setup ( backupSite . config ) ;
2025-08-01 23:20:51 +02:00
2021-11-03 12:10:37 -07:00
setProgress ( 'restore' , 'Downloading box backup' ) ;
2025-09-12 09:48:37 +02:00
await backuptask . restore ( backupSite , remotePath , ( progress ) => setProgress ( 'restore' , progress . message ) ) ;
2021-11-03 12:10:37 -07:00
setProgress ( 'restore' , 'Downloading mail backup' ) ;
2025-07-25 01:34:29 +02:00
const mailBackups = await backups . getByIdentifierAndStatePaged ( backups . BACKUP _IDENTIFIER _MAIL , backups . BACKUP _STATE _NORMAL , 1 , 1 ) ;
2021-11-03 12:10:37 -07:00
if ( mailBackups . length === 0 ) throw new BoxError ( BoxError . NOT _FOUND , 'mail backup not found' ) ;
2025-08-01 23:20:51 +02:00
const mailRemotePath = mailBackups [ 0 ] . remotePath ;
2025-09-12 09:48:37 +02:00
await backuptask . downloadMail ( backupSite , mailRemotePath , ( progress ) => setProgress ( 'restore' , progress . message ) ) ;
2021-11-03 12:10:37 -07:00
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
2025-10-06 16:39:01 +02:00
await backupSites . reinitAll ( ) ;
2025-10-06 19:50:58 +02:00
// when creating a sql dump during a full backup, the task has not completed yet. we mark it as completed here for the Sites UI to not indicate a crash
// siteId can be missing when restoring manually instead of via the backup config
const backupTasks = await tasks . list ( 1 , 1 , {
type : options . siteId ? tasks . TASK _FULL _BACKUP _PREFIX + options . siteId : null ,
prefix : ! options . siteId ? tasks . TASK _FULL _BACKUP _PREFIX : null // guess that the latest full backup was the site that was used
} ) ;
await tasks . setCompleted ( backupTasks [ 0 ] . id , { error : null } ) ;
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
2022-04-05 09:28:30 -07:00
await eventlog . add ( eventlog . ACTION _RESTORE , auditSource , { remotePath } ) ;
2018-11-11 09:29:11 -08:00
2025-10-02 11:37:26 +02:00
await appstore . checkSubscription ( ) ; // never throws. worst case, user has to visit the Account view to refresh subscription info
2025-10-02 11:19:56 +02:00
2025-12-05 16:12:59 +01:00
safe ( platform . onActivated ( { skipDnsSetup : options . skipDnsSetup } ) , { debug } ) ; // background
await backupSites . storageApi ( backupSite ) . teardown ( backupSite . config ) ;
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 ) {
2025-08-05 14:13:39 +02:00
assert . strictEqual ( typeof backupConfig , 'object' ) ; // { format, config, provider, encryptionPassword }
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' ) ;
2025-10-06 19:50:58 +02:00
assert . strictEqual ( typeof options , 'object' ) ; // { skipDnsSetup, siteId }
2021-08-27 09:52:24 -07:00
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
2025-09-12 09:48:37 +02:00
const backupSite = await backupSites . createPseudo ( {
2025-08-01 23:20:51 +02:00
id : ` cloudron-restore ` ,
provider : backupConfig . provider ,
config : backupConfig . config ,
format : backupConfig . format ,
2025-08-05 14:13:39 +02:00
encryptionPassword : backupConfig . encryptionPassword ? ? null ,
encryptedFilenames : ! ! backupConfig . encryptedFilenames
2025-08-01 23:20:51 +02:00
} ) ;
2024-04-09 13:20:01 +02:00
2025-12-02 15:19:59 +01:00
const ipv4Error = await network . testIPv4Config ( ipv4Config ) ;
if ( ipv4Error ) throw ipv4Error ;
const ipv6Error = await network . testIPv6Config ( ipv6Config ) ;
if ( ipv6Error ) throw ipv6Error ;
2021-08-27 09:52:24 -07:00
2025-09-12 09:48:37 +02:00
safe ( restoreTask ( backupSite , 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
}