Files
cloudron-box/src/settings.js
2023-05-07 20:51:02 +02:00

809 lines
26 KiB
JavaScript

'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,
getBackupConfig,
setBackupConfig,
setBackupCredentials,
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,
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',
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',
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,
retentionPolicy: { keepWithinSecs: 2 * 24 * 60 * 60 }, // 2 days
schedulePattern: '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.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 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 setBackupCredentials(credentials) {
assert.strictEqual(typeof credentials, 'object');
const currentConfig = await getBackupConfig();
// preserve these fields
const extra = _.pick(currentConfig, 'retentionPolicy', 'schedulePattern', 'copyConcurrency', 'syncConcurrency', 'memoryLimit', 'downloadConcurrency', 'deleteConcurrency', 'uploadPartSize');
const backupConfig = _.extend({}, credentials, extra);
backups.cleanupCacheFilesSync();
await set(exports.BACKUP_CONFIG_KEY, JSON.stringify(backupConfig));
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 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 = _.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.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(); }