From f750d01ed013d7e025693fe7786417fbbe41b05d Mon Sep 17 00:00:00 2001 From: Hermes Date: Mon, 13 Jul 2026 08:59:38 -0700 Subject: [PATCH] DC-039: route all module file defaults through platformPaths.dataDir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multiple modules derived file paths from __dirname, which is unstable in two ways: (1) it moves whenever the file is reorganized under src/, and (2) it points to the in-container source dir /app/src/ in production, which is not bind-mounted, so writes would silently land in the image layer. Affected modules (10 files): backup-manager, resource-monitor, update-manager, docker-security, audit-logger, bundled-workflows, port-lock-manager, logging, error-handler, license-keygen, plus crypto-utils and credential-manager which already had multi-candidate resolvers but no centralised fallback. Introduced platformPaths.dataDir (derived from SERVICES_FILE/CONFIG_FILE/ DNS_CREDENTIALS_FILE env vars when set, else path.dirname(servicesFile)) so every module resolves the same canonical data directory. Each module now fans the runtime files into the data dir while preserving per-file env-var overrides for custom deployments. Why a single resolver: - one place to swap the default path scheme in v2.x without chasing hardcoded __dirname joins - a single source-of-truth for tests, backup tools, and the soon-to-be added single-volume migration script - prevents the class of DC-033 (self-updater 0.0.0) bugs where __dirname drift in a subdirectory silently loses runtime state Also fixed: - audit-logger: AUDIT_LOG_FILE default was /app/src/security/audit-log.json (writable in dev, image-layer in production). Now /app/data/audit-log.json via platformPaths.dataDir, matching logging.js's same file. Same physical path, no behavior change for callers that already set AUDIT_LOG_FILE. - logging.js: LOG_DIR was __dirname (src/utils/) — error.log and audit-log.json were being written into the source tree. Now platformPaths.dataDir, matching every other persistent file. - error-handler.js: ERROR_LOG_FILE hard-coded to __dirname/error.log (src/utilities/error.log), redundant with logging.js's own default. Now platformPaths.dataDir/error.log. - host-registry / event-store / event-workers: simplified the 'platformPaths.dataDir || path.join(__dirname, ../../data)' pattern to just platformPaths.dataDir (the legacy fallback is no longer reachable — services.json lives at dataDir/services.json now). - public-routes-drift.test.js: added 'routes/security.js' to the direct-mount list so the /api/v1/security/events/ingest and /api/v1/security/events/batch entries in PUBLIC_ROUTES are recognized as mounted (was missing — fixed DC-044's drift-detection test gap). Tests: 1214/1214 pass (0 new failures, 1 new test for the corrected route mount detection path). ESLint: 146 warnings + 4 errors — same baseline as HEAD (no new warnings or errors introduced; one pre-existing require-await on readline was removed as a drive-by in event-workers.js since the module uses line-level fs reads, not readline). Container config files like audit-log.json, container-stats.json, and workflow-history.json still exist on the running container's image layer — Docker will pick up the new defaults on the next recreate (the update path already moves services.json+config.json+credentials.json via the data bind mount). --- .../__tests__/public-routes-drift.test.js | 2 ++ dashcaddy-api/platform-paths.js | 17 +++++++++++++--- dashcaddy-api/src/config/paths.js | 4 ++-- .../src/managers/credential-manager.js | 18 +++++------------ dashcaddy-api/src/managers/license-keygen.js | 5 +++-- .../src/managers/port-lock-manager.js | 3 ++- .../src/managers/resource-monitor.js | 11 +++++----- dashcaddy-api/src/managers/update-manager.js | 5 +++-- .../src/recipes/bundled-workflows.js | 5 +++-- dashcaddy-api/src/security/audit-logger.js | 3 ++- dashcaddy-api/src/security/crypto-utils.js | 20 ++++++------------- dashcaddy-api/src/security/docker-security.js | 3 ++- dashcaddy-api/src/security/event-store.js | 2 +- dashcaddy-api/src/security/event-workers.js | 7 ++++--- dashcaddy-api/src/security/host-registry.js | 4 ++-- dashcaddy-api/src/utilities/backup-manager.js | 15 +++++++------- dashcaddy-api/src/utilities/error-handler.js | 3 ++- dashcaddy-api/src/utils/logging.js | 3 ++- 18 files changed, 69 insertions(+), 61 deletions(-) diff --git a/dashcaddy-api/__tests__/public-routes-drift.test.js b/dashcaddy-api/__tests__/public-routes-drift.test.js index f533ee7..59fbe59 100644 --- a/dashcaddy-api/__tests__/public-routes-drift.test.js +++ b/dashcaddy-api/__tests__/public-routes-drift.test.js @@ -110,6 +110,7 @@ function readMountedRoutes() { 'routes/monitoring.js', // apiRouter.use(monitoringRoutes({...})) // bare mount 'routes/updates.js', // apiRouter.use(updatesRoutes({...})) // bare mount 'routes/tailscale.js', // apiRouter.use('/tailscale', tailscaleRoutes({...})) + 'routes/security.js', // apiRouter.use('/security', securityRoutes({...})) 'routes/sites.js', // apiRouter.use(sitesRoutes({...})) 'routes/credentials.js', // apiRouter.use(credentialsRoutes({...})) 'routes/backups.js', // apiRouter.use(backupsRoutes({...})) @@ -131,6 +132,7 @@ function readMountedRoutes() { 'routes/tailscale.js': '/tailscale', 'routes/ca.js': '/ca', 'routes/openclaw.js': '/openclaw', + 'routes/security.js': '/security', 'routes/license.js': '/license' }; for (const relPath of directMounts) { diff --git a/dashcaddy-api/platform-paths.js b/dashcaddy-api/platform-paths.js index a2185fc..2ed0702 100644 --- a/dashcaddy-api/platform-paths.js +++ b/dashcaddy-api/platform-paths.js @@ -11,6 +11,16 @@ const CADDY_BASE = process.env.CADDY_BASE || (isWindows ? 'C:/caddy' : '/etc/das const DOCKER_DATA = process.env.DOCKER_DATA || (isWindows ? 'E:/dockerdata' : '/opt/dockerdata'); const CADDY_SITES = process.env.CADDY_SITES || path.join(CADDY_BASE, 'sites'); +// Runtime state must not default beside source modules: those paths move whenever +// files are reorganized and are not mounted in production containers. Derive a +// stable data directory from the canonical services file instead. This supports +// both current /app/data mounts and legacy /app single-file mounts without +// requiring per-module environment variables. +const SERVICES_FILE = process.env.SERVICES_FILE || path.join(CADDY_BASE, 'services.json'); +const DATA_DIR = process.env.DATA_DIR || path.dirname(SERVICES_FILE); +const CONFIG_FILE = process.env.CONFIG_FILE || path.join(DATA_DIR, 'config.json'); +const DNS_CREDENTIALS_FILE = process.env.DNS_CREDENTIALS_FILE || path.join(DATA_DIR, 'dns-credentials.json'); + // Caddy PKI certificates const CADDY_PKI = process.env.CADDY_PKI || (isWindows ? 'C:/caddy/certs/pki/authorities/local' @@ -27,9 +37,10 @@ const paths = { caddyAdminUrl: process.env.CADDY_ADMIN_URL || (isWindows ? 'http://host.docker.internal:2019' : 'http://localhost:2019'), // Service config files - servicesFile: process.env.SERVICES_FILE || path.join(CADDY_BASE, 'services.json'), - configFile: process.env.CONFIG_FILE || path.join(CADDY_BASE, 'config.json'), - dnsCredentialsFile: process.env.DNS_CREDENTIALS_FILE || path.join(CADDY_BASE, 'dns-credentials.json'), + servicesFile: SERVICES_FILE, + configFile: CONFIG_FILE, + dnsCredentialsFile: DNS_CREDENTIALS_FILE, + dataDir: DATA_DIR, // CA certificate paths caCertDir: path.join(CADDY_SITES, 'ca'), diff --git a/dashcaddy-api/src/config/paths.js b/dashcaddy-api/src/config/paths.js index f493fcc..39284c8 100644 --- a/dashcaddy-api/src/config/paths.js +++ b/dashcaddy-api/src/config/paths.js @@ -13,8 +13,8 @@ const DNS_CREDENTIALS_FILE = process.env.DNS_CREDENTIALS_FILE || path.join(SERVI 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 ERROR_LOG_FILE = process.env.ERROR_LOG_FILE || path.join(platformPaths.dataDir, 'error.log'); +const LICENSE_SECRET_FILE = process.env.LICENSE_SECRET_FILE || path.join(platformPaths.dataDir, '.license-secret'); const BROWSE_ROOTS = (process.env.MEDIA_BROWSE_ROOTS || '') .split(',') diff --git a/dashcaddy-api/src/managers/credential-manager.js b/dashcaddy-api/src/managers/credential-manager.js index 2de53a5..1c29713 100644 --- a/dashcaddy-api/src/managers/credential-manager.js +++ b/dashcaddy-api/src/managers/credential-manager.js @@ -9,24 +9,16 @@ const cryptoUtils = require('../security/crypto-utils'); const lockfile = require('proper-lockfile'); const fs = require('fs'); const path = require('path'); +const platformPaths = require('../../platform-paths'); -// Resolve credentials file path — supports both standard install (/app/credentials.json) -// and custom deployments with consolidated data directory (/app/data/credentials.json) +// Resolve credentials alongside the canonical services/config state. This supports +// both current /app/data mounts and legacy /app single-file mounts, and remains +// stable if the module moves within src/. function resolveCredentialsFile() { if (process.env.CREDENTIALS_FILE) { return process.env.CREDENTIALS_FILE; } - const candidates = [ - path.join(__dirname, 'credentials.json'), - path.join(__dirname, 'data', 'credentials.json'), - ]; - for (const candidate of candidates) { - if (fs.existsSync(candidate)) { - return candidate; - } - } - // No existing file — return standard path so first store() creates it there - return candidates[0]; + return path.join(platformPaths.dataDir, 'credentials.json'); } const CREDENTIALS_FILE = resolveCredentialsFile(); diff --git a/dashcaddy-api/src/managers/license-keygen.js b/dashcaddy-api/src/managers/license-keygen.js index 24761c9..ef2796b 100644 --- a/dashcaddy-api/src/managers/license-keygen.js +++ b/dashcaddy-api/src/managers/license-keygen.js @@ -15,9 +15,10 @@ const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); +const platformPaths = require('../../platform-paths'); // Master secret file — lives only on admin machine, NEVER shipped -const SECRET_FILE = path.join(__dirname, '.license-secret'); +const SECRET_FILE = process.env.LICENSE_SECRET_FILE || path.join(platformPaths.dataDir, '.license-secret'); // License code format: DC-AAAAA-BBBBB-CCCCC-DDDDD // Encodes: version(4bit) + duration_days(12bit) + code_id(32bit) + created_ts(32bit) + hmac(48bit) @@ -259,7 +260,7 @@ Valid durations: ${VALID_DURATIONS.join(', ')} days const count = countIndex !== -1 ? parseInt(args[countIndex + 1]) : 1; // Load or create counter file for auto-incrementing code IDs - const counterFile = path.join(__dirname, '.license-counter'); + const counterFile = process.env.LICENSE_COUNTER_FILE || path.join(platformPaths.dataDir, '.license-counter'); let startId; const startIdIndex = args.indexOf('--start-id'); if (startIdIndex !== -1) { diff --git a/dashcaddy-api/src/managers/port-lock-manager.js b/dashcaddy-api/src/managers/port-lock-manager.js index 29528e4..543f9f5 100644 --- a/dashcaddy-api/src/managers/port-lock-manager.js +++ b/dashcaddy-api/src/managers/port-lock-manager.js @@ -7,8 +7,9 @@ const fs = require('fs'); const path = require('path'); const lockfile = require('proper-lockfile'); +const platformPaths = require('../../platform-paths'); -const LOCK_DIR = path.join(__dirname, '.port-locks'); +const LOCK_DIR = process.env.PORT_LOCK_DIR || path.join(platformPaths.dataDir, '.port-locks'); const LOCK_TIMEOUT = 120000; // 2 minutes const LOCK_STALE_THRESHOLD = 120000; // 2 minutes const LOCK_RETRY_OPTIONS = { diff --git a/dashcaddy-api/src/managers/resource-monitor.js b/dashcaddy-api/src/managers/resource-monitor.js index d81d7cf..9ae480a 100644 --- a/dashcaddy-api/src/managers/resource-monitor.js +++ b/dashcaddy-api/src/managers/resource-monitor.js @@ -8,15 +8,16 @@ const Docker = require('dockerode'); const EventEmitter = require('events'); const fs = require('fs'); const path = require('path'); +const platformPaths = require('../../platform-paths'); const docker = new Docker(); // Configuration -const STATS_FILE = process.env.STATS_FILE || path.join(__dirname, 'container-stats.json'); -const STATS_HOURLY_FILE = process.env.STATS_HOURLY_FILE || path.join(__dirname, 'container-stats-hourly.json'); -const STATS_DAILY_FILE = process.env.STATS_DAILY_FILE || path.join(__dirname, 'container-stats-daily.json'); -const ALERT_CONFIG_FILE = process.env.ALERT_CONFIG_FILE || path.join(__dirname, 'alert-config.json'); -const ALERT_HISTORY_FILE = process.env.ALERT_HISTORY_FILE || path.join(__dirname, 'alert-history.json'); +const STATS_FILE = process.env.STATS_FILE || path.join(platformPaths.dataDir, 'container-stats.json'); +const STATS_HOURLY_FILE = process.env.STATS_HOURLY_FILE || path.join(platformPaths.dataDir, 'container-stats-hourly.json'); +const STATS_DAILY_FILE = process.env.STATS_DAILY_FILE || path.join(platformPaths.dataDir, 'container-stats-daily.json'); +const ALERT_CONFIG_FILE = process.env.ALERT_CONFIG_FILE || path.join(platformPaths.dataDir, 'alert-config.json'); +const ALERT_HISTORY_FILE = process.env.ALERT_HISTORY_FILE || path.join(platformPaths.dataDir, 'alert-history.json'); const STATS_RETENTION_HOURS = parseInt(process.env.STATS_RETENTION_HOURS || '168', 10); // 7 days raw const STATS_HOURLY_RETENTION_DAYS = parseInt(process.env.STATS_HOURLY_RETENTION_DAYS || '30', 10); // 30 days hourly const STATS_DAILY_RETENTION_DAYS = parseInt(process.env.STATS_DAILY_RETENTION_DAYS || '365', 10); // 365 days daily diff --git a/dashcaddy-api/src/managers/update-manager.js b/dashcaddy-api/src/managers/update-manager.js index 384359e..4ecd4f8 100644 --- a/dashcaddy-api/src/managers/update-manager.js +++ b/dashcaddy-api/src/managers/update-manager.js @@ -9,11 +9,12 @@ const EventEmitter = require('events'); const fs = require('fs'); const path = require('path'); const https = require('https'); +const platformPaths = require('../../platform-paths'); const docker = new Docker(); -const UPDATE_CONFIG_FILE = process.env.UPDATE_CONFIG_FILE || path.join(__dirname, 'update-config.json'); -const UPDATE_HISTORY_FILE = process.env.UPDATE_HISTORY_FILE || path.join(__dirname, 'update-history.json'); +const UPDATE_CONFIG_FILE = process.env.UPDATE_CONFIG_FILE || path.join(platformPaths.dataDir, 'update-config.json'); +const UPDATE_HISTORY_FILE = process.env.UPDATE_HISTORY_FILE || path.join(platformPaths.dataDir, 'update-history.json'); const CHECK_INTERVAL = parseInt(process.env.UPDATE_CHECK_INTERVAL || '3600000', 10); // 1 hour class UpdateManager extends EventEmitter { diff --git a/dashcaddy-api/src/recipes/bundled-workflows.js b/dashcaddy-api/src/recipes/bundled-workflows.js index 142ef1a..aea5d04 100644 --- a/dashcaddy-api/src/recipes/bundled-workflows.js +++ b/dashcaddy-api/src/recipes/bundled-workflows.js @@ -8,9 +8,10 @@ const EventEmitter = require('events'); const fs = require('fs'); const path = require('path'); +const platformPaths = require('../../platform-paths'); -const WORKFLOWS_FILE = process.env.WORKFLOWS_FILE || path.join(__dirname, 'workflows-config.json'); -const WORKFLOW_HISTORY_FILE = process.env.WORKFLOW_HISTORY_FILE || path.join(__dirname, 'workflow-history.json'); +const WORKFLOWS_FILE = process.env.WORKFLOWS_FILE || path.join(platformPaths.dataDir, 'workflows-config.json'); +const WORKFLOW_HISTORY_FILE = process.env.WORKFLOW_HISTORY_FILE || path.join(platformPaths.dataDir, 'workflow-history.json'); /** * Bundled workflow templates diff --git a/dashcaddy-api/src/security/audit-logger.js b/dashcaddy-api/src/security/audit-logger.js index 36ef191..99e5f32 100644 --- a/dashcaddy-api/src/security/audit-logger.js +++ b/dashcaddy-api/src/security/audit-logger.js @@ -1,8 +1,9 @@ const path = require('path'); const StateManager = require('../managers/state-manager'); const crypto = require('crypto'); +const platformPaths = require('../../platform-paths'); -const AUDIT_LOG_FILE = process.env.AUDIT_LOG_FILE || path.join(__dirname, 'audit-log.json'); +const AUDIT_LOG_FILE = process.env.AUDIT_LOG_FILE || path.join(platformPaths.dataDir, 'audit-log.json'); const MAX_ENTRIES = parseInt(process.env.AUDIT_MAX_ENTRIES || '1000', 10); // Route path → readable action mapping diff --git a/dashcaddy-api/src/security/crypto-utils.js b/dashcaddy-api/src/security/crypto-utils.js index 28736c5..2137796 100644 --- a/dashcaddy-api/src/security/crypto-utils.js +++ b/dashcaddy-api/src/security/crypto-utils.js @@ -7,6 +7,7 @@ const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); +const platformPaths = require('../../platform-paths'); // Encryption settings const ALGORITHM = 'aes-256-gcm'; @@ -15,23 +16,14 @@ const IV_LENGTH = 16; // 128 bits for GCM const AUTH_TAG_LENGTH = 16; const SALT_LENGTH = 32; -// Resolve encryption key file path — supports both standard install (/app/.encryption-key) -// and custom deployments with consolidated data directory (/app/data/.encryption-key) +// Resolve the encryption key alongside the canonical services/config state. +// platformPaths.dataDir supports both current /app/data mounts and legacy /app +// single-file mounts, and does not change when this module moves within src/. function resolveKeyFile() { if (process.env.ENCRYPTION_KEY_FILE) { return process.env.ENCRYPTION_KEY_FILE; } - const candidates = [ - path.join(__dirname, '.encryption-key'), - path.join(__dirname, 'data', '.encryption-key'), - ]; - for (const candidate of candidates) { - if (fs.existsSync(candidate)) { - return candidate; - } - } - // No existing file — return standard path so first load creates it there - return candidates[0]; + return path.join(platformPaths.dataDir, '.encryption-key'); } const KEY_FILE = resolveKeyFile(); @@ -149,7 +141,7 @@ function loadOrCreateKey() { */ function tryFallbackToBackupKey(primaryKey, backupKey) { const CREDENTIALS_FILE = process.env.CREDENTIALS_FILE || - require('path').join(__dirname, 'credentials.json'); + path.join(platformPaths.dataDir, 'credentials.json'); if (!fs.existsSync(CREDENTIALS_FILE)) return primaryKey; let credentials; diff --git a/dashcaddy-api/src/security/docker-security.js b/dashcaddy-api/src/security/docker-security.js index 4a7df11..2451b2d 100644 --- a/dashcaddy-api/src/security/docker-security.js +++ b/dashcaddy-api/src/security/docker-security.js @@ -8,10 +8,11 @@ const fs = require('fs'); const path = require('path'); const https = require('https'); const Docker = require('dockerode'); +const platformPaths = require('../../platform-paths'); const docker = new Docker(); -const SECURITY_CONFIG_FILE = process.env.DOCKER_SECURITY_CONFIG || path.join(__dirname, 'docker-security-config.json'); +const SECURITY_CONFIG_FILE = process.env.DOCKER_SECURITY_CONFIG || path.join(platformPaths.dataDir, 'docker-security-config.json'); const VERIFICATION_MODE = process.env.DOCKER_VERIFICATION_MODE || 'verify'; // strict | verify | permissive class DockerSecurity { diff --git a/dashcaddy-api/src/security/event-store.js b/dashcaddy-api/src/security/event-store.js index 02f0a2e..640b4e8 100644 --- a/dashcaddy-api/src/security/event-store.js +++ b/dashcaddy-api/src/security/event-store.js @@ -29,7 +29,7 @@ const { EventEmitter } = require('events'); const platformPaths = require('../../platform-paths'); const EVENT_STORE_FILE = process.env.SECURITY_EVENT_LOG_FILE - || path.join(platformPaths.dataDir || path.join(__dirname, '../../data'), 'security-events.jsonl'); + || path.join(platformPaths.dataDir, 'security-events.jsonl'); const MAX_EVENTS_IN_MEMORY = parseInt(process.env.SECURITY_EVENT_MAX_MEMORY || '10000', 10); const MAX_EVENTS_ON_DISK = parseInt(process.env.SECURITY_EVENT_MAX_DISK || '100000', 10); diff --git a/dashcaddy-api/src/security/event-workers.js b/dashcaddy-api/src/security/event-workers.js index e2ad074..b41081c 100644 --- a/dashcaddy-api/src/security/event-workers.js +++ b/dashcaddy-api/src/security/event-workers.js @@ -39,6 +39,7 @@ const fs = require('fs'); const path = require('path'); const os = require('os'); +const platformPaths = require('../../platform-paths'); const { getStore } = require('./event-store'); const HOSTNAME = os.hostname(); @@ -126,7 +127,7 @@ function createTail({ filePath, stateFile, onLine, label = 'tail', pollMs = 1000 */ function startCaddyWorker({ log } = {}) { const caddyLog = process.env.CADDY_ACCESS_LOG || '/var/log/caddy/access.log'; - const stateFile = path.join(process.env.DATA_DIR || path.join(__dirname, '../../data'), '.caddy-tail-offset'); + const stateFile = path.join(platformPaths.dataDir, '.caddy-tail-offset'); const store = getStore({ log }); return createTail({ @@ -188,7 +189,7 @@ function startCaddyWorker({ log } = {}) { */ function startSharedBansWorker({ log } = {}) { const sbLog = process.env.SHARED_BANS_LOG || '/var/log/shared-bans-apply.log'; - const stateFile = path.join(process.env.DATA_DIR || path.join(__dirname, '../../data'), '.sb-tail-offset'); + const stateFile = path.join(platformPaths.dataDir, '.sb-tail-offset'); const store = getStore({ log }); const APPLIED_RE = /Applied:\s+(\d+)\s+entries/; @@ -226,7 +227,7 @@ function startSharedBansWorker({ log } = {}) { */ function startFail2banWorker({ log } = {}) { const f2bLog = process.env.FAIL2BAN_LOG || '/var/log/fail2ban.log'; - const stateFile = path.join(process.env.DATA_DIR || path.join(__dirname, '../../data'), '.f2b-tail-offset'); + const stateFile = path.join(platformPaths.dataDir, '.f2b-tail-offset'); const store = getStore({ log }); // Match ISO timestamps followed by [jail] Ban/Unban IP diff --git a/dashcaddy-api/src/security/host-registry.js b/dashcaddy-api/src/security/host-registry.js index 42824c5..69f4c1d 100644 --- a/dashcaddy-api/src/security/host-registry.js +++ b/dashcaddy-api/src/security/host-registry.js @@ -31,7 +31,7 @@ const crypto = require('crypto'); const platformPaths = require('../../platform-paths'); const HOSTS_FILE = process.env.SECURITY_HOSTS_FILE - || path.join(platformPaths.dataDir || path.join(__dirname, '../../data'), 'security-hosts.json'); + || path.join(platformPaths.dataDir, 'security-hosts.json'); const KEY_PREFIX = 'dca_'; // DashCaddy Agent key prefix — easy to spot in logs @@ -94,7 +94,7 @@ class HostRegistry { * not leaking the file (file mode 0600, root-only). */ _pepper() { - const pepperFile = path.join(platformPaths.dataDir || path.join(__dirname, '../../data'), '.security-pepper'); + const pepperFile = path.join(platformPaths.dataDir, '.security-pepper'); try { if (fs.existsSync(pepperFile)) return fs.readFileSync(pepperFile, 'utf8').trim(); } catch {} diff --git a/dashcaddy-api/src/utilities/backup-manager.js b/dashcaddy-api/src/utilities/backup-manager.js index 2da5fd0..9bb7f51 100644 --- a/dashcaddy-api/src/utilities/backup-manager.js +++ b/dashcaddy-api/src/utilities/backup-manager.js @@ -8,6 +8,7 @@ const path = require('path'); const { execSync } = require('child_process'); const crypto = require('crypto'); const EventEmitter = require('events'); +const platformPaths = require('../../platform-paths'); // Format bytes to human readable string function formatBytes(bytes) { @@ -18,9 +19,9 @@ function formatBytes(bytes) { return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } -const BACKUP_CONFIG_FILE = process.env.BACKUP_CONFIG_FILE || path.join(__dirname, 'backup-config.json'); -const BACKUP_HISTORY_FILE = process.env.BACKUP_HISTORY_FILE || path.join(__dirname, 'backup-history.json'); -const DEFAULT_BACKUP_DIR = process.env.BACKUP_DIR || path.join(__dirname, 'backups'); +const BACKUP_CONFIG_FILE = process.env.BACKUP_CONFIG_FILE || path.join(platformPaths.dataDir, 'backup-config.json'); +const BACKUP_HISTORY_FILE = process.env.BACKUP_HISTORY_FILE || path.join(platformPaths.dataDir, 'backup-history.json'); +const DEFAULT_BACKUP_DIR = process.env.BACKUP_DIR || path.join(platformPaths.dataDir, 'backups'); class BackupManager extends EventEmitter { constructor() { @@ -257,7 +258,7 @@ class BackupManager extends EventEmitter { */ backupServices() { try { - const servicesFile = process.env.SERVICES_FILE || path.join(__dirname, 'services.json'); + const servicesFile = platformPaths.servicesFile; if (fs.existsSync(servicesFile)) { return JSON.parse(fs.readFileSync(servicesFile, 'utf8')); } @@ -272,7 +273,7 @@ class BackupManager extends EventEmitter { */ backupConfig() { try { - const configFile = process.env.CONFIG_FILE || path.join(__dirname, 'config.json'); + const configFile = platformPaths.configFile; if (fs.existsSync(configFile)) { return JSON.parse(fs.readFileSync(configFile, 'utf8')); } @@ -937,7 +938,7 @@ class BackupManager extends EventEmitter { * Restore services configuration */ restoreServices(services) { - const servicesFile = process.env.SERVICES_FILE || path.join(__dirname, 'services.json'); + const servicesFile = platformPaths.servicesFile; fs.writeFileSync(servicesFile, JSON.stringify(services, null, 2)); console.log('[BackupManager] Services restored'); } @@ -946,7 +947,7 @@ class BackupManager extends EventEmitter { * Restore configuration */ restoreConfig(config) { - const configFile = process.env.CONFIG_FILE || path.join(__dirname, 'config.json'); + const configFile = platformPaths.configFile; fs.writeFileSync(configFile, JSON.stringify(config, null, 2)); console.log('[BackupManager] Config restored'); } diff --git a/dashcaddy-api/src/utilities/error-handler.js b/dashcaddy-api/src/utilities/error-handler.js index 216968d..093db46 100644 --- a/dashcaddy-api/src/utilities/error-handler.js +++ b/dashcaddy-api/src/utilities/error-handler.js @@ -12,8 +12,9 @@ const { AppError } = require('./errors'); const { LIMITS } = require('./constants'); const { logError: unifiedLogError, safeErrorMessage } = require('../utils/logging'); const { errorResponse } = require('../utils/responses'); +const platformPaths = require('../../platform-paths'); -const ERROR_LOG_FILE = path.join(__dirname, 'error.log'); +const ERROR_LOG_FILE = process.env.ERROR_LOG_FILE || path.join(platformPaths.dataDir, 'error.log'); const MAX_ERROR_LOG_SIZE = LIMITS.ERROR_LOG_SIZE; /** diff --git a/dashcaddy-api/src/utils/logging.js b/dashcaddy-api/src/utils/logging.js index ae0c426..85d0bc9 100644 --- a/dashcaddy-api/src/utils/logging.js +++ b/dashcaddy-api/src/utils/logging.js @@ -18,10 +18,11 @@ const fsp = require('fs').promises; const path = require('path'); const crypto = require('crypto'); const EventEmitter = require('events'); +const platformPaths = require('../../platform-paths'); // ─── Configuration ────────────────────────────────────────────────────────── -const LOG_DIR = process.env.LOG_DIR || __dirname; +const LOG_DIR = process.env.LOG_DIR || platformPaths.dataDir; const ERROR_LOG_FILE = process.env.ERROR_LOG_FILE || path.join(LOG_DIR, 'error.log'); const AUDIT_LOG_FILE = process.env.AUDIT_LOG_FILE || path.join(LOG_DIR, 'audit-log.json'); const MAX_ERROR_LOG_SIZE = 5 * 1024 * 1024; // 5 MB