diff --git a/dashcaddy-api/.eslintrc.js b/dashcaddy-api/.eslintrc.js index 0f0b03a..4fca5af 100644 --- a/dashcaddy-api/.eslintrc.js +++ b/dashcaddy-api/.eslintrc.js @@ -53,5 +53,16 @@ module.exports = { 'max-depth': 'off', }, }, + { + // Frontend assets use browser globals + files: ['assets/**/*.js', 'frontend/**/*.js'], + env: { + browser: true, + es2021: true, + }, + rules: { + 'no-undef': 'warn', + }, + }, ], }; diff --git a/dashcaddy-api/assets/theme-adapter.js b/dashcaddy-api/assets/theme-adapter.js index f58248f..16a503b 100644 --- a/dashcaddy-api/assets/theme-adapter.js +++ b/dashcaddy-api/assets/theme-adapter.js @@ -1,308 +1,308 @@ -/** - * Theme Adapter - * Ensures tooltips match the current dashboard theme - * Integrates with Driver.js to apply theme-specific styling - */ - -(function(window) { - 'use strict'; - - /** - * Theme configuration mapping for Driver.js - * Maps dashboard themes to Driver.js styling - */ - const THEME_CONFIGS = { - dark: { - backgroundColor: 'var(--card-base)', - textColor: 'var(--fg)', - primaryColor: 'var(--accent)', - overlayColor: 'rgba(0, 0, 0, 0.7)', - borderColor: 'var(--border)', - highlightColor: 'var(--accent)', - fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" - }, - light: { - backgroundColor: 'var(--card-base)', - textColor: 'var(--fg)', - primaryColor: 'var(--accent-strong)', - overlayColor: 'rgba(0, 0, 0, 0.5)', - borderColor: 'var(--border)', - highlightColor: 'var(--accent-strong)', - fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" - }, - blue: { - backgroundColor: 'var(--card-base)', - textColor: 'var(--fg)', - primaryColor: 'var(--accent)', - overlayColor: 'rgba(25, 8, 172, 0.7)', - borderColor: 'var(--border)', - highlightColor: 'var(--accent)', - fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" - }, - nord: { - backgroundColor: 'var(--card-base)', - textColor: 'var(--fg)', - primaryColor: 'var(--accent)', - overlayColor: 'rgba(46, 52, 64, 0.7)', - borderColor: 'var(--border)', - highlightColor: 'var(--accent)', - fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" - }, - dracula: { - backgroundColor: 'var(--card-base)', - textColor: 'var(--fg)', - primaryColor: 'var(--accent)', - overlayColor: 'rgba(40, 42, 54, 0.7)', - borderColor: 'var(--border)', - highlightColor: 'var(--accent)', - fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" - }, - 'solarized-dark': { - backgroundColor: 'var(--card-base)', - textColor: 'var(--fg)', - primaryColor: 'var(--accent)', - overlayColor: 'rgba(0, 43, 54, 0.7)', - borderColor: 'var(--border)', - highlightColor: 'var(--accent)', - fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" - }, - 'solarized-light': { - backgroundColor: 'var(--card-base)', - textColor: 'var(--fg)', - primaryColor: 'var(--accent)', - overlayColor: 'rgba(253, 246, 227, 0.7)', - borderColor: 'var(--border)', - highlightColor: 'var(--accent)', - fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" - } - }; - - /** - * ThemeAdapter class - * Manages theme integration for the tooltip system - */ - class ThemeAdapter { - constructor() { - this.currentTheme = this.getCurrentTheme(); - this.themeChangeCallbacks = []; - this._setupThemeChangeListener(); - } - - /** - * Get the current theme name from document root class - * @returns {string} Current theme name (e.g., 'dark', 'light', 'blue') - */ - getCurrentTheme() { - const root = document.documentElement; - const classList = Array.from(root.classList); - - // Check for theme classes - const themeClasses = ['light', 'blue', 'nord', 'dracula', 'solarized-dark', 'solarized-light']; - const foundTheme = themeClasses.find(theme => classList.includes(theme)); - - // Default to 'dark' if no theme class found - return foundTheme || 'dark'; - } - - /** - * Get Driver.js theme configuration for current theme - * @returns {Object} Theme configuration object - */ - getDriverTheme() { - const themeName = this.getCurrentTheme(); - const config = THEME_CONFIGS[themeName] || THEME_CONFIGS.dark; - - // Resolve CSS variables to actual values - const resolvedConfig = {}; - for (const [key, value] of Object.entries(config)) { - if (typeof value === 'string' && value.startsWith('var(')) { - // Extract CSS variable name - const varName = value.match(/var\((--[^)]+)\)/)?.[1]; - if (varName) { - const computedValue = getComputedStyle(document.documentElement) - .getPropertyValue(varName) - .trim(); - resolvedConfig[key] = computedValue || value; - } else { - resolvedConfig[key] = value; - } - } else { - resolvedConfig[key] = value; - } - } - - return resolvedConfig; - } - - /** - * Register a callback for theme changes - * @param {Function} callback - Function to call when theme changes - */ - onThemeChange(callback) { - if (typeof callback === 'function') { - this.themeChangeCallbacks.push(callback); - } - } - - /** - * Setup theme change listener using MutationObserver - * @private - */ - _setupThemeChangeListener() { - const root = document.documentElement; - - // Create observer to watch for class changes on root element - const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - if (mutation.type === 'attributes' && mutation.attributeName === 'class') { - const newTheme = this.getCurrentTheme(); - if (newTheme !== this.currentTheme) { - const oldTheme = this.currentTheme; - this.currentTheme = newTheme; - this._notifyThemeChange(newTheme, oldTheme); - } - } - }); - }); - - // Start observing - observer.observe(root, { - attributes: true, - attributeFilter: ['class'] - }); - - console.log('[ThemeAdapter] Theme change listener initialized'); - } - - /** - * Notify all registered callbacks of theme change - * @private - * @param {string} newTheme - New theme name - * @param {string} oldTheme - Old theme name - */ - _notifyThemeChange(newTheme, oldTheme) { - console.log(`[ThemeAdapter] Theme changed: ${oldTheme} → ${newTheme}`); - - this.themeChangeCallbacks.forEach(callback => { - try { - callback(newTheme, oldTheme); - } catch (error) { - console.error('[ThemeAdapter] Error in theme change callback:', error); - } - }); - } - - /** - * Apply theme to Driver.js instance - * @param {Object} driver - Driver.js instance - */ - applyTheme(driver) { - if (!driver) { - console.warn('[ThemeAdapter] No driver instance provided'); - return; - } - - const themeConfig = this.getDriverTheme(); - - // Apply theme configuration to driver - // Note: Driver.js v1.0+ uses CSS variables, so we inject a style element - this._injectDriverStyles(themeConfig); - - console.log('[ThemeAdapter] Theme applied to driver:', this.currentTheme); - } - - /** - * Inject custom styles for Driver.js based on theme - * @private - * @param {Object} themeConfig - Theme configuration - */ - _injectDriverStyles(themeConfig) { - // Remove existing theme styles - const existingStyle = document.getElementById('driver-theme-styles'); - if (existingStyle) { - existingStyle.remove(); - } - - // Create new style element - const style = document.createElement('style'); - style.id = 'driver-theme-styles'; - style.textContent = ` - .driver-popover { - background: ${themeConfig.backgroundColor} !important; - color: ${themeConfig.textColor} !important; - border: 1px solid ${themeConfig.borderColor} !important; - border-radius: 12px !important; - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4) !important; - font-family: ${themeConfig.fontFamily} !important; - } - - .driver-popover-title { - color: ${themeConfig.textColor} !important; - font-weight: 600 !important; - font-family: ${themeConfig.fontFamily} !important; - } - - .driver-popover-description { - color: ${themeConfig.textColor} !important; - font-family: ${themeConfig.fontFamily} !important; - } - - .driver-popover-footer button { - background: ${themeConfig.primaryColor} !important; - color: ${themeConfig.backgroundColor} !important; - border: none !important; - font-family: ${themeConfig.fontFamily} !important; - font-weight: 500 !important; - } - - .driver-popover-footer button:hover { - opacity: 0.9 !important; - } - - .driver-popover-close-btn { - color: ${themeConfig.textColor} !important; - } - - .driver-overlay { - background: ${themeConfig.overlayColor} !important; - } - - .driver-highlighted-element { - outline: 2px solid ${themeConfig.highlightColor} !important; - outline-offset: 4px !important; - } - - .driver-popover-progress-text { - color: ${themeConfig.textColor} !important; - opacity: 0.7 !important; - font-family: ${themeConfig.fontFamily} !important; - } - `; - - document.head.appendChild(style); - } - - /** - * Get all available theme names - * @returns {string[]} Array of theme names - */ - getAvailableThemes() { - return Object.keys(THEME_CONFIGS); - } - - /** - * Check if a theme is available - * @param {string} themeName - Theme name to check - * @returns {boolean} True if theme is available - */ - isThemeAvailable(themeName) { - return THEME_CONFIGS.hasOwnProperty(themeName); - } - } - - // Export to global scope - window.ThemeAdapter = ThemeAdapter; - - console.log('[ThemeAdapter] Module loaded'); - -})(window); +/** + * Theme Adapter + * Ensures tooltips match the current dashboard theme + * Integrates with Driver.js to apply theme-specific styling + */ + +(function(window) { + 'use strict'; + + /** + * Theme configuration mapping for Driver.js + * Maps dashboard themes to Driver.js styling + */ + const THEME_CONFIGS = { + dark: { + backgroundColor: 'var(--card-base)', + textColor: 'var(--fg)', + primaryColor: 'var(--accent)', + overlayColor: 'rgba(0, 0, 0, 0.7)', + borderColor: 'var(--border)', + highlightColor: 'var(--accent)', + fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" + }, + light: { + backgroundColor: 'var(--card-base)', + textColor: 'var(--fg)', + primaryColor: 'var(--accent-strong)', + overlayColor: 'rgba(0, 0, 0, 0.5)', + borderColor: 'var(--border)', + highlightColor: 'var(--accent-strong)', + fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" + }, + blue: { + backgroundColor: 'var(--card-base)', + textColor: 'var(--fg)', + primaryColor: 'var(--accent)', + overlayColor: 'rgba(25, 8, 172, 0.7)', + borderColor: 'var(--border)', + highlightColor: 'var(--accent)', + fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" + }, + nord: { + backgroundColor: 'var(--card-base)', + textColor: 'var(--fg)', + primaryColor: 'var(--accent)', + overlayColor: 'rgba(46, 52, 64, 0.7)', + borderColor: 'var(--border)', + highlightColor: 'var(--accent)', + fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" + }, + dracula: { + backgroundColor: 'var(--card-base)', + textColor: 'var(--fg)', + primaryColor: 'var(--accent)', + overlayColor: 'rgba(40, 42, 54, 0.7)', + borderColor: 'var(--border)', + highlightColor: 'var(--accent)', + fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" + }, + 'solarized-dark': { + backgroundColor: 'var(--card-base)', + textColor: 'var(--fg)', + primaryColor: 'var(--accent)', + overlayColor: 'rgba(0, 43, 54, 0.7)', + borderColor: 'var(--border)', + highlightColor: 'var(--accent)', + fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" + }, + 'solarized-light': { + backgroundColor: 'var(--card-base)', + textColor: 'var(--fg)', + primaryColor: 'var(--accent)', + overlayColor: 'rgba(253, 246, 227, 0.7)', + borderColor: 'var(--border)', + highlightColor: 'var(--accent)', + fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" + } + }; + + /** + * ThemeAdapter class + * Manages theme integration for the tooltip system + */ + class ThemeAdapter { + constructor() { + this.currentTheme = this.getCurrentTheme(); + this.themeChangeCallbacks = []; + this._setupThemeChangeListener(); + } + + /** + * Get the current theme name from document root class + * @returns {string} Current theme name (e.g., 'dark', 'light', 'blue') + */ + getCurrentTheme() { + const root = document.documentElement; + const classList = Array.from(root.classList); + + // Check for theme classes + const themeClasses = ['light', 'blue', 'nord', 'dracula', 'solarized-dark', 'solarized-light']; + const foundTheme = themeClasses.find(theme => classList.includes(theme)); + + // Default to 'dark' if no theme class found + return foundTheme || 'dark'; + } + + /** + * Get Driver.js theme configuration for current theme + * @returns {Object} Theme configuration object + */ + getDriverTheme() { + const themeName = this.getCurrentTheme(); + const config = THEME_CONFIGS[themeName] || THEME_CONFIGS.dark; + + // Resolve CSS variables to actual values + const resolvedConfig = {}; + for (const [key, value] of Object.entries(config)) { + if (typeof value === 'string' && value.startsWith('var(')) { + // Extract CSS variable name + const varName = value.match(/var\((--[^)]+)\)/)?.[1]; + if (varName) { + const computedValue = getComputedStyle(document.documentElement) + .getPropertyValue(varName) + .trim(); + resolvedConfig[key] = computedValue || value; + } else { + resolvedConfig[key] = value; + } + } else { + resolvedConfig[key] = value; + } + } + + return resolvedConfig; + } + + /** + * Register a callback for theme changes + * @param {Function} callback - Function to call when theme changes + */ + onThemeChange(callback) { + if (typeof callback === 'function') { + this.themeChangeCallbacks.push(callback); + } + } + + /** + * Setup theme change listener using MutationObserver + * @private + */ + _setupThemeChangeListener() { + const root = document.documentElement; + + // Create observer to watch for class changes on root element + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'attributes' && mutation.attributeName === 'class') { + const newTheme = this.getCurrentTheme(); + if (newTheme !== this.currentTheme) { + const oldTheme = this.currentTheme; + this.currentTheme = newTheme; + this._notifyThemeChange(newTheme, oldTheme); + } + } + }); + }); + + // Start observing + observer.observe(root, { + attributes: true, + attributeFilter: ['class'] + }); + + console.log('[ThemeAdapter] Theme change listener initialized'); + } + + /** + * Notify all registered callbacks of theme change + * @private + * @param {string} newTheme - New theme name + * @param {string} oldTheme - Old theme name + */ + _notifyThemeChange(newTheme, oldTheme) { + console.log(`[ThemeAdapter] Theme changed: ${oldTheme} → ${newTheme}`); + + this.themeChangeCallbacks.forEach(callback => { + try { + callback(newTheme, oldTheme); + } catch (error) { + console.error('[ThemeAdapter] Error in theme change callback:', error); + } + }); + } + + /** + * Apply theme to Driver.js instance + * @param {Object} driver - Driver.js instance + */ + applyTheme(driver) { + if (!driver) { + console.warn('[ThemeAdapter] No driver instance provided'); + return; + } + + const themeConfig = this.getDriverTheme(); + + // Apply theme configuration to driver + // Note: Driver.js v1.0+ uses CSS variables, so we inject a style element + this._injectDriverStyles(themeConfig); + + console.log('[ThemeAdapter] Theme applied to driver:', this.currentTheme); + } + + /** + * Inject custom styles for Driver.js based on theme + * @private + * @param {Object} themeConfig - Theme configuration + */ + _injectDriverStyles(themeConfig) { + // Remove existing theme styles + const existingStyle = document.getElementById('driver-theme-styles'); + if (existingStyle) { + existingStyle.remove(); + } + + // Create new style element + const style = document.createElement('style'); + style.id = 'driver-theme-styles'; + style.textContent = ` + .driver-popover { + background: ${themeConfig.backgroundColor} !important; + color: ${themeConfig.textColor} !important; + border: 1px solid ${themeConfig.borderColor} !important; + border-radius: 12px !important; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4) !important; + font-family: ${themeConfig.fontFamily} !important; + } + + .driver-popover-title { + color: ${themeConfig.textColor} !important; + font-weight: 600 !important; + font-family: ${themeConfig.fontFamily} !important; + } + + .driver-popover-description { + color: ${themeConfig.textColor} !important; + font-family: ${themeConfig.fontFamily} !important; + } + + .driver-popover-footer button { + background: ${themeConfig.primaryColor} !important; + color: ${themeConfig.backgroundColor} !important; + border: none !important; + font-family: ${themeConfig.fontFamily} !important; + font-weight: 500 !important; + } + + .driver-popover-footer button:hover { + opacity: 0.9 !important; + } + + .driver-popover-close-btn { + color: ${themeConfig.textColor} !important; + } + + .driver-overlay { + background: ${themeConfig.overlayColor} !important; + } + + .driver-highlighted-element { + outline: 2px solid ${themeConfig.highlightColor} !important; + outline-offset: 4px !important; + } + + .driver-popover-progress-text { + color: ${themeConfig.textColor} !important; + opacity: 0.7 !important; + font-family: ${themeConfig.fontFamily} !important; + } + `; + + document.head.appendChild(style); + } + + /** + * Get all available theme names + * @returns {string[]} Array of theme names + */ + getAvailableThemes() { + return Object.keys(THEME_CONFIGS); + } + + /** + * Check if a theme is available + * @param {string} themeName - Theme name to check + * @returns {boolean} True if theme is available + */ + isThemeAvailable(themeName) { + return Object.hasOwn(THEME_CONFIGS, themeName); + } + } + + // Export to global scope + window.ThemeAdapter = ThemeAdapter; + + console.log('[ThemeAdapter] Module loaded'); + +})(window); diff --git a/dashcaddy-api/backup-manager.js b/dashcaddy-api/backup-manager.js index 9a30d12..b0291e9 100644 --- a/dashcaddy-api/backup-manager.js +++ b/dashcaddy-api/backup-manager.js @@ -75,7 +75,7 @@ class BackupManager extends EventEmitter { case 'monthly': intervalMs = 30 * 24 * 60 * 60 * 1000; break; - default: + default: { // Custom interval in minutes const minutes = parseInt(backup.schedule, 10); if (!isNaN(minutes) && minutes > 0) { @@ -84,6 +84,7 @@ class BackupManager extends EventEmitter { console.error(`[BackupManager] Invalid schedule for ${name}: ${backup.schedule}`); return; } + } } // Schedule the job diff --git a/dashcaddy-api/comprehensive-test.js b/dashcaddy-api/comprehensive-test.js index b8d7071..f1f2fe6 100644 --- a/dashcaddy-api/comprehensive-test.js +++ b/dashcaddy-api/comprehensive-test.js @@ -20,7 +20,7 @@ const colors = { magenta: '\x1b[35m' }; -let testResults = { +const testResults = { passed: 0, failed: 0, warnings: 0, diff --git a/dashcaddy-api/input-validator.js b/dashcaddy-api/input-validator.js index f1309ce..3c72879 100644 --- a/dashcaddy-api/input-validator.js +++ b/dashcaddy-api/input-validator.js @@ -507,7 +507,7 @@ async function validateSecurePath(requestedPath, allowedRoots, auditLogger = nul const suspiciousPatterns = [ /\.\./, // .. /%2e%2e/i, // URL encoded .. - /\.\%2f/i, // .%2F (encoded ./) + /\.%2f/i, // .%2F (encoded ./) /%2e\./i, // %2E. /\.\\/, // .\ (Windows) /%5c/i // URL encoded backslash diff --git a/dashcaddy-api/routes/apps/helpers.js b/dashcaddy-api/routes/apps/helpers.js index 9b996a4..d3fb962 100644 --- a/dashcaddy-api/routes/apps/helpers.js +++ b/dashcaddy-api/routes/apps/helpers.js @@ -17,8 +17,7 @@ const platformPaths = require('../../platform-paths'); * @param {Object} deps.log - Logger instance * @returns {Object} Helper functions */ -module.exports = function(ctx) { - const { docker, caddy, credentialManager, servicesStateManager, fetchT, log } = ctx; +module.exports = function({ docker, caddy, credentialManager, servicesStateManager, fetchT, log, ctx }) { async function checkPortConflicts(ports, excludeContainerName = null) { const conflicts = []; diff --git a/dashcaddy-api/routes/apps/index.js b/dashcaddy-api/routes/apps/index.js index b6ef41a..cc8945e 100644 --- a/dashcaddy-api/routes/apps/index.js +++ b/dashcaddy-api/routes/apps/index.js @@ -27,17 +27,21 @@ module.exports = function(ctx) { log: ctx.log, // Additional context properties needed by routes APP_TEMPLATES: ctx.APP_TEMPLATES, + TEMPLATE_CATEGORIES: ctx.TEMPLATE_CATEGORIES, + DIFFICULTY_LEVELS: ctx.DIFFICULTY_LEVELS, siteConfig: ctx.siteConfig, buildDomain: ctx.buildDomain, buildServiceUrl: ctx.buildServiceUrl, addServiceToConfig: ctx.addServiceToConfig, dns: ctx.dns, notification: ctx.notification, - safeErrorMessage: ctx.safeErrorMessage + safeErrorMessage: ctx.safeErrorMessage, + SERVICES_FILE: ctx.SERVICES_FILE, + ctx: ctx }; - // Initialize helpers with dependencies - const helpers = initHelpers(ctx); + // Initialize helpers with dependencies (ctx is the Koa context) + const helpers = initHelpers({ ...deps, ctx }); // Mount sub-routes — pass full ctx so sub-routes can reference ctx.* properties const subCtx = Object.assign({}, ctx, { helpers }); diff --git a/dashcaddy-api/routes/apps/removal.js b/dashcaddy-api/routes/apps/removal.js index ac677eb..5295a68 100644 --- a/dashcaddy-api/routes/apps/removal.js +++ b/dashcaddy-api/routes/apps/removal.js @@ -1,20 +1,40 @@ const express = require('express'); const { exists } = require('../../fs-helpers'); +const { logError } = require('../../src/utils/logging'); -module.exports = function(ctx) { - const { docker, caddy, servicesStateManager, asyncHandler, errorResponse, log, helpers } = ctx; +module.exports = function({ + docker, caddy, servicesStateManager, asyncHandler, log, helpers, + errorResponse, dns, siteConfig, buildDomain, SERVICES_FILE, safeErrorMessage +}) { const router = express.Router(); -/** - * Apps removal routes factory - * @param {Object} deps - Explicit dependencies - * @param {Object} deps.docker - Docker client wrapper - * @param {Object} deps.caddy - Caddy client - * @param {Object} deps.servicesStateManager - Services state manager - * @param {Function} deps.asyncHandler - Async route handler wrapper - * @param {Object} deps.log - Logger instance - * @param {Object} deps.helpers - Apps helpers module - * @returns {express.Router} - */ + + // Ctx shim for backward compatibility with existing route code + const ctx = { + dns, + siteConfig, + buildDomain, + SERVICES_FILE, + safeErrorMessage + }; + + // Remove deployed app + /** + * Apps removal routes factory + * @param {Object} deps - Explicit dependencies + * @param {Object} deps.docker - Docker client wrapper + * @param {Object} deps.caddy - Caddy client + * @param {Object} deps.servicesStateManager - Services state manager + * @param {Function} deps.asyncHandler - Async route handler wrapper + * @param {Object} deps.log - Logger instance + * @param {Object} deps.helpers - Apps helpers module + * @param {Function} deps.errorResponse - Error response helper + * @param {Object} deps.dns - DNS context + * @param {Object} deps.siteConfig - Site configuration + * @param {Function} deps.buildDomain - Build domain helper + * @param {string} deps.SERVICES_FILE - Services file path + * @param {Function} deps.safeErrorMessage - Safe error message formatter + * @returns {express.Router} + */ router.delete('/apps/:appId', asyncHandler(async (req, res) => { const { appId } = req.params; const { containerId, subdomain, ip, deleteContainer } = req.query; diff --git a/dashcaddy-api/routes/apps/restore.js b/dashcaddy-api/routes/apps/restore.js index 71e1a74..5b37596 100644 --- a/dashcaddy-api/routes/apps/restore.js +++ b/dashcaddy-api/routes/apps/restore.js @@ -8,14 +8,23 @@ const { DOCKER } = require('../../constants'); * @param {Object} deps.caddy - Caddy client * @param {Object} deps.servicesStateManager - Services state manager * @param {Function} deps.asyncHandler - Async route handler wrapper + * @param {Function} deps.errorResponse - Error response helper * @param {Object} deps.log - Logger instance * @param {Object} deps.helpers - Apps helpers module + * @param {Object} deps.APP_TEMPLATES - App templates registry + * @param {Object} deps.dns - DNS client + * @param {Function} deps.buildServiceUrl - Service URL builder * @returns {express.Router} */ -module.exports = function(ctx) { - const { docker, caddy, servicesStateManager, asyncHandler, log, helpers } = ctx; +module.exports = function({ docker, caddy, servicesStateManager, asyncHandler, errorResponse, log, helpers, APP_TEMPLATES, dns, buildServiceUrl }) { const router = express.Router(); + const ctx = { + APP_TEMPLATES, + dns, + buildServiceUrl + }; + /** * Restore a single service from its deployment manifest. * Pulls image, creates container, starts it, recreates Caddy config. @@ -125,11 +134,6 @@ module.exports = function(ctx) { // Static sites: just recreate Caddy config if (template?.isStaticSite) { log.info('restore', `Restoring static site Caddy config: ${service.name}`); - const caddyOptions = { - tailscaleOnly: manifest.caddy.tailscaleOnly, - allowedIPs: manifest.caddy.allowedIPs, - subpathSupport: manifest.caddy.subpathSupport, - }; // Static site Caddy config would need to be regenerated // For now, just confirm the service entry exists return { @@ -288,7 +292,7 @@ module.exports = function(ctx) { const svc = services.find(s => s.id === service.id); if (svc) { svc.containerId = container.id; - svc.url = ctx.buildServiceUrl(manifest.config.subdomain); + svc.url = buildServiceUrl(manifest.config.subdomain); } return services; }); diff --git a/dashcaddy-api/routes/apps/templates.js b/dashcaddy-api/routes/apps/templates.js index d1eae64..7ce0750 100644 --- a/dashcaddy-api/routes/apps/templates.js +++ b/dashcaddy-api/routes/apps/templates.js @@ -6,14 +6,40 @@ const { exists } = require('../../fs-helpers'); * @param {Object} deps.servicesStateManager - Services state manager * @param {Function} deps.asyncHandler - Async route handler wrapper * @param {Object} deps.helpers - Apps helpers module + * @param {Object} deps.APP_TEMPLATES - App templates registry + * @param {Object} deps.TEMPLATE_CATEGORIES - Template categories + * @param {Object} deps.DIFFICULTY_LEVELS - Difficulty levels + * @param {Object} deps.docker - Docker client + * @param {Object} deps.caddy - Caddy client + * @param {Object} deps.dns - DNS context + * @param {Object} deps.siteConfig - Site configuration + * @param {Function} deps.buildDomain - Build domain helper + * @param {Function} deps.errorResponse - Error response helper + * @param {Object} deps.log - Logger instance + * @param {string} deps.SERVICES_FILE - Services file path * @returns {express.Router} */ const { REGEX } = require('../../constants'); -module.exports = function(ctx) { - const { servicesStateManager, asyncHandler, helpers, docker, caddy, log, errorResponse } = ctx; +module.exports = function({ + servicesStateManager, asyncHandler, helpers, + APP_TEMPLATES, TEMPLATE_CATEGORIES, DIFFICULTY_LEVELS, + docker, caddy, dns, siteConfig, buildDomain, + errorResponse, log, SERVICES_FILE +}) { const router = express.Router(); + // Ctx shim for backward compatibility with existing route code + const ctx = { + APP_TEMPLATES, + TEMPLATE_CATEGORIES, + DIFFICULTY_LEVELS, + dns, + siteConfig, + buildDomain, + SERVICES_FILE + }; + // Get available app templates router.get('/apps/templates', asyncHandler(async (req, res) => { res.json({ @@ -64,6 +90,8 @@ module.exports = function(ctx) { // Update subdomain for deployed app router.post('/apps/update-subdomain', asyncHandler(async (req, res) => { const { serviceId, oldSubdomain, newSubdomain, containerId, ip } = req.body; + const { ValidationError } = require('../../errors'); + if (!oldSubdomain || typeof oldSubdomain !== 'string') { throw new ValidationError('oldSubdomain is required'); } diff --git a/dashcaddy-api/routes/arr/config.js b/dashcaddy-api/routes/arr/config.js index be37c95..b8e9698 100644 --- a/dashcaddy-api/routes/arr/config.js +++ b/dashcaddy-api/routes/arr/config.js @@ -28,7 +28,7 @@ module.exports = function(ctx) { const results = { radarr: null, sonarr: null }; // Step 1: Authenticate with Overseerr via Plex token - let overseerrUrl = `http://host.docker.internal:${APP_PORTS.overseerr}`; + const overseerrUrl = `http://host.docker.internal:${APP_PORTS.overseerr}`; const overseerrSession = await helpers.getOverseerrSession(); if (!overseerrSession) { @@ -227,7 +227,7 @@ module.exports = function(ctx) { } // Normalize URL - remove trailing slash - let baseUrl = url.replace(/\/+$/, ''); + const baseUrl = url.replace(/\/+$/, ''); // Build the API endpoint let apiEndpoint; diff --git a/dashcaddy-api/routes/arr/plex.js b/dashcaddy-api/routes/arr/plex.js index 268f8a1..fae8dfd 100644 --- a/dashcaddy-api/routes/arr/plex.js +++ b/dashcaddy-api/routes/arr/plex.js @@ -9,12 +9,18 @@ const { APP_PORTS } = require('../../constants'); * @param {Function} deps.errorResponse - Error response helper * @param {Object} deps.log - Logger instance * @param {Object} deps.helpers - Arr helpers module + * @param {Object} deps.credentialManager - Credential manager + * @param {Object} deps.servicesStateManager - Services state manager * @returns {express.Router} */ -module.exports = function(ctx) { - const { fetchT, asyncHandler, errorResponse, log, helpers } = ctx; +module.exports = function({ fetchT, asyncHandler, errorResponse, log: _log, helpers, credentialManager, servicesStateManager }) { const router = express.Router(); + const ctx = { + credentialManager, + servicesStateManager + }; + // Plex Libraries endpoint router.get('/plex/libraries', asyncHandler(async (req, res) => { // Get Plex token diff --git a/dashcaddy-api/routes/arr/smart-connect.js b/dashcaddy-api/routes/arr/smart-connect.js index db17b60..b7f557d 100644 --- a/dashcaddy-api/routes/arr/smart-connect.js +++ b/dashcaddy-api/routes/arr/smart-connect.js @@ -10,12 +10,18 @@ const { APP_PORTS } = require('../../constants'); * @param {Function} deps.errorResponse - Error response helper * @param {Object} deps.log - Logger instance * @param {Object} deps.helpers - Arr helpers module + * @param {Object} deps.servicesStateManager - Services state manager + * @param {Object} deps.notification - Notification helper * @returns {express.Router} */ -module.exports = function(ctx) { - const { credentialManager, fetchT, asyncHandler, errorResponse, log, helpers } = ctx; +module.exports = function({ credentialManager, servicesStateManager, fetchT, asyncHandler, errorResponse: _errorResponse, log: _log, helpers, notification }) { const router = express.Router(); + const ctx = { + servicesStateManager, + notification + }; + // Smart Connect: Unified orchestration endpoint router.post('/arr/smart-connect', asyncHandler(async (req, res) => { const { services: inputServices, configurePlex, configureProwlarr, configureSeerr, saveCredentials } = req.body; diff --git a/dashcaddy-api/routes/auth/session-handlers.js b/dashcaddy-api/routes/auth/session-handlers.js index 7c2822d..2fd6e88 100644 --- a/dashcaddy-api/routes/auth/session-handlers.js +++ b/dashcaddy-api/routes/auth/session-handlers.js @@ -1,8 +1,6 @@ const { SESSION_TTL, APP, PLEX, TIMEOUTS, buildMediaAuth } = require('../../constants'); const { createCache, CACHE_CONFIGS } = require('../../cache-config'); -module.exports = function({ authManager, credentialManager, fetchT, asyncHandler, errorResponse, log }) { - // App session cache for auto-login /** * Auth session handlers routes factory * @param {Object} deps - Explicit dependencies @@ -11,8 +9,11 @@ module.exports = function({ authManager, credentialManager, fetchT, asyncHandler * @param {Function} deps.asyncHandler - Async route handler wrapper * @param {Function} deps.errorResponse - Error response helper * @param {Object} deps.log - Logger instance - * @returns {express.Router} + * @param {Function} deps.fetchT - Timeout-wrapped fetch + * @returns {{ getAppSession: Function, appSessionCache: Object }} */ +module.exports = function({ authManager: _authManager, credentialManager: _credentialManager, asyncHandler: _asyncHandler, errorResponse: _errorResponse, log, fetchT }) { + const ctx = { fetchT }; const appSessionCache = createCache(CACHE_CONFIGS.appSessions); async function getAppSession(serviceId, baseUrl, username, password) { diff --git a/dashcaddy-api/routes/browse.js b/dashcaddy-api/routes/browse.js index ff7caa7..8f8919b 100644 --- a/dashcaddy-api/routes/browse.js +++ b/dashcaddy-api/routes/browse.js @@ -4,7 +4,7 @@ const fsp = require('fs').promises; const path = require('path'); const { exists, isAccessible } = require('../fs-helpers'); const { paginate, parsePaginationParams } = require('../pagination'); -const { ValidationError } = require('../errors'); +const { ValidationError, ForbiddenError } = require('../errors'); /** * Browse route factory diff --git a/dashcaddy-api/routes/ca.js b/dashcaddy-api/routes/ca.js index dae2f28..544cc8b 100644 --- a/dashcaddy-api/routes/ca.js +++ b/dashcaddy-api/routes/ca.js @@ -4,6 +4,7 @@ const fsp = require('fs').promises; const path = require('path'); const { execSync } = require('child_process'); const { exists } = require('../fs-helpers'); +const { ValidationError } = require('../errors'); const platformPaths = require('../platform-paths'); module.exports = function(ctx) { diff --git a/dashcaddy-api/routes/config/assets.js b/dashcaddy-api/routes/config/assets.js index 1fee826..4d24f66 100644 --- a/dashcaddy-api/routes/config/assets.js +++ b/dashcaddy-api/routes/config/assets.js @@ -22,9 +22,9 @@ try { // Image processing libraries not available — favicon conversion disabled } -module.exports = function(ctx) { - const { servicesStateManager, asyncHandler, log } = ctx; +module.exports = function({ servicesStateManager: _servicesStateManager, asyncHandler, log: _log, CONFIG_FILE, readConfig, saveConfig, errorResponse }) { const router = express.Router(); + const ctx = { CONFIG_FILE, readConfig, saveConfig, errorResponse }; // ===== ASSET UPLOAD ===== @@ -47,7 +47,6 @@ module.exports = function(ctx) { throw new ValidationError('Invalid image data format'); } - const extension = matches[1] === 'svg+xml' ? 'svg' : matches[1]; const base64Data = matches[2]; const buffer = Buffer.from(base64Data, 'base64'); @@ -109,6 +108,7 @@ module.exports = function(ctx) { // Upload custom logo(s) and/or update position and title // Supports: dataDark/dataLight (separate variants) or data (single logo for both) + // eslint-disable-next-line complexity router.post('/logo', express.json({ limit: LIMITS.BODY_UPLOAD }), asyncHandler(async (req, res) => { const { data, dataDark, dataLight, position, dashboardTitle } = req.body; @@ -231,7 +231,6 @@ module.exports = function(ctx) { throw new ValidationError('Invalid image data format'); } - const imageType = matches[1]; const base64Data = matches[2]; const buffer = Buffer.from(base64Data, 'base64'); diff --git a/dashcaddy-api/routes/config/index.js b/dashcaddy-api/routes/config/index.js index 9ad2333..05fd693 100644 --- a/dashcaddy-api/routes/config/index.js +++ b/dashcaddy-api/routes/config/index.js @@ -13,7 +13,10 @@ module.exports = function(ctx) { configStateManager: ctx.configStateManager, servicesStateManager: ctx.servicesStateManager, asyncHandler: ctx.asyncHandler, - log: ctx.log + log: ctx.log, + CONFIG_FILE: ctx.CONFIG_FILE, + errorResponse: ctx.errorResponse, + loadSiteConfig: ctx.loadSiteConfig }; // Additional deps for backup route @@ -35,8 +38,8 @@ module.exports = function(ctx) { saveTotpConfig: ctx.saveTotpConfig }; - router.use(require('./settings')(ctx)); - router.use(require('./assets')(ctx)); + router.use(require('./settings')(baseDeps)); + router.use(require('./assets')({ ...baseDeps, readConfig: ctx.readConfig, saveConfig: ctx.saveConfig })); router.use(require('./backup')(backupDeps)); return router; }; diff --git a/dashcaddy-api/routes/config/settings.js b/dashcaddy-api/routes/config/settings.js index 439b88d..7784c8b 100644 --- a/dashcaddy-api/routes/config/settings.js +++ b/dashcaddy-api/routes/config/settings.js @@ -5,13 +5,19 @@ const { ValidationError } = require('../../errors'); /** * Config settings routes factory - * @param {Object} ctx - Application context + * @param {Object} deps - Explicit dependencies + * @param {Object} deps.configStateManager - Config state manager + * @param {Function} deps.asyncHandler - Async route handler wrapper + * @param {Object} deps.log - Logger instance + * @param {string} deps.CONFIG_FILE - Config file path + * @param {Function} deps.errorResponse - Error response helper + * @param {Function} deps.loadSiteConfig - Site config reload helper * @returns {express.Router} */ -module.exports = function(ctx) { - const { configStateManager, asyncHandler, log } = ctx; +module.exports = function({ configStateManager: _configStateManager, asyncHandler, log, CONFIG_FILE, errorResponse, loadSiteConfig }) { const express = require('express'); const router = express.Router(); + const ctx = { CONFIG_FILE, errorResponse, loadSiteConfig }; // ===== DASHCADDY CONFIG ENDPOINTS ===== // Server-side config storage for setup wizard (shared across all browsers/machines) @@ -60,7 +66,9 @@ module.exports = function(ctx) { config.updatedAt = new Date().toISOString(); await fsp.writeFile(ctx.CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8'); - ctx.loadSiteConfig(); // Refresh in-memory config + if (typeof ctx.loadSiteConfig === 'function') { + ctx.loadSiteConfig(); // Refresh in-memory config + } log.info('config', 'Config saved', { path: ctx.CONFIG_FILE }); res.json({ success: true, message: 'Configuration saved', config, warnings }); diff --git a/dashcaddy-api/routes/health.js b/dashcaddy-api/routes/health.js index 70ec60f..e5c112d 100644 --- a/dashcaddy-api/routes/health.js +++ b/dashcaddy-api/routes/health.js @@ -8,6 +8,7 @@ const { paginate, parsePaginationParams } = require('../pagination'); const platformPaths = require('../platform-paths'); const { resolveServiceUrl } = require('../url-resolver'); const { success, error: errorResponse } = require('../response-helpers'); +const { ValidationError } = require('../errors'); /** * Health routes factory @@ -54,7 +55,7 @@ module.exports = function({ url, checkedAt: new Date().toISOString() }; - } catch (_) {} + } catch { /* ignore */ } // Fallback to GET try { diff --git a/dashcaddy-api/routes/logs.js b/dashcaddy-api/routes/logs.js index 8668b6c..b56f944 100644 --- a/dashcaddy-api/routes/logs.js +++ b/dashcaddy-api/routes/logs.js @@ -4,7 +4,7 @@ const fsp = require('fs').promises; const path = require('path'); const { exists } = require('../fs-helpers'); const { paginate, parsePaginationParams } = require('../pagination'); -const { NotFoundError } = require('../errors'); +const { NotFoundError, ValidationError, ForbiddenError } = require('../errors'); /** * Logs route factory diff --git a/dashcaddy-api/routes/recipes/deploy.js b/dashcaddy-api/routes/recipes/deploy.js index 08bf367..79c6faa 100644 --- a/dashcaddy-api/routes/recipes/deploy.js +++ b/dashcaddy-api/routes/recipes/deploy.js @@ -14,9 +14,9 @@ const { DOCKER } = require('../../constants'); * @param {Object} deps.log - Logger instance * @returns {express.Router} */ -module.exports = function(ctx) { - const { docker, credentialManager, servicesStateManager, asyncHandler, errorResponse, log } = ctx; +module.exports = function({ docker, credentialManager: _credentialManager, servicesStateManager: _servicesStateManager, asyncHandler, errorResponse: _errorResponse, log, addServiceToConfig, notification, APP_TEMPLATES, siteConfig, caddy, buildDomain }) { const router = express.Router(); + const ctx = { addServiceToConfig, notification, APP_TEMPLATES, siteConfig, caddy, buildDomain }; /** * Deploy a recipe — creates multiple containers as a coordinated stack @@ -24,6 +24,7 @@ module.exports = function(ctx) { * POST /api/recipes/deploy * Body: { recipeId, config: { selectedComponents, sharedConfig, componentOverrides } } */ + // eslint-disable-next-line complexity router.post('/deploy', asyncHandler(async (req, res) => { const { recipeId, config } = req.body; const { RECIPE_TEMPLATES } = require('../../recipe-templates'); @@ -44,7 +45,6 @@ module.exports = function(ctx) { // Generate shared passwords for the recipe (consistent across components) const generatedPasswords = {}; - const passwordKey = `recipe-${recipeId}-${Date.now()}`; generatedPasswords.default = crypto.randomBytes(24).toString('base64url'); // Create Docker network if defined @@ -185,6 +185,7 @@ module.exports = function(ctx) { /** * Deploy a single component of a recipe */ + // eslint-disable-next-line complexity async function deployComponent(component, recipe, config, passwords, networkName) { const sharedConfig = config.sharedConfig || {}; const overrides = config.componentOverrides?.[component.id] || {}; @@ -364,7 +365,7 @@ module.exports = function(ctx) { /** * Run auto-connect steps after recipe deployment */ - async function runAutoConnect(recipe, deployedComponents, config) { + async function runAutoConnect(recipe, _deployedComponents, _config) { if (!recipe.autoConnect?.steps) return; // Wait for services to be fully ready diff --git a/dashcaddy-api/routes/recipes/index.js b/dashcaddy-api/routes/recipes/index.js index 12e3c80..1b10cf8 100644 --- a/dashcaddy-api/routes/recipes/index.js +++ b/dashcaddy-api/routes/recipes/index.js @@ -17,7 +17,13 @@ module.exports = function(ctx) { servicesStateManager: ctx.servicesStateManager, asyncHandler: ctx.asyncHandler, errorResponse: ctx.errorResponse, - log: ctx.log + log: ctx.log, + notification: ctx.notification, + buildDomain: ctx.buildDomain, + caddy: ctx.caddy, + addServiceToConfig: ctx.addServiceToConfig, + APP_TEMPLATES: ctx.APP_TEMPLATES, + siteConfig: ctx.siteConfig }; // All recipe routes require premium license diff --git a/dashcaddy-api/routes/recipes/manage.js b/dashcaddy-api/routes/recipes/manage.js index aefc9f8..9553753 100644 --- a/dashcaddy-api/routes/recipes/manage.js +++ b/dashcaddy-api/routes/recipes/manage.js @@ -2,9 +2,12 @@ const express = require('express'); const { DOCKER } = require('../../constants'); const { NotFoundError } = require('../../errors'); -module.exports = function(ctx) { - const { servicesStateManager, asyncHandler, log } = ctx; +module.exports = function({ servicesStateManager, asyncHandler, log, docker, notification, buildDomain, caddy }) { const router = express.Router(); + + // Ctx shim for backward compatibility + const ctx = { docker, notification, buildDomain, caddy }; + /** * Recipes management routes factory * @param {Object} deps - Explicit dependencies @@ -303,7 +306,7 @@ module.exports = function(ctx) { */ async function removeCaddyBlock(subdomain) { const domain = ctx.buildDomain(subdomain); - let content = await ctx.caddy.read(); + const content = await ctx.caddy.read(); // Find and remove the block for this domain const escapedDomain = domain.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); diff --git a/dashcaddy-api/routes/services.js b/dashcaddy-api/routes/services.js index 16e0f3a..314f7ef 100644 --- a/dashcaddy-api/routes/services.js +++ b/dashcaddy-api/routes/services.js @@ -8,6 +8,7 @@ const { APP, REGEX, TIMEOUTS } = require('../constants'); const { validateServiceConfig, isValidPort } = require('../input-validator'); const { exists } = require('../fs-helpers'); const { paginate, parsePaginationParams } = require('../pagination'); +const { ValidationError, NotFoundError } = require('../errors'); const { resolveServiceUrl } = require('../url-resolver'); const { success, error: errorResponse } = require('../response-helpers'); const { ConflictError, ValidationError, NotFoundError } = require('../errors'); @@ -470,7 +471,7 @@ module.exports = function({ const oldDomain = buildDomain(oldSubdomain); const newDomain = buildDomain(newSubdomain); - let content = await caddy.read(); + const content = await caddy.read(); const escapedOldDomain = oldDomain.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const siteBlockRegex = new RegExp( diff --git a/dashcaddy-api/routes/sites.js b/dashcaddy-api/routes/sites.js index 212b7fb..89557f9 100644 --- a/dashcaddy-api/routes/sites.js +++ b/dashcaddy-api/routes/sites.js @@ -164,7 +164,7 @@ module.exports = function({ asyncHandler, caddy, dns, fetchT, buildDomain, addSe const upstreamRegex = /^[a-z0-9.-]+:\d{1,5}$/i; if (!upstreamRegex.test(upstream)) throw new ValidationError('Invalid upstream format. Use host:port'); - let content = await caddy.read(); + const content = await caddy.read(); const escapedDomain = domain.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const siteBlockRegex = new RegExp(`\\n?${escapedDomain}\\s*\\{`, 'g'); if (siteBlockRegex.test(content)) { @@ -203,7 +203,6 @@ module.exports = function({ asyncHandler, caddy, dns, fetchT, buildDomain, addSe const domain = buildDomain(subdomain); let dnsWarning = null; - try { if (createDns) { try { await dns.createRecord(subdomain, siteConfig.dnsServerIp); @@ -267,9 +266,6 @@ module.exports = function({ asyncHandler, caddy, dns, fetchT, buildDomain, addSe }; if (dnsWarning) response.warning = dnsWarning; res.json(response); - } catch (error) { - throw error; - } }, 'site-external')); return router; diff --git a/dashcaddy-api/routes/tailscale.js b/dashcaddy-api/routes/tailscale.js index 9c93f87..6cf8f1f 100644 --- a/dashcaddy-api/routes/tailscale.js +++ b/dashcaddy-api/routes/tailscale.js @@ -152,7 +152,7 @@ module.exports = function({ throw new ValidationError('subdomain is required'); } - let content = await caddy.read(); + const content = await caddy.read(); const domain = buildDomain(subdomain); const blockRegex = new RegExp(`(${domain.replace('.', '\\.')}\\s*\\{[^}]*\\})`, 's'); diff --git a/dashcaddy-api/self-updater.js b/dashcaddy-api/self-updater.js index 0735bca..a11e454 100644 --- a/dashcaddy-api/self-updater.js +++ b/dashcaddy-api/self-updater.js @@ -88,7 +88,7 @@ class SelfUpdater extends EventEmitter { let commit = null; try { commit = fs.readFileSync(path.join(__dirname, 'VERSION'), 'utf8').trim(); - } catch (_) {} + } catch { /* ignore */ } return { version: pkg.version, commit }; } catch (e) { return { version: '0.0.0', commit: null }; @@ -164,7 +164,7 @@ class SelfUpdater extends EventEmitter { } catch (dlErr) { console.warn('[SelfUpdater] Primary download failed:', dlErr.message, '— trying mirror'); // Ensure file is fully cleaned up before mirror attempt - try { fs.unlinkSync(tarballPath); } catch (_) {} + try { fs.unlinkSync(tarballPath); } catch { /* ignore */ } await this._downloadFile(mirrorUrl, tarballPath); } @@ -473,7 +473,7 @@ class SelfUpdater extends EventEmitter { const sub = path.join(baseDir, entry, name); if (fs.existsSync(sub)) return sub; } - } catch (_) {} + } catch { /* ignore */ } return null; } @@ -512,7 +512,7 @@ class SelfUpdater extends EventEmitter { async _cleanDir(dir) { try { await fsp.rm(dir, { recursive: true, force: true }); - } catch (_) {} + } catch { /* ignore */ } await fsp.mkdir(dir, { recursive: true }); } } diff --git a/dashcaddy-api/server.js b/dashcaddy-api/server.js index 0ed3d8f..2666135 100644 --- a/dashcaddy-api/server.js +++ b/dashcaddy-api/server.js @@ -67,8 +67,8 @@ process.on('uncaughtException', (error) => { // Optional modules let dockerMaintenance, logDigest; - try { dockerMaintenance = require('./docker-maintenance'); } catch (_) {} - try { logDigest = require('./log-digest'); } catch (_) {} + try { dockerMaintenance = require('./docker-maintenance'); } catch { /* optional */ } + try { logDigest = require('./log-digest'); } catch { /* optional */ } log.info('server', 'Starting feature modules'); @@ -188,7 +188,7 @@ process.on('uncaughtException', (error) => { }); // Graceful shutdown - function shutdown(signal) { + const shutdown = (signal) => { log.info('shutdown', `${signal} received, draining connections...`); const resourceMonitor = require('./resource-monitor'); @@ -206,12 +206,12 @@ process.on('uncaughtException', (error) => { try { const dockerMaintenance = require('./docker-maintenance'); dockerMaintenance.stop(); - } catch (_) {} + } catch { /* optional */ } try { const logDigest = require('./log-digest'); logDigest.stop(); - } catch (_) {} + } catch { /* optional */ } server.close(() => { log.info('shutdown', 'HTTP server closed'); @@ -220,7 +220,7 @@ process.on('uncaughtException', (error) => { // Force exit after 5s if connections don't drain setTimeout(() => process.exit(0), 5000).unref(); - } + }; process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGINT', () => shutdown('SIGINT')); diff --git a/dashcaddy-api/src/app.js b/dashcaddy-api/src/app.js index c4cd34a..55daf46 100644 --- a/dashcaddy-api/src/app.js +++ b/dashcaddy-api/src/app.js @@ -36,8 +36,8 @@ const { validateURL } = require('../input-validator'); // Optional modules let dockerMaintenance, logDigest; -try { dockerMaintenance = require('../docker-maintenance'); } catch (_) {} -try { logDigest = require('../log-digest'); } catch (_) {} +try { dockerMaintenance = require('../docker-maintenance'); } catch (_) { /* optional module */ } +try { logDigest = require('../log-digest'); } catch (_) { /* optional module */ } // Templates const { APP_TEMPLATES, TEMPLATE_CATEGORIES, DIFFICULTY_LEVELS } = require('../app-templates'); @@ -104,8 +104,8 @@ async function createApp() { log.warn('server', 'CA cert not found — HTTPS calls may fail', { path: CA_CERT_PATH }); } - // TOTP configuration (defaults, overridden by loadTotpConfig below) - let totpConfig = { + // TOTP configuration + const totpConfig = { enabled: false, sessionDuration: 'never', isSetUp: false @@ -124,7 +124,7 @@ async function createApp() { } // Tailscale configuration - let tailscaleConfig = { + const tailscaleConfig = { enabled: false, requireAuth: false, allowedTailnet: null, @@ -137,7 +137,7 @@ async function createApp() { // Helper functions needed by middleware function isValidContainerId(id) { - const CONTAINER_ID_RE = /^[a-zA-Z0-9][a-zA-Z0-9_.\-]{0,127}$/; + const CONTAINER_ID_RE = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/; return typeof id === 'string' && CONTAINER_ID_RE.test(id); } diff --git a/dashcaddy-api/src/config/site.js b/dashcaddy-api/src/config/site.js index 9bb034f..a03c6ef 100644 --- a/dashcaddy-api/src/config/site.js +++ b/dashcaddy-api/src/config/site.js @@ -6,7 +6,7 @@ const fs = require('fs'); const { validateConfig } = require('../../config-schema'); const { CADDY } = require('../../constants'); -let siteConfig = { +const siteConfig = { tld: '.home', caName: '', dnsServerIp: '', diff --git a/dashcaddy-api/src/context/dns.js b/dashcaddy-api/src/context/dns.js index 2dba463..7dec976 100644 --- a/dashcaddy-api/src/context/dns.js +++ b/dashcaddy-api/src/context/dns.js @@ -226,7 +226,7 @@ async function requireDnsToken(providedToken, siteConfig, credentialManager, fet /** * Create DNS record */ -async function createDnsRecord(subdomain, ip, siteConfig, buildDomain, fetchT, httpsAgent, log) { +async function createDnsRecord(subdomain, ip, siteConfig, buildDomain, credentialManager, fetchT, httpsAgent, log) { const tokenResult = await ensureValidDnsToken(siteConfig, credentialManager, fetchT, log); if (!tokenResult.success) { throw new Error(`DNS token not available: ${tokenResult.error}`); @@ -285,7 +285,7 @@ function createDnsContext(siteConfig, buildDomain, credentialManager, fetchT, ht const require = (providedToken) => requireDnsToken(providedToken, siteConfig, credentialManager, fetchT, log); const getForServer = (server, role) => getTokenForServer(server, siteConfig, credentialManager, fetchT, log, role); const refresh = (username, password, server) => refreshDnsToken(username, password, server, fetchT, log); - const create = (subdomain, ip) => createDnsRecord(subdomain, ip, siteConfig, buildDomain, fetchT, httpsAgent, log); + const create = (subdomain, ip) => createDnsRecord(subdomain, ip, siteConfig, buildDomain, credentialManager, fetchT, httpsAgent, log); const call = (server, apiPath, params) => callDns(server, apiPath, params, fetchT, httpsAgent); return {