The documented hardening option for exposed deploys (monitoring: {public: false}
in config.json / MONITORING_PUBLIC env) never worked:
1. applyConfigFields dropped the monitoring key entirely
2. monitoring missing from config-schema KNOWN_KEYS (Unknown-key warnings)
3. MONITORING_PUBLIC frozen at mount + re-required singleton instead of injected dep
4. PUBLIC_ROUTES had unconditional duplicate entries defeating the gated spread
- site.js: copy monitoring through; drop dead write-only siteConfig.caName
- config-schema: +monitoring key, validateMonitoring (public must be boolean);
remove never-written typo-footgun keys setupCompleted/setupMode (git -S: zero writers ever)
- middleware: live isMonitoringPublic() (env > config > default public), per-request
gate via monitoring:true flag, remove duplicate unconditional route entries
- default unchanged (endpoints stay public — System Overview widget)
Tests: +11 (__tests__/monitoring-public-gate-dc096.test.js); suite 118/2735 green.
Judge: GLM-5.3 cold-read A (deleg_98aba845), URN urn:ump:zgvtskqljurasdagc632atnybb2p6gakk4cxcmjd3i4ivy7rvwta
96 lines
3.2 KiB
JavaScript
96 lines
3.2 KiB
JavaScript
/**
|
|
* Site configuration loader
|
|
* Loads and manages site-wide settings from config.json
|
|
*
|
|
* Includes automatic migration from older config versions (see migrations.js).
|
|
* Users never see the migration — it runs silently on startup, writes the
|
|
* updated config back, and the rest of the app only ever sees the current
|
|
* schema.
|
|
*/
|
|
const { validateConfig } = require('../utilities/config-schema');
|
|
const { CADDY } = require('../utilities/constants');
|
|
const { loadAndMigrate, CURRENT_VERSION } = require('./migrations');
|
|
|
|
const siteConfig = {
|
|
tld: '.home',
|
|
dnsServerIp: '',
|
|
dnsServerPort: CADDY.DEFAULT_DNS_PORT,
|
|
dashboardHost: '',
|
|
timezone: 'UTC',
|
|
dnsServers: {},
|
|
configurationType: 'homelab',
|
|
domain: '',
|
|
routingMode: 'subdomain'
|
|
};
|
|
|
|
function applyConfigFields(raw) {
|
|
siteConfig.tld = raw.tld || '.home';
|
|
if (!siteConfig.tld.startsWith('.')) siteConfig.tld = '.' + siteConfig.tld;
|
|
siteConfig.dnsServerIp = (raw.dns && raw.dns.ip) || '';
|
|
siteConfig.dnsServerPort = (raw.dns && raw.dns.port) || CADDY.DEFAULT_DNS_PORT;
|
|
siteConfig.dashboardHost = raw.dashboardHost || `status${siteConfig.tld}`;
|
|
siteConfig.timezone = raw.timezone || 'UTC';
|
|
siteConfig.dnsServers = raw.dnsServers || {};
|
|
siteConfig.configurationType = raw.configurationType || 'homelab';
|
|
siteConfig.domain = raw.domain || '';
|
|
siteConfig.routingMode = raw.routingMode || 'subdomain';
|
|
siteConfig.pylon = raw.pylon || null;
|
|
// DC-096: `monitoring` was previously NOT copied out of raw config, so the
|
|
// documented hardening option `monitoring: { public: false }` (middleware.js
|
|
// MONITORING_PUBLIC) silently never applied — siteConfig.monitoring stayed
|
|
// undefined forever. Copy it through so the middleware actually sees it.
|
|
siteConfig.monitoring = raw.monitoring || null;
|
|
}
|
|
function validateAndLogConfig(raw, log) {
|
|
const { valid, errors: configErrors, warnings: configWarnings } = validateConfig(raw);
|
|
if (log && log.warn) {
|
|
if (!valid) {
|
|
log.warn('config', 'Config validation errors', { errors: configErrors });
|
|
}
|
|
for (const w of configWarnings) {
|
|
log.warn('config', w);
|
|
}
|
|
}
|
|
}
|
|
|
|
function loadSiteConfig(CONFIG_FILE, log) {
|
|
try {
|
|
// Run migrations first — this handles config.json files from older
|
|
// versions of DashCaddy and writes the migrated version back to disk.
|
|
const raw = loadAndMigrate(CONFIG_FILE, log);
|
|
|
|
if (raw && Object.keys(raw).length > 0) {
|
|
validateAndLogConfig(raw, log);
|
|
applyConfigFields(raw);
|
|
}
|
|
} catch (e) {
|
|
if (log && log.error) {
|
|
log.error('config', e, null, { note: 'Failed to load site config' });
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Build a domain from subdomain + configured TLD or public domain */
|
|
function buildDomain(subdomain) {
|
|
if (siteConfig.configurationType === 'public' && siteConfig.domain) {
|
|
return `${subdomain}.${siteConfig.domain}`;
|
|
}
|
|
return `${subdomain}${siteConfig.tld}`;
|
|
}
|
|
|
|
/** Build full service URL (protocol + host + path) */
|
|
function buildServiceUrl(subdomain) {
|
|
if (siteConfig.routingMode === 'subdirectory' && siteConfig.domain) {
|
|
return `https://${siteConfig.domain}/${subdomain}`;
|
|
}
|
|
return `https://${buildDomain(subdomain)}`;
|
|
}
|
|
|
|
module.exports = {
|
|
siteConfig,
|
|
loadSiteConfig,
|
|
buildDomain,
|
|
buildServiceUrl,
|
|
CURRENT_VERSION
|
|
};
|