'use strict'; exports = module.exports = { getAutoupdatePattern, setAutoupdatePattern, getTimeZone, setTimeZone, getCloudronName, setCloudronName, getCloudronAvatar, setCloudronAvatar, getDynamicDnsConfig, setDynamicDnsConfig, getIPv6Config, setIPv6Config, getReverseProxyConfig, // no setter yet since we have no UI for this getUnstableAppsConfig, setUnstableAppsConfig, getBackupPolicy, setBackupPolicy, getBackupConfig, setBackupConfig, getServicesConfig, setServicesConfig, getExternalLdapConfig, setExternalLdapConfig, getDirectoryServerConfig, setDirectoryServerConfig, getRegistryConfig, setRegistryConfig, getLanguage, setLanguage, getCloudronId, setCloudronId, getAppstoreApiToken, setAppstoreApiToken, getAppstoreWebToken, setAppstoreWebToken, getSysinfoConfig, setSysinfoConfig, getFooter, setFooter, getProfileConfig, setProfileConfig, getAppstoreListingConfig, setAppstoreListingConfig, getFirewallBlocklist, setFirewallBlocklist, getTrustedIps, setTrustedIps, getGhosts, setGhosts, getSupportConfig, provider, list, initCache, // these values come from the cache apiServerOrigin, webServerOrigin, consoleServerOrigin, dashboardDomain, setDashboardLocation, setMailLocation, mailFqdn, mailDomain, dashboardOrigin, dashboardFqdn, isDemo, // booleans. if you add an entry here, be sure to fix list() DYNAMIC_DNS_KEY: 'dynamic_dns', UNSTABLE_APPS_KEY: 'unstable_apps', DEMO_KEY: 'demo', // json. if you add an entry here, be sure to fix list() BACKUP_CONFIG_KEY: 'backup_config', BACKUP_POLICY_KEY: 'backup_policy', SERVICES_CONFIG_KEY: 'services_config', EXTERNAL_LDAP_KEY: 'external_ldap_config', DIRECTORY_SERVER_KEY: 'user_directory_config', REGISTRY_CONFIG_KEY: 'registry_config', SYSINFO_CONFIG_KEY: 'sysinfo_config', // misnomer: ipv4 config APPSTORE_LISTING_CONFIG_KEY: 'appstore_listing_config', SUPPORT_CONFIG_KEY: 'support_config', PROFILE_CONFIG_KEY: 'profile_config', GHOSTS_CONFIG_KEY: 'ghosts_config', REVERSE_PROXY_CONFIG_KEY: 'reverseproxy_config', IPV6_CONFIG_KEY: 'ipv6_config', // strings AUTOUPDATE_PATTERN_KEY: 'autoupdate_pattern', TIME_ZONE_KEY: 'time_zone', CLOUDRON_NAME_KEY: 'cloudron_name', LANGUAGE_KEY: 'language', CLOUDRON_ID_KEY: 'cloudron_id', APPSTORE_API_TOKEN_KEY: 'appstore_api_token', APPSTORE_WEB_TOKEN_KEY: 'appstore_web_token', FIREWALL_BLOCKLIST_KEY: 'firewall_blocklist', TRUSTED_IPS_KEY: 'trusted_ips_key', API_SERVER_ORIGIN_KEY: 'api_server_origin', WEB_SERVER_ORIGIN_KEY: 'web_server_origin', CONSOLE_SERVER_ORIGIN_KEY: 'console_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, _set: set }; 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'), directoryServer = require('./directoryserver.js'), docker = require('./docker.js'), externalLdap = require('./externalldap.js'), moment = require('moment-timezone'), mounts = require('./mounts.js'), paths = require('./paths.js'), safe = require('safetydance'), sysinfo = require('./sysinfo.js'), tokens = require('./tokens.js'), translation = require('./translation.js'), users = require('./users.js'), _ = require('underscore'); const SETTINGS_FIELDS = [ 'name', 'value' ].join(','); const SETTINGS_BLOB_FIELDS = [ 'name', 'valueBlob' ].join(','); const gDefaults = (function () { const result = { }; result[exports.AUTOUPDATE_PATTERN_KEY] = cron.DEFAULT_AUTOUPDATE_PATTERN; result[exports.TIME_ZONE_KEY] = 'UTC'; result[exports.CLOUDRON_NAME_KEY] = 'Cloudron'; result[exports.DYNAMIC_DNS_KEY] = false; result[exports.IPV6_CONFIG_KEY] = { provider: 'noop' }; result[exports.UNSTABLE_APPS_KEY] = true; result[exports.LANGUAGE_KEY] = 'en'; result[exports.CLOUDRON_ID_KEY] = ''; result[exports.APPSTORE_API_TOKEN_KEY] = ''; result[exports.APPSTORE_WEB_TOKEN_KEY] = ''; result[exports.BACKUP_CONFIG_KEY] = { provider: 'filesystem', backupFolder: '/var/backups', format: 'tgz', encryption: null, }; result[exports.BACKUP_POLICY_KEY] = { retention: { keepWithinSecs: 2 * 24 * 60 * 60 }, // 2 days schedule: '00 00 23 * * *' // every day at 11pm }; result[exports.REVERSE_PROXY_CONFIG_KEY] = { ocsp: true }; result[exports.SERVICES_CONFIG_KEY] = {}; result[exports.EXTERNAL_LDAP_KEY] = { provider: 'noop', autoCreate: false }; result[exports.DIRECTORY_SERVER_KEY] = { enabled: false, secret: '', allowlist: '' // empty means allow all }; result[exports.REGISTRY_CONFIG_KEY] = { provider: 'noop' }; result[exports.SYSINFO_CONFIG_KEY] = { provider: 'generic' }; result[exports.PROFILE_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.TRUSTED_IPS_KEY] = ''; result[exports.API_SERVER_ORIGIN_KEY] = 'https://api.cloudron.io'; result[exports.WEB_SERVER_ORIGIN_KEY] = 'https://cloudron.io'; result[exports.CONSOLE_SERVER_ORIGIN_KEY] = 'https://console.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.GHOSTS_CONFIG_KEY] = {}; 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] = constants.FOOTER; 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'); } async function setAutoupdatePattern(pattern) { assert.strictEqual(typeof pattern, 'string'); if (pattern !== constants.AUTOUPDATE_PATTERN_NEVER) { // check if pattern is valid const job = safe.safeCall(function () { return new CronJob(pattern); }); if (!job) throw new BoxError(BoxError.BAD_FIELD, 'Invalid pattern'); } await set(exports.AUTOUPDATE_PATTERN_KEY, pattern); notifyChange(exports.AUTOUPDATE_PATTERN_KEY, pattern); } async function getAutoupdatePattern() { const pattern = await get(exports.AUTOUPDATE_PATTERN_KEY); if (pattern === null) return gDefaults[exports.AUTOUPDATE_PATTERN_KEY]; return pattern; } async function setTimeZone(tz) { assert.strictEqual(typeof tz, 'string'); if (moment.tz.names().indexOf(tz) === -1) throw new BoxError(BoxError.BAD_FIELD, 'Bad timeZone'); await set(exports.TIME_ZONE_KEY, tz); notifyChange(exports.TIME_ZONE_KEY, tz); } async function getTimeZone() { const tz = await get(exports.TIME_ZONE_KEY); if (tz === null) return gDefaults[exports.TIME_ZONE_KEY]; return tz; } async function getCloudronName() { const name = await get(exports.CLOUDRON_NAME_KEY); if (name === null) return gDefaults[exports.CLOUDRON_NAME_KEY]; return name; } async function setCloudronName(name) { assert.strictEqual(typeof name, 'string'); if (!name) throw new BoxError(BoxError.BAD_FIELD, 'name is empty'); // some arbitrary restrictions (for sake of ui layout) // if this is changed, adjust dashboard/branding.html if (name.length > 64) throw new BoxError(BoxError.BAD_FIELD, 'name cannot exceed 64 characters'); await set(exports.CLOUDRON_NAME_KEY, name); notifyChange(exports.CLOUDRON_NAME_KEY, name); } 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); } async function getDynamicDnsConfig() { const enabled = await get(exports.DYNAMIC_DNS_KEY); if (enabled === null) return gDefaults[exports.DYNAMIC_DNS_KEY]; return !!enabled; // db holds string values only } async function setDynamicDnsConfig(enabled) { assert.strictEqual(typeof enabled, 'boolean'); await set(exports.DYNAMIC_DNS_KEY, enabled ? 'enabled' : ''); // db holds string values only notifyChange(exports.DYNAMIC_DNS_KEY, enabled); } async function getIPv6Config() { const value = await get(exports.IPV6_CONFIG_KEY); if (value === null) return gDefaults[exports.IPV6_CONFIG_KEY]; return JSON.parse(value); } async function setIPv6Config(ipv6Config) { assert.strictEqual(typeof ipv6Config, 'object'); if (isDemo()) throw new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'); const error = await sysinfo.testIPv6Config(ipv6Config); if (error) throw error; await set(exports.IPV6_CONFIG_KEY, JSON.stringify(ipv6Config)); notifyChange(exports.IPV6_CONFIG_KEY, ipv6Config); } async function getUnstableAppsConfig() { const result = await get(exports.UNSTABLE_APPS_KEY); if (result === null) return 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); } async function getBackupPolicy() { const result = await get(exports.BACKUP_POLICY_KEY); if (result === null) return gDefaults[exports.BACKUP_POLICY_KEY]; return JSON.parse(result); } async function setBackupPolicy(policy) { assert.strictEqual(typeof policy, 'object'); const error = await backups.validatePolicy(policy); if (error) throw error; await set(exports.BACKUP_POLICY_KEY, JSON.stringify(policy)); notifyChange(exports.BACKUP_POLICY_KEY, policy); } async function getBackupConfig() { const value = await get(exports.BACKUP_CONFIG_KEY); if (value === null) return gDefaults[exports.BACKUP_CONFIG_KEY]; const backupConfig = JSON.parse(value); // { provider, token, password, region, prefix, bucket } return backupConfig; } async function setBackupConfig(backupConfig) { assert.strictEqual(typeof backupConfig, 'object'); const oldConfig = await getBackupConfig(); backups.injectPrivateFields(backupConfig, oldConfig); if (mounts.isManagedProvider(backupConfig.provider)) { let error = mounts.validateMountOptions(backupConfig.provider, backupConfig.mountOptions); if (error) throw error; [error] = await safe(mounts.tryAddMount(mounts.mountObjectFromBackupConfig(backupConfig), { timeout: 10 })); // 10 seconds if (error) { if (mounts.isManagedProvider(oldConfig.provider)) { // put back the old mount configuration debug('setBackupConfig: rolling back to previous mount configuration'); await safe(mounts.tryAddMount(mounts.mountObjectFromBackupConfig(oldConfig), { timeout: 10 })); } throw error; } } const error = await backups.testConfig(backupConfig); if (error) throw 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', 'encryptedFilenames' ].some(p => backupConfig[p] !== oldConfig[p])) { debug('setBackupConfig: clearing backup cache'); backups.cleanupCacheFilesSync(); } await set(exports.BACKUP_CONFIG_KEY, JSON.stringify(backupConfig)); if (mounts.isManagedProvider(oldConfig.provider) && !mounts.isManagedProvider(backupConfig.provider)) { debug('setBackupConfig: removing old backup mount point'); await safe(mounts.removeMount(mounts.mountObjectFromBackupConfig(oldConfig))); } notifyChange(exports.BACKUP_CONFIG_KEY, backupConfig); } async function getServicesConfig() { const value = await get(exports.SERVICES_CONFIG_KEY); if (value === null) return gDefaults[exports.SERVICES_CONFIG_KEY]; return JSON.parse(value); } async function setServicesConfig(platformConfig) { await set(exports.SERVICES_CONFIG_KEY, JSON.stringify(platformConfig)); notifyChange(exports.SERVICES_CONFIG_KEY, platformConfig); } async function getExternalLdapConfig() { const value = await get(exports.EXTERNAL_LDAP_KEY); if (value === null) return gDefaults[exports.EXTERNAL_LDAP_KEY]; const config = JSON.parse(value); if (!config.autoCreate) config.autoCreate = false; // ensure new keys return config; } async function setExternalLdapConfig(externalLdapConfig) { assert.strictEqual(typeof externalLdapConfig, 'object'); if (isDemo()) throw new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'); const currentConfig = await getExternalLdapConfig(); externalLdap.injectPrivateFields(externalLdapConfig, currentConfig); const error = await externalLdap.testConfig(externalLdapConfig); if (error) throw error; await set(exports.EXTERNAL_LDAP_KEY, JSON.stringify(externalLdapConfig)); notifyChange(exports.EXTERNAL_LDAP_KEY, externalLdapConfig); } async function getDirectoryServerConfig() { const value = await get(exports.DIRECTORY_SERVER_KEY); if (value === null) return gDefaults[exports.DIRECTORY_SERVER_KEY]; return JSON.parse(value); } async function setDirectoryServerConfig(directoryServerConfig) { assert.strictEqual(typeof directoryServerConfig, 'object'); if (isDemo()) throw new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'); const config = { enabled: directoryServerConfig.enabled, secret: directoryServerConfig.secret, // if list is empty, we allow all IPs allowlist: directoryServerConfig.allowlist || '' }; await directoryServer.validateConfig(config); await set(exports.DIRECTORY_SERVER_KEY, JSON.stringify(config)); await directoryServer.applyConfig(config); notifyChange(exports.DIRECTORY_SERVER_KEY, config); } async function getRegistryConfig() { const value = await get(exports.REGISTRY_CONFIG_KEY); if (value === null) return gDefaults[exports.REGISTRY_CONFIG_KEY]; return JSON.parse(value); } async function setRegistryConfig(registryConfig) { assert.strictEqual(typeof registryConfig, 'object'); const currentConfig = await getRegistryConfig(); docker.injectPrivateFields(registryConfig, currentConfig); await docker.testRegistryConfig(registryConfig); await set(exports.REGISTRY_CONFIG_KEY, JSON.stringify(registryConfig)); notifyChange(exports.REGISTRY_CONFIG_KEY, registryConfig); } async function getSysinfoConfig() { const value = await get(exports.SYSINFO_CONFIG_KEY); if (value === null) return gDefaults[exports.SYSINFO_CONFIG_KEY]; return JSON.parse(value); } async function setSysinfoConfig(sysinfoConfig) { assert.strictEqual(typeof sysinfoConfig, 'object'); if (isDemo()) throw new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'); const error = await sysinfo.testIPv4Config(sysinfoConfig); if (error) throw error; await set(exports.SYSINFO_CONFIG_KEY, JSON.stringify(sysinfoConfig)); notifyChange(exports.SYSINFO_CONFIG_KEY, sysinfoConfig); } async function getProfileConfig() { const value = await get(exports.PROFILE_CONFIG_KEY); if (value === null) return gDefaults[exports.PROFILE_CONFIG_KEY]; return JSON.parse(value); } async function setProfileConfig(directoryConfig) { assert.strictEqual(typeof directoryConfig, 'object'); if (isDemo()) throw new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode'); const oldConfig = await getProfileConfig(); await set(exports.PROFILE_CONFIG_KEY, JSON.stringify(directoryConfig)); if (directoryConfig.mandatory2FA && !oldConfig.mandatory2FA) { debug('setProfileConfig: logging out non-2FA users to enforce 2FA'); const allUsers = await users.list(); for (const user of allUsers) { if (!user.twoFactorAuthenticationEnabled) await tokens.delByUserIdAndType(user.id, tokens.ID_WEBADMIN); } } notifyChange(exports.PROFILE_CONFIG_KEY, directoryConfig); } async function getReverseProxyConfig() { const value = await get(exports.REVERSE_PROXY_CONFIG_KEY); if (value === null) return gDefaults[exports.REVERSE_PROXY_CONFIG_KEY]; return JSON.parse(value); } async function getAppstoreListingConfig() { const value = await get(exports.APPSTORE_LISTING_CONFIG_KEY); if (value === null) return gDefaults[exports.APPSTORE_LISTING_CONFIG_KEY]; return JSON.parse(value); } async function setAppstoreListingConfig(listingConfig) { assert.strictEqual(typeof listingConfig, 'object'); await set(exports.APPSTORE_LISTING_CONFIG_KEY, JSON.stringify(listingConfig)); notifyChange(exports.APPSTORE_LISTING_CONFIG_KEY, listingConfig); } 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)); } async function getTrustedIps() { const value = await get(exports.TRUSTED_IPS_KEY); if (value === null) return gDefaults[exports.TRUSTED_IPS_KEY]; return value; } async function setTrustedIps(trustedIps) { assert.strictEqual(typeof trustedIps, 'string'); await set(exports.TRUSTED_IPS_KEY, trustedIps); } async function getGhosts() { const value = await get(exports.GHOSTS_CONFIG_KEY); if (value === null) return gDefaults[exports.GHOSTS_CONFIG_KEY]; return JSON.parse(value); } async function setGhosts(ghosts) { assert.strictEqual(typeof ghosts, 'object'); await set(exports.GHOSTS_CONFIG_KEY, JSON.stringify(ghosts)); notifyChange(exports.GHOSTS_CONFIG_KEY, ghosts); } async function getSupportConfig() { const value = await get(exports.SUPPORT_CONFIG_KEY); if (value === null) return gDefaults[exports.SUPPORT_CONFIG_KEY]; return JSON.parse(value); } async function getLanguage() { const value = await get(exports.LANGUAGE_KEY); if (value === null) return gDefaults[exports.LANGUAGE_KEY]; return value; } async function setLanguage(language) { assert.strictEqual(typeof language, 'string'); const languages = await translation.getLanguages(); if (languages.indexOf(language) === -1) throw new BoxError(BoxError.NOT_FOUND, 'Language not found'); await set(exports.LANGUAGE_KEY, language); notifyChange(exports.LANGUAGE_KEY, language); } async function getCloudronId() { const value = await get(exports.CLOUDRON_ID_KEY); if (value === null) return gDefaults[exports.CLOUDRON_ID_KEY]; return value; } async function setCloudronId(cid) { assert.strictEqual(typeof cid, 'string'); await set(exports.CLOUDRON_ID_KEY, cid); notifyChange(exports.CLOUDRON_ID_KEY, cid); } async function getAppstoreApiToken() { const value = await get(exports.APPSTORE_API_TOKEN_KEY); if (value === null) return gDefaults[exports.APPSTORE_API_TOKEN_KEY]; return value; } async function setAppstoreApiToken(token) { assert.strictEqual(typeof token, 'string'); await set(exports.APPSTORE_API_TOKEN_KEY, token); notifyChange(exports.APPSTORE_API_TOKEN_KEY, token); } async function getAppstoreWebToken() { const value = await get(exports.APPSTORE_WEB_TOKEN_KEY); if (value === null) return gDefaults[exports.APPSTORE_WEB_TOKEN_KEY]; return value; } async function setAppstoreWebToken(token) { assert.strictEqual(typeof token, 'string'); await set(exports.APPSTORE_WEB_TOKEN_KEY, token); notifyChange(exports.APPSTORE_WEB_TOKEN_KEY, token); } async function list() { const settings = await database.query(`SELECT ${SETTINGS_FIELDS} FROM settings WHERE value IS NOT NULL ORDER BY name`); const result = Object.assign({}, 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_POLICY_KEY, exports.BACKUP_CONFIG_KEY, exports.IPV6_CONFIG_KEY, exports.PROFILE_CONFIG_KEY, exports.SERVICES_CONFIG_KEY, exports.EXTERNAL_LDAP_KEY, exports.REGISTRY_CONFIG_KEY, exports.SYSINFO_CONFIG_KEY, exports.REVERSE_PROXY_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], consoleServerOrigin: allSettings[exports.CONSOLE_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 async function setDashboardLocation(dashboardDomain, dashboardFqdn) { assert.strictEqual(typeof dashboardDomain, 'string'); assert.strictEqual(typeof dashboardFqdn, 'string'); await set(exports.DASHBOARD_DOMAIN_KEY, dashboardDomain); await set(exports.DASHBOARD_FQDN_KEY, dashboardFqdn); gCache.dashboardDomain = dashboardDomain; gCache.dashboardFqdn = dashboardFqdn; } async function setMailLocation(mailDomain, mailFqdn) { assert.strictEqual(typeof mailDomain, 'string'); assert.strictEqual(typeof mailFqdn, 'string'); await set(exports.MAIL_DOMAIN_KEY, mailDomain); await set(exports.MAIL_FQDN_KEY, mailFqdn); gCache.mailDomain = mailDomain; gCache.mailFqdn = mailFqdn; } async function setApiServerOrigin(origin) { assert.strictEqual(typeof origin, 'string'); await set(exports.API_SERVER_ORIGIN_KEY, origin); gCache.apiServerOrigin = origin; notifyChange(exports.API_SERVER_ORIGIN_KEY, origin); } async function getFooter() { const value = await get(exports.FOOTER_KEY); if (value === null) return gDefaults[exports.FOOTER_KEY]; return value; } async function setFooter(footer) { assert.strictEqual(typeof footer, 'string'); await set(exports.FOOTER_KEY, footer); notifyChange(exports.FOOTER_KEY, footer); } function provider() { return gCache.provider; } function apiServerOrigin() { return gCache.apiServerOrigin; } function webServerOrigin() { return gCache.webServerOrigin; } function consoleServerOrigin() { return gCache.consoleServerOrigin; } 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(); }