825 lines
27 KiB
JavaScript
825 lines
27 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,
|
|
|
|
getUserDirectoryConfig,
|
|
setUserDirectoryConfig,
|
|
|
|
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',
|
|
USER_DIRECTORY_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'),
|
|
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'),
|
|
userdirectory = require('./userdirectory.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] = 'America/Los_Angeles';
|
|
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.USER_DIRECTORY_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
|
|
|
|
if (mounts.isManagedProvider(backupConfig.provider) || backupConfig.provider === 'mountpoint') {
|
|
const hostPath = mounts.isManagedProvider(backupConfig.provider) ? paths.MANAGED_BACKUP_MOUNT_DIR : backupConfig.mountPoint;
|
|
backupConfig.mountStatus = await mounts.getStatus(backupConfig.provider, hostPath); // { state, message }
|
|
}
|
|
|
|
return backupConfig;
|
|
}
|
|
|
|
function mountOptionsChanged(currentConfig, backupConfig) {
|
|
return currentConfig.provider !== backupConfig.provider
|
|
|| currentConfig.mountPoint !== backupConfig.mountPoint
|
|
|| !_.isEqual(currentConfig.mountOptions, backupConfig.mountOptions);
|
|
}
|
|
|
|
async function setBackupConfig(backupConfig) {
|
|
assert.strictEqual(typeof backupConfig, 'object');
|
|
|
|
const oldConfig = await getBackupConfig();
|
|
|
|
backups.injectPrivateFields(backupConfig, oldConfig);
|
|
|
|
if (mounts.isManagedProvider(backupConfig.provider) && (!mounts.isManagedProvider(oldConfig.provider) || mountOptionsChanged(oldConfig, backupConfig))) {
|
|
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' ].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);
|
|
|
|
await backups.configureCollectd(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);
|
|
|
|
await backups.configureCollectd(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 getUserDirectoryConfig() {
|
|
const value = await get(exports.USER_DIRECTORY_KEY);
|
|
if (value === null) return gDefaults[exports.USER_DIRECTORY_KEY];
|
|
return JSON.parse(value);
|
|
}
|
|
|
|
async function setUserDirectoryConfig(userDirectoryConfig) {
|
|
assert.strictEqual(typeof userDirectoryConfig, 'object');
|
|
|
|
if (isDemo()) throw new BoxError(BoxError.BAD_FIELD, 'Not allowed in demo mode');
|
|
|
|
const config = {
|
|
enabled: userDirectoryConfig.enabled,
|
|
secret: userDirectoryConfig.secret,
|
|
// if list is empty, we allow all IPs
|
|
allowlist: userDirectoryConfig.allowlist || ''
|
|
};
|
|
|
|
await userdirectory.validateConfig(config);
|
|
await set(exports.USER_DIRECTORY_KEY, JSON.stringify(config));
|
|
await userdirectory.applyConfig(config);
|
|
|
|
notifyChange(exports.USER_DIRECTORY_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(); }
|