[glm-grade=B] fix: dead dashboard WS, corrupted error.log, auth-polling storm, re-auth freeze + CVE bumps
CI / Test & Lint (push) Canceled after 0s
CI / Security audit (push) Canceled after 0s

Adversarial audit 2026-08-16 (GLM-5.3 delegate, 2 rounds, 141 tool calls):

P0-1: Dashboard WebSocket (/api/v1/ws) dead on EVERY boot since DC-076.
server.js passed module exports (DependencyManager class, {AutoRestartManager}
namespace, SSLMonitor class) instead of createApp()'s live instances — first
.on() threw ERR_INVALID_ARG_TYPE, catch swallowed it. Fix: app.locals.ctx
exposed in src/app.js; server.js passes all 8 real EventEmitter instances.

P0-2: error.log corrupted since 2026-07-14. errorMiddleware called
logError(FILE, SIZE, path, err, meta) — 5 args into a 3-arg wrapper —
logging 'Error: 5242880' garbage every ~60s and DISCARDING the real error
object. Fix: correct 3-arg call + legacy-shape guard in logErrorWrapper +
~74 log.error sites swept to pass real error objects (AST-verified scope-
safe 71/71, 29/29 modules load clean).

P0-3: auth-polling storm (stranded grade=B commit never landed in prod):
401/403 behind TOTP gate hammered /api/v1/services/status + SSE reconnect
every 2-8s, with misleading direct-probe fallback marking services 'up'.
Fix landed + B-round MEDIUM follow-up: TOTP re-auth success now clears
_dcAuthLost, resumes SSE (new _sseResume clears the latch), and refreshes.

Also: eslintignore static-sites/ (33→0 errors); nodemailer 8→9.0.5 and
sharp 0.33→0.35.3 (3 high CVEs killed; jest green on new majors);
dockerode@5/uuid deferred (semver-major, Docker API surface).

Verification: 80/80 suites, 1837/1837 tests; ESLint 0 errors/743 warnings;
node --check all changed files; bundles rebuilt + SW cache bumped.

Judges: Codex quota-dead until Aug 19 (verified live) — GLM adversarial
delegate per operator directive 2026-08-07. Round 1: 98-call mechanical
verification (timed out pre-verdict). Round 2 (this grade): B, one MEDIUM
(re-auth freeze) — fixed in this commit as prescribed.
This commit is contained in:
Krystie
2026-08-16 04:18:07 -07:00
parent 295c63ce94
commit e99413150e
39 changed files with 533 additions and 355 deletions
+1
View File
@@ -3,3 +3,4 @@ coverage/
dist/ dist/
build/ build/
*.min.js *.min.js
static-sites/
+270 -159
View File
@@ -19,13 +19,13 @@
"js-yaml": "^4.1.1", "js-yaml": "^4.1.1",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"lru-cache": "^10.4.3", "lru-cache": "^10.4.3",
"nodemailer": "^8.0.4", "nodemailer": "^9.0.5",
"otplib": "^12.0.1", "otplib": "^12.0.1",
"pdfkit": "^0.15.2", "pdfkit": "^0.15.2",
"png-to-ico": "^2.1.8", "png-to-ico": "^2.1.8",
"proper-lockfile": "^4.1.2", "proper-lockfile": "^4.1.2",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"sharp": "^0.33.5", "sharp": "^0.35.3",
"ssh2-sftp-client": "^11.0.0", "ssh2-sftp-client": "^11.0.0",
"validator": "^13.11.0", "validator": "^13.11.0",
"webdav": "^5.7.1", "webdav": "^5.7.1",
@@ -564,9 +564,9 @@
} }
}, },
"node_modules/@emnapi/runtime": { "node_modules/@emnapi/runtime": {
"version": "1.8.1", "version": "1.11.3",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
"integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
@@ -771,10 +771,19 @@
"dev": true, "dev": true,
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/@img/colour": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@img/sharp-darwin-arm64": { "node_modules/@img/sharp-darwin-arm64": {
"version": "0.33.5", "version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz",
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -784,19 +793,19 @@
"darwin" "darwin"
], ],
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.0.4" "@img/sharp-libvips-darwin-arm64": "1.3.2"
} }
}, },
"node_modules/@img/sharp-darwin-x64": { "node_modules/@img/sharp-darwin-x64": {
"version": "0.33.5", "version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz",
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -806,19 +815,38 @@
"darwin" "darwin"
], ],
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.0.4" "@img/sharp-libvips-darwin-x64": "1.3.2"
}
},
"node_modules/@img/sharp-freebsd-wasm32": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz",
"integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==",
"license": "Apache-2.0",
"optional": true,
"os": [
"freebsd"
],
"dependencies": {
"@img/sharp-wasm32": "0.35.3"
},
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
} }
}, },
"node_modules/@img/sharp-libvips-darwin-arm64": { "node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.0.4", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz",
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -832,9 +860,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-darwin-x64": { "node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.0.4", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz",
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -848,9 +876,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-arm": { "node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.0.5", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz",
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -864,9 +892,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-arm64": { "node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.0.4", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz",
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -879,10 +907,42 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
} }
}, },
"node_modules/@img/sharp-libvips-linux-ppc64": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz",
"integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==",
"cpu": [
"ppc64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-riscv64": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz",
"integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==",
"cpu": [
"riscv64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-s390x": { "node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.0.4", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz",
"integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@@ -896,9 +956,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-x64": { "node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.0.4", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz",
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -912,9 +972,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linuxmusl-arm64": { "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.0.4", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz",
"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -928,9 +988,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linuxmusl-x64": { "node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.0.4", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz",
"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -944,9 +1004,9 @@
} }
}, },
"node_modules/@img/sharp-linux-arm": { "node_modules/@img/sharp-linux-arm": {
"version": "0.33.5", "version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz",
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -956,19 +1016,19 @@
"linux" "linux"
], ],
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.0.5" "@img/sharp-libvips-linux-arm": "1.3.2"
} }
}, },
"node_modules/@img/sharp-linux-arm64": { "node_modules/@img/sharp-linux-arm64": {
"version": "0.33.5", "version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz",
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -978,19 +1038,63 @@
"linux" "linux"
], ],
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.0.4" "@img/sharp-libvips-linux-arm64": "1.3.2"
}
},
"node_modules/@img/sharp-linux-ppc64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz",
"integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==",
"cpu": [
"ppc64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.3.2"
}
},
"node_modules/@img/sharp-linux-riscv64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz",
"integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==",
"cpu": [
"riscv64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-riscv64": "1.3.2"
} }
}, },
"node_modules/@img/sharp-linux-s390x": { "node_modules/@img/sharp-linux-s390x": {
"version": "0.33.5", "version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz",
"integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@@ -1000,19 +1104,19 @@
"linux" "linux"
], ],
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.0.4" "@img/sharp-libvips-linux-s390x": "1.3.2"
} }
}, },
"node_modules/@img/sharp-linux-x64": { "node_modules/@img/sharp-linux-x64": {
"version": "0.33.5", "version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz",
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1022,19 +1126,19 @@
"linux" "linux"
], ],
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.0.4" "@img/sharp-libvips-linux-x64": "1.3.2"
} }
}, },
"node_modules/@img/sharp-linuxmusl-arm64": { "node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.33.5", "version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz",
"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1044,19 +1148,19 @@
"linux" "linux"
], ],
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4" "@img/sharp-libvips-linuxmusl-arm64": "1.3.2"
} }
}, },
"node_modules/@img/sharp-linuxmusl-x64": { "node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.33.5", "version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz",
"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1066,38 +1170,73 @@
"linux" "linux"
], ],
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.0.4" "@img/sharp-libvips-linuxmusl-x64": "1.3.2"
} }
}, },
"node_modules/@img/sharp-wasm32": { "node_modules/@img/sharp-wasm32": {
"version": "0.33.5", "version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz",
"integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==",
"cpu": [
"wasm32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"@emnapi/runtime": "^1.2.0" "@emnapi/runtime": "^1.11.1"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-webcontainers-wasm32": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz",
"integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==",
"cpu": [
"wasm32"
],
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"@img/sharp-wasm32": "0.35.3"
},
"engines": {
"node": ">=20.9.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-arm64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz",
"integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=20.9.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
} }
}, },
"node_modules/@img/sharp-win32-ia32": { "node_modules/@img/sharp-win32-ia32": {
"version": "0.33.5", "version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz",
"integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -1107,16 +1246,16 @@
"win32" "win32"
], ],
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": "^20.9.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
} }
}, },
"node_modules/@img/sharp-win32-x64": { "node_modules/@img/sharp-win32-x64": {
"version": "0.33.5", "version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz",
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1126,7 +1265,7 @@
"win32" "win32"
], ],
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
@@ -2623,19 +2762,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2654,16 +2780,6 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"license": "MIT",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/combined-stream": { "node_modules/combined-stream": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -6002,9 +6118,9 @@
} }
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
@@ -6127,9 +6243,9 @@
} }
}, },
"node_modules/nodemailer": { "node_modules/nodemailer": {
"version": "8.0.11", "version": "9.0.5",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.11.tgz", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.0.5.tgz",
"integrity": "sha512-nrO/pDAUKl+wXX+lx16tDLbnm0fW6sK/x8mgohaCpg+CdCEl482bD4tCuAZk2DyliruiNTIZxRCoWkDqJEnAiA==", "integrity": "sha512-wvjiKvjczmsN7U/8006JOdXubgBk2XFAbioDMbT+sM7cPs0QrhJTa6KBRX7P5REGGkDcLUz/EarWidb8G8C1jQ==",
"license": "MIT-0", "license": "MIT-0",
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
@@ -7242,48 +7358,58 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/sharp": { "node_modules/sharp": {
"version": "0.33.5", "version": "0.35.3",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz",
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==",
"hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"color": "^4.2.3", "@img/colour": "^1.1.0",
"detect-libc": "^2.0.3", "detect-libc": "^2.1.2",
"semver": "^7.6.3" "semver": "^7.8.5"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": ">=20.9.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-arm64": "0.35.3",
"@img/sharp-darwin-x64": "0.33.5", "@img/sharp-darwin-x64": "0.35.3",
"@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-freebsd-wasm32": "0.35.3",
"@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-darwin-arm64": "1.3.2",
"@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-darwin-x64": "1.3.2",
"@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.3.2",
"@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-arm64": "1.3.2",
"@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linux-ppc64": "1.3.2",
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linux-riscv64": "1.3.2",
"@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.3.2",
"@img/sharp-linux-arm": "0.33.5", "@img/sharp-libvips-linux-x64": "1.3.2",
"@img/sharp-linux-arm64": "0.33.5", "@img/sharp-libvips-linuxmusl-arm64": "1.3.2",
"@img/sharp-linux-s390x": "0.33.5", "@img/sharp-libvips-linuxmusl-x64": "1.3.2",
"@img/sharp-linux-x64": "0.33.5", "@img/sharp-linux-arm": "0.35.3",
"@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linux-arm64": "0.35.3",
"@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-linux-ppc64": "0.35.3",
"@img/sharp-wasm32": "0.33.5", "@img/sharp-linux-riscv64": "0.35.3",
"@img/sharp-win32-ia32": "0.33.5", "@img/sharp-linux-s390x": "0.35.3",
"@img/sharp-win32-x64": "0.33.5" "@img/sharp-linux-x64": "0.35.3",
"@img/sharp-linuxmusl-arm64": "0.35.3",
"@img/sharp-linuxmusl-x64": "0.35.3",
"@img/sharp-webcontainers-wasm32": "0.35.3",
"@img/sharp-win32-arm64": "0.35.3",
"@img/sharp-win32-ia32": "0.35.3",
"@img/sharp-win32-x64": "0.35.3"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
} }
}, },
"node_modules/sharp/node_modules/semver": { "node_modules/sharp/node_modules/semver": {
"version": "7.7.3", "version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"license": "ISC", "license": "ISC",
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
@@ -7393,21 +7519,6 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/simple-swizzle": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
"integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/simple-swizzle/node_modules/is-arrayish": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
"license": "MIT"
},
"node_modules/sisteransi": { "node_modules/sisteransi": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
+2 -2
View File
@@ -33,13 +33,13 @@
"js-yaml": "^4.1.1", "js-yaml": "^4.1.1",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"lru-cache": "^10.4.3", "lru-cache": "^10.4.3",
"nodemailer": "^8.0.4", "nodemailer": "^9.0.5",
"otplib": "^12.0.1", "otplib": "^12.0.1",
"pdfkit": "^0.15.2", "pdfkit": "^0.15.2",
"png-to-ico": "^2.1.8", "png-to-ico": "^2.1.8",
"proper-lockfile": "^4.1.2", "proper-lockfile": "^4.1.2",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"sharp": "^0.33.5", "sharp": "^0.35.3",
"ssh2-sftp-client": "^11.0.0", "ssh2-sftp-client": "^11.0.0",
"validator": "^13.11.0", "validator": "^13.11.0",
"webdav": "^5.7.1", "webdav": "^5.7.1",
+3 -3
View File
@@ -95,7 +95,7 @@ module.exports = function({ docker, caddy, credentialManager, servicesStateManag
log.info('deploy', 'DashCA: For full features, copy certificate files to ' + destPath); log.info('deploy', 'DashCA: For full features, copy certificate files to ' + destPath);
log.info('deploy', 'DashCA: Static site deployment completed successfully'); log.info('deploy', 'DashCA: Static site deployment completed successfully');
} catch (error) { } catch (error) {
log.error('deploy', 'DashCA deployment error', { error: error.message }); log.error('deploy', error, null, { note: 'DashCA deployment error' });
throw new Error(`DashCA deployment failed: ${error.message}`); throw new Error(`DashCA deployment failed: ${error.message}`);
} }
} }
@@ -231,7 +231,7 @@ module.exports = function({ docker, caddy, credentialManager, servicesStateManag
await portLockManager.releasePorts(lockId); await portLockManager.releasePorts(lockId);
log.info('deploy', 'Port locks released after error', { lockId }); log.info('deploy', 'Port locks released after error', { lockId });
} catch (releaseError) { } catch (releaseError) {
log.error('deploy', 'Failed to release port locks', { lockId, error: releaseError.message }); log.error('deploy', releaseError, null, { note: 'Failed to release port locks', lockId });
} }
} }
throw deployError; throw deployError;
@@ -425,7 +425,7 @@ module.exports = function({ docker, caddy, credentialManager, servicesStateManag
} catch (error) { } catch (error) {
try { await logError('app-deploy', error, { appId, config }); } catch (_) { /* logError failure should not mask original error */ } try { await logError('app-deploy', error, { appId, config }); } catch (_) { /* logError failure should not mask original error */ }
const msg = error?.message || String(error || 'Unknown error'); const msg = error?.message || String(error || 'Unknown error');
log.error('deploy', 'Deployment failed', { appId, error: msg }); log.error('deploy', error, null, { note: 'Deployment failed', appId });
const template = ctx.APP_TEMPLATES[appId]; const template = ctx.APP_TEMPLATES[appId];
try { ctx.notification.send('deploymentFailed', 'Deployment Failed', `Failed to deploy **${template?.name || appId}**.\nError: ${msg}`, 'error'); } catch (_) {} try { ctx.notification.send('deploymentFailed', 'Deployment Failed', `Failed to deploy **${template?.name || appId}**.\nError: ${msg}`, 'error'); } catch (_) {}
errorResponse(res, 500, ctx.safeErrorMessage(error)); errorResponse(res, 500, ctx.safeErrorMessage(error));
+1 -1
View File
@@ -297,7 +297,7 @@ module.exports = function({ docker, caddy, servicesStateManager, asyncHandler, e
// P0-5 fix: was `errorResponse(res, 500, err.message)` which leaks internal // P0-5 fix: was `errorResponse(res, 500, err.message)` which leaks internal
// error details (paths, stack traces, library error codes) to the client. // error details (paths, stack traces, library error codes) to the client.
// Log the actual error server-side and return a generic message. // Log the actual error server-side and return a generic message.
log.error('apps-revert', 'Revert failed', { error: err.message, stack: err.stack }); log.error('apps-revert', err, null, { note: 'Revert failed', stack: err.stack });
errorResponse(res, 500, 'Revert failed'); errorResponse(res, 500, 'Revert failed');
} }
}, 'apps-revert')); }, 'apps-revert'));
+1 -1
View File
@@ -148,7 +148,7 @@ module.exports = function({
} }
} catch (error) { } catch (error) {
results.caddy = `failed: ${error.message}`; results.caddy = `failed: ${error.message}`;
log.error('caddy', 'Caddy update error', { error: error.message }); log.error('caddy', error, null, { note: 'Caddy update error' });
} }
try { try {
+3 -3
View File
@@ -37,7 +37,7 @@ module.exports = function({ docker, credentialManager, fetchT, log }) {
stream.on('error', () => resolve(null)); stream.on('error', () => resolve(null));
}); });
} catch (error) { } catch (error) {
log.error('docker', 'Failed to get API key', { containerName, error: error.message }); log.error('docker', error, null, { note: 'Failed to get API key', containerName });
return null; return null;
} }
} }
@@ -71,7 +71,7 @@ module.exports = function({ docker, credentialManager, fetchT, log }) {
stream.on('error', () => resolve(null)); stream.on('error', () => resolve(null));
}); });
} catch (error) { } catch (error) {
log.error('docker', 'Failed to get Plex token', { error: error.message }); log.error('docker', error, null, { note: 'Failed to get Plex token' });
return null; return null;
} }
} }
@@ -123,7 +123,7 @@ module.exports = function({ docker, credentialManager, fetchT, log }) {
const sessionCookie = setCookie.split(';')[0]; const sessionCookie = setCookie.split(';')[0];
return { cookie: sessionCookie, plexToken }; return { cookie: sessionCookie, plexToken };
} catch (e) { } catch (e) {
log.error('arr', 'Could not get Seerr session', { error: e.message }); log.error('arr', e, null, { note: 'Could not get Seerr session' });
return null; return null;
} }
} }
+1 -1
View File
@@ -162,7 +162,7 @@ module.exports = function({ docker, log, asyncHandler, workflowEngine }) {
await newContainer.start(); await newContainer.start();
} catch (startError) { } catch (startError) {
// Clean up the failed container so it doesn't block future attempts // Clean up the failed container so it doesn't block future attempts
log.error('docker', 'Failed to start new container', { containerName, error: startError.message }); log.error('docker', startError, null, { note: 'Failed to start new container', containerName });
if (newContainer) { if (newContainer) {
try { await newContainer.remove({ force: true }); } catch (e) { /* already gone */ } try { await newContainer.remove({ force: true }); } catch (e) { /* already gone */ }
} }
+8 -8
View File
@@ -110,7 +110,7 @@ module.exports = function({
...(result.instructions ? { manual: true, instructions: result.instructions } : {}) ...(result.instructions ? { manual: true, instructions: result.instructions } : {})
}); });
} catch (error) { } catch (error) {
log.error('dns', 'Universal DNS record creation error', { error: error.message }); log.error('dns', error, null, { note: 'Universal DNS record creation error' });
errorResponse(res, safeErrorMessage(error), 500); errorResponse(res, safeErrorMessage(error), 500);
} }
}, 'dns-universal-create')); }, 'dns-universal-create'));
@@ -136,7 +136,7 @@ module.exports = function({
...(result.instructions ? { manual: true, instructions: result.instructions } : {}) ...(result.instructions ? { manual: true, instructions: result.instructions } : {})
}); });
} catch (error) { } catch (error) {
log.error('dns', 'Universal DNS record deletion error', { error: error.message }); log.error('dns', error, null, { note: 'Universal DNS record deletion error' });
errorResponse(res, safeErrorMessage(error), 500); errorResponse(res, safeErrorMessage(error), 500);
} }
}, 'dns-universal-delete')); }, 'dns-universal-delete'));
@@ -167,7 +167,7 @@ module.exports = function({
throw new NotFoundError('No records found for domain'); throw new NotFoundError('No records found for domain');
} }
} catch (error) { } catch (error) {
log.error('dns', 'Universal DNS resolve error', { error: error.message }); log.error('dns', error, null, { note: 'Universal DNS resolve error' });
errorResponse(res, safeErrorMessage(error), error.statusCode || 500); errorResponse(res, safeErrorMessage(error), error.statusCode || 500);
} }
}, 'dns-universal-resolve')); }, 'dns-universal-resolve'));
@@ -283,7 +283,7 @@ module.exports = function({
// Error handled by middleware // Error handled by middleware
} }
} catch (error) { } catch (error) {
log.error('dns', 'DNS record creation error', { error: error.message }); log.error('dns', error, null, { note: 'DNS record creation error' });
errorResponse(res, safeErrorMessage(error), 500, { details: error.cause?.code || 'fetch failed' }); errorResponse(res, safeErrorMessage(error), 500, { details: error.cause?.code || 'fetch failed' });
} }
}, 'dns-create-record')); }, 'dns-create-record'));
@@ -328,7 +328,7 @@ module.exports = function({
// Error handled by middleware // Error handled by middleware
} }
} catch (error) { } catch (error) {
log.error('dns', 'DNS resolve error', { error: error.message }); log.error('dns', error, null, { note: 'DNS resolve error' });
// Error handled by middleware // Error handled by middleware
} }
}, 'dns-resolve')); }, 'dns-resolve'));
@@ -465,7 +465,7 @@ module.exports = function({
}); });
} catch (error) { } catch (error) {
log.error('dns', 'DNS logs proxy error', { error: error.message }); log.error('dns', error, null, { note: 'DNS logs proxy error' });
// Error handled by middleware // Error handled by middleware
} }
}, 'dns-logs')); }, 'dns-logs'));
@@ -723,7 +723,7 @@ module.exports = function({
// Error handled by middleware // Error handled by middleware
} }
} catch (error) { } catch (error) {
log.error('dns', 'DNS update check error', { error: error.message }); log.error('dns', error, null, { note: 'DNS update check error' });
// Error handled by middleware // Error handled by middleware
} }
}, 'dns-check-update')); }, 'dns-check-update'));
@@ -791,7 +791,7 @@ module.exports = function({
manualUpdateRequired: true manualUpdateRequired: true
}); });
} catch (error) { } catch (error) {
log.error('dns', 'DNS update error', { error: error.message }); log.error('dns', error, null, { note: 'DNS update error' });
// Error handled by middleware // Error handled by middleware
} }
}, 'dns-update')); }, 'dns-update'));
+1 -1
View File
@@ -165,7 +165,7 @@ async function handleExec(ws, containerId, log, auth) {
}); });
} catch (err) { } catch (err) {
log.error('exec', 'Failed to start exec session', { containerId, error: err.message }); log.error('exec', err, null, { note: 'Failed to start exec session', containerId });
if (ws.readyState === ws.OPEN) { if (ws.readyState === ws.OPEN) {
ws.send(JSON.stringify({ type: 'error', message: err.message })); ws.send(JSON.stringify({ type: 'error', message: err.message }));
ws.close(); ws.close();
+1 -1
View File
@@ -149,7 +149,7 @@ module.exports = function({ docker, credentialManager: _credentialManager, servi
ok(res, response); ok(res, response);
} catch (error) { } catch (error) {
log.error('recipe', 'Recipe deployment failed', { recipeId, error: error.message }); log.error('recipe', error, null, { note: 'Recipe deployment failed', recipeId });
// Cleanup: remove partially deployed containers // Cleanup: remove partially deployed containers
for (const deployed of deployedComponents) { for (const deployed of deployedComponents) {
+1 -1
View File
@@ -421,7 +421,7 @@ module.exports = function({
resyncHealthChecker?.().catch(() => {}); resyncHealthChecker?.().catch(() => {});
success(res, { message: `Service "${name}" added to dashboard` }); success(res, { message: `Service "${name}" added to dashboard` });
} catch (error) { } catch (error) {
log.error('deploy', 'Error adding service', { error: error.message }); log.error('deploy', error, null, { note: 'Error adding service' });
if (error.message.includes('already exists')) { if (error.message.includes('already exists')) {
errorResponse(res, safeErrorMessage(error), 409); errorResponse(res, safeErrorMessage(error), 409);
} else { } else {
+1 -1
View File
@@ -46,7 +46,7 @@ module.exports = function({ asyncHandler, ok, caddy, dns, fetchT, buildDomain, a
if (!response.ok) { if (!response.ok) {
const errorText = await response.text(); const errorText = await response.text();
log.error('caddy', 'Caddy reload failed', { error: errorText }); log.error('caddy', new Error(`Caddy reload failed: ${errorText.slice(0, 500)}`));
throw new Error('Caddy reload failed. Check server logs for details.'); throw new Error('Caddy reload failed. Check server logs for details.');
} }
+1 -1
View File
@@ -31,7 +31,7 @@ module.exports = function({ asyncHandler, log }) {
themes[slug] = data; themes[slug] = data;
} }
} catch (e) { } catch (e) {
log.error('themes', 'Failed to read themes', { error: e.message }); log.error('themes', e, null, { note: 'Failed to read themes' });
} }
return themes; return themes;
} }
+25 -26
View File
@@ -68,30 +68,29 @@ process.on('uncaughtException', (error) => {
attachExecWS(server, log, authManager); attachExecWS(server, log, authManager);
log.info('server', 'WebSocket exec handler attached (auth enforced)'); log.info('server', 'WebSocket exec handler attached (auth enforced)');
// DC-076: Attach dashboard WebSocket for real-time updates // DC-076: Attach dashboard WebSocket for real-time updates.
// createApp() returns the live manager instances — use those instead
// of re-requiring the modules (which yields singletons for some
// managers and raw classes / namespace objects for others; calling
// .on() on a class threw on every boot and silently killed the WS).
try { try {
const { ctx } = app.locals;
const createDashboardWS = require('./src/websocket/dashboard-ws'); const createDashboardWS = require('./src/websocket/dashboard-ws');
const resourceMonitor = require('./src/managers/resource-monitor');
const healthChecker = require('./src/monitoring/health-checker');
const updateManager = require('./src/managers/update-manager');
const dependencyManager = require('./src/managers/dependency-manager');
const autoRestartManager = require('./src/managers/auto-restart-manager');
const configDriftDetector = require('./src/managers/config-drift-detector');
const sslMonitor = require('./src/monitoring/ssl-monitor');
createDashboardWS(server, { createDashboardWS(server, {
resourceMonitor, resourceMonitor: ctx.resourceMonitor,
healthChecker, healthChecker: ctx.healthChecker,
updateManager, updateManager: ctx.updateManager,
dependencyManager, dependencyManager: ctx.dependencyManager,
autoRestartManager, autoRestartManager: ctx.autoRestartManager,
driftDetector: configDriftDetector, driftDetector: ctx.driftDetector,
sslMonitor, sslMonitor: ctx.sslMonitor,
dnsPropagationChecker: ctx.dnsPropagationChecker,
log, log,
}); });
log.info('server', 'Dashboard WebSocket attached at /api/v1/ws'); log.info('server', 'Dashboard WebSocket attached at /api/v1/ws');
} catch (err) { } catch (err) {
log.error('server', 'Dashboard WebSocket failed to attach', { error: err.message }); log.error('server', err, null, { feature: 'dashboard-ws' });
} }
// Start feature modules // Start feature modules
@@ -136,7 +135,7 @@ process.on('uncaughtException', (error) => {
workflowEngine = new WorkflowEngine(workflowCtx); workflowEngine = new WorkflowEngine(workflowCtx);
log.info('server', 'Workflow engine initialized'); log.info('server', 'Workflow engine initialized');
} catch (err) { } catch (err) {
log.error('server', 'Workflow engine failed to initialize', { error: err.message }); log.error('server', err, null, { note: 'Workflow engine failed to initialize' });
} }
} }
@@ -145,7 +144,7 @@ process.on('uncaughtException', (error) => {
// Clean up stale port locks // Clean up stale port locks
portLockManager.cleanupStaleLocks() portLockManager.cleanupStaleLocks()
.then(() => log.info('server', 'Port lock cleanup completed')) .then(() => log.info('server', 'Port lock cleanup completed'))
.catch(err => log.error('server', 'Port lock cleanup failed', { error: err.message })); .catch(err => log.error('server', err, null, { note: 'Port lock cleanup failed' }));
// Resource monitoring // Resource monitoring
try { try {
@@ -156,7 +155,7 @@ process.on('uncaughtException', (error) => {
} }
log.info('server', 'Resource monitoring started'); log.info('server', 'Resource monitoring started');
} catch (err) { } catch (err) {
log.error('server', 'Resource monitoring failed to start', { error: err.message }); log.error('server', err, null, { note: 'Resource monitoring failed to start' });
} }
// Backup manager // Backup manager
@@ -164,7 +163,7 @@ process.on('uncaughtException', (error) => {
backupManager.start(); backupManager.start();
log.info('server', 'Backup manager started'); log.info('server', 'Backup manager started');
} catch (err) { } catch (err) {
log.error('server', 'Backup manager failed to start', { error: err.message }); log.error('server', err, null, { note: 'Backup manager failed to start' });
} }
// Security event workers (Caddy access log, fail2ban, shared_bans) // Security event workers (Caddy access log, fail2ban, shared_bans)
@@ -175,7 +174,7 @@ process.on('uncaughtException', (error) => {
startSecurityWorkers({ log }); startSecurityWorkers({ log });
log.info('server', 'Security event workers started'); log.info('server', 'Security event workers started');
} catch (err) { } catch (err) {
log.error('server', 'Security event workers failed to start', { error: err.message }); log.error('server', err, null, { note: 'Security event workers failed to start' });
} }
// Connect workflow engine to update manager for pre-update events // Connect workflow engine to update manager for pre-update events
@@ -206,7 +205,7 @@ process.on('uncaughtException', (error) => {
healthChecker.start(); healthChecker.start();
log.info('server', 'Health checker started'); log.info('server', 'Health checker started');
} catch (err) { } catch (err) {
log.error('server', 'Health checker failed to start', { error: err.message }); log.error('server', err, null, { note: 'Health checker failed to start' });
} }
})(); })();
@@ -215,7 +214,7 @@ process.on('uncaughtException', (error) => {
updateManager.start(); updateManager.start();
log.info('server', 'Update manager started'); log.info('server', 'Update manager started');
} catch (err) { } catch (err) {
log.error('server', 'Update manager failed to start', { error: err.message }); log.error('server', err, null, { note: 'Update manager failed to start' });
} }
// Self-updater // Self-updater
@@ -234,7 +233,7 @@ process.on('uncaughtException', (error) => {
}) })
.catch(() => {}); .catch(() => {});
} catch (err) { } catch (err) {
log.error('server', 'Self-updater failed to start', { error: err.message }); log.error('server', err, null, { note: 'Self-updater failed to start' });
} }
// Docker maintenance (optional) // Docker maintenance (optional)
@@ -257,7 +256,7 @@ process.on('uncaughtException', (error) => {
} }
}); });
} catch (err) { } catch (err) {
log.error('server', 'Docker maintenance failed to start', { error: err.message }); log.error('server', err, null, { note: 'Docker maintenance failed to start' });
} }
} }
@@ -271,7 +270,7 @@ process.on('uncaughtException', (error) => {
log.info('digest', `Daily digest generated for ${date}`); log.info('digest', `Daily digest generated for ${date}`);
}); });
} catch (err) { } catch (err) {
log.error('server', 'Log digest failed to start', { error: err.message }); log.error('server', err, null, { note: 'Log digest failed to start' });
} }
} }
+10 -6
View File
@@ -318,7 +318,7 @@ async function createApp() {
const { writeJsonFile } = require('./utilities/fs-helpers'); const { writeJsonFile } = require('./utilities/fs-helpers');
await writeJsonFile(config.TOTP_CONFIG_FILE, totpConfig); await writeJsonFile(config.TOTP_CONFIG_FILE, totpConfig);
} catch (e) { } catch (e) {
log.error('config', 'Could not save TOTP config', { error: e.message }); log.error('config', e, null, { note: 'Could not save TOTP config' });
} }
} }
@@ -437,7 +437,7 @@ async function createApp() {
ctx.workflowEngine = workflowEngine; ctx.workflowEngine = workflowEngine;
log.info('app', 'Workflow engine initialized'); log.info('app', 'Workflow engine initialized');
} catch (err) { } catch (err) {
log.error('app', 'Failed to initialize workflow engine', { error: err.message }); log.error('app', err, null, { note: 'Failed to initialize workflow engine' });
} }
} }
@@ -501,12 +501,12 @@ async function createApp() {
if (ctx.notification && ctx.resourceMonitor) { if (ctx.notification && ctx.resourceMonitor) {
ctx.resourceMonitor.on('alert', (alertData) => { ctx.resourceMonitor.on('alert', (alertData) => {
ctx.notification.sendAlert(alertData).catch(err => { ctx.notification.sendAlert(alertData).catch(err => {
log.error('notification', 'Failed to send alert', { error: err.message }); log.error('notification', err, null, { note: 'Failed to send alert' });
}); });
}); });
ctx.resourceMonitor.on('auto-restart', (data) => { ctx.resourceMonitor.on('auto-restart', (data) => {
ctx.notification.sendServiceEvent('auto-restart', data).catch(err => { ctx.notification.sendServiceEvent('auto-restart', data).catch(err => {
log.error('notification', 'Failed to send auto-restart notification', { error: err.message }); log.error('notification', err, null, { note: 'Failed to send auto-restart notification' });
}); });
}); });
} }
@@ -514,12 +514,12 @@ async function createApp() {
if (ctx.notification && ctx.backupManager) { if (ctx.notification && ctx.backupManager) {
ctx.backupManager.on('backup-complete', (data) => { ctx.backupManager.on('backup-complete', (data) => {
ctx.notification.send('backup-complete', data).catch(err => { ctx.notification.send('backup-complete', data).catch(err => {
log.error('notification', 'Failed to send backup-complete', { error: err.message }); log.error('notification', err, null, { note: 'Failed to send backup-complete' });
}); });
}); });
ctx.backupManager.on('backup-failed', (data) => { ctx.backupManager.on('backup-failed', (data) => {
ctx.notification.send('backup-failed', data).catch(err => { ctx.notification.send('backup-failed', data).catch(err => {
log.error('notification', 'Failed to send backup-failed', { error: err.message }); log.error('notification', err, null, { note: 'Failed to send backup-failed' });
}); });
}); });
} }
@@ -1097,6 +1097,10 @@ async function createApp() {
app.use('/api', notFoundHandler); app.use('/api', notFoundHandler);
app.use(errorMiddleware); app.use(errorMiddleware);
// Expose ctx on the app for entry points (server.js dashboard-WS wiring)
// without changing the returned shape for existing callers/tests.
app.locals.ctx = ctx;
return { app, log, config: config.siteConfig, licenseManager }; return { app, log, config: config.siteConfig, licenseManager };
} }
+1 -1
View File
@@ -97,7 +97,7 @@ function loadAndMigrate(configFile, log) {
raw = JSON.parse(fs.readFileSync(configFile, 'utf8')); raw = JSON.parse(fs.readFileSync(configFile, 'utf8'));
} catch (e) { } catch (e) {
if (log && log.error) { if (log && log.error) {
log.error('config-migration', 'Failed to parse config.json, using defaults', { error: e.message }); log.error('config-migration', e, null, { note: 'Failed to parse config.json, using defaults' });
} }
raw = null; raw = null;
} }
+1 -1
View File
@@ -62,7 +62,7 @@ function loadSiteConfig(CONFIG_FILE, log) {
} }
} catch (e) { } catch (e) {
if (log && log.error) { if (log && log.error) {
log.error('config', 'Failed to load site config', { error: e.message }); log.error('config', e, null, { note: 'Failed to load site config' });
} }
} }
} }
+3 -3
View File
@@ -74,7 +74,7 @@ async function refreshDnsToken(username, password, server, fetchT, log) {
return { success: false, error: result.errorMessage || 'Login failed' }; return { success: false, error: result.errorMessage || 'Login failed' };
} catch (error) { } catch (error) {
log.error('dns', 'DNS token refresh error', { error: error.message }); log.error('dns', error, null, { note: 'DNS token refresh error' });
return { success: false, error: error.message }; return { success: false, error: error.message };
} }
} }
@@ -141,7 +141,7 @@ async function ensureValidDnsToken(siteConfig, credentialManager, fetchT, log) {
return await refreshDnsToken(username, password, server || primaryIp, fetchT, log); return await refreshDnsToken(username, password, server || primaryIp, fetchT, log);
} }
} catch (err) { } catch (err) {
log.error('dns', 'Credential manager error', { error: err.message }); log.error('dns', err, null, { note: 'Credential manager error' });
} }
return { return {
@@ -237,7 +237,7 @@ async function getTokenForServer(targetServer, siteConfig, credentialManager, fe
return await authenticateToServer(username, password); return await authenticateToServer(username, password);
} }
} catch (err) { } catch (err) {
log.error('dns', 'Credential manager error', { server: targetServer, error: err.message }); log.error('dns', err, null, { note: 'Credential manager error', server: targetServer });
} }
return { success: false, error: 'No DNS credentials configured' }; return { success: false, error: 'No DNS credentials configured' };
+1 -1
View File
@@ -121,7 +121,7 @@ function assembleContext({
try { try {
fs.writeFileSync(TAILSCALE_CONFIG_FILE, JSON.stringify(meta, null, 2), 'utf8'); fs.writeFileSync(TAILSCALE_CONFIG_FILE, JSON.stringify(meta, null, 2), 'utf8');
} catch (e) { } catch (e) {
log.error('tailscale-coord', 'Failed to write tailscale-config.json', { error: e.message }); log.error('tailscale-coord', e, null, { note: 'Failed to write tailscale-config.json' });
} }
} }
async function getCoordClient() { async function getCoordClient() {
+1 -1
View File
@@ -103,7 +103,7 @@ function createProviderDnsContext(siteConfig, buildDomain, credentialManager, fe
} }
return { success: false, error: result.errorMessage || 'Login failed' }; return { success: false, error: result.errorMessage || 'Login failed' };
} catch (error) { } catch (error) {
log.error('dns', 'DNS token refresh error', { error: error.message }); log.error('dns', error, null, { note: 'DNS token refresh error' });
return { success: false, error: error.message }; return { success: false, error: error.message };
} }
} }
+2 -6
View File
@@ -172,9 +172,7 @@ class DNSPropagationChecker extends EventEmitter {
expectedIp, expectedIp,
totalTime: result.totalTime totalTime: result.totalTime
}, 'success').catch(err => { }, 'success').catch(err => {
this.log.error('dns-propagation', 'Failed to send propagation notification', { this.log.error('dns-propagation', err, null, { note: 'Failed to send propagation notification' });
error: err.message
});
}); });
} }
} else { } else {
@@ -187,9 +185,7 @@ class DNSPropagationChecker extends EventEmitter {
expectedIp, expectedIp,
totalTime: result.totalTime totalTime: result.totalTime
}, 'warning').catch(err => { }, 'warning').catch(err => {
this.log.error('dns-propagation', 'Failed to send timeout notification', { this.log.error('dns-propagation', err, null, { note: 'Failed to send timeout notification' });
error: err.message
});
}); });
} }
} }
@@ -118,7 +118,7 @@ class TechnitiumDNSProvider extends BaseDNSProvider {
return await this._doLogin(username, password); return await this._doLogin(username, password);
} }
} catch (err) { } catch (err) {
log.error('technitium', 'Global credential error', { error: err.message }); log.error('technitium', err, null, { note: 'Global credential error' });
} }
return { return {
@@ -164,7 +164,7 @@ class TechnitiumDNSProvider extends BaseDNSProvider {
return { success: false, error: result.errorMessage || 'Login failed' }; return { success: false, error: result.errorMessage || 'Login failed' };
} catch (error) { } catch (error) {
log.error('technitium', 'Login error', { error: error.message }); log.error('technitium', error, null, { note: 'Login error' });
return { success: false, error: error.message }; return { success: false, error: error.message };
} }
} }
@@ -363,7 +363,7 @@ class TechnitiumDNSProvider extends BaseDNSProvider {
const parsed = this._parseLogText(logText, limit); const parsed = this._parseLogText(logText, limit);
return { success: true, logs: parsed }; return { success: true, logs: parsed };
} catch (error) { } catch (error) {
log.error('technitium', 'Failed to fetch DNS logs', { error: error.message }); log.error('technitium', error, null, { note: 'Failed to fetch DNS logs' });
throw new Error(`Failed to get DNS logs: ${error.message}`); throw new Error(`Failed to get DNS logs: ${error.message}`);
} }
} }
@@ -449,7 +449,7 @@ class TechnitiumDNSProvider extends BaseDNSProvider {
throw new Error(result.errorMessage || 'Restart failed'); throw new Error(result.errorMessage || 'Restart failed');
} catch (error) { } catch (error) {
log.error('technitium', 'DNS restart error', { error: error.message }); log.error('technitium', error, null, { note: 'DNS restart error' });
throw new Error(`Failed to restart DNS server: ${error.message}`); throw new Error(`Failed to restart DNS server: ${error.message}`);
} }
} }
@@ -483,7 +483,7 @@ class TechnitiumDNSProvider extends BaseDNSProvider {
throw new Error(result.errorMessage || 'Update check failed'); throw new Error(result.errorMessage || 'Update check failed');
} catch (error) { } catch (error) {
log.error('technitium', 'Update check error', { error: error.message }); log.error('technitium', error, null, { note: 'Update check error' });
throw new Error(`Failed to check for updates: ${error.message}`); throw new Error(`Failed to check for updates: ${error.message}`);
} }
} }
@@ -86,7 +86,7 @@ class AutoRestartManager extends EventEmitter {
} }
this.log.info('auto-restart', 'Policies loaded', { count: this.policies.size }); this.log.info('auto-restart', 'Policies loaded', { count: this.policies.size });
} catch (err) { } catch (err) {
this.log.error('auto-restart', 'Failed to load policies', { error: err.message }); this.log.error('auto-restart', err, null, { note: 'Failed to load policies' });
} }
// Listen to health checker status transitions // Listen to health checker status transitions
@@ -246,7 +246,7 @@ class AutoRestartManager extends EventEmitter {
...eventData, ...eventData,
}); });
} catch (notifErr) { } catch (notifErr) {
this.log.error('auto-restart', 'Notification failed', { error: notifErr.message }); this.log.error('auto-restart', notifErr, null, { note: 'Notification failed' });
} }
return { action: 'max-reached', ...eventData }; return { action: 'max-reached', ...eventData };
@@ -312,7 +312,7 @@ class AutoRestartManager extends EventEmitter {
...successData, ...successData,
}); });
} catch (notifErr) { } catch (notifErr) {
this.log.error('auto-restart', 'Notification failed', { error: notifErr.message }); this.log.error('auto-restart', notifErr, null, { note: 'Notification failed' });
} }
this.log.info('auto-restart', 'Container restarted', { this.log.info('auto-restart', 'Container restarted', {
@@ -349,7 +349,7 @@ class AutoRestartManager extends EventEmitter {
...failData, ...failData,
}); });
} catch (notifErr) { } catch (notifErr) {
this.log.error('auto-restart', 'Notification failed', { error: notifErr.message }); this.log.error('auto-restart', notifErr, null, { note: 'Notification failed' });
} }
this.log.error('auto-restart', 'Restart failed', { this.log.error('auto-restart', 'Restart failed', {
@@ -478,7 +478,7 @@ class AutoRestartManager extends EventEmitter {
} }
await writeJsonFile(this.policiesFile, obj); await writeJsonFile(this.policiesFile, obj);
} catch (err) { } catch (err) {
this.log.error('auto-restart', 'Failed to save policies', { error: err.message }); this.log.error('auto-restart', err, null, { note: 'Failed to save policies' });
} }
} }
@@ -75,7 +75,7 @@ class ConfigDriftDetector extends EventEmitter {
const data = await this.servicesStateManager.read(); const data = await this.servicesStateManager.read();
services = Array.isArray(data) ? data : (data.services || []); services = Array.isArray(data) ? data : (data.services || []);
} catch (err) { } catch (err) {
this.log.error('drift', 'Failed to read services', { error: err.message }); this.log.error('drift', err, null, { note: 'Failed to read services' });
} }
// Gather live Docker containers // Gather live Docker containers
@@ -83,7 +83,7 @@ class ConfigDriftDetector extends EventEmitter {
try { try {
containers = await this.docker.client.listContainers({ all: true }); containers = await this.docker.client.listContainers({ all: true });
} catch (err) { } catch (err) {
this.log.error('drift', 'Failed to list containers', { error: err.message }); this.log.error('drift', err, null, { note: 'Failed to list containers' });
} }
// Build lookup maps // Build lookup maps
@@ -51,7 +51,7 @@ class NotificationManager extends EventEmitter {
this.config = this._mergeConfig(DEFAULT_CONFIG, data); this.config = this._mergeConfig(DEFAULT_CONFIG, data);
} }
} catch (error) { } catch (error) {
this.log.error('notification', 'Failed to load config', { error: error.message }); this.log.error('notification', error, null, { note: 'Failed to load config' });
} }
} }
@@ -89,7 +89,7 @@ class NotificationManager extends EventEmitter {
fs.writeFileSync(this.NOTIFICATIONS_FILE, JSON.stringify(this.config, null, 2)); fs.writeFileSync(this.NOTIFICATIONS_FILE, JSON.stringify(this.config, null, 2));
return true; return true;
} catch (error) { } catch (error) {
this.log.error('notification', 'Failed to save config', { error: error.message }); this.log.error('notification', error, null, { note: 'Failed to save config' });
throw error; throw error;
} }
} }
@@ -429,7 +429,7 @@ class NotificationManager extends EventEmitter {
const interval = (this.config.healthCheck?.intervalMinutes || 5) * 60 * 1000; const interval = (this.config.healthCheck?.intervalMinutes || 5) * 60 * 1000;
this.healthDaemonInterval = setInterval(() => { this.healthDaemonInterval = setInterval(() => {
this.checkHealth().catch(err => { this.checkHealth().catch(err => {
this.log.error('notification', 'Health check failed', { error: err.message }); this.log.error('notification', err, null, { note: 'Health check failed' });
}); });
}, interval); }, interval);
@@ -488,7 +488,7 @@ class NotificationManager extends EventEmitter {
lastCheck: this.config.healthCheck.lastCheck lastCheck: this.config.healthCheck.lastCheck
}; };
} catch (error) { } catch (error) {
this.log.error('notification', 'Health check error', { error: error.message }); this.log.error('notification', error, null, { note: 'Health check error' });
throw error; throw error;
} }
} }
@@ -331,7 +331,7 @@ class DiskSpaceMonitor extends EventEmitter {
result.error = err.message; result.error = err.message;
result.completedAt = new Date().toISOString(); result.completedAt = new Date().toISOString();
if (this.log) { if (this.log) {
this.log.error('disk', 'Disk cleanup failed', { error: err.message, level }); this.log.error('disk', err, null, { note: 'Disk cleanup failed', level });
} }
return result; return result;
} }
+5 -5
View File
@@ -143,7 +143,7 @@ class SSLMonitor extends EventEmitter {
try { try {
servicesData = await this.ctx.servicesStateManager.read(); servicesData = await this.ctx.servicesStateManager.read();
} catch (err) { } catch (err) {
this.log.error('ssl-monitor', 'Failed to read services', { error: err.message }); this.log.error('ssl-monitor', err, null, { note: 'Failed to read services' });
return this.getStatus(); return this.getStatus();
} }
@@ -212,13 +212,13 @@ class SSLMonitor extends EventEmitter {
// Initial check (non-blocking) // Initial check (non-blocking)
this.checkAll().catch(err => { this.checkAll().catch(err => {
this.log.error('ssl-monitor', 'Initial SSL check failed', { error: err.message }); this.log.error('ssl-monitor', err, null, { note: 'Initial SSL check failed' });
}); });
// Schedule periodic checks // Schedule periodic checks
this.intervalHandle = setInterval(() => { this.intervalHandle = setInterval(() => {
this.checkAll().catch(err => { this.checkAll().catch(err => {
this.log.error('ssl-monitor', 'Periodic SSL check failed', { error: err.message }); this.log.error('ssl-monitor', err, null, { note: 'Periodic SSL check failed' });
}); });
}, this.config.intervalMs); }, this.config.intervalMs);
@@ -299,7 +299,7 @@ class SSLMonitor extends EventEmitter {
clearInterval(this.intervalHandle); clearInterval(this.intervalHandle);
this.intervalHandle = setInterval(() => { this.intervalHandle = setInterval(() => {
this.checkAll().catch(err => { this.checkAll().catch(err => {
this.log.error('ssl-monitor', 'Periodic SSL check failed', { error: err.message }); this.log.error('ssl-monitor', err, null, { note: 'Periodic SSL check failed' });
}); });
}, this.config.intervalMs); }, this.config.intervalMs);
} }
@@ -355,7 +355,7 @@ class SSLMonitor extends EventEmitter {
validTo: certResult.validTo validTo: certResult.validTo
}, level <= THRESHOLDS.CRITICAL ? 'error' : 'warning'); }, level <= THRESHOLDS.CRITICAL ? 'error' : 'warning');
} catch (err) { } catch (err) {
this.log.error('ssl-monitor', 'Failed to send SSL notification', { error: err.message }); this.log.error('ssl-monitor', err, null, { note: 'Failed to send SSL notification' });
} }
} }
} else if (level === null) { } else if (level === null) {
+1 -1
View File
@@ -81,7 +81,7 @@ class PluginManager extends EventEmitter {
workflowActions: [...this.workflowActions.keys()], workflowActions: [...this.workflowActions.keys()],
}); });
} catch (err) { } catch (err) {
this.log.error('plugins', 'Failed to scan plugin directory', { error: err.message }); this.log.error('plugins', err, null, { note: 'Failed to scan plugin directory' });
this.loaded = true; // Don't crash — just run without plugins this.loaded = true; // Don't crash — just run without plugins
} }
} }
+1 -8
View File
@@ -7,15 +7,9 @@
* ./error-logger.js and its ./error.log file have been retired. * ./error-logger.js and its ./error.log file have been retired.
*/ */
const path = require('path');
const { AppError } = require('./errors'); const { AppError } = require('./errors');
const { LIMITS } = require('./constants');
const { logError: unifiedLogError, safeErrorMessage } = require('../utils/logging'); const { logError: unifiedLogError, safeErrorMessage } = require('../utils/logging');
const { errorResponse } = require('../utils/responses'); const { errorResponse } = require('../utils/responses');
const platformPaths = require('../../platform-paths');
const ERROR_LOG_FILE = process.env.ERROR_LOG_FILE || path.join(platformPaths.dataDir, 'error.log');
const MAX_ERROR_LOG_SIZE = LIMITS.ERROR_LOG_SIZE;
/** /**
* Global error handling middleware * Global error handling middleware
@@ -24,11 +18,10 @@ const MAX_ERROR_LOG_SIZE = LIMITS.ERROR_LOG_SIZE;
function errorMiddleware(err, req, res, next) { function errorMiddleware(err, req, res, next) {
// Log all errors with request context (unified, same file the rest of the app uses) // Log all errors with request context (unified, same file the rest of the app uses)
unifiedLogError( unifiedLogError(
ERROR_LOG_FILE,
MAX_ERROR_LOG_SIZE,
req.path, req.path,
err, err,
{ {
req,
method: req.method, method: req.method,
ip: req.ip, ip: req.ip,
userId: req.user?.id, userId: req.user?.id,
@@ -228,7 +228,7 @@ async function syncHealthCheckerServices({ log, SERVICES_FILE, servicesStateMana
log.info('health', 'Health checker synced', { added, updated, removed }); log.info('health', 'Health checker synced', { added, updated, removed });
} }
} catch (error) { } catch (error) {
log.error('health', 'Error syncing health checker', { error: error.message }); log.error('health', error, null, { note: 'Error syncing health checker' });
} }
} }
+8
View File
@@ -417,6 +417,14 @@ function safeErrorMessage(error) {
// Supports: logError(context, error, extra) → existing route call pattern // Supports: logError(context, error, extra) → existing route call pattern
async function logErrorWrapper(ctx, err, extra) { async function logErrorWrapper(ctx, err, extra) {
// Guard against legacy call shapes that used to corrupt error.log:
// the old 5-arg form logError(file, maxSize, path, err, meta) made ctx
// a file path and turned maxSize (a number) into the "error". Detect and
// normalize so the real error always reaches error.log.
if (typeof ctx === 'string' && /^\/.*\.(log|json)$/.test(ctx) && typeof err === 'number') {
// Legacy shape: (file, size, reqPath, error, meta) → shift args.
[ctx, err, extra] = [arguments[2], arguments[3], { ...arguments[4], req: undefined }];
}
const req = extra?.req; const req = extra?.req;
const payload = extra ? { ...extra } : {}; const payload = extra ? { ...extra } : {};
if (payload.req) delete payload.req; if (payload.req) delete payload.req;
+89 -89
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
File diff suppressed because one or more lines are too long
+18 -1
View File
@@ -365,6 +365,9 @@
} }
async function refreshAll() { async function refreshAll() {
// Skip if auth has been lost (e.g. TOTP gate activated externally).
// The polling interval in init.js also checks this flag.
if (window._dcAuthLost) return;
if (refreshInFlight) { if (refreshInFlight) {
refreshQueued = true; refreshQueued = true;
return refreshInFlight; return refreshInFlight;
@@ -417,9 +420,21 @@
refreshInFlight = (async () => { refreshInFlight = (async () => {
try { try {
const response = await fetch('/api/v1/services/status', { cache: 'no-store' }); const response = await fetch('/api/v1/services/status', { cache: 'no-store' });
if (response.status === 401 || response.status === 403) {
// Auth lost — stop the polling loop and close SSE; do NOT fall
// through to direct probes (those would misleadingly mark
// services as healthy since /probe/ treats 401/403 as "up").
window._dcAuthLost = true;
if (window._sseReconnect && window._sseClose) {
window._sseClose(); // tell SSE to stop reconnecting
}
updateStamp('auth required');
return; // skip the fallback entirely
}
if (!response.ok) { if (!response.ok) {
throw new Error(`Status refresh failed (${response.status})`); throw new Error(`Status refresh failed (${response.status})`);
} }
window._dcAuthLost = false; // auth working again
const data = await response.json(); const data = await response.json();
applyBatchResults(data.statuses || {}); applyBatchResults(data.statuses || {});
updateStamp('last check', data.checkedAt || new Date()); updateStamp('last check', data.checkedAt || new Date());
@@ -434,9 +449,11 @@
} }
} finally { } finally {
refreshInFlight = null; refreshInFlight = null;
if (refreshQueued) { if (refreshQueued && !window._dcAuthLost) {
refreshQueued = false; refreshQueued = false;
setTimeout(() => { window.refreshAll(); }, 0); setTimeout(() => { window.refreshAll(); }, 0);
} else {
refreshQueued = false;
} }
} }
})(); })();
+6 -1
View File
@@ -63,7 +63,12 @@
window.buildGrid(); window.buildGrid();
animateTopCards(); animateTopCards();
window.refreshAll(); window.refreshAll();
setInterval(window.refreshAll, DC.POLL.DASHBOARD); setInterval(() => {
// Stop polling if the session has been invalidated (e.g. TOTP gate
// now active, or user logged out). Avoids relentless 401/403 noise.
if (window._dcAuthLost) return;
window.refreshAll();
}, DC.POLL.DASHBOARD);
if (typeof window.refreshCredsButtons === 'function') window.refreshCredsButtons(); if (typeof window.refreshCredsButtons === 'function') window.refreshCredsButtons();
if (typeof window.refreshMonitoringWidgets === 'function') window.refreshMonitoringWidgets(); if (typeof window.refreshMonitoringWidgets === 'function') window.refreshMonitoringWidgets();
// Update auth card (may have already been updated by the auto-load IIFE but ensure it's correct) // Update auth card (may have already been updated by the auto-load IIFE but ensure it's correct)
+36 -1
View File
@@ -3,14 +3,18 @@
let es = null; let es = null;
let reconnectDelay = 1000; let reconnectDelay = 1000;
const MAX_RECONNECT = 30000; const MAX_RECONNECT = 30000;
let _sseFailCount = 0;
let _sseManuallyClosed = false;
function connect() { function connect() {
if (es) { try { es.close(); } catch (_) {} } if (es) { try { es.close(); } catch (_) {} }
if (_sseManuallyClosed) return; // auth-lost: don't reconnect
es = new EventSource('/api/v1/events/stream'); es = new EventSource('/api/v1/events/stream');
es.addEventListener('connected', () => { es.addEventListener('connected', () => {
reconnectDelay = 1000; // reset backoff reconnectDelay = 1000; // reset backoff
_sseFailCount = 0; // reset failure counter
debug('[SSE] Connected to event stream'); debug('[SSE] Connected to event stream');
}); });
@@ -101,15 +105,46 @@
// Reconnect on error // Reconnect on error
es.onerror = () => { es.onerror = () => {
es.close(); es.close();
// If auth was explicitly lost (401/403 from the polling loop),
// don't attempt reconnection at all.
if (window._dcAuthLost || _sseManuallyClosed) {
console.warn('[SSE] Auth lost — stopping reconnection');
return;
}
// Transient failures: retry with exponential backoff, stop after 5
_sseFailCount++;
if (_sseFailCount > 5) {
console.warn('[SSE] Max reconnect attempts reached — stopping (server unreachable)');
return;
}
console.warn(`[SSE] Disconnected, reconnecting in ${reconnectDelay / 1000}s...`); console.warn(`[SSE] Disconnected, reconnecting in ${reconnectDelay / 1000}s...`);
setTimeout(connect, reconnectDelay); setTimeout(connect, reconnectDelay);
reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT); reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT);
}; };
} }
// Called by grid.js when the polling loop detects auth loss (401/403)
function closeAndStop() {
_sseManuallyClosed = true;
if (es) { try { es.close(); } catch (_) {} }
}
// Called by totp-auth.js after a successful mid-session re-auth:
// clears the latch so connect() can proceed again and resets the
// failure backoff. (Plain _sseReconnect/connect() would early-return
// on the latch forever — the user would need a manual F5.)
function resumeAfterReauth() {
_sseManuallyClosed = false;
_sseFailCount = 0;
reconnectDelay = 1000;
connect();
}
// Start on page load // Start on page load
connect(); connect();
// Expose for debugging // Expose for debugging and cross-module coordination
window._sseReconnect = connect; window._sseReconnect = connect;
window._sseClose = closeAndStop;
window._sseResume = resumeAfterReauth;
})(); })();
+9
View File
@@ -125,6 +125,15 @@
if (typeof window.initializeDashboard === 'function') { if (typeof window.initializeDashboard === 'function') {
window.initializeDashboard(); window.initializeDashboard();
} }
// Resume live updates after mid-session re-auth. The auth-loss
// handlers latched polling + SSE off when the session expired
// (grid.js sets _dcAuthLost, live-events.js latches the stream
// closed); a fresh login must clear both and reconnect, or the
// dashboard stays frozen on stale data until a manual F5.
window._dcAuthLost = false;
if (typeof window._sseResume === 'function') window._sseResume();
else if (typeof window._sseReconnect === 'function') window._sseReconnect();
if (typeof window.refreshAll === 'function') window.refreshAll();
} else { } else {
errorEl.textContent = data.error || 'Invalid code'; errorEl.textContent = data.error || 'Invalid code';
errorEl.className = 'totp-error'; errorEl.className = 'totp-error';
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'dashcaddy-shell-4a75cb88af'; const CACHE = 'dashcaddy-shell-78eab743c2';
const PRECACHE = [ const PRECACHE = [
'/', '/',
'/index.html', '/index.html',