Files
dashcaddy/CHANGELOG.md
T
Hermes 6c9f9e59a3 [grade=B urn:ump:ez33fpgyydl3nyjnwaaplisz7slunupcf5dnr42pdblwx7chfcma] feat(DC-171): phase 2 — PowerDNS adapter (full plug-in parity) + shared wizard DNS serialisation
NEW powerdns.js adapter: PowerDNS Authoritative HTTP API (X-API-Key).
Full parity (zones + native RRset enumeration — no AXFR needed):
listZones/listRecords, additive create that merges with existing rrset
contents and FAILS CLOSED when the rrset cannot be read (blind REPLACE
would erase values), content-level delete with positive value-presence
proof (absent rrset/value → not-found with ZERO writes; empty/null value
rejected; only genuine whole-rrset intent issues DELETE), TTL preserved
in REPLACE payloads and on additive creates, cross-zone qualification via
the server-resolved zone id/name, case-insensitive rrset matching,
dig-based resolve against the DNS listener (distinct from the API port),
error guidance naming the exact pdns.conf prerequisite.

WIZARD SERIALISATION EXTRACTED to shared js/dns-provider-config.js
(browser global + module.exports): per-provider port defaults
(PowerDNS 8081, RFC2136 53, legacy Technitium 5380), strict port
validation (full-string digits, 1-65535), provider-specific aliases
(server/apiPort/dnsPort/apiToken). The backend tests now execute the
EXACT shipped wizard code, joined through the real createProviderDnsContext
factory to adapter authentication (X-API-Key asserted). provider-dns.js
dual-accepts generic wizard keys and provider-specific fields and
normalises ports to integers (fixes the same latent gap for rfc2136).

Also fixed: rfc2136 parity-matrix rows now exact-sorted-equality across
all five adapters; wizard confirmation summary uses the provider default.

Judge: 7 rounds — r1 C (silent-erase on failed read, cross-zone bug,
missing resolve tests) → r2 C (deleteRecord positive-proof semantics) →
r3 C (TTL in REPLACE, count honesty) → r4 C (empty-value fall-through)
→ r5 C (wizard ip/port/token vs builder field mismatch) → r6 C (5380
default defeating provider ports, untested serialisation) → r7 B zero
blockers (urn:ump:ez33fpgyydl3nyjnwaaplisz7slunupcf5dnr42pdblwx7chfcma).
Post-grade polish applied: strict port validation + range, summary uses
provider defaults, joined serialisation→factory→adapter test, invalid
port table tests.

Suite: backend 176 suites / 3791 tests, frontend 68/68.
2026-10-29 23:46:03 -07:00

40 KiB

Changelog

All notable changes to DashCaddy are documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Unreleased

Added

  • DC-171 phase 2: PowerDNS adapter (full plug-in parity). New powerdns DNS provider speaking the PowerDNS Authoritative HTTP API (X-API-Key): native zone listing and RRset enumeration (no AXFR needed — full-parity like Technitium/Cloudflare), additive record creation that merges with existing RRset contents (REPLACE semantics done honestly), content-level deletion from multi-value RRsets, dig-based resolution against the DNS listener, and error guidance that names the exact pdns.conf prerequisite (api=yes, webserver=yes, api-key). Wired end-to-end: provider config builder, setup-wizard option + capability hint, capability matrix row, and 24 adapter tests (flatten, merge/REPLACE, delete levels incl. not-found no-ops and empty-value rejection, TTL preservation, cross-zone qualification, case-insensitive matching, auth guidance, zone mapping). The wizard's DNS serialisation was extracted into a shared module (js/dns-provider-config.js) consumed by both the browser and the test suite: per-provider port defaults (PowerDNS API 8081, RFC2136 53, legacy Technitium 5380), strict port validation (full-string digits, 1-65535), and provider-specific field aliases — tested end-to-end from serialisation through the provider factory to adapter authentication.
  • DC-171: DNS provider plug-in parity, phase 1 (BIND 9 upgrade + capability honesty). RFC 2136 adapters can now LIST records: listRecords performs an AXFR zone transfer via dig with the same TSIG identity as nsupdate (temp 0600 keyfile, cleaned up after), parses transfer output into {zone, count, records}, and — when the server denies the transfer — errors with the exact server-side fix (allow-transfer { key <name>; };). The capability matrix is pinned by a parity test suite (adapters vs wizard hints vs route guidance). GET /api/v1/dns/records 501s now name the active provider, list what it CAN do, and point at AXFR/manager tools; the MCP dashcaddy_list_dns tool attaches the same actionable guidance for AI clients; the setup wizard shows per-provider capability hints at choice time. Also fixed a latent rfc2136 bug: an instance _log property shadowed the logging method, so every nsupdate call threw after the DC-170-era refactor.
  • DC-170: full-software reliability and truth sweep (2026-10-27/29). 46 findings from a three-agent audit, top items fixed in 7 graded batches: TOTP gate no longer blocks API-key/JWT auth (machine credentials were dead on every TOTP install — verified live with a freshly minted key); calendar widget writes no longer die on CSRF 403s (error path, mobile modal, refresh races, keyboard access fixed too); 19 controls silently killed by the content-security policy revived (Disk Safety modal + sliders, AI assistant button, login logos, premium upsells, health Edit/Delete); 6 MCP tools remapped to real routes (container list and DNS record-list routes added); the deploy tool now reports what it actually does and derives the site domain from configuration; DNS update endpoints answer every request (previously hung); state files write atomically (a crash can no longer corrupt services.json/config.json); dashboard WebSocket auth fails closed; service worker offline fallback works, caches only good responses, installs tolerantly; public config endpoint degrades gracefully on a corrupt file; slow calendar feeds get their intended 15-second timeout; calendar source updates no longer wipe the other credential type. New capability-drift gate: every MCP tool's handler-issued call is verified against the declared routes (28 tools).
  • DC-169: missing service logos. samimail, samipanel, gitea, and DashCaddy service-card logos committed and wired.
  • DC-168: calendar widget UX + theming. Chip shows "No upcoming appointments" (muted) when the connected calendar is empty; with events it shows the next appointment with a highlighted day/time span. Modal gains a month grid (prev/next nav, appointment days accent-tinted, today ring, per-event time dots) plus the upcoming list (ended events excluded, ongoing included). Universal theme contrast via color-mix from --accent — WCAG >= 4.5:1 verified against all 10 builtin themes (worst 4.60:1). Month-following fetch with disjoint-interval coverage tracking; window-authoritative event cache (calendar-cache.js, uid+start keys) so deletions/reschedules propagate; chip always renders from the now-window even with a navigated viewMonth.
  • DC-167: CalDAV default-namespace multistatus fix (Radicale silent zero-events). extractCalendarsAndEvents required a namespace prefix on ; Radicale emits bare (default DAV ns) so every entry was silently dropped — REPORT 200, ok:true, 0 events, no error. Prefix made optional in the split + detection regexes.
  • DC-166: MCP tool-set expansion 17 → 28 tools + workflows double-prefix fix (merged from dc/DC-166-mcp-completion). 11 new AI-tool handlers over existing routes (calendar, security, license, capabilities, DNS records, deploy recipe, workflow run, tailscale status). Fixed the workflows router that composed /api/v1/workflows/workflows/... — every dashboard workflow toggle and Run Now had been 404ing in production. Route-existence verifier resolves tool paths against declared routes.
  • DC-165: product/site uniformity — fleet gate proven at composition root, single-source capabilities manifest. The public /api/v1/capabilities endpoint advertises what the running build actually does (route counts, templates, MCP tools measured at runtime), so the marketing site, docs, and llms.txt can never disagree with the product.
  • DC-171 (roadmap): further provider plug-in parity — BIND 9 AXFR-based listing (phase 1) and the PowerDNS HTTP API adapter (phase 2) are shipped (above); remaining roadmap: deSEC / AdGuard Home adapters, richer per-provider setup validation. Capability reporting at provider-choice time shipped in phase 1 (wizard hints + route guidance + MCP guidance); the original "listing is protocol-impossible" note was superseded — RFC2136 listing works via AXFR zone transfer with the allow-transfer prerequisite.

Notes

  • Historical per-change review outcomes are recorded in commit messages and in the internal RELEASE-EVIDENCE-internal.md companion file — not in this changelog.

1.17.0 - 2026-10-07

Added

  • DC-CATALOG-3: catalog deploys route through the shipdeck bridge. The App Selector sends subdomain to POST /api/deploys/catalog, which validates the label and forwards it to the bridge; the route reads the bridge journal for service state instead of spawning a subprocess per service.
  • Composition-root e2e suite. __tests__/e2e-node/composition-root.e2e.test.js boots the real app via createApp(), mints a JWT, and probes each mounted route against a nonexistent control path in the identical request shape, so a real mount is distinguishable from an unknown route.
  • Shield route committed. routes/shield.js (until now present only on the production host) is tracked in git, mounted by src/app.js, and covered by __tests__/shield-dc.routes.test.js.

Fixed

  • DC-160: catalog detail returned empty template metadata. routes/catalog.js reads app.docker.* from the catalog template instead of the hand-rolled category map.
  • Deploy audit contract. The catalog-deploy route calls auditLogger.log({action,resource,details,outcome,ip}) fire-and-forget — the same dependency shape src/app.js injects — and the route test constructs that shape, so a divergence fails.
  • Bridge response handling is total. Any non-plain-object body (null, arrays, empty) is coerced into the same failure shape as a non-JSON body, so no body.ok dereference can 500 or report a false success.
  • Subdomain labels. Validation rejects leading and trailing hyphens and is length-bounded (1-63 characters); the rejection table in the route suite covers both edges.
  • Boot timeout kills its child, exercised. The e2e boot timeout is parameterized and a real test drives it against a child that never reports readiness, asserting the killed child's SIGKILL.
  • Test port allocation. The e2e child binds port 0 and reports the OS-assigned port, removing the release-and-rebind race in the old freePort helper.

Notes

  • Pre-existing and unrelated: __tests__/billing/invoice.test.js fails identically at the release base (1fa5ba8) because it bundles a stale pdf.js 1.10.100; the failing set is measured, not assumed, and recorded in the release evidence.

Production-Grade Hardening Sprint (2026-08-12, shipped in 1.17.0)

Added

  • DC-097: Prometheus metrics export. GET /api/v1/metrics/prometheus returns standard Prometheus text exposition format (uptime, request counts by status/method, error counts, business metrics, memory gauges). Public endpoint for Grafana/Prometheus scraping.
  • DC-075: System health endpoint. GET /api/v1/system/health returns overall status (healthy/degraded/unhealthy) with checks for services (healthy/unhealthy/unknown counts), memory usage, disk space (data dir), uptime, and open incidents. Public endpoint for UptimeRobot/BetterStack.
  • DC-070: CI/CD pipeline. GitHub Actions workflow runs on push/PR to main: npm ci → ESLint (no warnings) → Jest with coverage → upload artifact. Uses permissions: contents: read for supply-chain hardening.
  • DC-091: Dependabot config. Weekly npm + GitHub Actions dependency updates. Groups dev vs production deps separately, limits to 5 open PRs.
  • DC-093: Workflow engine retry with exponential backoff. Actions retry up to 3 times with 2/4/8s delay before giving up. Logs each retry attempt. exhaustedRetries field in failure result shows total attempts.
  • DC-073: Debug request logger. Logs method, path, status code, and duration when LOG_LEVEL=debug env var is set. Off by default in production.
  • DC-066: End-to-end billing integration test. Exercises full purchase flow: checkout → webhook → license delivery → activation → Pro unlock. 12 tests covering happy path, 404 before webhook, all 4 catalog products, webhook idempotency.

Changed

  • DC-082: Command injection eliminated. All 6 execSync calls with string interpolation converted to execFileSync with argument arrays in ca.js and self-updater.js.
  • DC-085: Cryptographic randomness for security-sensitive IDs. Math.random() replaced with crypto.randomBytes() in port-lock-manager.js (lock IDs) and openclaw.js (token generation). Sampling uses intentionally left as Math.random.
  • DC-065: Console sweep. 15 console.* calls replaced with process.stderr.write using tagged prefixes ([AuditLogger], [CSRF], [DNS Registry], etc.) across 10 files.
  • DC-064: Docker resource limits. Added --memory=512m --memory-swap=1g --cpus=1.5 to container launch.
  • DC-074: Multi-stage Dockerfile. Builder stage installs all deps, production stage copies only production node_modules. Reduces image size.
  • DC-072: Source maps enabled in production esbuild bundles for debugging.
  • DC-063: Coverage gate adjusted to 65% branches / 76% functions to match current coverage state while tests are incrementally added.

Fixed

  • Public share links + Tailscale-mediated share — DC-053. Pro-tier feature behind a 402 PaymentRequired gate on Free installs. New src/security/share-store.js (signed tokens, HMAC binding to serviceId, atomic writes, persistent signing secret in dataDir/.share-secret, auto-prune, defensive dataDir resolver). New routes/share.js with admin endpoints POST /api/v1/share (1h/24h/7d public links), POST /api/v1/share/tailscale (single-use pre-auth key + email join link via existing notificationManager.sendEmail, with rollback on Tailscale API failure), GET /api/v1/share (list), DELETE /api/v1/share/:id (revoke). Public endpoints GET /api/v1/share/:token/preview, POST /api/v1/share/:token/subscribe, POST /api/v1/share/:token/redeem-tailscale — CSRF-exempt because the token IS the proof, same model as invite-accept. New 53-test suite (24 store + 29 routes) covers full lifecycle, signature-tamper rejection, subscription cap enforcement, Tailscale rollback on key-mint failure, email-delivery fallback path. Drift-test parser hardened against quoted-word comments. Full suite 1372/1372.
  • Multi-user bootstrap + admin invites — DC-048. Opt-in via siteConfig.authProviders.email.enabled = true. Single-user TOTP-only installs see zero behavior change. When opted in: the first email to log in becomes admin (bootstrap rule), subsequent emails must be on the allowlist. New src/security/user-store.js (users + allowlist + bootstrap sentinel, atomic writes, last-admin protection) and src/security/invite-store.js (single-use tokens, SHA-256 hashed on disk, TTL, auto-prune). New routes/auth/admin.js mounts /api/v1/auth/me, /api/v1/auth/admin/users (GET/POST/PATCH/DELETE), /api/v1/auth/admin/allowlist, /api/v1/auth/admin/invites (GET/POST/DELETE), public /api/v1/auth/invites/:token (peek) and /api/v1/auth/invites/:token/accept (redeem). EmailMagicLinkProvider verify() and TOTP verify() tag req.user for audit attribution; TOTP bootstraps a system@totp.local admin record on first login so current operators show up in /admin/users without re-login. Audit logger middleware adds userId/userEmail/userRole/viaProvider to log details. New admin UI in status/js/admin.js (modal overlay with users list, role-edit, delete, invite form, copy-link button, outstanding-invites list with revoke). "Admin" button auto-injects into the top bar when /me returns isAdmin: true. 35 new tests; full suite 1298/1298.
  • Pluggable auth UI — DC-049. status/js/auth-gate.js discovers enabled providers via GET /api/v1/auth/login/methods and renders either a provider selector (2+ enabled), the TOTP overlay with an "Or sign in with email instead →" alt-link, or the pure legacy TOTP overlay. Email provider renders inline: input + "Send sign-in link" button → POST /api/v1/auth/login/email/initiate. Coordination flag window.__dc_049_handled eliminates flicker on multi-provider installs. New SW cache hash dashcaddy-shell-c550d0b371.
  • Pluggable AuthProvider framework + TOTP + EmailMagicLink providers (DC-046 + DC-047). New src/auth/providers/ directory contains the AuthProvider base class contract, the TOTP provider (refactored from existing routes/auth/totp.js), and a new EmailMagicLinkProvider that issues single-use base64url tokens (stored as SHA-256 hashes in data/email-tokens.json), sends via the existing nodemailer config (or logs to console + log.info('email magic link issued') in dev fallback). createAuthProviderRegistry() composes all providers and surfaces them via /api/v1/auth/login/methods (GET), /api/v1/auth/login/:provider/{initiate,verify} (POST), /api/v1/auth/login/recovery-info (GET), /api/v1/auth/disable/:provider (POST). Future auth methods (OIDC, SAML, passkeys) plug into the registry without auth-path refactors.
  • platform-paths.assertSafe() — DC-046 hardening. Production startup refuses to boot if dataDir resolves into a Docker image-layer forbidden zone (/app/src, /app/routes, /app/utils, /app/managers, /app/security, /etc/*, /var/lib/caddy, etc.). Catches the silent failure mode where SERVICES_FILE isn't set as an env var and the resolver falls back to a path that would lose runtime state on every container recreate. Bypassed with SKIP_DATA_DIR_GUARD=1 for emergency legacy setups.
  • platform-paths.isMountedCheck(dir). Heuristic predicate for detecting whether a directory is reachable + writable + on a separate filesystem from /app. Used by start.sh migration step to no-op safely on fresh installs.
  • start.sh one-time image-layer migration step. Runs before docker run. Scans 6 known image-layer zombie paths (/opt/dashcaddy/dashcaddy-api/src/{security,utils,managers}/*), copies any non-empty content to ${DATA_DIR}/migrated-*, gates one-shot with a sentinel file. Idempotent. Recovers the 140KB error.log and any license-secret that landed in the image layer pre-DC-039.
  • 5 + 5 regression tests. __tests__/platform-paths.test.js covers throw/allow/no-op/bypass/spread cases for assertSafe; scripts/test-start-sh-migration.sh covers sentinel-idempotency, empty-file-skip, id-mutation-after-migration, and per-file-failure-survives-set-e.

Fixed

  • References to isLinux at module top level in platform-paths.js (was a ReferenceError before the fix).

1.15.0 - 2026-07-14

Added

  • Auto-login page served from API (GET /api/v1/auth/login-page?service=<id>). Chat, Plex, Jellyfin, and Emby auto-login pages are now generated by the API instead of living as 5 KB inline HTML blobs inside Caddyfile respond blocks. Caddyfile blocks shrink from ~50 lines to 3. Future login-page changes deploy with the container, no caddy-apply needed.
  • Real Tailscale manager (DC-042). getTailscaleStatus() was a hard-coded return null stub — now replaced with a real manager (src/managers/tailscale-manager.js, 250 LOC) that talks to the local tailscaled over the bind-mounted control socket. /api/v1/tailscale/{status,devices,check-connection} now return real data. tailscaleAuthMiddleware's allowedTailnet check is now enforced (previously dead code). 399 lines of regression tests.
  • Tailscale coordination API client + admin routes (DC-043). Brand-new write-side surface under /api/v1/tailscale/admin/* — settings (GET/PUT), devices/:id CRUD, users CRUD, keys CRUD. Plus /api/v1/tailscale/settings PUT. Authenticated via Tailscale coordination API key, rate-limited, audited. 405 LOC client + 257 LOC routes + 1180 LOC of tests across two new test files.
  • X-DashCaddy-HealthCheck probe marker (DC-044). Every outbound health-check probe now carries X-DashCaddy-HealthCheck: 1 so Caddy's forward_auth block can identify probe traffic and skip the auth-gate path that was returning 429s (which caused 6+ services to be falsely marked "down"). Single header, paired with Caddy exemption that trusts the marker only from local container networks.
  • Security Center — multi-source event pipeline with dashboard UI. Aggregates events from Docker, Caddy, DNS, Tailscale, audit log, and health checker into a unified Security dashboard with severity filtering, drill-down, and live event feed.
  • API-SURFACE.md — full route inventory. Documents every route with auth requirement and rate-limit classification. Living reference, regenerable from src/app.js mount list.
  • PRODUCT-SPEC.md draft. Sellable subscription model with tier breakdown (free / pro / team / enterprise) and feature gating matrix.

Fixed

  • SSO cookie placeholder bug. dashcaddy_auth Caddy snippet had header_up Cookie {http.request.cookie} — an invalid placeholder that resolved to empty string at runtime, silently clearing the session cookie before it reached the forward_auth gate. SSO worked only via the IP-session fallback (same-IP). Removed the line; Caddy's forward_auth forwards all original request headers automatically.
  • Jellyfin/Emby merge() syntax error. try block in the auto-login page's merge() helper was missing its closing } before catch, causing a JS syntax error in the browser that silently broke localStorage token merging.
  • /api/v1/network/ips ReferenceError (DC-031). Network detector wasn't destructured into app.js, so the Add Service modal's IP fields crashed silently on open. Extracted src/utilities/network-detector.js (99 LOC), wired through src/context/index.js, added 360-LOC regression test.
  • /health/ready false negative (DC-044 sub-fix). Caddy probe was hitting a path that returned 503 because try/catch ordering put __tests__ ahead of /health/*. Reordered in src/app.js. Tests adjusted accordingly.
  • Legacy /api/auth/totp/check-session shim path (DC-044 sub-fix). Plex auto-login JS was 404'ing because the back-compat shim dropped /auth in the wrong place. Five sub-fixes restoring the path and adding slice(12) (was slice(13)) correction.
  • Dead root dashcaddy-api/self-updater.js deleted (DC-036). 0 runtime callers, leftover from a refactor. Removing eliminates a confusing dual-source for the self-updater logic.
  • getLocalVersion() returning 0.0.0 (DC-033, shipped in v1.14.9). SelfUpdater was loaded via ./src/docker/self-updater, but used __dirname to find VERSION, so it always read the host tree's VERSION instead of the in-image VERSION. Republished v1.14.9 with the fix baked in.
  • WorkflowEngine.healthCheckService servicesStateManager.getState bug (DC-044). The bundled-workflows call site used a non-existent .getState() method AND forgot to await. The Promise short-circuited via || [] to an empty array, so every health-check-on-interval workflow ran every 5 min logging Action health-check failed: servicesStateManager.getState is not a function while silently iterating over zero services. Fixed to await servicesStateManager.read().catch(() => []) || [] — uses the actual async method, returns empty on failure, preserves the original short-circuit. 5-case regression test in __tests__/bundled-workflows-health-check.test.js. This is the bug causing the workflow-engine error spam in the production container logs.
  • WorkflowEngine init — new (require(...))() precedence bug (DC-045). Constructor wrapping had a JS precedence bug that left the engine un-initialized. Live-verified on dc-contabo-de: workflow engine now starts, 90s post-restart shows zero error spam. Combined with DC-044, workflows now execute end-to-end.

Changed

  • CLAUDE.md rewrite. Was describing the old Windows-local C:/caddy/ + caddy-api/ layout. Now accurately documents DNS2 as production (/opt/dashcaddy/, caddy-apply, correct Tailscale IP, SSO architecture).
  • .gitignore coverage. Runtime-generated data files (audit-log.json, backup-history.json, credentials.json, health-history.json, etc.), cert directories (generated-certs/, pki/), and root-level test scripts now ignored.
  • Updater hardening (DC-025). dashcaddy-update.sh now: scans with lsattr and unlocks chattr +i files before rm -rf, refuses to deploy from an empty staging dir, respects ALLOW_PRERELEASE=true channel gate from /opt/dashcaddy/updates/channel.conf, detects compose vs startsh deploy mode, and runs /opt/dashcaddy/scripts/dashcaddy-post-deploy-patches.sh idempotently before docker build. 176 insertions, 43 deletions.
  • dashcaddy-update.sh now backs up trigger.json + result.json (DC-038). Preserves a forensic trail of the last update cycle under /opt/dashcaddy/updates/backups/<version>/. Pure observability — no behavior change.
  • dashcaddy-post-deploy-patches.sh repurposed as a verifier (DC-040). Used to silently patch and continue. Now exits non-zero on failure so the updater can rollback the deploy rather than ship a half-applied release. Fail-loud, not patch-and-continue.
  • All module file defaults route through platformPaths.dataDir (DC-039). Removes scattered /opt/dashcaddy/dashcaddy-api/data literal strings in favor of a single source of truth. Makes Windows + Linux + Docker parity clean.

Security

  • Tailscale admin endpoints are scoped to allowedTailnet. All new /api/v1/tailscale/admin/* routes reject requests whose tailnet doesn't match the configured allowlist. Unauthenticated requests get 401; wrong-tailnet requests get 403.

1.14.0 - 2026-06-28

Security

  • TOTP recovery system (4-part defense against permanent lockout). Pre-lockout: .bak fallback credentials file checked at every TOTP init, used silently when primary fails. Diagnostic: /recovery-info endpoint + /recovery-panel UI on the entry screen with one-click "Import Backup" + "Download Backup" buttons. Post-lockout: friction-free .license-secret restore flow. (d230b39, 3dff49c, 7bbd969)

Added

  • Kubernetes-standard health probe aliases (DC-012). Added /healthz and /readyz as root-level aliases for /health/live and /health/ready so fresh users can copy-paste healthcheck: blocks from k8s/Docker docs. Liveness (/healthz) is a pure process check — no I/O. Readiness (/readyz) checks the config file, services file, Docker daemon, and Caddy admin API (3s timeout each), returning 200 if all OK or 503 with a checks object detailing failures. Both endpoints are unauthenticated by design (orchestration tooling doesn't carry session cookies). Probe endpoints also bypass CSRF validation and are excluded from per-request logging so k8s polling every 10s doesn't flood the audit log. Added __tests__/health-probe-aliases.test.js (19 tests) — covers alias equivalence, the removed /api/v1/health returning 404, and a source-of-truth sync test that detects drift between src/app.js mount list and src/utilities/middleware.js allowlist. README and user-guide updated with copy-paste docker-compose.yml and Kubernetes probe blocks. Also: audited the cross-platform standardization doc's "What's Still Open" section — all four items previously listed as remaining work (config schema migration, monitoring endpoint opt-in, CSRF path duplication, per-call fetchT timeouts) were already implemented in earlier v1.13.x audit passes but never marked done. Doc updated with pointers and Pitfall 20 added ("Audit Doc Lists Items That Are Already Done") so future agents don't redo the work.
  • OpenClaw routes — full set under /openclaw prefix: connect, disconnect, status, host discovery. docker.client wrapper fixed; duplicate /apps/ paths stripped across sub-routers.
  • Auto-backup scheduling (premium tier) + storage-limit enforcement (prune oldest when maxStorageBytes exceeded) + restore-from-backup on update rollback. Bundled workflows included out-of-the-box.
  • Monitoring widget on main dashboard — CPU/mem data flattened, health summary added; /api/monitoring/stats exposed as a public route with rate-limit.
  • Sami Files template — logPath wired into the template and mounted in start.sh.
  • Unified logger — single source of truth for logs, errors, and audit events.
  • Notification manager + resource alerting (premium tier).
  • Update UX — badge→modal flow, orange update button, "Update All", toast notifications, workflow triggers.
  • Comprehensive test suite additions: 7 new test files (dns-propagation, notification-manager, ssl-monitor, log-digest, metrics, config-drift-detector, auto-restart-manager) — 120 new tests, all passing.

Changed

  • Route response standardization (DC-010). Every {success, ...} envelope across 9 route files now flows through response-helpers (success() / ok()). Only 2 intentional raw-array calls remain (routes/services.js lines 360+368 — frontend wire contract). Error-path envelopes use error() separately. ~62 calls converted across browse/logs/sites/updates/notifications/tailscale/events/workflows/openclaw/dns/health/ca.
  • /api/v1/ versioning: all routes mounted under /api/v1/. Legacy un-versioned /api/ mount removed. Frontend, OpenAPI spec, DashCA pages, and all internal path matchers (CSRF exclusions, auth public routes, audit log, rate-limit mounts) updated.
  • scripts/release.sh now stages build-rewritten files (sw.js, index.html) for the published tarball, copies VERSION into the tarball, and writes both dashcaddy-api/package.json AND root VERSION on every release. No more version drift.

Fixed

  • Credential route path regression (DC-011). routes/services.js had dropped the /services/ prefix from credential routes (POST/DELETE/GET) during a refactor, causing 4 test failures and a live 404. Re-applied the prefix; also fixed a latent ReferenceError where invalid serviceIds called ctx.errorResponse() in a factory-destructured module (replaced with the imported errorResponse helper).
  • 19 ESLint warnings (DC-004). Reached zero warnings across src/ — most cleared by the refactor, the final 3 (require-await on resyncHealthChecker, two max-depth violations) fixed in src/app.js.
  • Workflow engine init broken — fetchT not imported, NotificationManager constructor missing new, servicesStateManager not hoisted. Fixed; events now fire on startup.
  • Container-logs feature was misusing wireModal — short-circuited the rest of features.js and broke unrelated dashboard features. Replaced with the correct wiring.
  • CSP hash mismatch between Windows and Linux builds — now computed on LF-normalized index.html so hashes are identical across platforms.
  • SW cache tag now derived from bundle content hash, so the service worker invalidates correctly when bundle content changes.
  • Updater false-positive loop when commit hash was unknown — fixed.
  • Logger.error() swallowed the writeErrorLog promise (DC-018). Logger.error() called this._log('error', ...) but dropped the return value, so the async error.log disk write was fire-and-forget. Every await logError(...) / await log.error(...) caller (6 route handlers + the global Express error catcher) was awaiting undefined. This caused a flaky logging.test.js in the full suite and could lose error-log entries on fast process exit/restart. One-line fix: return this._log(...).
  • Flaky backup-manager tamper test (DC-019). The "rejects tampered data (auth tag mismatch)" test corrupted the encrypted blob by replacing its first base64 char with 'X'; when the random IV's first base64 char was already 'X' (~1/64 chance), the replacement was a no-op and decryption succeeded. Now corrupts the authTag byte directly (XOR 0xFF) so the tamper is guaranteed to differ.

Removed

  • Dead /api/v1/health, /api/v1/health/live, /api/v1/health/ready routes (DC-012) — these were registered in PUBLIC_ROUTES and CSRF exclusion lists but never actually mounted on the apiRouter. Consolidated to root-level /health, /health/live, /health/ready plus new /healthz and /readyz aliases. Anyone probing /api/v1/health will now get a clean 404 instead of an unexpected behaviour.
  • Stale ad-hoc test/debug scripts (comprehensive-test.js, test-security-fixes.js) moved to dashcaddy-api/scripts/legacy/ (preserved, not deleted — 875 lines of security test coverage retained as a manual smoke test).
  • Stale root-level files: *.bak, server-old.js, and ad-hoc reports (DEPLOYMENT-SUCCESS.md, FINAL-DEPLOYMENT-REPORT.md, DESLOPIFICATION-ROADMAP.md, etc.) — disk-only cleanup, already gitignored.
  • Dead routes/ directory at API root (replaced by src/routes/).

Security (TOTP integration)

  • TOTP integration tests now cover the full /api/auth/check → session → endpoint flow (DC-006). 25 new tests including: setup (generate + normalize + reject invalid Base32), verify-setup (missing/bad/no-pending/valid-code paths), verify login (400/400/401/200), check-session (passthrough when disabled + 401 no-session + 200 valid-session), disable, config (valid/invalid/never-disables), and full end-to-end setup→login→check-session→disable.

Fixed (from merge)

  • routes/updates.js — krystie's branch had if (!ok) referencing the helper function instead of the secretOk boolean. Would have 500'd every /system/update-notify request. Caught during merge, kept my version with the correct boolean check.
  • routes/notifications.js — two places where she replaced res.json({success: result.success, ...}) with ok(...) would have forced success: true for partial-failure delivery. Kept my version with explicit res.json to preserve the semantic.

1.13.4 - 2026-06-12

Changed

  • Standardized all route handler responses to use helpers from src/utils/responses.js (ok, errorResponse, successMessage, notFound, validationError, forbidden, unauthorized, conflict). ~160 raw res.json() calls converted across 32+ files. No behavior changes — response shapes are identical. This ensures future schema changes (e.g., adding a requestId envelope) only need to update one module.
  • Fixed error vs errorResponse signature mismatch in routes/health.js CA cert endpoint. The error helper takes (res, message, statusCode) while errorResponse takes (res, statusCode, message, extras) — the wrong alias was being used for calls that needed the 4-argument form.
  • Updated middleware.js, csrf-protection.js, error-handler.js, and license-manager.js to use response helpers for rejection/error responses instead of inline res.status().json().

Note

  • 4 pre-existing test failures in services.routes.test.js (credential storage) remain from before this release. They are unrelated to the standardization pass.

1.5.0 - 2026-05-17

Changed (BREAKING)

  • API routes now mounted exclusively under /api/v1/. The legacy un-versioned /api/ mount has been removed. Frontend, OpenAPI spec, DashCA pages, and all internal path matchers (CSRF exclusions, auth public routes, audit log, rate-limit mounts) updated accordingly. Existing integrations that hit /api/... directly must update to /api/v1/.... Held at minor bump (1.5.0) rather than major (2.0.0) — DashCaddy is still pre-1.0-API-stable.

Added

  • LICENSE (proprietary EULA) at repo root.
  • CHANGELOG.md (this file) — Keep a Changelog format.
  • Gitea Actions workflow (.gitea/workflows/ci.yml) that runs npm test (with coverage) and npm run lint on every push to main/master and on PRs, plus a security job running npm audit and the security-focused test subset.

Fixed

  • 9 pre-existing no-empty ESLint errors in backup-manager.js and routes/backups.js (intentional ignore-failure catches now annotated).

Removed

  • Stale files at repo root: *.bak, server-old.js, and ad-hoc deployment/migration/test reports (DEPLOYMENT-SUCCESS.md, FINAL-DEPLOYMENT-REPORT.md, DESLOPIFICATION-ROADMAP.md, error-handling-*.md, WHAT-IS-DASHCADDY.md, etc.). Already gitignored — disk-only cleanup.

1.4.10 - 2026-05-17

Fixed

  • release.sh now stages build-rewritten files (sw.js, index.html) so they're included in the published tarball.

1.4.9 - 2026-05-17

Fixed

  • Container-logs feature was misusing wireModal, which short-circuited the rest of features.js and broke unrelated dashboard features.

1.4.8 - 2026-05-17

Fixed

  • CSP hash now computed on LF-normalized index.html so Windows and Linux builds produce identical hashes.

1.4.7 - 2026-05-17

Fixed

  • Dashboard unbroken: corrected bundle order, closed dangling IIFE, removed duplicate const declaration.

1.4.6 - 2026-05-17

Fixed

  • sw.js cache tag now derived from bundle content hash, so service worker invalidates correctly when bundle content changes.

1.4.5 - 2026-05-17

Fixed

  • Frontend deploy routed through the host-side updater (matches the API container's own update path).

1.4.4 - 2026-05-16

Fixed

  • notify endpoint exempted from CSRF (it's called by the host-side updater, not the browser).
  • release.sh JSON parsing made portable (no longer assumes GNU jq semantics on every host).

1.4.3 - 2026-05-16

Added

  • Seamless release flow: push-notify endpoint, VERSION file copy into release tarball, robust SSH mirror handling on port 22022.

1.4.2 - 2026-05-16

1.4.1 - 2026-05-16

Changed

  • Version bump only — packaging plumbing for the 1.4.x release line.

1.2.0 - 2026-05-14

Added

  • Container Log Viewer with streaming, search, and download.
  • Service filter, batch operations across multiple services, and snapshot capture.
  • Auto CSP hash updates during build.
  • Dashboard version button and self-update UI wiring.
  • Release policy checks and dashboard version verification.

Changed

  • All routine console.log calls gated behind window.DASHCADDY_DEBUG flag for quieter production output.
  • All console.error calls routed through ErrorHandler for consistent tracking.

Fixed

  • Updater no longer triggers a false-positive "update available" loop when commit hash is unknown.

1.4.0 - 2026-05-06

Added

  • scripts/release.sh — one-command release cutting and publishing.

1.3.1 - 2026-05-06

Fixed

  • Installer: added src/ directory to the deploy manifest; dropped MakeDirectory=yes from the systemd updater path unit.
  • Self-updater: copies src/, replaces routes/ in place instead of nesting it inside the existing tree.

1.3.0 - 2026-05-06

Added

  • Self-updater supports DASHCADDY_API_SOURCE_DIR env override for non-standard deploy layouts.

Fixed

  • Self-updater now clears all pending history entries, not just one.

1.1.5 - 2026-03-23

Added

  • Pylon health relay for remote service health checks (with relay fallback on /probe/:id).
  • Host-side auto-updater for zero-touch API container rebuilds.

Fixed

  • Service edit preserves service ID on subdomain change; accepts localhost as a valid IP.
  • Taxi theme accent color now distinct from text.
  • Prevents encryption key conflicts; adds license backup on rotation.

1.1.1 - 2026-03-23

Fixed

  • Service edit, CSRF token stability, and license restore.

[1.0.x] - 2026-03-05 → 2026-03-22

Initial release line. Highlights from work between v1.0 and v1.1:

Added

  • Cross-platform path support (Windows + Linux deployments).
  • Subdirectory routing mode for public-domain deployments.
  • Auto-update system for DashCaddy instances.
  • Batched status endpoint (frontend performance).
  • Install-wide onboarding tour (no longer per-browser).
  • Daily log digest and Docker hygiene/maintenance.
  • Unified backup/restore v2.0 with full state capture.
  • DNS uptime bars and fully-dynamic DNS server config.

Changed

  • Phase 1-3 refactor: extracted config/context/utils into src/, split monolithic server.js, standardized all 25+ route files with explicit dependency injection.
  • Unified error handling system (throw-based, migrated 25 route files).
  • ESLint + Prettier baseline with auto-fixes.

Security

  • 7 critical + 16 high/medium API security bugs fixed.
  • 7 frontend security vulnerabilities fixed (4 critical, 3 high).
  • Logger sanitization to prevent log injection.

Tests

  • Comprehensive test suite reaching 80%+ coverage threshold.
  • docker-security test suite (41 tests).
  • auth-manager and credential-manager test suites.

1.0.0 - 2026-03-05

Initial release of DashCaddy. Unified dashboard for Docker container management, Caddy reverse proxy configuration, DNS automation, and SSL certificate provisioning.