Three coordinated changes to stop every gated *.sami service flipping
red after ~20 probes:
1. health-checker.js _doRequest() now sends X-DashCaddy-HealthCheck: 1
on every outgoing probe. Caddy uses this header (combined with a
trusted source IP via the new @healthcheckProbe matcher in the
dashcaddy_auth snippet) to bypass forward_auth for local container
probes. Without the bypass, forward_auth 401's every probe, and the
authLimiter (20 req / 15 min, DC-027) caps us out within minutes.
2. evaluateHealth() default expectedStatusCodes now includes 401, 403,
and 429. Defense in depth — if a future Caddy reload drops the
bypass, 429 from the rate-limited gate no longer marks the service
as down (it just means the gate answered, which proves the service
is reachable through Caddy).
3. (start.sh — already shipped on the running container, will land
with the next release build) ca.sami now maps to 100.121.150.22
(DNS2) instead of 127.0.0.1, which is the container's own loopback
where nothing serves :443. The CA web UI lives on DNS2's Caddy.
Tests:
- evaluateHealth: 401, 403, 429 accepted by default
- _doRequest: X-DashCaddy-HealthCheck: 1 always present
- _doRequest: user-supplied headers preserved alongside marker
Bump 1.14.7 → 1.14.8.
[DC-026] routes/auth/sso-gate.js — fix sessionDuration='never' bypass
Both /auth/gate/:serviceId and /auth/app-token/:serviceId had a session
check gated on `sessionDuration !== 'never'`. An admin setting TOTP to
never-expire accidentally created an authentication-free path to credential
injection (Basic Auth, X-Api-Key, Plex/Prowlarr tokens). Patched: session
required whenever TOTP is enabled, period. Added 8 regression tests.
[DC-027] src/utilities/middleware.js — rate limit /auth/*
New authLimiter (20 req / 15 min) on /auth/keys, /auth/jwt, /auth/gate,
/auth/app-token. These endpoints expose credentials and were unmetered.
Without this, an attacker with a guessed session cookie could burn through
every credential-touching endpoint. Added 5 tests.
[DC-028] src/security/audit-logger.js — log credential exposures
/auth/gate and /auth/app-token were in SKIP_PATHS, silently dropping
every credential-exposure event from the audit log. Combined with the
GET-skip rule, NONE of these events were being recorded. Now logged
with named actions: auth.credential-injection, auth.app-token-issue,
auth.api-key-generate, auth.api-key-revoke, auth.jwt-mint. Added 9 tests.
[start.sh] Disable in-container self-updater
DASHCADDY_UPDATE_ENABLED=false. Without this, the container kept writing
trigger.json every 30 min and clobbered my in-progress host edits. The
path unit on the host is still active for manual triggers, but the
container won't auto-update itself — only when an admin clicks the
update button or a new release is manually published.
[package.json] Bump to 1.14.7
Test results: 1066/1066 passing across 39 suites (added 22 new tests).
Following DC-021 (commit 10f72af) which restored working require paths and
license-keygen.js, this commit bumps the version metadata so the next
release build publishes v1.14.6 instead of re-tagging v1.14.4.
The source is functionally v1.14.4 + fixes; the version bump tells the
updater we're ahead of upstream's broken v1.14.4.
Convert ~160 raw res.json()/res.status().json() calls across 32+ files
to use centralized helpers from src/utils/responses.js (ok, errorResponse,
successMessage, notFound, validationError, forbidden, unauthorized, conflict).
No behavior changes — response shapes are identical. Future schema changes
(e.g., requestId envelope) only need to update one module.
Fix error vs errorResponse signature mismatch in routes/health.js CA cert
endpoint where error(res, message, statusCode) was being called with
errorResponse(res, statusCode, message, extras) argument order.
Files changed: middleware.js, csrf-protection.js, error-handler.js,
license-manager.js, src/app.js, and 27 route files.
Test suite: 755 pass / 4 pre-existing failures (services credential tests).
Three small cleanups for v1.14.0:
1. /caddy/cas now uses standard success envelope
Was: { status: 'success', data: { cas: caList } }
Now: { success: true, cas: caList }
Updated frontend service-infrastructure.js to match.
2. /api/health/ca now uses standard envelope + meaningful HTTP codes
Was: { status, message, daysUntilExpiration } with 200 on every error
Now: { success, caStatus, message|error, daysUntilExpiration }
with 200 / 404 / 500 as appropriate
caStatus field preserves the original 'healthy'/'warning'/'critical'/'error'
semantic so any future consumer of the CA-health state still has it.
Tests updated to match.
3. Dead timeout: keys in fetchT opts are now a warning, not a silent strip
src/utils/http.js:41 used to do without telling
anyone. Callers that wrote fetchT(url, { timeout: 5000 }) got the default
5s timeout with no indication that their explicit value was ignored.
Now it logs a warning naming the call site, then strips the key.
Fixed 4 call sites that had stale timeout: keys:
- src/context/caddy.js
- src/context/dns.js
- src/context/provider-dns.js
- routes/dns.js (2 places)
Two cleanups in one pass for the v1.14.0 'works on any platform' theme:
1. Response helpers — merged src/utils/responses.js and the root-level
response-helpers.js into a single module at src/utils/responses.js.
The old module had a richer set (created, noContent, validationError,
unauthorized, forbidden, notFound, conflict) and is now re-exported
from the new location. Updated 15 routes to import from
src/utils/responses and deleted the root response-helpers.js.
2. Error logger — error-handler.js now uses the unified
src/utils/logging.js#logError (same one src/app.js uses), so all errors
go to one log file with one rotation policy. Removed the dead
asyncHandler export (the real one is in src/utils/async-handler.js
and is used everywhere). Deleted the legacy error-logger.js.
Both are invisible to users — same HTTP response shapes, same log file
path, same error format. Internal-only refactor.
The readiness probe was using asyncHandler directly, but this codebase's
asyncHandler has signature (logError, fn, context) — first arg is the logger.
Switched to boundAsyncHandler which is what every other route in src/app.js
uses. Verified working on both DNS2 (Docker) and Contabo (systemd).
8 new tests in __tests__/health-endpoints.test.js verify both endpoints.
- dns-providers/: adapter base class + registry with auto-discovery
- technitium.js: wraps existing Technitium API calls into adapter interface
- cloudflare.js: Cloudflare API v4 adapter (zones, records, credentials)
- rfc2136.js: RFC 2136 dynamic DNS via nsupdate (BIND, PowerDNS, etc.)
- manual.js: no-op adapter for external DNS management with instructions
- provider-dns.js: provider-aware DNS context, resolves active adapter from config
- Universal helper methods: universalCreateRecord/Delete/ResolveRecord
- All 7 route files updated to use universal methods instead of raw dns.call()
- Setup wizard: provider dropdown (Technitium, Cloudflare, RFC 2136, Manual)
- DNS template selector: added Cloudflare and External/Manual options
- Config schema: validates dns.provider field
- Capability gating on Technitium-specific endpoints (logs, restart, update)
- Backward compatible: no provider set = auto-detect (technitium if dns.ip exists)
- install.sh now deploys the src/ directory alongside routes/.
Without this, fresh installs of v1.3.0+ produce containers whose
Dockerfile references src/ but the directory is missing on the host
filesystem, so docker build fails with "/src: not found".
- The fallback heredoc that writes /etc/systemd/system/dashcaddy-
updater.path drops MakeDirectory=yes for the same reason it was
removed from the on-disk unit (e994ad1): systemd creates the watched
trigger.json path as an empty directory on unit start, blocking
every subsequent update with EISDIR.
Bumped to 1.3.1 so the existing v1.3.0 instance auto-updates and
picks up these and the host-script fix from 0cf6323.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v1.2.0 was published as a tarball but its package.json bump was never
committed back to git. This release picks up where that gap left off
and includes two fixes that v1.2.0 (commit a216dd8) was missing:
- 6abba43: clear ALL pending self-update history entries (not just
the first), so stuck installs unwind cleanly.
- 0460129: allow apiSourceDir to be overridden via the
DASHCADDY_API_SOURCE_DIR env var, so installs that don't follow
the default /etc/dashcaddy/sites/dashcaddy-api/ layout (e.g. older
deployments under /opt/dashcaddy/) can point the auto-updater at
the right path without patching the constructor.
Without these, instances on the older /opt/dashcaddy/ layout get
stuck in a 30-min retry loop where every update attempt fails with
'cp: cannot create directory /etc/dashcaddy/sites/dashcaddy-api/'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Container exec/shell via WebSocket + xterm.js (subtle >_ button on cards)
- Live dashboard updates via SSE (resource alerts, health changes, update notices)
- Docker Compose import with YAML parsing, preview, and dependency-ordered deploy
- Volume & network management modal with disk usage overview
- CPU/memory resource limits on deploy and live update
- Email SMTP notifications (nodemailer) alongside Discord/Telegram/ntfy
- Scheduled auto-update scheduler with maintenance windows (daily/weekly/monthly)
New deps: ws, js-yaml, nodemailer
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix service edit double-write bug (was creating duplicate entries)
- Add editable display name field to service edit modal
- Backend update endpoint now accepts name, logo, and recalculates url
- Fix CSRF token regeneration breaking all POST requests (nonce was
being regenerated on every request, invalidating cached tokens)
- CSRF nonce now persists across requests, rotated only on TOTP login
- Frontend secureFetch auto-retries on CSRF failure with fresh token
- Restore lifetime license activation on DNS2
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- self-updater.js: polls for new versions, downloads/verifies tarballs,
triggers host-side rebuild via systemd path unit
- dashcaddy-update.sh + systemd units: host-side container rebuild with
automatic rollback on health check failure
- 7 new /api/v1/system/* endpoints for version info, update check/apply,
rollback, and update history
- Frontend: DashCaddy tab in Updates modal with version display,
changelog, update button, rollback, and notification dot
- install.sh: updater service installation, volume mounts, env vars
- build-release.sh + webhook-handler.js: release server pipeline
(Gitea webhook → build tarball → deploy to get.dashcaddy.net)
- Dockerfile: DASHCADDY_COMMIT build arg → VERSION file
- Version bump to 1.1.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Full codebase including API server (32 modules + routes), dashboard frontend,
DashCA certificate distribution, installer script, and deployment skills.