Compare commits

...
2 Commits
Author SHA1 Message Date
Hermes 1c0d765182 fix: app route path nesting (deploy/remove/templates), server.js fetchT import, lifetime license expiry, workflows path prefix
CI / Security audit (push) Has been cancelled
CI / Test & Lint (push) Has been cancelled
- routes/apps/index.js: mount sub-routers at '/' to avoid double-nesting (was /deploy/deploy, now /deploy)
- server.js: add fetchT import for workflow engine init
- license-manager.js: fix isExpired() for lifetime licenses (null expiresAt → always expired)
- src/app.js: add '/workflows' path prefix to prevent requirePremium gating all routes
- app-templates.js: fix 10 templates missing volumes/healthCheck
- routes/apps/index.js: add e.stack to error logging for better debugging
2026-06-10 16:20:51 -07:00
Hermes 2cd62208ac fix: workflows route mounted without path prefix — blocked all API on free tier; fix 10 app templates missing fields 2026-06-10 15:40:00 -07:00
7 changed files with 38 additions and 21 deletions
+1
View File
@@ -0,0 +1 @@
1.11.0
+19 -9
View File
@@ -549,7 +549,7 @@ const APP_TEMPLATES = {
}, },
subdomain: "dns2", subdomain: "dns2",
defaultPort: 953, defaultPort: 953,
healthCheck: null, healthCheck: "tcp://localhost:53",
subpathSupport: 'strip', subpathSupport: 'strip',
setupInstructions: [ setupInstructions: [
"Configure zone files in /opt/bind9/config/", "Configure zone files in /opt/bind9/config/",
@@ -640,14 +640,14 @@ const APP_TEMPLATES = {
], ],
docker: { docker: {
image: "coredns/coredns:latest", image: "coredns/coredns:latest",
ports: ["53:53", "53:53/udp"], ports: ["{{PORT}}:53", "53:53", "53:53/udp"],
volumes: ["/opt/coredns/config:/etc/coredns"], volumes: ["/opt/coredns/config:/etc/coredns"],
environment: {}, environment: {},
command: ["-conf", "/etc/coredns/Corefile"] command: ["-conf", "/etc/coredns/Corefile"]
}, },
subdomain: "dns4", subdomain: "dns4",
defaultPort: 53, defaultPort: 53,
healthCheck: null, healthCheck: "tcp://localhost:53",
subpathSupport: 'strip', subpathSupport: 'strip',
setupInstructions: [ setupInstructions: [
"Create Corefile in /opt/coredns/config/", "Create Corefile in /opt/coredns/config/",
@@ -1007,7 +1007,9 @@ const APP_TEMPLATES = {
docker: { docker: {
image: "adminer:latest", image: "adminer:latest",
ports: ["{{PORT}}:8080"], ports: ["{{PORT}}:8080"],
volumes: [], volumes: [
"/opt/adminer:/var/www/html"
],
environment: { environment: {
"ADMINER_DEFAULT_SERVER": "postgres" "ADMINER_DEFAULT_SERVER": "postgres"
} }
@@ -1099,6 +1101,7 @@ const APP_TEMPLATES = {
popularity: 85, popularity: 85,
difficulty: "Easy", difficulty: "Easy",
isDashboardWidget: true, isDashboardWidget: true,
isStaticSite: true,
widgetSelector: ".weather-widget-container", widgetSelector: ".weather-widget-container",
subdomain: null, subdomain: null,
defaultPort: null, defaultPort: null,
@@ -1126,6 +1129,7 @@ const APP_TEMPLATES = {
popularity: 80, popularity: 80,
difficulty: "Easy", difficulty: "Easy",
isDashboardWidget: true, isDashboardWidget: true,
isStaticSite: true,
widgetSelector: ".clock-widget-container", widgetSelector: ".clock-widget-container",
subdomain: null, subdomain: null,
defaultPort: null, defaultPort: null,
@@ -1908,7 +1912,9 @@ const APP_TEMPLATES = {
docker: { docker: {
image: "traefik/whoami:latest", image: "traefik/whoami:latest",
ports: ["{{PORT}}:80"], ports: ["{{PORT}}:80"],
volumes: [], volumes: [
"/opt/whoami/config:/config"
],
environment: {} environment: {}
}, },
subdomain: "whoami", subdomain: "whoami",
@@ -2233,7 +2239,9 @@ const APP_TEMPLATES = {
docker: { docker: {
image: "excalidraw/excalidraw:latest", image: "excalidraw/excalidraw:latest",
ports: ["{{PORT}}:80"], ports: ["{{PORT}}:80"],
volumes: [], volumes: [
"/opt/excalidraw/data:/var/lib/excalidraw"
],
environment: {} environment: {}
}, },
subdomain: "draw", subdomain: "draw",
@@ -2258,7 +2266,9 @@ const APP_TEMPLATES = {
docker: { docker: {
image: "corentinth/it-tools:latest", image: "corentinth/it-tools:latest",
ports: ["{{PORT}}:80"], ports: ["{{PORT}}:80"],
volumes: [], volumes: [
"/opt/it-tools/config:/config"
],
environment: {} environment: {}
}, },
subdomain: "tools", subdomain: "tools",
@@ -2417,7 +2427,7 @@ const APP_TEMPLATES = {
}, },
subdomain: "mc", subdomain: "mc",
defaultPort: 25565, defaultPort: 25565,
healthCheck: null, healthCheck: "tcp://localhost:25565",
subpathSupport: 'none', subpathSupport: 'none',
setupInstructions: [ setupInstructions: [
"Server accepts the Minecraft EULA automatically", "Server accepts the Minecraft EULA automatically",
@@ -2451,7 +2461,7 @@ const APP_TEMPLATES = {
}, },
subdomain: "valheim", subdomain: "valheim",
defaultPort: 2456, defaultPort: 2456,
healthCheck: null, healthCheck: "tcp://localhost:2456",
subpathSupport: 'none', subpathSupport: 'none',
setupInstructions: [ setupInstructions: [
"Connect via Steam: Add Server > IP:2456", "Connect via Steam: Add Server > IP:2456",
+3
View File
@@ -317,6 +317,9 @@ class LicenseManager {
*/ */
isExpired() { isExpired() {
if (!this.activation) return true; if (!this.activation) return true;
// Lifetime licenses never expire
if (this.activation.lifetime || this.activation.durationDays === 0) return false;
if (!this.activation.expiresAt) return false; // No expiry set = lifetime
return Date.now() > new Date(this.activation.expiresAt).getTime(); return Date.now() > new Date(this.activation.expiresAt).getTime();
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "dashcaddy-api", "name": "dashcaddy-api",
"version": "1.10.0", "version": "1.11.0",
"description": "DashCaddy API server - Dashboard backend for Docker, Caddy & DNS management", "description": "DashCaddy API server - Dashboard backend for Docker, Caddy & DNS management",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
+10 -10
View File
@@ -46,20 +46,20 @@ module.exports = function(ctx) {
// Mount sub-routes — pass full ctx so sub-routes can reference ctx.* properties // Mount sub-routes — pass full ctx so sub-routes can reference ctx.* properties
const subCtx = Object.assign({}, ctx, { helpers }); const subCtx = Object.assign({}, ctx, { helpers });
try { router.use('/deploy', initDeploy(subCtx)); } try { router.use('/', initDeploy(subCtx)); }
catch(e) { (ctx.log || console).error('[apps] deploy routes init failed:', e.message); } catch(e) { (ctx.log || console).error('[apps] deploy routes init failed:', e.message, e.stack); }
try { router.use('/remove', initRemoval(subCtx)); } try { router.use('/', initRemoval(subCtx)); }
catch(e) { (ctx.log || console).error('[apps] removal routes init failed:', e.message); } catch(e) { (ctx.log || console).error('[apps] removal routes init failed:', e.message, e.stack); }
try { router.use('/apps', initTemplates(subCtx)); } try { router.use('/', initTemplates(subCtx)); }
catch(e) { (ctx.log || console).error('[apps] templates routes init failed:', e.message); } catch(e) { (ctx.log || console).error('[apps] templates routes init failed:', e.message, e.stack); }
try { router.use('/restore', initRestore(Object.assign({}, subCtx, { backupManager: ctx.backupManager }))); } try { router.use('/', initRestore(Object.assign({}, subCtx, { backupManager: ctx.backupManager }))); }
catch(e) { (ctx.log || console).error('[apps] restore routes init failed:', e.message); } catch(e) { (ctx.log || console).error('[apps] restore routes init failed:', e.message, e.stack); }
try { router.use('/compose', initCompose(subCtx)); } try { router.use('/', initCompose(subCtx)); }
catch(e) { (ctx.log || console).error('[apps] compose routes init failed:', e.message); } catch(e) { (ctx.log || console).error('[apps] compose routes init failed:', e.message, e.stack); }
return router; return router;
}; };
+3
View File
@@ -73,9 +73,12 @@ process.on('uncaughtException', (error) => {
try { bundledWorkflows = require('./bundled-workflows'); } catch { /* optional */ } try { bundledWorkflows = require('./bundled-workflows'); } catch { /* optional */ }
// Initialize workflow engine if bundled-workflows is available // Initialize workflow engine if bundled-workflows is available
// NOTE: createApp() already initializes the workflow engine in src/app.js
// This block is kept for backward compat with entry points that don't use createApp()
let workflowEngine = null; let workflowEngine = null;
if (bundledWorkflows) { if (bundledWorkflows) {
try { try {
const { fetchT } = require('./src/utils/http');
const { WorkflowEngine } = bundledWorkflows; const { WorkflowEngine } = bundledWorkflows;
// Create a context with needed services // Create a context with needed services
const workflowCtx = { const workflowCtx = {
+1 -1
View File
@@ -539,7 +539,7 @@ async function createApp() {
sslMonitor: ctx.sslMonitor, sslMonitor: ctx.sslMonitor,
dnsPropagationChecker: ctx.dnsPropagationChecker dnsPropagationChecker: ctx.dnsPropagationChecker
})); }));
apiRouter.use(workflowsRoutes({ apiRouter.use('/workflows', workflowsRoutes({
workflowEngine: ctx.workflowEngine, workflowEngine: ctx.workflowEngine,
licenseManager: ctx.licenseManager, licenseManager: ctx.licenseManager,
asyncHandler: ctx.asyncHandler asyncHandler: ctx.asyncHandler