'use strict'; exports = module.exports = { setup: setup, restore: restore, activate: activate, getStatus: getStatus }; var assert = require('assert'), async = require('async'), backups = require('./backups.js'), BoxError = require('./boxerror.js'), branding = require('./branding.js'), constants = require('./constants.js'), cloudron = require('./cloudron.js'), debug = require('debug')('box:provision'), domains = require('./domains.js'), eventlog = require('./eventlog.js'), mail = require('./mail.js'), semver = require('semver'), settings = require('./settings.js'), sysinfo = require('./sysinfo.js'), users = require('./users.js'), tld = require('tldjs'), tokens = require('./tokens.js'), _ = require('underscore'); const NOOP_CALLBACK = function (error) { if (error) debug(error); }; // we cannot use tasks since the tasks table gets overwritten when db is imported let gProvisionStatus = { setup: { active: false, message: '', errorMessage: null }, restore: { active: false, message: '', errorMessage: null } }; function setProgress(task, message, callback) { debug(`setProgress: ${task} - ${message}`); gProvisionStatus[task].message = message; callback(); } function unprovision(callback) { assert.strictEqual(typeof callback, 'function'); debug('unprovision'); // TODO: also cancel any existing configureWebadmin task async.series([ settings.setAdminLocation.bind(null, '', ''), mail.clearDomains, domains.clear ], callback); } function setup(dnsConfig, sysinfoConfig, auditSource, callback) { assert.strictEqual(typeof dnsConfig, 'object'); assert.strictEqual(typeof sysinfoConfig, 'object'); assert.strictEqual(typeof auditSource, 'object'); assert.strictEqual(typeof callback, 'function'); if (gProvisionStatus.setup.active || gProvisionStatus.restore.active) return callback(new BoxError(BoxError.BAD_STATE, 'Already setting up or restoring')); gProvisionStatus.setup = { active: true, errorMessage: '', message: 'Adding domain' }; function done(error) { gProvisionStatus.setup.active = false; gProvisionStatus.setup.errorMessage = error ? error.message : ''; callback(error); } users.isActivated(function (error, activated) { if (error) return done(error); if (activated) return done(new BoxError(BoxError.CONFLICT, 'Already activated', { activate: true })); unprovision(function (error) { if (error) return done(error); const domain = dnsConfig.domain.toLowerCase(); const zoneName = dnsConfig.zoneName ? dnsConfig.zoneName : (tld.getDomain(domain) || domain); debug(`provision: Setting up Cloudron with domain ${domain} and zone ${zoneName}`); let data = { zoneName: zoneName, provider: dnsConfig.provider, config: dnsConfig.config, fallbackCertificate: dnsConfig.fallbackCertificate || null, tlsConfig: dnsConfig.tlsConfig || { provider: 'letsencrypt-prod' }, dkimSelector: 'cloudron' }; async.series([ settings.setMailLocation.bind(null, domain, `${constants.ADMIN_LOCATION}.${domain}`), // default mail location. do this before we add the domain for upserting mail DNS domains.add.bind(null, domain, data, auditSource), sysinfo.testConfig.bind(null, sysinfoConfig) ], function (error) { if (error) return done(error); callback(); // now that args are validated run the task in the background async.series([ settings.setSysinfoConfig.bind(null, sysinfoConfig), cloudron.setupDnsAndCert.bind(null, constants.ADMIN_LOCATION, domain, auditSource, (progress) => setProgress('setup', progress.message, NOOP_CALLBACK)), cloudron.setDashboardDomain.bind(null, domain, auditSource), setProgress.bind(null, 'setup', 'Done'), eventlog.add.bind(null, eventlog.ACTION_PROVISION, auditSource, { }) ], function (error) { gProvisionStatus.setup.active = false; gProvisionStatus.setup.errorMessage = error ? error.message : ''; }); }); }); }); } 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); users.createOwner(username, password, email, displayName, auditSource, function (error, userObject) { if (error && error.reason === BoxError.ALREADY_EXISTS) return callback(new BoxError(BoxError.CONFLICT, 'Already activated')); if (error) return callback(error); tokens.add(tokens.ID_WEBADMIN, userObject.id, Date.now() + constants.DEFAULT_TOKEN_EXPIRATION, {}, function (error, result) { if (error) return callback(error); eventlog.add(eventlog.ACTION_ACTIVATE, auditSource, { }); callback(null, { userId: userObject.id, token: result.accessToken, expires: result.expires }); setImmediate(cloudron.onActivated.bind(null, NOOP_CALLBACK)); // hack for now to not block the above http response }); }); } function restore(backupConfig, backupId, version, sysinfoConfig, auditSource, callback) { assert.strictEqual(typeof backupConfig, 'object'); assert.strictEqual(typeof backupId, 'string'); assert.strictEqual(typeof version, 'string'); assert.strictEqual(typeof sysinfoConfig, 'object'); assert.strictEqual(typeof auditSource, 'object'); assert.strictEqual(typeof callback, 'function'); if (!semver.valid(version)) return callback(new BoxError(BoxError.BAD_FIELD, 'version is not a valid semver', { field: 'version' })); if (constants.VERSION !== version) return callback(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) return callback(new BoxError(BoxError.BAD_STATE, 'Already setting up or restoring')); gProvisionStatus.restore = { active: true, errorMessage: '', message: 'Testing backup config' }; function done(error) { gProvisionStatus.restore.active = false; gProvisionStatus.restore.errorMessage = error ? error.message : ''; callback(error); } users.isActivated(function (error, activated) { if (error) return done(error); if (activated) return done(new BoxError(BoxError.CONFLICT, 'Already activated. Restore with a fresh Cloudron installation.')); backups.testProviderConfig(backupConfig, function (error) { if (error) return done(error); if ('password' in backupConfig) { backupConfig.encryption = backups.generateEncryptionKeysSync(backupConfig.password); delete backupConfig.password; } else { backupConfig.encryption = null; } sysinfo.testConfig(sysinfoConfig, function (error) { if (error) return done(error); debug(`restore: restoring from ${backupId} from provider ${backupConfig.provider} with format ${backupConfig.format}`); callback(); // now that the fields are validated, continue task in the background async.series([ setProgress.bind(null, 'restore', 'Downloading backup'), backups.restore.bind(null, backupConfig, backupId, (progress) => setProgress('restore', progress.message, NOOP_CALLBACK)), settings.setSysinfoConfig.bind(null, sysinfoConfig), (done) => { const adminDomain = settings.adminDomain(); // load this fresh from after the backup.restore async.series([ cloudron.setupDnsAndCert.bind(null, constants.ADMIN_LOCATION, adminDomain, auditSource, (progress) => setProgress('restore', progress.message, NOOP_CALLBACK)), cloudron.setDashboardDomain.bind(null, adminDomain, auditSource) ], done); }, settings.setBackupCredentials.bind(null, backupConfig), // update just the credentials and not the policy and flags eventlog.add.bind(null, eventlog.ACTION_RESTORE, auditSource, { backupId }), ], function (error) { gProvisionStatus.restore.active = false; gProvisionStatus.restore.errorMessage = error ? error.message : ''; if (!error) cloudron.onActivated(NOOP_CALLBACK); }); }); }); }); } function getStatus(callback) { assert.strictEqual(typeof callback, 'function'); users.isActivated(function (error, activated) { if (error) return callback(error); settings.getAll(function (error, allSettings) { if (error) return callback(error); callback(null, _.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.adminDomain() ? settings.adminFqdn() : null, language: allSettings[settings.LANGUAGE_KEY], activated: activated, provider: settings.provider() // used by setup wizard of marketplace images }, gProvisionStatus)); }); }); }