diff --git a/dashcaddy-api/src/docker/self-updater.js b/dashcaddy-api/src/docker/self-updater.js index 0da2101..4023b70 100644 --- a/dashcaddy-api/src/docker/self-updater.js +++ b/dashcaddy-api/src/docker/self-updater.js @@ -10,6 +10,7 @@ const EventEmitter = require('events'); const https = require('https'); const http = require('http'); +const { log } = require('../utils/logging'); const fs = require('fs'); const fsp = require('fs').promises; const path = require('path'); @@ -86,7 +87,7 @@ class SelfUpdater extends EventEmitter { start() { if (!this.config.enabled || this.checkTimer) return; - console.log('[SelfUpdater] Starting auto-update checks every %ds', this.config.checkInterval / 1000); + log.info('updater', 'Starting auto-update checks', { intervalMs: this.config.checkInterval }); // First check after a short delay (let server finish startup) setTimeout(() => { @@ -124,7 +125,7 @@ class SelfUpdater extends EventEmitter { return { version: pkg.version, commit }; } catch { /* try next candidate */ } } - console.error('[SelfUpdater] getLocalVersion failed: no candidate package.json found'); + log.error('updater', 'getLocalVersion failed: no candidate package.json found'); return { version: '0.0.0', commit: null }; } @@ -158,7 +159,7 @@ class SelfUpdater extends EventEmitter { // Fire-and-forget; the response shouldn't block on the container rebuild. setImmediate(() => { this._autoCheckAndApply().catch(err => - console.error('[SelfUpdater] %s-triggered update error: %s', triggeredBy, err.message) + log.error('updater', err, { triggeredBy }) ); }); return { accepted: true, triggeredBy }; @@ -174,7 +175,7 @@ class SelfUpdater extends EventEmitter { try { remote = await this._fetchJson(`${this.config.updateUrl}/version.json`); } catch (primaryErr) { - console.warn('[SelfUpdater] Primary server failed:', primaryErr.message, '— trying mirror'); + log.warn('updater', 'Primary server failed, trying mirror', { error: primaryErr.message }); try { remote = await this._fetchJson(`${this.config.mirrorUrl}/version.json`); sourceUrl = this.config.mirrorUrl; @@ -240,7 +241,7 @@ class SelfUpdater extends EventEmitter { try { await this._downloadFile(primaryUrl, tarballPath); } catch (dlErr) { - console.warn('[SelfUpdater] Primary download failed:', dlErr.message, '— trying mirror'); + log.warn('updater', 'Primary download failed, trying mirror', { error: dlErr.message }); // Ensure file is fully cleaned up before mirror attempt try { fs.unlinkSync(tarballPath); } catch { /* ignore */ } await this._downloadFile(mirrorUrl, tarballPath); @@ -468,11 +469,11 @@ class SelfUpdater extends EventEmitter { try { const result = await this.checkForUpdate(); if (result.available && result.remote) { - console.log('[SelfUpdater] Update available: %s → %s', result.local.version, result.remote.version); + log.info('updater', 'Update available', { localVersion: result.local.version, remoteVersion: result.remote.version }); await this.applyUpdate(result.remote); } } catch (e) { - console.error('[SelfUpdater] Auto-update error:', e.message); + log.error('updater', e, { phase: 'autoUpdate' }); } } @@ -606,7 +607,7 @@ class SelfUpdater extends EventEmitter { fs.mkdirSync(path.dirname(this.notifySecretFile), { recursive: true }); fs.writeFileSync(this.notifySecretFile, `${secret}\n`, { mode: 0o600 }); } catch (error) { - console.warn('[SelfUpdater] Failed to persist notify secret:', error.message); + log.warn('updater', 'Failed to persist notify secret', { error: error.message }); } return secret; } @@ -626,7 +627,7 @@ class SelfUpdater extends EventEmitter { fs.mkdirSync(path.dirname(this.config.instanceIdFile), { recursive: true }); fs.writeFileSync(this.config.instanceIdFile, `${instanceId}\n`, 'utf8'); } catch (error) { - console.warn('[SelfUpdater] Failed to persist instance ID:', error.message); + log.warn('updater', 'Failed to persist instance ID', { error: error.message }); } return instanceId; } @@ -644,7 +645,7 @@ class SelfUpdater extends EventEmitter { try { fs.writeFileSync(historyPath, JSON.stringify(history, null, 2)); } catch (e) { - console.error('[SelfUpdater] Failed to save history:', e.message); + log.error('updater', e, { operation: 'saveHistory' }); } } diff --git a/dashcaddy-api/src/managers/port-lock-manager.js b/dashcaddy-api/src/managers/port-lock-manager.js index 543f9f5..d52ce39 100644 --- a/dashcaddy-api/src/managers/port-lock-manager.js +++ b/dashcaddy-api/src/managers/port-lock-manager.js @@ -8,6 +8,7 @@ const fs = require('fs'); const path = require('path'); const lockfile = require('proper-lockfile'); const platformPaths = require('../../platform-paths'); +const { log } = require('../utils/logging'); const LOCK_DIR = process.env.PORT_LOCK_DIR || path.join(platformPaths.dataDir, '.port-locks'); const LOCK_TIMEOUT = 120000; // 2 minutes @@ -35,7 +36,7 @@ class PortLockManager { ensureLockDirectory() { if (!fs.existsSync(LOCK_DIR)) { fs.mkdirSync(LOCK_DIR, { recursive: true }); - console.log('[PortLockManager] Created lock directory:', LOCK_DIR); + log.info('portlock', 'Created lock directory', { dir: LOCK_DIR }); } } @@ -63,7 +64,7 @@ class PortLockManager { const releaseFunctions = []; try { - console.log(`[PortLockManager] Acquiring locks for ports: ${sortedPorts.join(', ')}`); + log.info('portlock', 'Acquiring locks', { ports: sortedPorts }); // Acquire locks in sorted order to prevent deadlocks for (const port of sortedPorts) { @@ -83,7 +84,7 @@ class PortLockManager { acquiredLocks.push(port); releaseFunctions.push(release); - console.log(`[PortLockManager] Locked port ${port}`); + log.info('portlock', 'Locked port', { port }); } // Store lock information @@ -93,18 +94,18 @@ class PortLockManager { timestamp: Date.now() }); - console.log(`[PortLockManager] Successfully acquired all locks (ID: ${lockId})`); + log.info('portlock', 'Acquired all locks', { lockId }); return lockId; } catch (error) { // Release any locks we managed to acquire - console.error(`[PortLockManager] Failed to acquire all locks:`, error.message); + log.error('portlock', error, { operation: 'acquire', lockId }); for (const release of releaseFunctions) { try { await release(); } catch (releaseError) { - console.error(`[PortLockManager] Error releasing lock during cleanup:`, releaseError.message); + log.error('portlock', releaseError, { operation: 'releaseCleanup', lockId }); } } @@ -120,11 +121,11 @@ class PortLockManager { const lockInfo = this.activeLocks.get(lockId); if (!lockInfo) { - console.warn(`[PortLockManager] Lock ID ${lockId} not found (may have been released already)`); + log.warn('portlock', 'Lock ID not found', { lockId }); return; } - console.log(`[PortLockManager] Releasing locks for ports: ${lockInfo.ports.join(', ')}`); + log.info('portlock', 'Releasing locks', { lockId, ports: lockInfo.ports }); const errors = []; @@ -133,16 +134,16 @@ class PortLockManager { await release(); } catch (error) { errors.push(error.message); - console.error(`[PortLockManager] Error releasing lock:`, error.message); + log.error('portlock', error, { operation: 'release', lockId }); } } this.activeLocks.delete(lockId); if (errors.length > 0) { - console.warn(`[PortLockManager] Released locks with ${errors.length} errors`); + log.warn('portlock', 'Released locks with errors', { lockId, errorCount: errors.length }); } else { - console.log(`[PortLockManager] Successfully released all locks (ID: ${lockId})`); + log.info('portlock', 'Released all locks', { lockId }); } } @@ -151,7 +152,7 @@ class PortLockManager { * Removes locks older than LOCK_STALE_THRESHOLD */ async cleanupStaleLocks() { - console.log('[PortLockManager] Cleaning up stale locks...'); + log.info('portlock', 'Cleaning up stale locks'); this.ensureLockDirectory(); @@ -174,20 +175,20 @@ class PortLockManager { // Lock is stale or not locked, safe to remove fs.unlinkSync(lockFilePath); cleaned++; - console.log(`[PortLockManager] Removed stale lock: ${file}`); + log.info('portlock', 'Removed stale lock', { file }); } } catch (error) { // File might not exist or might have been removed by another process if (error.code !== 'ENOENT') { errors++; - console.warn(`[PortLockManager] Error checking lock ${file}:`, error.message); + log.warn('portlock', 'Error checking lock', { file, error: error.message }); } } } - console.log(`[PortLockManager] Cleanup complete: ${cleaned} stale locks removed, ${errors} errors`); + log.info('portlock', 'Cleanup complete', { cleaned, errors }); } catch (error) { - console.error('[PortLockManager] Error during cleanup:', error.message); + log.error('portlock', error, { operation: 'cleanup' }); } } diff --git a/dashcaddy-api/src/security/crypto-utils.js b/dashcaddy-api/src/security/crypto-utils.js index 2137796..e27df30 100644 --- a/dashcaddy-api/src/security/crypto-utils.js +++ b/dashcaddy-api/src/security/crypto-utils.js @@ -8,6 +8,7 @@ const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); const platformPaths = require('../../platform-paths'); +const { log } = require('../utils/logging'); // Encryption settings const ALGORITHM = 'aes-256-gcm'; @@ -65,7 +66,7 @@ function loadOrCreateKey() { // Check for key in environment variable first if (process.env.DASHCADDY_ENCRYPTION_KEY) { encryptionKey = Buffer.from(process.env.DASHCADDY_ENCRYPTION_KEY, 'hex'); - console.log('[Crypto] Using encryption key from environment variable'); + log.info('crypto', 'Using encryption key from environment variable'); return encryptionKey; } @@ -75,16 +76,16 @@ function loadOrCreateKey() { const keyData = fs.readFileSync(KEY_FILE, 'utf8').trim(); if (keyData.length >= 64) { encryptionKey = Buffer.from(keyData, 'hex'); - console.log('[Crypto] Loaded encryption key from file'); + log.info('crypto', 'Loaded encryption key from file'); // First-run bootstrap: if .bak doesn't exist yet, write the current // key to it. This ensures the silent recovery path is available from // the very next restart without requiring an explicit rotateKey(). if (!fs.existsSync(KEY_FILE + '.bak')) { try { fs.writeFileSync(KEY_FILE + '.bak', keyData, { mode: 0o600 }); - console.log(`[Crypto] Seeded ${KEY_FILE}.bak with current key for future fallback`); + log.info('crypto', 'Seeded .bak key file for future fallback'); } catch (e) { - console.warn('[Crypto] Could not seed .bak key file:', e.message); + log.warn('crypto', 'Could not seed .bak key file', { error: e.message }); } } // Try fallback to .bak key if primary can't decrypt existing credentials. @@ -98,14 +99,14 @@ function loadOrCreateKey() { encryptionKey = tryFallbackToBackupKey(Buffer.from(keyData, 'hex'), Buffer.from(backupData, 'hex')); } } catch (e) { - console.warn('[Crypto] Could not check backup key:', e.message); + log.warn('crypto', 'Could not check backup key', { error: e.message }); } } return encryptionKey; } // File exists but key is invalid/empty - will generate new one below } catch (error) { - console.error('[Crypto] Error loading key file:', error.message); + log.error('crypto', error, { operation: 'loadKey' }); } } @@ -115,10 +116,10 @@ function loadOrCreateKey() { try { // Save key to file with restricted permissions fs.writeFileSync(KEY_FILE, encryptionKey.toString('hex'), { mode: 0o600 }); - console.log('[Crypto] Generated and saved new encryption key'); + log.info('crypto', 'Generated and saved new encryption key'); } catch (error) { - console.warn('[Crypto] Could not save key to file:', error.message); - console.warn('[Crypto] Key will be regenerated on restart - credentials will need to be re-entered'); + log.warn('crypto', 'Could not save key to file', { error: error.message }); + log.warn('crypto', 'Key will be regenerated on restart - credentials will need to be re-entered'); } return encryptionKey; @@ -171,12 +172,7 @@ function tryFallbackToBackupKey(primaryKey, backupKey) { if (tryDecrypt(primaryKey)) return primaryKey; if (tryDecrypt(backupKey)) { - console.warn( - '[Crypto] Primary encryption key failed to decrypt credentials; ' + - 'fell back to .encryption-key.bak. The current primary key was set ' + - 'without preserving the original. Consider rotating the key explicitly ' + - 'via the credential-manager API to avoid this warning next restart.' - ); + log.warn('crypto', 'Primary encryption key failed to decrypt credentials; fell back to .encryption-key.bak. Consider rotating the key explicitly via the credential-manager API.'); return backupKey; } return primaryKey; // neither works — credential-manager.diagnose() will report 'unreadable' @@ -291,7 +287,7 @@ function decryptFields(obj, fields = null) { try { result[field] = decrypt(result[field]); } catch (error) { - console.error(`[Crypto] Failed to decrypt field '${field}':`, error.message); + log.error('crypto', error, { field, operation: 'decryptField' }); // Leave the field as-is if decryption fails } } @@ -315,7 +311,7 @@ function migrateToEncrypted(credentials, sensitiveFields) { return credentials; // Already encrypted } - console.log('[Crypto] Migrating plaintext credentials to encrypted format'); + log.info('crypto', 'Migrating plaintext credentials to encrypted format'); return encryptFields(credentials, sensitiveFields); } @@ -340,10 +336,10 @@ function readEncryptedFile(filePath, sensitiveFields = ['password', 'token', 'ap } // Plain text data - migrate it - console.log(`[Crypto] Found plaintext data in ${filePath}, will encrypt on next save`); + log.info('crypto', 'Found plaintext data', { filePath }); return parsed; } catch (error) { - console.error(`[Crypto] Error reading ${filePath}:`, error.message); + log.error('crypto', error, { filePath, operation: 'readFile' }); return null; } } @@ -357,7 +353,7 @@ function readEncryptedFile(filePath, sensitiveFields = ['password', 'token', 'ap function writeEncryptedFile(filePath, credentials, sensitiveFields = ['password', 'token', 'apiKey', 'secret']) { const encrypted = encryptFields(credentials, sensitiveFields); fs.writeFileSync(filePath, JSON.stringify(encrypted, null, 2), 'utf8'); - console.log(`[Crypto] Saved encrypted credentials to ${filePath}`); + log.info('crypto', 'Saved encrypted credentials', { filePath }); } /** @@ -377,7 +373,7 @@ function rotateKey() { try { fs.writeFileSync(KEY_FILE + '.bak', oldKey.toString('hex'), { mode: 0o600 }); } catch (error) { - console.warn(`[Crypto] Could not save backup key to ${KEY_FILE}.bak:`, error.message); + log.warn('crypto', 'Could not save backup key', { error: error.message }); } try { diff --git a/dashcaddy-api/src/security/docker-security.js b/dashcaddy-api/src/security/docker-security.js index 2451b2d..4a4e06e 100644 --- a/dashcaddy-api/src/security/docker-security.js +++ b/dashcaddy-api/src/security/docker-security.js @@ -9,6 +9,7 @@ const path = require('path'); const https = require('https'); const Docker = require('dockerode'); const platformPaths = require('../../platform-paths'); +const { log } = require('../utils/logging'); const docker = new Docker(); @@ -19,7 +20,7 @@ class DockerSecurity { constructor() { this.config = this.loadConfig(); this.mode = VERIFICATION_MODE; - console.log(`[DockerSecurity] Initialized in ${this.mode} mode`); + log.info('security', 'Docker security initialized', { mode: this.mode }); } /** @@ -32,7 +33,7 @@ class DockerSecurity { return JSON.parse(data); } } catch (error) { - console.warn(`[DockerSecurity] Failed to load config: ${error.message}`); + log.warn('security', 'Failed to load config', { error: error.message }); } // Default configuration @@ -51,7 +52,7 @@ class DockerSecurity { try { fs.writeFileSync(SECURITY_CONFIG_FILE, JSON.stringify(this.config, null, 2)); } catch (error) { - console.error(`[DockerSecurity] Failed to save config: ${error.message}`); + log.error('security', error, { operation: 'saveConfig' }); } } @@ -110,7 +111,7 @@ class DockerSecurity { repository = repository.split(':')[0]; } - console.log(`[DockerSecurity] Fetching manifest for ${registry}/${repository}:${tag}`); + log.info('security', 'Fetching manifest', { registry, repository, tag }); return new Promise((resolve, reject) => { const isDockerHub = registry === 'registry-1.docker.io'; @@ -216,7 +217,7 @@ class DockerSecurity { if (this.config.updateTrustedOnPull) { this.config.trustedDigests[imageName] = actualDigest; this.saveConfig(); - console.log(`[DockerSecurity] Added trusted digest for ${imageName}`); + log.info('security', 'Added trusted digest', { imageName }); } } } else if (actualDigest === trustedDigest) { @@ -250,26 +251,26 @@ class DockerSecurity { * @returns {Promise} Verification result */ async verifyPulledImage(imageName) { - console.log(`[DockerSecurity] Verifying image: ${imageName}`); + log.info('security', 'Verifying image', { imageName }); try { const actualDigest = await this.getImageDigest(imageName); const result = await this.verifyImageDigest(imageName, actualDigest); if (result.action === 'reject') { - console.error(`[DockerSecurity] REJECTED: ${result.reason}`); + log.error('security', 'Image REJECTED', { imageName, reason: result.reason }); throw new Error(`Image verification failed: ${result.reason}`); } else if (result.action === 'warn') { - console.warn(`[DockerSecurity] WARNING: ${result.reason}`); - console.warn(`[DockerSecurity] Expected: ${result.trustedDigest}`); - console.warn(`[DockerSecurity] Actual: ${result.actualDigest}`); + log.warn('security', 'Image WARNING', { imageName, reason: result.reason }); + log.warn('security', 'Expected digest', { imageName, digest: result.trustedDigest }); + log.warn('security', 'Actual digest', { imageName, digest: result.actualDigest }); } else { - console.log(`[DockerSecurity] ACCEPTED: ${result.reason}`); + log.info('security', 'Image ACCEPTED', { imageName, reason: result.reason }); } return result; } catch (error) { - console.error(`[DockerSecurity] Verification error: ${error.message}`); + log.error('security', error, { imageName, operation: 'verify' }); if (this.mode === 'strict') { throw error; @@ -294,7 +295,7 @@ class DockerSecurity { setTrustedDigest(imageName, digest) { this.config.trustedDigests[imageName] = digest; this.saveConfig(); - console.log(`[DockerSecurity] Updated trusted digest for ${imageName}`); + log.info('security', 'Updated trusted digest', { imageName }); } /** @@ -304,7 +305,7 @@ class DockerSecurity { removeTrustedDigest(imageName) { delete this.config.trustedDigests[imageName]; this.saveConfig(); - console.log(`[DockerSecurity] Removed trusted digest for ${imageName}`); + log.info('security', 'Removed trusted digest', { imageName }); } /** @@ -325,7 +326,7 @@ class DockerSecurity { this.mode = mode; this.config.verificationMode = mode; this.saveConfig(); - console.log(`[DockerSecurity] Verification mode set to: ${mode}`); + log.info('security', 'Verification mode set', { mode }); } /** diff --git a/dashcaddy-api/src/security/event-workers.js b/dashcaddy-api/src/security/event-workers.js index b41081c..8cc5eea 100644 --- a/dashcaddy-api/src/security/event-workers.js +++ b/dashcaddy-api/src/security/event-workers.js @@ -37,6 +37,7 @@ */ const fs = require('fs'); +const { log } = require('../utils/logging'); const path = require('path'); const os = require('os'); const platformPaths = require('../../platform-paths'); @@ -95,7 +96,7 @@ function createTail({ filePath, stateFile, onLine, label = 'tail', pollMs = 1000 for (const line of lines) { if (line.trim()) { try { onLine(line); } catch (e) { - console.error(`[${label}] onLine threw:`, e.message); + log.error('events', e, { worker: label, phase: 'onLine' }); } } } @@ -106,7 +107,7 @@ function createTail({ filePath, stateFile, onLine, label = 'tail', pollMs = 1000 setTimeout(tick, pollMs); }); stream.on('error', (e) => { - console.error(`[${label}] read error:`, e.message); + log.error('events', e, { worker: label, phase: 'read' }); setTimeout(tick, pollMs * 5); }); }); @@ -267,11 +268,11 @@ function startFail2banWorker({ log } = {}) { function startAll({ log } = {}) { const workers = []; try { workers.push(startCaddyWorker({ log })); } - catch (e) { console.error('[workers] caddy worker failed to start:', e.message); } + catch (e) { log.error('events', e, { worker: 'caddy', phase: 'start' }); } try { workers.push(startSharedBansWorker({ log })); } - catch (e) { console.error('[workers] shared_bans worker failed to start:', e.message); } + catch (e) { log.error('events', e, { worker: 'shared_bans', phase: 'start' }); } try { workers.push(startFail2banWorker({ log })); } - catch (e) { console.error('[workers] fail2ban worker failed to start:', e.message); } + catch (e) { log.error('events', e, { worker: 'fail2ban', phase: 'start' }); } return { stop() { workers.forEach(w => { try { w.stop(); } catch {} }); }, workers, diff --git a/dashcaddy-api/src/security/keychain-manager.js b/dashcaddy-api/src/security/keychain-manager.js index 66f5908..29d9e82 100644 --- a/dashcaddy-api/src/security/keychain-manager.js +++ b/dashcaddy-api/src/security/keychain-manager.js @@ -7,6 +7,7 @@ const { execSync, execFileSync } = require('child_process'); const os = require('os'); const crypto = require('crypto'); +const { log } = require('../utils/logging'); const SERVICE_NAME = 'DashCaddy'; const ACCOUNT_PREFIX = 'dashcaddy'; @@ -44,7 +45,7 @@ class KeychainManager { } return false; } catch { - console.warn('[Keychain] OS keychain not available, will use encrypted file storage'); + log.warn('keychain', 'OS keychain not available, will use encrypted file storage'); return false; } } @@ -72,7 +73,7 @@ class KeychainManager { } return false; } catch (error) { - console.error(`[Keychain] Failed to store ${key}:`, error.message); + log.error('keychain', error, { key, operation: 'store' }); return false; } } @@ -99,7 +100,7 @@ class KeychainManager { } return null; } catch (error) { - console.error(`[Keychain] Failed to retrieve ${key}:`, error.message); + log.error('keychain', error, { key, operation: 'retrieve' }); return null; } } @@ -126,7 +127,7 @@ class KeychainManager { } return false; } catch (error) { - console.error(`[Keychain] Failed to delete ${key}:`, error.message); + log.error('keychain', error, { key, operation: 'delete' }); return false; } }