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 crashing with 'res.status is not a function' because
asyncHandler(async (req, res) => {...}, 'health-ready') was called directly,
but asyncHandler's signature is (logError, fn, context) — first arg is the
logger, not the handler. The fix uses boundAsyncHandler like all other routes
in the file do.
Added 8 unit tests for both /health/live and /health/ready:
- live always 200 (liveness ≠ readiness)
- ready returns 503 when config/services/docker fail
- no 'res.status is not a function' crash when dependencies fail
- all 4 check keys present in response
Also added MONITORING_PUBLIC env var (defaults true) and the new health
endpoints to PUBLIC_ROUTES so k8s probes can hit them without auth.
When config.json schema changes between versions, register a migration
function in src/config/migrations.js. On startup, loadSiteConfig() detects
the stored version, runs all migrations forward, and writes the result back.
Users never see the migration — it runs silently and the rest of the app
only ever sees the current schema.
Includes:
- v0 → v1: normalize dns from string to object
- v1 → v2: add dns.provider field (default 'technitium')
- Forward compat: configs from future versions left untouched
- Idempotent: re-running on already-migrated config is a no-op
- Safe: no user data is removed during migration
21 unit tests covering edge cases: null input, forward compat, corrupt
JSON, missing parent dirs, idempotency, full migration chain.
Cross-platform hardening — removes all hardcoded /app/ paths from route files
and routes them through platform-paths.js so the app works the same way
regardless of Docker layout (single-file mount vs consolidated data dir).
Changes:
- platform-paths.js: add generatedCertsDir, pkiDir, containerUpdatesDir,
containerFrontendDir, containerAssetsDir, resolveAssetsPath()
- self-updater.js: UPDATE_URL/MIRROR_URL/CHANNEL env var overrides
- routes/ca.js: use platformPaths for cert paths and generated certs dir
- routes/services.js: use platformPaths.pkiRootCert
- routes/themes.js: derive THEMES_DIR from platformPaths.servicesFile
- routes/config/assets.js + backup.js: use resolveAssetsPath() fallback
- routes/services.js + src/app.js: use platformPaths.pkiRootCert
- server.js: HOST env var support, parse PORT as int
- src/app.js: GET /api/v1/version (public, no auth), global request timeout,
disable x-powered-by, trust proxy
- pylon/dashcaddy-pylon.js: PYLON_HOST env var, graceful shutdown on SIGTERM/SIGINT
A fresh user can now deploy with a custom Docker layout (e.g. /opt/dc/data/
as a single volume mount) and the app finds its files automatically, no env
var configuration required.
- deploy.js: wrap logError/notification in try/catch so they never mask the original deploy error
- deploy.js: use optional chaining for error.message access
- logging.js: safeErrorMessage handles null/undefined error gracefully
- 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)
- openClawRoutes was mounted at root causing /status vs /openclaw/status mismatch
- ctx.docker is a typed wrapper {client,pull,...} — all calls now use docker.client.*
- templates/deploy/removal/restore sub-routers had /apps/ hardcoded in inner routes
causing double-stacking when mounted under /apps (→ /apps/apps/templates etc)
- openclaw.js: GET /status, POST /deploy, GET/POST /proxy/*, DELETE /
- Set Domain=.sami on session + CSRF cookies so browsers send them to all subdomains
- This fixes Caddy forward_auth returning 401 for radarr/sonarr/prowlarr
- Fix login URL concatenation bug (radarr.samilogin -> radarr.sami/login)
- Fix getSetCookie() missing from _httpsFetch/_httpFetch response objects
- Fix array/string handling for set-cookie header in session-handlers fallback
- Refactor csrf-protection to createCSRFMiddleware() factory with cookieDomain support
- Pass renewCSRFToken through middleware deps chain to TOTP route
- Remove legacy /api/ mount; all routes now under /api/v1/ only
- Update path matchers (CSRF excludes, public routes, audit log, rate limits)
- Move standalone routes (/api/network/ips, /api/docs, /api/docs/spec) to v1
- Update openapi.yaml (110 paths), CA pages, and 4 lingering frontend files
- Add LICENSE (proprietary EULA), CHANGELOG.md (Keep a Changelog format)
- Add .gitea/workflows/ci.yml (test+lint and security audit jobs)
- Fix 9 pre-existing no-empty lint errors so CI starts green
- Drop ad-hoc scratch reports and *.bak files from repo root
All 739 jest tests pass. Lint is clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- monitoring.js: Added log dependency, replaced console.log with log.warn
- themes.js: Added log dependency, replaced console.error with log.error
- src/app.js: Pass log to monitoringRoutes and themesRoutes
This fixes error messages being lost to stdout instead of proper log files.
- 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>
The lightweight probe endpoint used by the dashboard for live status
checks had no Pylon integration. When DNS2 (Singapore) tried to probe
home network services directly, all probes timed out with 502. Now
falls back to the configured Pylon relay before the domain fallback.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The modular refactor changed function signatures to destructured deps but
left internal ctx.* references intact, causing "ctx is not defined" errors
on /api/config, /api/logo, and many other endpoints. Also implements
loadTotpConfig and saveTotpConfig which were left as stubs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create src/config/paths.js for all file paths and env vars
- Create src/config/site.js for site configuration loading
- Create src/config/index.js as unified config export
- Prepare for server.js modularization (Phase 2.1)
Part of deslopification roadmap: break 1997-line server.js into layers