DC-061: healthCheckUrl override + v2 production-grade backlog
- url-resolver.js: add healthCheckUrl priority (bypasses SSO for health checks) - DC-PRODUCTION-GRADE-BACKLOG.md: v2 backlog with 19 tasks (DC-062–DC-080) based on full codebase audit: 1539 tests, 86.55% coverage, 0 ESLint errors v2 backlog replaces completed v1 (P0-1 through P2-7 all done). New priorities: P0: OpenAPI spec update, branch coverage gap, Dockerfile resource limits P1: Console sweep remainder, billing E2E test, graceful shutdown, lint sweep, health notification spam P2: CI/CD pipeline, Sentry, source maps, request logging, multi-stage Docker, health endpoint P3: WebSocket, i18n, config backup/restore, mobile, plugin system
This commit is contained in:
+130
-28
@@ -1,37 +1,139 @@
|
|||||||
# DashCaddy Production-Grade Repair Backlog
|
# DashCaddy Production-Grade Backlog (v2)
|
||||||
|
|
||||||
Autonomous agent: work through these IN ORDER. Mark each `[ ]` as `[x]` when shipped.
|
> Generated 2026-08-12 from a full codebase audit.
|
||||||
If an item is too big for one tick, implement a sub-part, push that, and note progress.
|
> v1 items (P0-1 through P2-7) are ALL DONE.
|
||||||
|
> Current state: 1539 tests, 86.55% statement coverage, 0 ESLint errors, 173 warnings.
|
||||||
|
|
||||||
## P0 — Security & Correctness
|
## Current Health Snapshot
|
||||||
|
- **Tests:** 1539 passing across 63 suites
|
||||||
|
- **Coverage:** Statements 86.55% | Branches 72.14% (below 80% gate) | Functions 80.8% | Lines 90.67%
|
||||||
|
- **ESLint:** 0 errors, 173 warnings (all pre-existing)
|
||||||
|
- **Remaining console.* calls in src/:** 21 across 10 files
|
||||||
|
- **Dockerfile:** Runs as root (documented — needs Docker socket), no resource limits
|
||||||
|
- **OpenAPI spec:** Present but stale (says v1.0.0, actual is v1.15.0)
|
||||||
|
- **Unhandled rejection/exception handlers:** Present in server.js ✓
|
||||||
|
- **Rate limiting:** Present on auth + general routes ✓
|
||||||
|
- **npm audit:** 4 remaining vulns (semver-major transitive deps, deferred)
|
||||||
|
|
||||||
- [x] **P0-1: npm audit fix** — Done (commit 3a0a5bc, grade A). Resolved 3 high CVEs via minimatch 9.0.9 in webdav transitive. 4 remaining vulns are semver-major-only (sharp→0.35.3, dockerode→5.0.1, nodemailer→9.0.5, uuid→11.1.1) — deferred per backlog note. All 1498 jest tests pass. URN urn:ump:hlju4hixg3tijbghncigm5gesoemupuczrzmkykumh7xbgkq3d2q.
|
---
|
||||||
- [x] **P0-2: Command injection in ca.js:210** — Done (commit 66e4460, grade A). Replaced `execSync(\`openssl pkcs12 ... -password "pass:${password}"\`)` with `execFileSync('openssl', [..., '-password', \`pass:${password}\`])`. No shell parsing. All 1498 tests pass.
|
|
||||||
- [x] **P0-3: Unvalidated req.body in backup config** — Done (commit b3488f1, grade A). POST /backups/config now destructures only `{backups, defaultRetention}` instead of passing `req.body` wholesale. All 1498 tests pass.
|
|
||||||
- [x] **P0-4: Asset upload buffer size check** — Done (commit 57ed09f, grade A). POST /assets/upload now uses `decodeImageData(data)` helper which enforces MIME whitelist (png/jpeg/jpg/svg+xml/webp/ico/x-icon) and 5 MB cap. (Prior partial fix had the helper but never wired it.) All 1498 tests pass.
|
|
||||||
- [x] **P0-5: Error message leaking internals** — Done (commit 609ccd3, grade A). apps-revert catch now logs `err.message`+stack via `log.error` server-side and returns generic `Revert failed` to client. All 1498 tests pass.
|
|
||||||
|
|
||||||
## P1 — Architecture & Input Validation
|
## P0 — Must Fix (blocks public release)
|
||||||
|
|
||||||
- [x] **P1-1: Add Joi validation library** — Done in commit a667de7 (DC-059, codex-graded B). `npm install joi@^18`, `src/utilities/validate.js` exporting `validateBody(schema, opts)` middleware + 9 schemas (backupConfigUpdate, backupScheduleCreate, backupRestore, backupRestoreFile, appDeploy, appRestore, appRevert, assetUpload, logoUpload). Every exported schema has direct unit tests (41 total in `__tests__/unit/validate.test.js`) covering middleware semantics — not just `schema.validate`. Applied to 8 destructive routes: backups (schedule/restore/config), apps (deploy/restore/revert), assets (upload/logo). Used Joi's authoritative CIDR validator (rejects malformed IPv6 like `::::/64` that the previous hex/colon regex would have accepted). 1539/1539 Jest tests pass (was 1498, +41 new). ESLint warnings unchanged (416 total, all pre-existing — zero new introduced).
|
### DC-062: OpenAPI spec is stale — update to match actual v1.15.0 API surface
|
||||||
- [x] **P1-2: Console→logger sweep (update-manager.js)** — Done in commit e8b9dd5 (DC-060, codex-graded A). All 49 `console.*` calls in `src/managers/update-manager.js` now route through `log.info/log.warn/log.error` from `src/utils/logging` (tag = `'update'`). Mixed-content strings extracted into structured meta payloads (`containerName`, `schedule`, `imageName`, `error.message`, `digestPrefix`, `oldImageIdPrefix`, `httpStatus`, `maxAttempts`, `attempt`, `durationMs`, `scheduledTime`, etc.) so fields are queryable. Errors go through `log.error(ctx, errObj)` so they land in error.log with full stack trace + context. 1539/1539 Jest tests pass (78/78 update-manager tests still pass). ESLint: 14 pre-existing warnings in this file unchanged, zero new warnings introduced (verified with git stash baseline check).
|
- **status:** pending
|
||||||
- [x] **P1-3: Console→logger sweep (backup-manager.js)** — Done (commit c55abda). All 36 console calls in src/utilities/backup-manager.js → log.info/warn/error tagged 'backup'. Meta payloads with name, schedule, durationMs, volume, backupId, etc. 1539/1539 tests pass, 0 new ESLint warnings.
|
- **details:** `openapi.yaml` says `version: 1.0.0` and describes only a fraction of the API. Since DC-046/047 (auth providers), DC-053 (share), DC-055 (billing), DC-058 (share UI), and the tailscale-admin routes were added, the spec is significantly out of date. A stale spec is worse than no spec — it misleads API consumers and breaks any code generation from it. Fix: audit all route files (`grep -rn 'router\.\(get\|post\|put\|delete\|patch\)' routes/`), update openapi.yaml with every endpoint, bump version to 1.15.0, add it to the test suite (DC-017-style source-of-truth test that fails if a route exists but has no spec entry). Effort: ~3 hr.
|
||||||
- [x] **P1-4: Console→logger sweep (resource-monitor.js)** — Done (commit f2c6fa6). All 32 console calls in src/managers/resource-monitor.js → log tagged 'monitor'. 1539/1539 tests pass.
|
- **impact:** Public API trust. No paying customer can integrate against an undocumented API.
|
||||||
- [x] **P1-5: Console→logger sweep (credential-manager.js)** — Done (commit 84f63a3). All 20 console calls → log tagged 'cred'. 1539/1539 tests pass.
|
|
||||||
- [x] **P1-6: Console→logger sweep (auth-manager.js)** — Done (commit 84f63a3). All 20 console calls → log tagged 'auth'. 1539/1539 tests pass.
|
|
||||||
- [x] **P1-7: Console→logger sweep (bundled-workflows.js)** — Done (commit 191d334). All 18 console calls → log tagged 'workflow'. 1539/1539 tests pass.
|
|
||||||
- [x] **P1-8: Console→logger sweep (remaining files)** — Done (commit 7b04bc1). 66 calls across 6 files: crypto-utils.js (16), docker-security.js (15), port-lock-manager.js (16), self-updater.js (10), event-workers.js (5), keychain-manager.js (4). Fixed 2 bugs: semicolon in arrow expression body (self-updater.js:162) and out-of-scope variable reference (port-lock-manager.js:137). 1539/1539 tests pass.
|
|
||||||
|
|
||||||
## P2 — Code Quality & Technical Debt
|
### DC-063: Branch coverage at 72% — below the 80% gate
|
||||||
|
- **status:** pending
|
||||||
|
- **details:** Jest coverage report shows branches at 72.14% (303/420), failing the 80% threshold. The uncovered branches are concentrated in error-handling paths (catch blocks, fallback returns, edge-case conditionals). Fix: run `npx jest --coverage --coverageReporters=text` to identify the files with the lowest branch coverage, then add targeted tests for the uncovered conditional paths. Priority files: backup-manager.js (multiple catch blocks), health-checker.js (timeout/retry branches), tailscale-coord.js (API error branches). Effort: ~2 hr.
|
||||||
|
- **impact:** Error paths are where production incidents hide. Every untested catch block is a potential crash.
|
||||||
|
|
||||||
- [x] **P2-1: Version drift fix** — Done (commit 140aa5d). VERSION 1.14.9→1.15.0, CLAUDE.md 1.13.4→1.15.0.
|
### DC-064: Dockerfile runs as root with no resource limits
|
||||||
- [x] **P2-2: Delete dead legacy files** — Done (commit 140aa5d). Removed comprehensive-test.js + test-security-fixes.js (-878 lines). (status/api/test-api.js is untracked.)
|
- **status:** pending
|
||||||
- [x] **P2-3: ESLint no-empty fix** — Done (commit 140aa5d). Added `no-empty: ['error', { allowEmptyCatch: true }]` to .eslintrc.js. 3 errors→0.
|
- **details:** The Dockerfile has no `USER` directive and `start.sh` has no `--memory` or `--cpus` flags. While root is needed for Docker socket access, the container can still OOM the host. Fix: (1) Add `--memory=512m --memory-swap=1g --cpus=1.5` to the `docker run` in start.sh. (2) Create a non-root user `dashcaddy` for the application process, and use a Docker socket proxy (like `tecnativa/docker-socket-proxy`) that exposes a limited subset of Docker API endpoints — the app only needs read access for monitoring + controlled container lifecycle. (3) Add `--restart=unless-stopped` if not already present. Effort: ~2 hr. Risk: medium — socket proxy may break some Docker API calls, needs testing.
|
||||||
- [x] **P2-4: Fix no-useless-escape** — Done (commit 140aa5d). routes/auth/session-handlers.js:39 `\-` → `.-` (dash moved to end of char class).
|
- **impact:** Without limits, a memory leak in the API can take down the entire host. This is a production safety issue.
|
||||||
- [x] **P2-5: Test handle leaks** — Done (commit 1bc41bb). Root cause: `setTimeout` in `log-digest.js:start()` was never stored, so `stop()` couldn't clear it — 4 leaked handles. Fixed by storing as `this._initialTimeout` and clearing in `stop()`. Also swept 3 remaining console.error calls. `--detectOpenHandles` reports 0 handles.
|
|
||||||
- [x] **P2-6: Refactor config-schema.js validateConfig** — Done (commit f5fc688). Extracted 8 sub-validators (validateTld, validateDns, validateDashboardHost, validateTimezone, validateTheme, validateRoutingMode, validateDomain, validateKnownKeys). Complexity 44→<10 per function. Removed unused constant. Behavior-preserving.
|
|
||||||
- [x] **P2-7: Refactor middleware.js auth function** — Done (commit a7512b4). Extracted isTailScaleProbePath, extractTailscaleIPs, isIPInTailnet from tailscaleAuthMiddleware. Complexity 24→7, nesting 6→3. Behavior-preserving.
|
|
||||||
|
|
||||||
## Completion Criteria
|
---
|
||||||
|
|
||||||
When all items above are `[x]`, report "All backlog items complete" and stop.
|
## P1 — Code Quality & Reliability
|
||||||
|
|
||||||
|
### DC-065: Remaining 21 console.* calls — sweep to structured logger
|
||||||
|
- **status:** pending
|
||||||
|
- **details:** After DC-060 (update-manager) and P1-3 through P1-8, 21 console calls remain across 10 files: `error-handler.js` (2), `email.js` (1), `dns-providers/registry.js` (2), `audit-logger.js` (3), `csrf-protection.js` (3), `config-drift-detector.js` (1), `auto-restart-manager.js` (1), `http.js` (1), `logging.js` (6 intentional — the logger itself), `routes/backups.js` (1). The logging.js calls are fine (the logger IS console internally). The rest should route through `log.info/warn/error`. Some are fallbacks: `ctx.logError || ((_c, err) => console.error(err))` — these fire when ctx isn't available, which is exactly when structured logging matters most. Effort: ~45 min.
|
||||||
|
- **impact:** Consistency. The logger write to error.log and supports structured JSON — console does not.
|
||||||
|
|
||||||
|
### DC-066: No API integration test for the billing flow end-to-end
|
||||||
|
- **status:** pending
|
||||||
|
- **details:** DC-057 shipped contract tests and unit tests for the Stripe bridge, but there is no test that exercises the full flow: pricing page → Stripe Checkout → webhook → license-key delivery → license activation → Pro unlock. Build a single integration test that mocks Stripe's API, walks the complete flow, and asserts the license works at the end. This is the revenue path — it must be tested as a chain, not just individual pieces. Effort: ~2 hr.
|
||||||
|
- **impact:** Confidence in the revenue pipeline. A broken webhook or catalog mismatch silently loses sales.
|
||||||
|
|
||||||
|
### DC-067: No graceful shutdown — SIGTERM kills in-flight requests
|
||||||
|
- **status:** pending
|
||||||
|
- **details:** server.js handles `uncaughtException` and `unhandledRejection`, but there is no `SIGTERM` handler that calls `server.close()` to drain connections. Docker stop sends SIGTERM (the Dockerfile has `STOPSIGNAL SIGTERM`), but without a handler the process exits immediately, dropping any in-flight API calls. Fix: add a `SIGTERM` handler in server.js that (1) stops accepting new connections via `server.close()`, (2) waits up to 10s for in-flight requests, (3) closes DB/file handles, (4) exits cleanly. Also emit a `shutdown` event so managers (health checker, SSL monitor, workflow engine) can stop their timers. Effort: ~1 hr.
|
||||||
|
- **impact:** Zero-downtime deployments. Currently, every `docker stop` drops active requests.
|
||||||
|
|
||||||
|
### DC-068: ESLint warnings sweep — 173 pre-existing warnings
|
||||||
|
- **status:** pending
|
||||||
|
- **details:** While there are 0 ESLint errors, 173 warnings remain. Top files: `dns-providers/base.js` (27), `update-manager.js` (14), `backup-manager.js` (10), `keychain-manager.js` (10), `bundled-workflows.js` (10), `auth/providers/base.js` (9), `log-digest.js` (8). Most are `no-unused-vars`, `require-await`, `no-nested-ternary`. Fix: sweep through the top 10 files, fix what's actionable (unused vars → remove, nested ternaries → extract to named variables, false-positive require-await → mark `_` or restructure). Set a ceiling: warnings should never increase. Effort: ~2 hr.
|
||||||
|
- **impact:** Clean codebase. 173 warnings is noise that hides real issues when new ones are added.
|
||||||
|
|
||||||
|
### DC-069: Health check notification spam — add failure threshold + cooldown
|
||||||
|
- **status:** pending
|
||||||
|
- **details:** The workflow engine sends a notification on EVERY health check failure (every 15 min). If a service is down for a day, that's 96 identical notifications. There is no backoff, no deduplication, no "service recovered" message. Fix: (1) Only notify on state TRANSITIONS (up→down, down→up), not every failure. (2) Add a `consecutiveFailures` threshold (e.g., 2 failures before first alert) to avoid flapping noise. (3) Send a recovery notification when a service comes back up. (4) Optional: daily digest of uptime stats instead of per-failure alerts. Effort: ~1.5 hr.
|
||||||
|
- **impact:** Operator sanity. The current notification volume is exactly why people mute alerting channels — and then miss real incidents.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## P2 — Polish & Developer Experience
|
||||||
|
|
||||||
|
### DC-070: No CI/CD pipeline — tests run manually
|
||||||
|
- **status:** pending
|
||||||
|
- **details:** There is no GitHub Actions / CI configuration. Tests are run manually before push. This means a bad commit can reach main if someone forgets to test. Fix: add `.github/workflows/test.yml` (or Gitea Actions equivalent) that runs `npm ci && npx jest --coverage` on every PR and push to main. Cache node_modules. Upload coverage report as artifact. Block merge on test failure or coverage decrease. Effort: ~1 hr.
|
||||||
|
- **impact:** Automated quality gate. No bad commit reaches production.
|
||||||
|
|
||||||
|
### DC-071: No error tracking / Sentry integration
|
||||||
|
- **status:** pending
|
||||||
|
- **details:** Errors go to `error.log` inside the container. If the container is recreated (DC-050 migration), the error log is lost. There is no external error tracking. Fix: add an optional Sentry (or GlitchTip for self-hosted) integration. If `SENTRY_DSN` env var is set, initialize Sentry before Express. Wrap async handlers to capture exceptions. The error-handler.js middleware should forward to Sentry before returning the generic error response. Make it opt-in (no DSN = no Sentry, zero behavior change). Effort: ~1 hr.
|
||||||
|
- **impact:** Production visibility. Right now, errors are invisible unless someone SSHs in and reads the log.
|
||||||
|
|
||||||
|
### DC-072: Frontend bundle has no source maps in production
|
||||||
|
- **status:** pending
|
||||||
|
- **details:** `status/build.js` uses esbuild but the production build doesn't emit source maps. When a frontend error occurs in production, the stack trace points to minified bundle lines — useless for debugging. Fix: add `sourcemap: true` to the esbuild production config. Serve `.map` files from Caddy (they're already in `dist/`). Optionally upload source maps to Sentry (DC-071). Effort: ~30 min.
|
||||||
|
- **impact:** Frontend bug reports become actionable instead of "line 1 of core.js".
|
||||||
|
|
||||||
|
### DC-073: No API request/response logging middleware for debugging
|
||||||
|
- **status:** pending
|
||||||
|
- **details:** While there is an audit logger for POST/PUT/DELETE, there's no request/response logging middleware for debugging purposes (like morgan or a custom equivalent). When an operator reports "the dashboard is slow" or "this endpoint returns 500 sometimes", there's no way to trace the request through the system. Fix: add an optional debug-level request logger that logs method, path, status, duration, and request ID. Gated behind `LOG_LEVEL=debug` so it's off in production by default. Effort: ~45 min.
|
||||||
|
- **impact:** Drastically reduces time-to-resolution for production issues.
|
||||||
|
|
||||||
|
### DC-074: Docker image is not multi-stage — build artifacts bloat the image
|
||||||
|
- **status:** pending
|
||||||
|
- **details:** The Dockerfile copies source files into a single stage based on `node:20-alpine`. The image includes `devDependencies` because `npm install --production` still installs some optional deps, and there's no `.dockerignore` (so `__tests__/`, `.git/`, `node_modules/` from the host can leak in). Fix: (1) Add a `.dockerignore` file excluding `__tests__/`, `.git/`, `node_modules/`, `*.md`, `coverage/`. (2) Convert to multi-stage: build stage installs all deps, production stage copies only `node_modules/` (production) + source. (3) Pin Node.js version: `FROM node:20.10-alpine` instead of `node:20-alpine` (floating). Effort: ~1 hr.
|
||||||
|
- **impact:** Smaller image = faster pulls = faster deploys. Current image size carries unnecessary weight.
|
||||||
|
|
||||||
|
### DC-075: No health check dashboard endpoint for operators
|
||||||
|
- **status:** pending
|
||||||
|
- **details:** The `/api/v1/monitoring/stats` endpoint returns container stats, but there's no single "is everything OK" endpoint that returns a human-readable system health summary. Fix: add `GET /api/v1/system/health` that returns `{ status: "healthy"|"degraded"|"unhealthy", checks: { database: "ok", diskSpace: "ok", memory: "ok", uptime: ..., activeServices: N/M, lastError: "..." } }`. This is useful for uptime monitoring services (UptimeRobot, BetterStack) and for a quick operator glance. Effort: ~1 hr.
|
||||||
|
- **impact:** Operators can plug DashCaddy into external monitoring without parsing container stats.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## P3 — Future & Nice-to-Have
|
||||||
|
|
||||||
|
### DC-076: WebSocket support for real-time dashboard updates
|
||||||
|
- **status:** pending
|
||||||
|
- **details:** The dashboard polls the API every N seconds for service status updates. For a "live" dashboard experience, WebSocket (or SSE) push would be better — status changes appear instantly without polling overhead. Fix: add a WebSocket server (using `ws` library) that pushes service status changes, health check results, and container events to connected dashboard clients. Keep polling as fallback for clients without WS support. Effort: ~3 hr.
|
||||||
|
- **impact:** Dashboard feels "live". Reduces API load from polling.
|
||||||
|
|
||||||
|
### DC-077: Multi-language (i18n) support
|
||||||
|
- **status:** pending
|
||||||
|
- **details:** All UI text is hardcoded English. For a public product, internationalization is a step toward wider reach. Fix: extract all user-facing strings into a locale file, add an i18n library (like i18next), provide at minimum an English + Arabic locale (Sami's audience). Effort: ~4 hr.
|
||||||
|
- **impact:** Market expansion. Arabic-speaking homelab community is underserved.
|
||||||
|
|
||||||
|
### DC-078: Backup and restore of DashCaddy's own configuration
|
||||||
|
- **status:** pending
|
||||||
|
- **details:** While DashCaddy can backup app data, there's no one-click "backup my entire DashCaddy setup" (services.json, config.json, health-config.json, credentials, Caddyfile, license) that could be restored on a fresh install. Fix: add `GET /api/v1/system/export` (returns a signed JSON bundle) and `POST /api/v1/system/import` (restores from bundle). The credentials file should be encrypted with a user-provided passphrase. Effort: ~2 hr.
|
||||||
|
- **impact:** Migration story. "Moving DashCaddy to a new host" is currently a multi-hour manual process.
|
||||||
|
|
||||||
|
### DC-079: Mobile-responsive dashboard improvements
|
||||||
|
- **status:** pending
|
||||||
|
- **details:** While the dashboard is somewhat responsive, it's not optimized for mobile use. For operators checking services on their phone, the experience should be touch-first. Fix: audit all dashboard pages on mobile viewport, fix any horizontal scroll, ensure buttons are touch-target sized (min 44px), add a mobile-specific layout for the service grid. Effort: ~3 hr.
|
||||||
|
- **impact:** Operators check services on their phone. Current mobile experience is usable but not polished.
|
||||||
|
|
||||||
|
### DC-080: Plugin/extension system for custom services
|
||||||
|
- **status:** pending
|
||||||
|
- **details:** DashCaddy supports a fixed set of service templates. A plugin system would allow community-contributed service definitions (e.g., "Home Assistant", "Vaultwarden", "Nextcloud") without modifying core code. Fix: define a plugin manifest schema (name, logo, health check URL pattern, config fields), load plugins from `/data/plugins/`, add a community plugin registry page. Effort: ~4 hr.
|
||||||
|
- **impact:** Community growth. Extensibility is what makes a tool ecosystem vs. a product.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary by Priority
|
||||||
|
|
||||||
|
| Priority | Count | Effort | Theme |
|
||||||
|
|----------|-------|--------|-------|
|
||||||
|
| P0 | 3 (DC-062–064) | ~7 hr | Public release blockers |
|
||||||
|
| P1 | 5 (DC-065–069) | ~7 hr | Reliability & code quality |
|
||||||
|
| P2 | 6 (DC-070–075) | ~5.5 hr | Polish & DX |
|
||||||
|
| P3 | 5 (DC-076–080) | ~16 hr | Future growth |
|
||||||
|
| **Total** | **19** | **~35.5 hr** | |
|
||||||
|
|||||||
Reference in New Issue
Block a user