'use strict'; exports = module.exports = { getAutoupdatePattern, setAutoupdatePattern, getTimeZone, setTimeZone, getCloudronName, setCloudronName, getCloudronAvatar, setCloudronAvatar, getDynamicDnsConfig, setDynamicDnsConfig, getUnstableAppsConfig, setUnstableAppsConfig, getBackupConfig, setBackupConfig, setBackupCredentials, getServicesConfig, setServicesConfig, getExternalLdapConfig, setExternalLdapConfig, getRegistryConfig, setRegistryConfig, getLicenseKey, setLicenseKey, getLanguage, setLanguage, getCloudronId, setCloudronId, getCloudronToken, setCloudronToken, getSysinfoConfig, setSysinfoConfig, getFooter, setFooter, getDirectoryConfig, setDirectoryConfig, getAppstoreListingConfig, setAppstoreListingConfig, getFirewallBlocklist, setFirewallBlocklist, getSupportConfig, provider, list, initCache, // these values come from the cache apiServerOrigin, webServerOrigin, dashboardDomain, setDashboardLocation, setMailLocation, mailFqdn, mailDomain, dashboardOrigin, dashboardFqdn, isDemo, // booleans. if you add an entry here, be sure to fix getAll DYNAMIC_DNS_KEY: 'dynamic_dns', UNSTABLE_APPS_KEY: 'unstable_apps', DEMO_KEY: 'demo', // json. if you add an entry here, be sure to fix getAll BACKUP_CONFIG_KEY: 'backup_config', SERVICES_CONFIG_KEY: 'services_config', EXTERNAL_LDAP_KEY: 'external_ldap_config', REGISTRY_CONFIG_KEY: 'registry_config', SYSINFO_CONFIG_KEY: 'sysinfo_config', APPSTORE_LISTING_CONFIG_KEY: 'appstore_listing_config', SUPPORT_CONFIG_KEY: 'support_config', DIRECTORY_CONFIG_KEY: 'directory_config', // strings AUTOUPDATE_PATTERN_KEY: 'autoupdate_pattern', TIME_ZONE_KEY: 'time_zone', CLOUDRON_NAME_KEY: 'cloudron_name', LICENSE_KEY: 'license_key', LANGUAGE_KEY: 'language', CLOUDRON_ID_KEY: 'cloudron_id', CLOUDRON_TOKEN_KEY: 'cloudron_token', // apstore token FIREWALL_BLOCKLIST_KEY: 'firewall_blocklist', API_SERVER_ORIGIN_KEY: 'api_server_origin', WEB_SERVER_ORIGIN_KEY: 'web_server_origin', DASHBOARD_DOMAIN_KEY: 'admin_domain', DASHBOARD_FQDN_KEY: 'admin_fqdn', MAIL_DOMAIN_KEY: 'mail_domain', MAIL_FQDN_KEY: 'mail_fqdn', PROVIDER_KEY: 'provider', FOOTER_KEY: 'footer', // blobs CLOUDRON_AVATAR_KEY: 'cloudron_avatar', // testing _setApiServerOrigin: setApiServerOrigin, _clear: clear }; const assert = require('assert'), backups = require('./backups.js'), BoxError = require('./boxerror.js'), constants = require('./constants.js'), cron = require('./cron.js'), CronJob = require('cron').CronJob, database = require('./database.js'), debug = require('debug')('box:settings'), docker = require('./docker.js'), externalLdap = require('./externalldap.js'), moment = require('moment-timezone'), mounts = require('./mounts.js'), paths = require('./paths.js'), safe = require('safetydance'), settingsdb = require('./settingsdb.js'), sysinfo = require('./sysinfo.js'), translation = require('./translation.js'), _ = require('underscore'); const SETTINGS_FIELDS = [ 'name', 'value' ].join(','); const SETTINGS_BLOB_FIELDS = [ 'name', 'valueBlob' ].join(','); let gDefaults = (function () { var result = { }; result[exports.AUTOUPDATE_PATTERN_KEY] = cron.DEFAULT_AUTOUPDATE_PATTERN; result[exports.TIME_ZONE_KEY] = 'America/Los_Angeles'; result[exports.CLOUDRON_NAME_KEY] = 'Cloudron'; result[exports.DYNAMIC_DNS_KEY] = false; result[exports.UNSTABLE_APPS_KEY] = true; result[exports.LICENSE_KEY] = ''; result[exports.LANGUAGE_KEY] = 'en'; result[exports.CLOUDRON_ID_KEY] = ''; result[exports.CLOUDRON_TOKEN_KEY] = ''; result[exports.BACKUP_CONFIG_KEY] = { provider: 'filesystem', backupFolder: '/var/backups', format: 'tgz', encryption: null, retentionPolicy: { keepWithinSecs: 2 * 24 * 60 * 60 }, // 2 days schedulePattern: '00 00 23 * * *' // every day at 11pm }; result[exports.SERVICES_CONFIG_KEY] = {}; result[exports.EXTERNAL_LDAP_KEY] = { provider: 'noop', autoCreate: false }; result[exports.REGISTRY_CONFIG_KEY] = { provider: 'noop' }; result[exports.SYSINFO_CONFIG_KEY] = { provider: 'generic' }; result[exports.DIRECTORY_CONFIG_KEY] = { lockUserProfiles: false, mandatory2FA: false }; result[exports.DASHBOARD_DOMAIN_KEY] = ''; result[exports.DASHBOARD_FQDN_KEY] = ''; result[exports.MAIL_DOMAIN_KEY] = ''; result[exports.MAIL_FQDN_KEY] = ''; result[exports.FIREWALL_BLOCKLIST_KEY] = ''; result[exports.API_SERVER_ORIGIN_KEY] = 'https://api.cloudron.io'; result[exports.WEB_SERVER_ORIGIN_KEY] = 'https://cloudron.io'; result[exports.DEMO_KEY] = false; result[exports.APPSTORE_LISTING_CONFIG_KEY] = { blacklist: [], whitelist: null // null imples nothing is whitelisted. this is an array }; result[exports.SUPPORT_CONFIG_KEY] = { email: 'support@cloudron.io', remoteSupport: true, ticketFormBody: 'Use this form to open support tickets. You can also write directly to [support@cloudron.io](mailto:support@cloudron.io).\n\n' + '* [Knowledge Base & App Docs](https://docs.cloudron.io/apps/?support_view)\n' + '* [Custom App Packaging & API](https://docs.cloudron.io/custom-apps/tutorial/?support_view)\n' + '* [Forum](https://forum.cloudron.io/)\n\n', submitTickets: true }; result[exports.FOOTER_KEY] = ''; return result; })(); let gCache = {}; function notifyChange(key, value) { assert.strictEqual(typeof key, 'string'); // value is a variant cron.handleSettingsChanged(key, value); } async function get(key) { assert.strictEqual(typeof key, 'string'); const result = await database.query(`SELECT ${SETTINGS_FIELDS} FROM settings WHERE name = ?`, [ key ]); if (result.length === 0) return null; // can't return the default value here because we might need to massage/json parse the result return result[0].value; } async function set(key, value) { assert.strictEqual(typeof key, 'string'); assert(value === null || typeof value === 'string'); await database.query('INSERT INTO settings (name, value) VALUES (?, ?) ON DUPLICATE KEY UPDATE value=VALUES(value)', [ key, value ]); // don't rely on affectedRows here since it gives 2 } async function getBlob(key) { assert.strictEqual(typeof key, 'string'); const result = await database.query(`SELECT ${SETTINGS_BLOB_FIELDS} FROM settings WHERE name = ?`, [ key ]); if (result.length === 0) return null; return result[0].valueBlob; } async function setBlob(key, value) { assert.strictEqual(typeof key, 'string'); assert(value === null || Buffer.isBuffer(value)); await database.query('INSERT INTO settings (name, valueBlob) VALUES (?, ?) ON DUPLICATE KEY UPDATE valueBlob=VALUES(valueBlob)', [ key, value ]); // don't rely on affectedRows here since it gives 2 } async function clear() { await database.query('DELETE FROM settings'); } function setAutoupdatePattern(pattern, callback) { assert.strictEqual(typeof pattern, 'string'); assert.strictEqual(typeof callback, 'function'); if (pattern !== constants.AUTOUPDATE_PATTERN_NEVER) { // check if pattern is valid var job = safe.safeCall(function () { return new CronJob(pattern); }); if (!job) return callback(new BoxError(BoxError.BAD_FIELD, 'Invalid pattern', { field: 'pattern' })); } settingsdb.set(exports.AUTOUPDATE_PATTERN_KEY, pattern, function (error) { if (error) return callback(error); notifyChange(exports.AUTOUPDATE_PATTERN_KEY, pattern); return callback(null); }); } function getAutoupdatePattern(callback) { assert.strictEqual(typeof callback, 'function'); settingsdb.get(exports.AUTOUPDATE_PATTERN_KEY, function (error, pattern) { if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.AUTOUPDATE_PATTERN_KEY]); if (error) return callback(error); callback(null, pattern); }); } function setTimeZone(tz, callback) { assert.strictEqual(typeof tz, 'string'); assert.strictEqual(typeof callback, 'function'); if (moment.tz.names().indexOf(tz) === -1) return callback(new BoxError(BoxError.BAD_FIELD, 'Bad timeZone', { field: 'timezone' })); settingsdb.set(exports.TIME_ZONE_KEY, tz, function (error) { if (error) return callback(error); notifyChange(exports.TIME_ZONE_KEY, tz); return callback(null); }); } function getTimeZone(callback) { assert.strictEqual(typeof callback, 'function'); settingsdb.get(exports.TIME_ZONE_KEY, function (error, tz) { if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.TIME_ZONE_KEY]); if (error) return callback(error); callback(null, tz); }); } function getCloudronName(callback) { assert.strictEqual(typeof callback, 'function'); settingsdb.get(exports.CLOUDRON_NAME_KEY, function (error, name) { if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.CLOUDRON_NAME_KEY]); if (error) return callback(error); callback(null, name); }); } function setCloudronName(name, callback) { assert.strictEqual(typeof name, 'string'); assert.strictEqual(typeof callback, 'function'); if (!name) return callback(new BoxError(BoxError.BAD_FIELD, 'name is empty', { field: 'name' })); // some arbitrary restrictions (for sake of ui layout) // if this is changed, adjust dashboard/branding.html if (name.length > 64) return callback(new BoxError(BoxError.BAD_FIELD, 'name cannot exceed 64 characters', { field: 'name' })); settingsdb.set(exports.CLOUDRON_NAME_KEY, name, function (error) { if (error) return callback(error); notifyChange(exports.CLOUDRON_NAME_KEY, name); return callback(null); }); } async function getCloudronAvatar() { let avatar = await getBlob(exports.CLOUDRON_AVATAR_KEY); if (avatar) return avatar; // try default fallback avatar = safe.fs.readFileSync(paths.CLOUDRON_DEFAULT_AVATAR_FILE); if (avatar) return avatar; throw new BoxError(BoxError.FS_ERROR, `Could not read avatar: ${safe.error.message}`); } async function setCloudronAvatar(avatar) { assert(Buffer.isBuffer(avatar)); await setBlob(exports.CLOUDRON_AVATAR_KEY, avatar); } function getDynamicDnsConfig(callback) { assert.strictEqual(typeof callback, 'function'); settingsdb.get(exports.DYNAMIC_DNS_KEY, function (error, enabled) { if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.DYNAMIC_DNS_KEY]); if (error) return callback(error); callback(null, !!enabled); // settingsdb holds string values only }); } function setDynamicDnsConfig(enabled, callback) { assert.strictEqual(typeof enabled, 'boolean'); assert.strictEqual(typeof callback, 'function'); // settingsdb takes string values only settingsdb.set(exports.DYNAMIC_DNS_KEY, enabled ? 'enabled' : '', function (error) { if (error) return callback(error); notifyChange(exports.DYNAMIC_DNS_KEY, enabled); return callback(null); }); } async function getUnstableAppsConfig() { const result = await get(exports.UNSTABLE_APPS_KEY); if (result === null) gDefaults[exports.UNSTABLE_APPS_KEY]; return !!result; // db holds string values only } async function setUnstableAppsConfig(enabled) { assert.strictEqual(typeof enabled, 'boolean'); await set(exports.UNSTABLE_APPS_KEY, enabled ? 'enabled' : ''); // db holds string values only notifyChange(exports.UNSTABLE_APPS_KEY, enabled); } function getBackupConfig(callback) { assert.strictEqual(typeof callback, 'function'); settingsdb.get(exports.BACKUP_CONFIG_KEY, async function (error, value) { if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.BACKUP_CONFIG_KEY]); if (error) return callback(error); const backupConfig = JSON.parse(value); // provider, token, password, region, prefix, bucket if (backupConfig.provider === 'sshfs' || backupConfig.provider === 'cifs' || backupConfig.provider === 'nfs' || backupConfig.provider === 'ext4' || backupConfig.provider === 'mountpoint') { backupConfig.mountStatus = await mounts.getStatus(backupConfig.provider, backupConfig.mountPoint); // { state, message } } callback(null, backupConfig); }); } function mountOptionsChanged(currentConfig, backupConfig) { return currentConfig.provider !== backupConfig.provider || currentConfig.mountPoint !== backupConfig.mountPoint || !_.isEqual(currentConfig.mountOptions, backupConfig.mountOptions); } function mountObject(backupConfig) { return { name: 'backup', hostPath: backupConfig.mountPoint, mountType: backupConfig.provider, mountOptions: backupConfig.mountOptions }; } function setBackupConfig(backupConfig, callback) { assert.strictEqual(typeof backupConfig, 'object'); assert.strictEqual(typeof callback, 'function'); getBackupConfig(async function (error, oldConfig) { if (error) return callback(error); backups.injectPrivateFields(backupConfig, oldConfig); if (mounts.isMountProvider(backupConfig.provider) && (!mounts.isMountProvider(oldConfig.provider) || mountOptionsChanged(oldConfig, backupConfig))) { error = mounts.validateMountOptions(backupConfig.provider, backupConfig.mountOptions); if (error) return callback(error); [error] = await safe(mounts.tryAddMount(mountObject(backupConfig), { timeout: 10 })); // 10 seconds if (error) { if (mounts.isMountProvider(oldConfig.provider)) { // put back the old mount configuration debug('setBackupConfig: rolling back to previous mount configuration'); await safe(mounts.tryAddMount(mountObject(oldConfig), { timeout: 10 })); } return callback(error); } } backups.testConfig(backupConfig, async function (error) { if (error) return callback(error); if ('password' in backupConfig) { // user set password backupConfig.encryption = backups.generateEncryptionKeysSync(backupConfig.password); delete backupConfig.password; } // if any of these changes, we have to clear the cache if ([ 'format', 'provider', 'prefix', 'bucket', 'region', 'endpoint', 'backupFolder', 'mountPoint', 'encryption' ].some(p => backupConfig[p] !== oldConfig[p])) { debug('setBackupConfig: clearing backup cache'); backups.cleanupCacheFilesSync(); } settingsdb.set(exports.BACKUP_CONFIG_KEY, JSON.stringify(backupConfig), async function (error) { if (error) return callback(error); if (mounts.isMountProvider(oldConfig.provider) && !mounts.isMountProvider(backupConfig.provider)) { debug('setBackupConfig: removing old backup mount point'); await safe(mounts.removeMount(mountObject(oldConfig))); } notifyChange(exports.BACKUP_CONFIG_KEY, backupConfig); backups.configureCollectd(backupConfig, callback); }); }); }); } function setBackupCredentials(credentials, callback) { assert.strictEqual(typeof credentials, 'object'); assert.strictEqual(typeof callback, 'function'); getBackupConfig(function (error, currentConfig) { if (error) return callback(error); // preserve these fields const extra = _.pick(currentConfig, 'retentionPolicy', 'schedulePattern', 'copyConcurrency', 'syncConcurrency', 'memoryLimit', 'downloadConcurrency', 'deleteConcurrency', 'uploadPartSize'); const backupConfig = _.extend({}, credentials, extra); backups.cleanupCacheFilesSync(); settingsdb.set(exports.BACKUP_CONFIG_KEY, JSON.stringify(backupConfig), function (error) { if (error) return callback(error); notifyChange(exports.BACKUP_CONFIG_KEY, backupConfig); backups.configureCollectd(backupConfig, callback); }); }); } function getServicesConfig(callback) { assert.strictEqual(typeof callback, 'function'); settingsdb.get(exports.SERVICES_CONFIG_KEY, function (error, value) { if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.SERVICES_CONFIG_KEY]); if (error) return callback(error); callback(null, JSON.parse(value)); }); } function setServicesConfig(platformConfig, callback) { assert.strictEqual(typeof callback, 'function'); settingsdb.set(exports.SERVICES_CONFIG_KEY, JSON.stringify(platformConfig), function (error) { if (error) return callback(error); notifyChange(exports.SERVICES_CONFIG_KEY, platformConfig); callback(null); }); } function getExternalLdapConfig(callback) { assert.strictEqual(typeof callback, 'function'); settingsdb.get(exports.EXTERNAL_LDAP_KEY, function (error, value) { if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.EXTERNAL_LDAP_KEY]); if (error) return callback(error); let config = JSON.parse(value); // ensure new keys if (!config.autoCreate) config.autoCreate = false; callback(null, config); }); } function setExternalLdapConfig(externalLdapConfig, callback) { assert.strictEqual(typeof externalLdapConfig, 'object'); assert.strictEqual(typeof callback, 'function'); if (isDemo()) return callback(new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode')); getExternalLdapConfig(function (error, currentConfig) { if (error) return callback(error); externalLdap.injectPrivateFields(externalLdapConfig, currentConfig); externalLdap.testConfig(externalLdapConfig, function (error) { if (error) return callback(error); settingsdb.set(exports.EXTERNAL_LDAP_KEY, JSON.stringify(externalLdapConfig), function (error) { if (error) return callback(error); notifyChange(exports.EXTERNAL_LDAP_KEY, externalLdapConfig); callback(null); }); }); }); } function getRegistryConfig(callback) { assert.strictEqual(typeof callback, 'function'); settingsdb.get(exports.REGISTRY_CONFIG_KEY, function (error, value) { if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.REGISTRY_CONFIG_KEY]); if (error) return callback(error); callback(null, JSON.parse(value)); }); } function setRegistryConfig(registryConfig, callback) { assert.strictEqual(typeof registryConfig, 'object'); assert.strictEqual(typeof callback, 'function'); getRegistryConfig(function (error, curentConfig) { if (error) return callback(error); docker.injectPrivateFields(registryConfig, curentConfig); docker.testRegistryConfig(registryConfig, function (error) { if (error) return callback(error); settingsdb.set(exports.REGISTRY_CONFIG_KEY, JSON.stringify(registryConfig), function (error) { if (error) return callback(error); notifyChange(exports.REGISTRY_CONFIG_KEY, registryConfig); callback(null); }); }); }); } function getSysinfoConfig(callback) { assert.strictEqual(typeof callback, 'function'); settingsdb.get(exports.SYSINFO_CONFIG_KEY, function (error, value) { if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.SYSINFO_CONFIG_KEY]); if (error) return callback(error); callback(null, JSON.parse(value)); }); } function setSysinfoConfig(sysinfoConfig, callback) { assert.strictEqual(typeof sysinfoConfig, 'object'); assert.strictEqual(typeof callback, 'function'); if (isDemo()) return callback(new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode')); sysinfo.testConfig(sysinfoConfig, function (error) { if (error) return callback(error); settingsdb.set(exports.SYSINFO_CONFIG_KEY, JSON.stringify(sysinfoConfig), function (error) { if (error) return callback(error); notifyChange(exports.SYSINFO_CONFIG_KEY, sysinfoConfig); callback(null); }); }); } async function getDirectoryConfig() { const value = await get(exports.DIRECTORY_CONFIG_KEY); if (value === null) return gDefaults[exports.DIRECTORY_CONFIG_KEY]; return JSON.parse(value); } async function setDirectoryConfig(directoryConfig) { assert.strictEqual(typeof directoryConfig, 'object'); if (isDemo()) throw new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'); await set(exports.DIRECTORY_CONFIG_KEY, JSON.stringify(directoryConfig)); notifyChange(exports.DIRECTORY_CONFIG_KEY, directoryConfig); } function getAppstoreListingConfig(callback) { assert.strictEqual(typeof callback, 'function'); settingsdb.get(exports.APPSTORE_LISTING_CONFIG_KEY, function (error, value) { if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.APPSTORE_LISTING_CONFIG_KEY]); if (error) return callback(error); callback(null, JSON.parse(value)); }); } function setAppstoreListingConfig(listingConfig, callback) { assert.strictEqual(typeof listingConfig, 'object'); assert.strictEqual(typeof callback, 'function'); settingsdb.set(exports.APPSTORE_LISTING_CONFIG_KEY, JSON.stringify(listingConfig), function (error) { if (error) return callback(error); notifyChange(exports.APPSTORE_LISTING_CONFIG_KEY, listingConfig); callback(null); }); } async function getFirewallBlocklist() { const value = await getBlob(exports.FIREWALL_BLOCKLIST_KEY); if (value === null) return gDefaults[exports.FIREWALL_BLOCKLIST_KEY]; return value.toString('utf8'); } async function setFirewallBlocklist(blocklist) { assert.strictEqual(typeof blocklist, 'string'); // store in blob since the value field is TEXT and has 16kb size limit await setBlob(exports.FIREWALL_BLOCKLIST_KEY, Buffer.from(blocklist)); } function getSupportConfig(callback) { assert.strictEqual(typeof callback, 'function'); settingsdb.get(exports.SUPPORT_CONFIG_KEY, function (error, value) { if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.SUPPORT_CONFIG_KEY]); if (error) return callback(error); callback(null, JSON.parse(value)); }); } function getLicenseKey(callback) { assert.strictEqual(typeof callback, 'function'); settingsdb.get(exports.LICENSE_KEY, function (error, value) { if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.LICENSE_KEY]); if (error) return callback(error); callback(null, value); }); } function setLicenseKey(licenseKey, callback) { assert.strictEqual(typeof licenseKey, 'string'); assert.strictEqual(typeof callback, 'function'); settingsdb.set(exports.LICENSE_KEY, licenseKey, function (error) { if (error) return callback(error); notifyChange(exports.LICENSE_KEY, licenseKey); callback(null); }); } function getLanguage(callback) { assert.strictEqual(typeof callback, 'function'); settingsdb.get(exports.LANGUAGE_KEY, function (error, value) { if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.LANGUAGE_KEY]); if (error) return callback(error); callback(null, value); }); } function setLanguage(language, callback) { assert.strictEqual(typeof language, 'string'); assert.strictEqual(typeof callback, 'function'); translation.getLanguages(function (error, languages) { if (error) return callback(error); if (languages.indexOf(language) === -1) return callback(new BoxError(BoxError.NOT_FOUND)); settingsdb.set(exports.LANGUAGE_KEY, language, function (error) { if (error) return callback(error); notifyChange(exports.LANGUAGE_KEY, language); callback(null); }); }); } function getCloudronId(callback) { assert.strictEqual(typeof callback, 'function'); settingsdb.get(exports.CLOUDRON_ID_KEY, function (error, value) { if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.CLOUDRON_ID_KEY]); if (error) return callback(error); callback(null, value); }); } function setCloudronId(cid, callback) { assert.strictEqual(typeof cid, 'string'); assert.strictEqual(typeof callback, 'function'); settingsdb.set(exports.CLOUDRON_ID_KEY, cid, function (error) { if (error) return callback(error); notifyChange(exports.CLOUDRON_ID_KEY, cid); callback(null); }); } function getCloudronToken(callback) { assert.strictEqual(typeof callback, 'function'); settingsdb.get(exports.CLOUDRON_TOKEN_KEY, function (error, value) { if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.CLOUDRON_TOKEN_KEY]); if (error) return callback(error); callback(null, value); }); } function setCloudronToken(token, callback) { assert.strictEqual(typeof token, 'string'); assert.strictEqual(typeof callback, 'function'); settingsdb.set(exports.CLOUDRON_TOKEN_KEY, token, function (error) { if (error) return callback(error); notifyChange(exports.CLOUDRON_TOKEN_KEY, token); callback(null); }); } async function list() { const settings = await database.query(`SELECT ${SETTINGS_FIELDS} FROM settings WHERE value IS NOT NULL ORDER BY name`); const result = _.extend({ }, gDefaults); settings.forEach(function (setting) { result[setting.name] = setting.value; }); // convert booleans result[exports.DYNAMIC_DNS_KEY] = !!result[exports.DYNAMIC_DNS_KEY]; result[exports.UNSTABLE_APPS_KEY] = !!result[exports.UNSTABLE_APPS_KEY]; result[exports.DEMO_KEY] = !!result[exports.DEMO_KEY]; // convert JSON objects [exports.BACKUP_CONFIG_KEY, exports.DIRECTORY_CONFIG_KEY, exports.SERVICES_CONFIG_KEY, exports.EXTERNAL_LDAP_KEY, exports.REGISTRY_CONFIG_KEY, exports.SYSINFO_CONFIG_KEY ].forEach(function (key) { result[key] = typeof result[key] === 'object' ? result[key] : safe.JSON.parse(result[key]); }); return result; } async function initCache() { debug('initCache: pre-load settings'); const allSettings = await list(); const provider = safe.fs.readFileSync(paths.PROVIDER_FILE, 'utf8'); gCache = { apiServerOrigin: allSettings[exports.API_SERVER_ORIGIN_KEY], webServerOrigin: allSettings[exports.WEB_SERVER_ORIGIN_KEY], dashboardDomain: allSettings[exports.DASHBOARD_DOMAIN_KEY], dashboardFqdn: allSettings[exports.DASHBOARD_FQDN_KEY], mailDomain: allSettings[exports.MAIL_DOMAIN_KEY], mailFqdn: allSettings[exports.MAIL_FQDN_KEY], isDemo: allSettings[exports.DEMO_KEY], provider: provider ? provider.trim() : 'generic' }; } // this is together so we can do this in a transaction later function setDashboardLocation(dashboardDomain, dashboardFqdn, callback) { assert.strictEqual(typeof dashboardDomain, 'string'); assert.strictEqual(typeof dashboardFqdn, 'string'); assert.strictEqual(typeof callback, 'function'); settingsdb.set(exports.DASHBOARD_DOMAIN_KEY, dashboardDomain, function (error) { if (error) return callback(error); settingsdb.set(exports.DASHBOARD_FQDN_KEY, dashboardFqdn, function (error) { if (error) return callback(error); gCache.dashboardDomain = dashboardDomain; gCache.dashboardFqdn = dashboardFqdn; callback(null); }); }); } function setMailLocation(mailDomain, mailFqdn, callback) { assert.strictEqual(typeof mailDomain, 'string'); assert.strictEqual(typeof mailFqdn, 'string'); assert.strictEqual(typeof callback, 'function'); settingsdb.set(exports.MAIL_DOMAIN_KEY, mailDomain, function (error) { if (error) return callback(error); settingsdb.set(exports.MAIL_FQDN_KEY, mailFqdn, function (error) { if (error) return callback(error); gCache.mailDomain = mailDomain; gCache.mailFqdn = mailFqdn; callback(null); }); }); } function setApiServerOrigin(origin, callback) { assert.strictEqual(typeof origin, 'string'); assert.strictEqual(typeof callback, 'function'); settingsdb.set(exports.API_SERVER_ORIGIN_KEY, origin, function (error) { if (error) return callback(error); gCache.apiServerOrigin = origin; notifyChange(exports.API_SERVER_ORIGIN_KEY, origin); callback(null); }); } function getFooter(callback) { assert.strictEqual(typeof callback, 'function'); settingsdb.get(exports.FOOTER_KEY, function (error, value) { if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.FOOTER_KEY]); if (error) return callback(error); callback(null, value); }); } function setFooter(footer, callback) { assert.strictEqual(typeof footer, 'string'); assert.strictEqual(typeof callback, 'function'); settingsdb.set(exports.FOOTER_KEY, footer, function (error) { if (error) return callback(error); notifyChange(exports.FOOTER_KEY, footer); callback(null); }); } function provider() { return gCache.provider; } function apiServerOrigin() { return gCache.apiServerOrigin; } function webServerOrigin() { return gCache.webServerOrigin; } function dashboardDomain() { return gCache.dashboardDomain; } function dashboardFqdn() { return gCache.dashboardFqdn; } function isDemo() { return gCache.isDemo; } function mailDomain() { return gCache.mailDomain; } function mailFqdn() { return gCache.mailFqdn; } function dashboardOrigin() { return 'https://' + dashboardFqdn(); }