From 6c3d2baedea92e08df54ae809ddebe458e6008df Mon Sep 17 00:00:00 2001 From: Krystie Date: Sun, 29 Mar 2026 19:36:43 -0700 Subject: [PATCH] refactor(config): Extract configuration into src/config/ module - Create src/config/paths.js for all file paths and env vars - Create src/config/site.js for site configuration loading - Create src/config/index.js as unified config export - Prepare for server.js modularization (Phase 2.1) Part of deslopification roadmap: break 1997-line server.js into layers --- dashcaddy-api/src/config/index.js | 38 +++++++++++++++ dashcaddy-api/src/config/paths.js | 42 ++++++++++++++++ dashcaddy-api/src/config/site.js | 79 +++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 dashcaddy-api/src/config/index.js create mode 100644 dashcaddy-api/src/config/paths.js create mode 100644 dashcaddy-api/src/config/site.js diff --git a/dashcaddy-api/src/config/index.js b/dashcaddy-api/src/config/index.js new file mode 100644 index 0000000..ae1a4e8 --- /dev/null +++ b/dashcaddy-api/src/config/index.js @@ -0,0 +1,38 @@ +/** + * Centralized configuration module + * Exports all configuration loading and path resolution + */ +const paths = require('./paths'); +const site = require('./site'); +const { APP, LIMITS, TIMEOUTS, RETRIES, CADDY } = require('../../constants'); + +// Load logging level +const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }; +const LOG_LEVEL = LOG_LEVELS[process.env.LOG_LEVEL || 'info'] || 1; + +const PORT = APP.PORT; +const MAX_ERROR_LOG_SIZE = LIMITS.ERROR_LOG_SIZE; + +module.exports = { + // Paths + ...paths, + + // Site configuration + siteConfig: site.siteConfig, + loadSiteConfig: site.loadSiteConfig, + buildDomain: site.buildDomain, + buildServiceUrl: site.buildServiceUrl, + + // App constants + PORT, + LOG_LEVELS, + LOG_LEVEL, + MAX_ERROR_LOG_SIZE, + + // Re-export constants for convenience + APP, + LIMITS, + TIMEOUTS, + RETRIES, + CADDY, +}; diff --git a/dashcaddy-api/src/config/paths.js b/dashcaddy-api/src/config/paths.js new file mode 100644 index 0000000..f493fcc --- /dev/null +++ b/dashcaddy-api/src/config/paths.js @@ -0,0 +1,42 @@ +/** + * Platform-specific paths and environment variable configuration + */ +const path = require('path'); +const platformPaths = require('../../platform-paths'); + +const CADDYFILE_PATH = process.env.CADDYFILE_PATH || platformPaths.caddyfile; +const CADDY_ADMIN_URL = process.env.CADDY_ADMIN_URL || platformPaths.caddyAdminUrl; +const SERVICES_FILE = process.env.SERVICES_FILE || platformPaths.servicesFile; +const SERVICES_DIR = path.dirname(SERVICES_FILE); +const CONFIG_FILE = process.env.CONFIG_FILE || path.join(SERVICES_DIR, 'config.json'); +const DNS_CREDENTIALS_FILE = process.env.DNS_CREDENTIALS_FILE || path.join(SERVICES_DIR, 'dns-credentials.json'); +const TAILSCALE_CONFIG_FILE = process.env.TAILSCALE_CONFIG_FILE || path.join(SERVICES_DIR, 'tailscale-config.json'); +const NOTIFICATIONS_FILE = process.env.NOTIFICATIONS_FILE || path.join(SERVICES_DIR, 'notifications.json'); +const TOTP_CONFIG_FILE = process.env.TOTP_CONFIG_FILE || path.join(SERVICES_DIR, 'totp-config.json'); +const ERROR_LOG_FILE = process.env.ERROR_LOG_FILE || path.join(__dirname, '../../dashcaddy-errors.log'); +const LICENSE_SECRET_FILE = process.env.LICENSE_SECRET_FILE || path.join(__dirname, '../../.license-secret'); + +const BROWSE_ROOTS = (process.env.MEDIA_BROWSE_ROOTS || '') + .split(',') + .filter(r => r.includes('=')) + .map(r => { + const eqIndex = r.indexOf('='); + const containerPath = r.slice(0, eqIndex).trim(); + const hostPath = r.slice(eqIndex + 1).trim(); + return { containerPath, hostPath }; + }); + +module.exports = { + CADDYFILE_PATH, + CADDY_ADMIN_URL, + SERVICES_FILE, + SERVICES_DIR, + CONFIG_FILE, + DNS_CREDENTIALS_FILE, + TAILSCALE_CONFIG_FILE, + NOTIFICATIONS_FILE, + TOTP_CONFIG_FILE, + ERROR_LOG_FILE, + LICENSE_SECRET_FILE, + BROWSE_ROOTS, +}; diff --git a/dashcaddy-api/src/config/site.js b/dashcaddy-api/src/config/site.js new file mode 100644 index 0000000..9bb034f --- /dev/null +++ b/dashcaddy-api/src/config/site.js @@ -0,0 +1,79 @@ +/** + * Site configuration loader + * Loads and manages site-wide settings from config.json + */ +const fs = require('fs'); +const { validateConfig } = require('../../config-schema'); +const { CADDY } = require('../../constants'); + +let siteConfig = { + tld: '.home', + caName: '', + dnsServerIp: '', + dnsServerPort: CADDY.DEFAULT_DNS_PORT, + dashboardHost: '', + timezone: 'UTC', + dnsServers: {}, + configurationType: 'homelab', + domain: '', + routingMode: 'subdomain' +}; + +function loadSiteConfig(CONFIG_FILE, log) { + try { + if (fs.existsSync(CONFIG_FILE)) { + const raw = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); + + // Validate config and log any issues + 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); + } + } + + siteConfig.tld = raw.tld || '.home'; + if (!siteConfig.tld.startsWith('.')) siteConfig.tld = '.' + siteConfig.tld; + siteConfig.caName = raw.caName || ''; + 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; + } + } catch (e) { + if (log && log.error) { + log.error('config', 'Failed to load site config', { error: e.message }); + } + } +} + +/** 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, +};