From b4e09af712403ea727f51b742366f453e0893cb0 Mon Sep 17 00:00:00 2001 From: Hermes Date: Thu, 29 Oct 2026 02:29:07 -0700 Subject: [PATCH] [grade=A urn:ump:swcz3quualvlmdanpqm5v46cr6zkazes7erbftk6rj3jxv6ip4fa] docs(DC-170): changelog rebuild, version sync, API-SURFACE regen (audit batch 7) Customer-facing professionalism tier (AUDIT-2026-10-27.md findings 34-39): - CHANGELOG.md rebuilt to Keep-a-Changelog: ONE Unreleased section, the 2026-08-12 hardening sprint folded into 1.17.0 (no v1.16.0 tag exists, verified via git tag -l), ALL 7 judge-grade/URN lines moved out of customer copy into RELEASE-EVIDENCE-internal.md, missing DC-166/169 entries added, full DC-170 sweep entry written, DC-171 DNS-parity ticket recorded, date order strictly descending across all 22 sections (1.1.5 restored with its true content after the re-sort ate the heading), compare-link chain filled (Unreleased from v1.17.0, 1.17.0 from v1.14.6-patched, 18 missing version links added) - VERSION 1.15.0 -> 1.17.0 + openapi.yaml 1.15.0 -> 1.17.0 (three-way drift closed: VERSION=package.json=openapi; start.sh build stamps single-sourced) - README: quickstart endpoints rewritten against the live router (the fictional /api/caddy/{config,reload} section replaced with the real /api/v1/caddy/* routes from routes/sites.js), 50+ -> 78 templates (proven in VERIFY-OUTPUT.txt via require of src/docker/app-templates), version badge 1.17.0, yourusername links -> sami7777, roadmap reconciled with shipped features (multi-user DC-048, DNS UI DC-166, backup automation marked shipped), verification note scoped to the MCP drift gate's actual coverage - API-SURFACE.md regenerated: 443 handler rows / 368 unique paths / 441 unique operations / 88 public rows, Known Limitations section (static extraction, line-oriented, may omit multiline/computed/chained registrations), exact duplicate registrations with file:line - VERIFY-OUTPUT.txt: verbatim command/output transcript proving all version + count claims (sha256 of the generated doc recorded and matching) - state-manager test mock extended for the atomic tmp+rename contract Judge rounds: r1 D -> r2 D -> r3 D -> r4 D -> r5 C -> r6 C -> r7 C -> r8 C -> r9 C -> r10 A (each round fixed blockers in-lane; no bypasses). r10 independently reproduced all counts and the generated doc hash against the live repository. --- API-SURFACE.md | 898 ++++++++++++++++++++--------------- CHANGELOG.md | 240 ++++------ README.md | 65 +-- RELEASE-EVIDENCE-internal.md | 13 + VERIFY-OUTPUT.txt | 30 ++ dashcaddy-api/VERSION | 2 +- dashcaddy-api/openapi.yaml | 2 +- 7 files changed, 679 insertions(+), 571 deletions(-) create mode 100644 RELEASE-EVIDENCE-internal.md create mode 100644 VERIFY-OUTPUT.txt diff --git a/API-SURFACE.md b/API-SURFACE.md index e86bf1d..f942781 100644 --- a/API-SURFACE.md +++ b/API-SURFACE.md @@ -1,50 +1,44 @@ # DashCaddy API Surface -> **Generated:** 2026-07-13 -> **Total routes:** 285 -> **Files scanned:** 47 -> **Source of truth:** router.* registrations in `dashcaddy-api/routes/` + root paths in `src/app.js` +> **Handler rows (method+path per route file):** 443 +> **Public rows (matched by PUBLIC_ROUTES allowlist):** 88 +> **Methodology:** statically derived from route declarations + the mount/composition map in the generator (lazy and composed mounts encoded from src/app.js with cited line refs). Code-derived counts are authoritative for what is DECLARED; boot-time verification lives in the MCP route-existence drift gate. +> **Files scanned:** 81 +> **Generated by:** `dashcaddy/scripts/api-surface-extract.py` ## Auth & Rate Limit Model -**Auth classification:** -- `public` = in `PUBLIC_ROUTES` allowlist (`src/utilities/middleware.js:310-364`), bypasses TOTP -- `protected` = requires valid TOTP session cookie (`dashcaddy_session`) OR API key/JWT token - -**Rate limits** (from `RATE_LIMITS` in `src/utilities/constants.js:69`): -- `GENERAL` = 1000 req / 15 min / IP — default for all `/api/v1/*` -- `STRICT` = 20 req / 15 min / IP — auth key endpoints (`/auth/keys`, `/auth/jwt`, `/auth/gate`, `/auth/app-token`) -- `TOTP` = 10 req / 15 min / IP — TOTP verify/setup - -**CSRF:** TOTP session uses double-submit cookie pattern. State-changing requests (POST/PUT/DELETE/PATCH) require `X-CSRF-Token` header matching the `csrf_token` cookie. - ---- +- `public` = in `PUBLIC_ROUTES` (`src/utilities/middleware.js`), bypasses TOTP +- `protected` = requires TOTP session OR API key/JWT +- CSRF: state-changing requests need `X-CSRF-Token` matching `csrf_token` cookie +- Rate limits: GENERAL 1000/15m, STRICT 20/15m (auth keys), TOTP 10/15m ## Summary by Area | Area | Routes | Public | Protected | |---|---:|---:|---:| +| Other | 135 | 13 | 122 | +| Authentication | 34 | 18 | 16 | | App catalog | 28 | 0 | 28 | +| DNS | 20 | 0 | 20 | | Tailscale | 20 | 20 | 0 | -| Backups | 19 | 0 | 19 | -| DNS | 19 | 0 | 19 | | Monitoring | 19 | 3 | 16 | +| Backups | 18 | 0 | 18 | +| Logs | 17 | 0 | 17 | | Updates | 16 | 6 | 10 | -| Authentication | 15 | 10 | 5 | -| Logs | 15 | 0 | 15 | | Configuration | 13 | 9 | 4 | -| Health | 12 | 2 | 10 | +| Health | 13 | 3 | 10 | | Services | 12 | 4 | 8 | -| Containers (lifecycle) | 10 | 0 | 10 | +| Containers (lifecycle) | 11 | 0 | 11 | | Core / system | 9 | 6 | 3 | | Dependencies | 8 | 0 | 8 | | Notifications | 8 | 0 | 8 | | App recipes | 8 | 0 | 8 | | Docker resources | 7 | 0 | 7 | | Caddy / sites | 7 | 0 | 7 | +| Certificate authority | 6 | 3 | 3 | | Updates / workflows | 6 | 0 | 6 | | Auto-restart | 5 | 0 | 5 | -| Certificate authority | 5 | 5 | 0 | | OpenClaw integration | 5 | 0 | 5 | | Config drift | 4 | 0 | 4 | | Licensing | 4 | 2 | 2 | @@ -52,8 +46,190 @@ | Theming | 3 | 1 | 2 | | Service credentials | 2 | 0 | 2 | | Events | 2 | 0 | 2 | -| Internal helpers | 1 | 0 | 1 | -| **TOTAL** | **285** | **68** | **217** | +| **TOTAL** | **443** | **88** | **355** | + +## Other + +_135 routes_ + +| Method | Full path | Auth | Rate limit | Defined in | +|---|---|---|---|---| +| GET | `/api/v1/ai/capabilities` | protected | GENERAL (1000/15m) | `routes/ai-intent.js:681` | +| POST | `/api/v1/ai/intent` | protected | GENERAL (1000/15m) | `routes/ai-intent.js:569` | +| DELETE | `/api/v1/ai/settings` | protected | GENERAL (1000/15m) | `routes/ai-intent.js:642` | +| GET | `/api/v1/ai/settings` | protected | GENERAL (1000/15m) | `routes/ai-intent.js:597` | +| PUT | `/api/v1/ai/settings` | protected | GENERAL (1000/15m) | `routes/ai-intent.js:614` | +| POST | `/api/v1/ai/settings/test` | protected | GENERAL (1000/15m) | `routes/ai-intent.js:652` | +| DELETE | `/api/v1/audit-logs` | protected | GENERAL (1000/15m) | `routes/audit-log.js:176` | +| GET | `/api/v1/audit-logs` | protected | GENERAL (1000/15m) | `routes/audit-log.js:63` | +| GET | `/api/v1/audit-logs/actions` | protected | GENERAL (1000/15m) | `routes/audit-log.js:148` | +| POST | `/api/v1/billing/checkout` | public | GENERAL (1000/15m) | `routes/billing.js:143` | +| GET | `/api/v1/billing/lookup/:sessionId` | public | GENERAL (1000/15m) | `routes/billing.js:184` | +| GET | `/api/v1/caddy/upstreams` | protected | GENERAL (1000/15m) | `routes/caddy-upstreams.js:66` | +| POST | `/api/v1/caddy/upstreams/:host/mute` | protected | GENERAL (1000/15m) | `routes/caddy-upstreams.js:120` | +| POST | `/api/v1/caddy/upstreams/:host/unmute` | protected | GENERAL (1000/15m) | `routes/caddy-upstreams.js:133` | +| GET | `/api/v1/caddy/upstreams/incidents` | protected | GENERAL (1000/15m) | `routes/caddy-upstreams.js:80` | +| POST | `/api/v1/caddy/upstreams/mute` | protected | GENERAL (1000/15m) | `routes/caddy-upstreams.js:107` | +| POST | `/api/v1/caddycode/generate` | protected | GENERAL (1000/15m) | `routes/caddycode.js:244` | +| GET | `/api/v1/caddycode/templates` | protected | GENERAL (1000/15m) | `routes/caddycode.js:320` | +| POST | `/api/v1/caddycode/validate` | protected | GENERAL (1000/15m) | `routes/caddycode.js:276` | +| POST | `/api/v1/calendar/discover` | protected | GENERAL (1000/15m) | `routes/calendar.js:280` | +| GET | `/api/v1/calendar/events` | protected | GENERAL (1000/15m) | `routes/calendar.js:176` | +| POST | `/api/v1/calendar/radicale/provision` | protected | GENERAL (1000/15m) | `routes/calendar.js:234` | +| GET | `/api/v1/calendar/sources` | protected | GENERAL (1000/15m) | `routes/calendar.js:99` | +| POST | `/api/v1/calendar/sources` | protected | GENERAL (1000/15m) | `routes/calendar.js:105` | +| DELETE | `/api/v1/calendar/sources/:id` | protected | GENERAL (1000/15m) | `routes/calendar.js:166` | +| PUT | `/api/v1/calendar/sources/:id` | protected | GENERAL (1000/15m) | `routes/calendar.js:131` | +| GET | `/api/v1/capabilities` | public | GENERAL (1000/15m) | `routes/capabilities.js:38` | +| GET | `/api/v1/catalog` | protected | GENERAL (1000/15m) | `routes/catalog.js:52` | +| GET | `/api/v1/catalog/:appId` | protected | GENERAL (1000/15m) | `routes/catalog.js:102` | +| GET | `/api/v1/catalog/search` | protected | GENERAL (1000/15m) | `routes/catalog.js:82` | +| GET | `/api/v1/deploys/catalog/apps` | protected | GENERAL (1000/15m) | `routes/deploys.js:101` | +| POST | `/api/v1/deploys/catalog/deploy` | protected | GENERAL (1000/15m) | `routes/deploys.js:113` | +| POST | `/api/v1/deploys/deploy` | protected | GENERAL (1000/15m) | `routes/deploys.js:244` | +| POST | `/api/v1/deploys/gitea-repos` | protected | GENERAL (1000/15m) | `routes/deploys.js:358` | +| POST | `/api/v1/deploys/install` | protected | GENERAL (1000/15m) | `routes/deploys.js:300` | +| GET | `/api/v1/deploys/journal` | protected | GENERAL (1000/15m) | `routes/deploys.js:197` | +| GET | `/api/v1/deploys/repos` | protected | GENERAL (1000/15m) | `routes/deploys.js:181` | +| POST | `/api/v1/deploys/rollback` | protected | GENERAL (1000/15m) | `routes/deploys.js:271` | +| GET | `/api/v1/deploys/services` | protected | GENERAL (1000/15m) | `routes/deploys.js:189` | +| GET | `/api/v1/deploys/status` | protected | GENERAL (1000/15m) | `routes/deploys.js:210` | +| POST | `/api/v1/disaster/backup` | protected | GENERAL (1000/15m) | `routes/disaster-recovery.js:135` | +| POST | `/api/v1/disaster/restore` | protected | GENERAL (1000/15m) | `routes/disaster-recovery.js:223` | +| GET | `/api/v1/disaster/status` | protected | GENERAL (1000/15m) | `routes/disaster-recovery.js:410` | +| GET | `/api/v1/discover` | protected | GENERAL (1000/15m) | `routes/discover.js:45` | +| POST | `/api/v1/discover/adopt` | protected | GENERAL (1000/15m) | `routes/discover-adopt.js:39` | +| GET | `/api/v1/disk-settings/` | protected | GENERAL (1000/15m) | `routes/disk-settings.js:13` | +| POST | `/api/v1/disk-settings/` | protected | GENERAL (1000/15m) | `routes/disk-settings.js:54` | +| POST | `/api/v1/disk-settings/cleanup` | protected | GENERAL (1000/15m) | `routes/disk-settings.js:92` | +| GET | `/api/v1/disk/` | protected | GENERAL (1000/15m) | `routes/disk-space.js:88` | +| GET | `/api/v1/disk/breakdown` | protected | GENERAL (1000/15m) | `routes/disk-space.js:94` | +| POST | `/api/v1/disk/cleanup` | protected | GENERAL (1000/15m) | `routes/disk-space.js:130` | +| GET | `/api/v1/disk/config` | protected | GENERAL (1000/15m) | `routes/disk-space.js:100` | +| POST | `/api/v1/disk/config` | protected | GENERAL (1000/15m) | `routes/disk-space.js:105` | +| POST | `/api/v1/fleet/deploy` | protected | GENERAL (1000/15m) | `routes/fleet.js:311` | +| GET | `/api/v1/fleet/hosts` | protected | GENERAL (1000/15m) | `routes/fleet.js:136` | +| POST | `/api/v1/fleet/hosts` | protected | GENERAL (1000/15m) | `routes/fleet.js:142` | +| DELETE | `/api/v1/fleet/hosts/:hostId` | protected | GENERAL (1000/15m) | `routes/fleet.js:223` | +| GET | `/api/v1/fleet/status` | protected | GENERAL (1000/15m) | `routes/fleet.js:246` | +| GET | `/api/v1/i18n/languages` | public | GENERAL (1000/15m) | `routes/i18n.js:16` | +| GET | `/api/v1/i18n/translations/:lang` | public | GENERAL (1000/15m) | `routes/i18n.js:28` | +| GET | `/api/v1/log-insights` | protected | GENERAL (1000/15m) | `routes/log-insights.js:103` | +| POST | `/api/v1/log-insights/dispose` | protected | GENERAL (1000/15m) | `routes/log-insights.js:222` | +| DELETE | `/api/v1/mcp/` | protected | GENERAL (1000/15m) | `routes/mcp.js:276` | +| GET | `/api/v1/mcp/` | protected | GENERAL (1000/15m) | `routes/mcp.js:266` | +| POST | `/api/v1/mcp/` | protected | GENERAL (1000/15m) | `routes/mcp.js:165` | +| POST | `/api/v1/mcp/config` | protected | GENERAL (1000/15m) | `routes/mcp.js:315` | +| GET | `/api/v1/mcp/connections` | protected | GENERAL (1000/15m) | `routes/mcp.js:302` | +| GET | `/api/v1/mcp/info` | protected | GENERAL (1000/15m) | `routes/mcp.js:285` | +| GET | `/api/v1/network/access/` | protected | GENERAL (1000/15m) | `routes/network-access.js:79` | +| PUT | `/api/v1/network/access/` | protected | GENERAL (1000/15m) | `routes/network-access.js:85` | +| GET | `/api/v1/network/access/effective/:principalType/:principalId` | protected | GENERAL (1000/15m) | `routes/network-access.js:135` | +| DELETE | `/api/v1/network/access/grant` | protected | GENERAL (1000/15m) | `routes/network-access.js:116` | +| POST | `/api/v1/network/access/grant` | protected | GENERAL (1000/15m) | `routes/network-access.js:101` | +| POST | `/api/v1/network/devices/:id/revoke` | protected | GENERAL (1000/15m) | `routes/network-lifecycle.js:92` | +| GET | `/api/v1/network/forward-auth/` | public | GENERAL (1000/15m) | `routes/network-forward-auth.js:134` | +| POST | `/api/v1/network/forward-auth/` | public | GENERAL (1000/15m) | `routes/network-forward-auth.js:135` | +| GET | `/api/v1/network/groups/` | protected | GENERAL (1000/15m) | `routes/network-groups.js:67` | +| POST | `/api/v1/network/groups/` | protected | GENERAL (1000/15m) | `routes/network-groups.js:72` | +| DELETE | `/api/v1/network/groups/:id` | protected | GENERAL (1000/15m) | `routes/network-groups.js:90` | +| PATCH | `/api/v1/network/groups/:id` | protected | GENERAL (1000/15m) | `routes/network-groups.js:81` | +| GET | `/api/v1/network/invites/` | protected | GENERAL (1000/15m) | `routes/network-invites.js:161` | +| POST | `/api/v1/network/invites/` | protected | GENERAL (1000/15m) | `routes/network-invites.js:179` | +| DELETE | `/api/v1/network/invites/:id` | protected | GENERAL (1000/15m) | `routes/network-invites.js:246` | +| GET | `/api/v1/network/people/` | protected | GENERAL (1000/15m) | `routes/network-people.js:60` | +| POST | `/api/v1/network/people/` | protected | GENERAL (1000/15m) | `routes/network-people.js:73` | +| DELETE | `/api/v1/network/people/:id` | protected | GENERAL (1000/15m) | `routes/network-people.js:125` | +| GET | `/api/v1/network/people/:id` | protected | GENERAL (1000/15m) | `routes/network-people.js:65` | +| PATCH | `/api/v1/network/people/:id` | protected | GENERAL (1000/15m) | `routes/network-people.js:101` | +| POST | `/api/v1/network/people/:id/block` | protected | GENERAL (1000/15m) | `routes/network-lifecycle.js:74` | +| POST | `/api/v1/network/people/:id/remove` | protected | GENERAL (1000/15m) | `routes/network-lifecycle.js:87` | +| POST | `/api/v1/network/people/:id/unblock` | protected | GENERAL (1000/15m) | `routes/network-lifecycle.js:79` | +| POST | `/api/v1/network/people/sync` | protected | GENERAL (1000/15m) | `routes/network-people.js:151` | +| GET | `/api/v1/network/resources/` | protected | GENERAL (1000/15m) | `routes/network-resources.js:63` | +| POST | `/api/v1/network/resources/` | protected | GENERAL (1000/15m) | `routes/network-resources.js:74` | +| DELETE | `/api/v1/network/resources/:name` | protected | GENERAL (1000/15m) | `routes/network-resources.js:93` | +| GET | `/api/v1/network/resources/:name` | protected | GENERAL (1000/15m) | `routes/network-resources.js:68` | +| PATCH | `/api/v1/network/resources/:name` | protected | GENERAL (1000/15m) | `routes/network-resources.js:84` | +| GET | `/api/v1/network/wg/:token` | protected | GENERAL (1000/15m) | `routes/network-wg-redeem.js:278` | +| POST | `/api/v1/network/wg/:token` | protected | GENERAL (1000/15m) | `routes/network-wg-redeem.js:288` | +| GET | `/api/v1/network/wizard/` | protected | GENERAL (1000/15m) | `routes/network-wizard.js:207` | +| POST | `/api/v1/network/wizard/admin` | protected | GENERAL (1000/15m) | `routes/network-wizard.js:160` | +| POST | `/api/v1/network/wizard/ca` | protected | GENERAL (1000/15m) | `routes/network-wizard.js:149` | +| POST | `/api/v1/network/wizard/complete` | protected | GENERAL (1000/15m) | `routes/network-wizard.js:171` | +| POST | `/api/v1/network/wizard/connect` | protected | GENERAL (1000/15m) | `routes/network-wizard.js:127` | +| POST | `/api/v1/network/wizard/dns` | protected | GENERAL (1000/15m) | `routes/network-wizard.js:138` | +| POST | `/api/v1/network/wizard/domain` | protected | GENERAL (1000/15m) | `routes/network-wizard.js:116` | +| POST | `/api/v1/network/wizard/mode` | protected | GENERAL (1000/15m) | `routes/network-wizard.js:105` | +| GET | `/api/v1/network/wizard/state` | protected | GENERAL (1000/15m) | `routes/network-wizard.js:99` | +| GET | `/api/v1/network/wizard/status` | protected | GENERAL (1000/15m) | `routes/network-wizard.js:182` | +| GET | `/api/v1/security/events` | protected | GENERAL (1000/15m) | `routes/security.js:52` | +| GET | `/api/v1/security/events/:id` | protected | GENERAL (1000/15m) | `routes/security.js:207` | +| POST | `/api/v1/security/events/batch` | public | GENERAL (1000/15m) | `routes/security.js:235` | +| POST | `/api/v1/security/events/ingest` | public | GENERAL (1000/15m) | `routes/security.js:216` | +| GET | `/api/v1/security/events/perimeter` | protected | GENERAL (1000/15m) | `routes/security.js:96` | +| GET | `/api/v1/security/events/stats` | protected | GENERAL (1000/15m) | `routes/security.js:71` | +| GET | `/api/v1/security/events/stream` | protected | GENERAL (1000/15m) | `routes/security.js:176` | +| GET | `/api/v1/security/hosts` | protected | GENERAL (1000/15m) | `routes/security.js:262` | +| POST | `/api/v1/security/hosts` | protected | GENERAL (1000/15m) | `routes/security.js:267` | +| DELETE | `/api/v1/security/hosts/:id` | protected | GENERAL (1000/15m) | `routes/security.js:294` | +| GET | `/api/v1/security/hosts/:id` | protected | GENERAL (1000/15m) | `routes/security.js:280` | +| PATCH | `/api/v1/security/hosts/:id` | protected | GENERAL (1000/15m) | `routes/security.js:287` | +| GET | `/api/v1/security/hosts/:id/health` | protected | GENERAL (1000/15m) | `routes/security.js:305` | +| POST | `/api/v1/security/hosts/:id/rotate-key` | protected | GENERAL (1000/15m) | `routes/security.js:335` | +| GET | `/api/v1/share` | protected | GENERAL (1000/15m) | `routes/share.js:287` | +| POST | `/api/v1/share` | protected | GENERAL (1000/15m) | `routes/share.js:147` | +| DELETE | `/api/v1/share/:id` | protected | GENERAL (1000/15m) | `routes/share.js:292` | +| GET | `/api/v1/share/:token/preview` | public | GENERAL (1000/15m) | `routes/share.js:328` | +| POST | `/api/v1/share/:token/redeem-tailscale` | public | GENERAL (1000/15m) | `routes/share.js:367` | +| POST | `/api/v1/share/:token/subscribe` | public | GENERAL (1000/15m) | `routes/share.js:345` | +| POST | `/api/v1/share/tailscale` | protected | GENERAL (1000/15m) | `routes/share.js:183` | +| GET | `/api/v1/shield` | protected | GENERAL (1000/15m) | `routes/shield.js:108` | +| GET | `/api/v1/version` | public | GENERAL (1000/15m) | `routes/version.js:30` | +| POST | `/api/v1/wizard/apply` | protected | GENERAL (1000/15m) | `routes/wizard.js:146` | +| GET | `/api/v1/wizard/categories` | protected | GENERAL (1000/15m) | `routes/wizard.js:76` | +| POST | `/api/v1/wizard/recommend` | protected | GENERAL (1000/15m) | `routes/wizard.js:88` | + +## Authentication + +_34 routes_ + +| Method | Full path | Auth | Rate limit | Defined in | +|---|---|---|---|---| +| GET | `/api/v1/auth/admin/allowlist` | protected | GENERAL (1000/15m) | `routes/auth/admin.js:200` | +| GET | `/api/v1/auth/admin/invites` | protected | GENERAL (1000/15m) | `routes/auth/admin.js:207` | +| POST | `/api/v1/auth/admin/invites` | protected | GENERAL (1000/15m) | `routes/auth/admin.js:212` | +| DELETE | `/api/v1/auth/admin/invites/:id` | protected | GENERAL (1000/15m) | `routes/auth/admin.js:287` | +| GET | `/api/v1/auth/admin/users` | protected | GENERAL (1000/15m) | `routes/auth/admin.js:146` | +| POST | `/api/v1/auth/admin/users` | protected | GENERAL (1000/15m) | `routes/auth/admin.js:151` | +| DELETE | `/api/v1/auth/admin/users/:id` | protected | GENERAL (1000/15m) | `routes/auth/admin.js:186` | +| PATCH | `/api/v1/auth/admin/users/:id` | protected | GENERAL (1000/15m) | `routes/auth/admin.js:172` | +| GET | `/api/v1/auth/app-token/:serviceId` | public | STRICT (20/15m) | `routes/auth/sso-gate.js:105` | +| POST | `/api/v1/auth/disable/:provider` | public | GENERAL (1000/15m) | `routes/auth/login.js:102` | +| GET | `/api/v1/auth/gate/:serviceId` | public | STRICT (20/15m) | `routes/auth/sso-gate.js:27` | +| GET | `/api/v1/auth/invites/:token` | public | GENERAL (1000/15m) | `routes/auth/admin.js:296` | +| POST | `/api/v1/auth/invites/:token/accept` | public | GENERAL (1000/15m) | `routes/auth/admin.js:315` | +| POST | `/api/v1/auth/jwt` | protected | STRICT (20/15m) | `routes/auth/keys.js:103` | +| GET | `/api/v1/auth/keys` | protected | STRICT (20/15m) | `routes/auth/keys.js:36` | +| POST | `/api/v1/auth/keys` | protected | STRICT (20/15m) | `routes/auth/keys.js:47` | +| DELETE | `/api/v1/auth/keys/:keyId` | protected | STRICT (20/15m) | `routes/auth/keys.js:81` | +| GET | `/api/v1/auth/login-page` | public | GENERAL (1000/15m) | `routes/auth/sso-gate.js:270` | +| POST | `/api/v1/auth/login/:provider/initiate` | public | GENERAL (1000/15m) | `routes/auth/login.js:57` | +| POST | `/api/v1/auth/login/:provider/verify` | public | GENERAL (1000/15m) | `routes/auth/login.js:74` | +| GET | `/api/v1/auth/login/methods` | public | GENERAL (1000/15m) | `routes/auth/login.js:49` | +| GET | `/api/v1/auth/login/recovery-info` | public | GENERAL (1000/15m) | `routes/auth/login.js:88` | +| GET | `/api/v1/auth/me` | protected | GENERAL (1000/15m) | `routes/auth/admin.js:112` | +| GET | `/api/v1/auth/me` | protected | GENERAL (1000/15m) | `routes/auth/index.js:158` | +| GET | `/api/v1/auth/sso-exchange` | public | GENERAL (1000/15m) | `routes/auth/sso-gate.js:245` | +| GET | `/api/v1/auth/sso-handoff` | protected | GENERAL (1000/15m) | `routes/auth/sso-gate.js:221` | +| GET | `/api/v1/totp/check-session` | public | TOTP (10/15m) | `routes/auth/totp.js:246` | +| GET | `/api/v1/totp/config` | public | TOTP (10/15m) | `routes/auth/totp.js:31` | +| POST | `/api/v1/totp/config` | public | TOTP (10/15m) | `routes/auth/totp.js:307` | +| POST | `/api/v1/totp/disable` | protected | TOTP (10/15m) | `routes/auth/totp.js:274` | +| GET | `/api/v1/totp/recovery-info` | public | TOTP (10/15m) | `routes/auth/totp.js:57` | +| POST | `/api/v1/totp/setup` | public | TOTP (10/15m) | `routes/auth/totp.js:117` | +| POST | `/api/v1/totp/verify` | public | TOTP (10/15m) | `routes/auth/totp.js:195` | +| POST | `/api/v1/totp/verify-setup` | public | TOTP (10/15m) | `routes/auth/totp.js:158` | ## App catalog @@ -61,34 +237,61 @@ _28 routes_ | Method | Full path | Auth | Rate limit | Defined in | |---|---|---|---|---| -| DELETE | `/api/v1/:appId` | protected | GENERAL (1000/15m) | `routes/apps/removal.js:39` | -| GET | `/api/v1/:appId/backup-points` | protected | GENERAL (1000/15m) | `routes/apps/restore.js:131` | -| POST | `/api/v1/:appId/restore` | protected | GENERAL (1000/15m) | `routes/apps/restore.js:38` | -| POST | `/api/v1/:appId/revert/:filename` | protected | GENERAL (1000/15m) | `routes/apps/restore.js:185` | +| DELETE | `/api/v1/apps/:appId` | protected | GENERAL (1000/15m) | `routes/apps/removal.js:39` | +| GET | `/api/v1/apps/:appId/backup-points` | protected | GENERAL (1000/15m) | `routes/apps/restore.js:133` | +| POST | `/api/v1/apps/:appId/restore` | protected | GENERAL (1000/15m) | `routes/apps/restore.js:40` | +| POST | `/api/v1/apps/:appId/revert/:filename` | protected | GENERAL (1000/15m) | `routes/apps/restore.js:187` | +| POST | `/api/v1/apps/check-existing` | protected | GENERAL (1000/15m) | `routes/apps/deploy.js:242` | +| DELETE | `/api/v1/apps/compose-stack/:stackName` | protected | GENERAL (1000/15m) | `routes/apps/compose.js:308` | +| POST | `/api/v1/apps/deploy` | protected | GENERAL (1000/15m) | `routes/apps/deploy.js:255` | +| POST | `/api/v1/apps/deploy-compose` | protected | GENERAL (1000/15m) | `routes/apps/compose.js:170` | +| POST | `/api/v1/apps/import-compose` | protected | GENERAL (1000/15m) | `routes/apps/compose.js:159` | +| GET | `/api/v1/apps/ports/:basePort/suggest` | protected | GENERAL (1000/15m) | `routes/apps/templates.js:77` | +| GET | `/api/v1/apps/ports/:port/check` | protected | GENERAL (1000/15m) | `routes/apps/templates.js:65` | +| POST | `/api/v1/apps/restore-all` | protected | GENERAL (1000/15m) | `routes/apps/restore.js:60` | +| GET | `/api/v1/apps/restore-status` | protected | GENERAL (1000/15m) | `routes/apps/restore.js:99` | +| GET | `/api/v1/apps/templates` | protected | GENERAL (1000/15m) | `routes/apps/templates.js:45` | +| GET | `/api/v1/apps/templates/:appId` | protected | GENERAL (1000/15m) | `routes/apps/templates.js:54` | +| POST | `/api/v1/apps/update-subdomain` | protected | GENERAL (1000/15m) | `routes/apps/templates.js:91` | | POST | `/api/v1/arr/auto-setup` | protected | GENERAL (1000/15m) | `routes/arr/config.js:282` | | POST | `/api/v1/arr/configure-overseerr` | protected | GENERAL (1000/15m) | `routes/arr/config.js:27` | | GET | `/api/v1/arr/credentials` | protected | GENERAL (1000/15m) | `routes/arr/credentials.js:109` | | POST | `/api/v1/arr/credentials` | protected | GENERAL (1000/15m) | `routes/arr/credentials.js:21` | | DELETE | `/api/v1/arr/credentials/:service` | protected | GENERAL (1000/15m) | `routes/arr/credentials.js:134` | | GET | `/api/v1/arr/detect` | protected | GENERAL (1000/15m) | `routes/arr/detect.js:20` | -| GET | `/api/v1/arr/quality-profiles` | protected | GENERAL (1000/15m) | `routes/arr/config.js:497` | -| POST | `/api/v1/arr/quality-profiles` | protected | GENERAL (1000/15m) | `routes/arr/config.js:566` | +| GET | `/api/v1/arr/quality-profiles` | protected | GENERAL (1000/15m) | `routes/arr/config.js:499` | +| POST | `/api/v1/arr/quality-profiles` | protected | GENERAL (1000/15m) | `routes/arr/config.js:568` | | POST | `/api/v1/arr/smart-connect` | protected | GENERAL (1000/15m) | `routes/arr/smart-connect.js:26` | | GET | `/api/v1/arr/smart-detect` | protected | GENERAL (1000/15m) | `routes/arr/detect.js:78` | | POST | `/api/v1/arr/test-connection` | protected | GENERAL (1000/15m) | `routes/arr/config.js:208` | -| POST | `/api/v1/check-existing` | protected | GENERAL (1000/15m) | `routes/apps/deploy.js:241` | -| DELETE | `/api/v1/compose-stack/:stackName` | protected | GENERAL (1000/15m) | `routes/apps/compose.js:308` | -| POST | `/api/v1/deploy` | protected | GENERAL (1000/15m) | `routes/apps/deploy.js:254` | -| POST | `/api/v1/deploy-compose` | protected | GENERAL (1000/15m) | `routes/apps/compose.js:170` | -| POST | `/api/v1/import-compose` | protected | GENERAL (1000/15m) | `routes/apps/compose.js:159` | | GET | `/api/v1/plex/libraries` | protected | GENERAL (1000/15m) | `routes/arr/plex.js:26` | -| GET | `/api/v1/ports/:basePort/suggest` | protected | GENERAL (1000/15m) | `routes/apps/templates.js:77` | -| GET | `/api/v1/ports/:port/check` | protected | GENERAL (1000/15m) | `routes/apps/templates.js:65` | -| POST | `/api/v1/restore-all` | protected | GENERAL (1000/15m) | `routes/apps/restore.js:58` | -| GET | `/api/v1/restore-status` | protected | GENERAL (1000/15m) | `routes/apps/restore.js:97` | -| GET | `/api/v1/templates` | protected | GENERAL (1000/15m) | `routes/apps/templates.js:45` | -| GET | `/api/v1/templates/:appId` | protected | GENERAL (1000/15m) | `routes/apps/templates.js:54` | -| POST | `/api/v1/update-subdomain` | protected | GENERAL (1000/15m) | `routes/apps/templates.js:91` | + +## DNS + +_20 routes_ + +| Method | Full path | Auth | Rate limit | Defined in | +|---|---|---|---|---| +| GET | `/api/v1/dns/check-update` | protected | GENERAL (1000/15m) | `routes/dns.js:684` | +| DELETE | `/api/v1/dns/credentials` | protected | GENERAL (1000/15m) | `routes/dns.js:612` | +| POST | `/api/v1/dns/credentials` | protected | GENERAL (1000/15m) | `routes/dns.js:505` | +| GET | `/api/v1/dns/logs` | protected | GENERAL (1000/15m) | `routes/dns.js:352` | +| GET | `/api/v1/dns/propagation` | protected | GENERAL (1000/15m) | `routes/dns.js:828` | +| GET | `/api/v1/dns/propagation/:domain` | protected | GENERAL (1000/15m) | `routes/dns.js:873` | +| POST | `/api/v1/dns/propagation/verify` | protected | GENERAL (1000/15m) | `routes/dns.js:841` | +| GET | `/api/v1/dns/provider/status` | protected | GENERAL (1000/15m) | `routes/dns.js:55` | +| GET | `/api/v1/dns/providers` | protected | GENERAL (1000/15m) | `routes/dns.js:48` | +| DELETE | `/api/v1/dns/record` | protected | GENERAL (1000/15m) | `routes/dns.js:191` | +| POST | `/api/v1/dns/record` | protected | GENERAL (1000/15m) | `routes/dns.js:240` | +| GET | `/api/v1/dns/records` | protected | GENERAL (1000/15m) | `routes/dns.js:72` | +| POST | `/api/v1/dns/refresh-token` | protected | GENERAL (1000/15m) | `routes/dns.js:670` | +| GET | `/api/v1/dns/resolve` | protected | GENERAL (1000/15m) | `routes/dns.js:307` | +| POST | `/api/v1/dns/restart/:dnsId` | protected | GENERAL (1000/15m) | `routes/dns.js:636` | +| GET | `/api/v1/dns/token-status` | protected | GENERAL (1000/15m) | `routes/dns.js:489` | +| DELETE | `/api/v1/dns/universal/record` | protected | GENERAL (1000/15m) | `routes/dns.js:134` | +| POST | `/api/v1/dns/universal/record` | protected | GENERAL (1000/15m) | `routes/dns.js:86` | +| GET | `/api/v1/dns/universal/resolve` | protected | GENERAL (1000/15m) | `routes/dns.js:160` | +| POST | `/api/v1/dns/update` | protected | GENERAL (1000/15m) | `routes/dns.js:753` | ## Tailscale @@ -96,78 +299,26 @@ _20 routes_ | Method | Full path | Auth | Rate limit | Defined in | |---|---|---|---|---| -| GET | `/api/v1/tailscale/acl` | public | GENERAL (1000/15m) | `routes/tailscale.js:301` | -| GET | `/api/v1/tailscale/admin/devices` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:154` | -| DELETE | `/api/v1/tailscale/admin/devices/:id` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:170` | -| GET | `/api/v1/tailscale/admin/keys` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:202` | -| POST | `/api/v1/tailscale/admin/keys` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:211` | -| DELETE | `/api/v1/tailscale/admin/keys/:id` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:237` | -| GET | `/api/v1/tailscale/admin/users` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:191` | -| GET | `/api/v1/tailscale/api-devices` | public | GENERAL (1000/15m) | `routes/tailscale.js:274` | -| GET | `/api/v1/tailscale/check-connection` | public | GENERAL (1000/15m) | `routes/tailscale.js:96` | +| GET | `/api/v1/tailscale/acl` | public | GENERAL (1000/15m) | `routes/tailscale.js:316` | +| GET | `/api/v1/tailscale/admin/devices` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:280` | +| DELETE | `/api/v1/tailscale/admin/devices/:id` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:296` | +| GET | `/api/v1/tailscale/admin/keys` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:328` | +| POST | `/api/v1/tailscale/admin/keys` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:337` | +| DELETE | `/api/v1/tailscale/admin/keys/:id` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:369` | +| GET | `/api/v1/tailscale/admin/users` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:317` | +| GET | `/api/v1/tailscale/api-devices` | public | GENERAL (1000/15m) | `routes/tailscale.js:289` | +| GET | `/api/v1/tailscale/check-connection` | public | GENERAL (1000/15m) | `routes/tailscale.js:107` | | POST | `/api/v1/tailscale/config` | public | GENERAL (1000/15m) | `routes/tailscale.js:80` | -| GET | `/api/v1/tailscale/devices` | public | GENERAL (1000/15m) | `routes/tailscale.js:113` | -| DELETE | `/api/v1/tailscale/oauth-config` | public | GENERAL (1000/15m) | `routes/tailscale.js:259` | -| POST | `/api/v1/tailscale/oauth-config` | public | GENERAL (1000/15m) | `routes/tailscale.js:201` | -| POST | `/api/v1/tailscale/protect-service` | public | GENERAL (1000/15m) | `routes/tailscale.js:147` | -| DELETE | `/api/v1/tailscale/settings` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:124` | -| GET | `/api/v1/tailscale/settings` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:60` | -| PUT | `/api/v1/tailscale/settings` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:76` | -| POST | `/api/v1/tailscale/settings/test` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:131` | +| GET | `/api/v1/tailscale/devices` | public | GENERAL (1000/15m) | `routes/tailscale.js:124` | +| DELETE | `/api/v1/tailscale/oauth-config` | public | GENERAL (1000/15m) | `routes/tailscale.js:274` | +| POST | `/api/v1/tailscale/oauth-config` | public | GENERAL (1000/15m) | `routes/tailscale.js:216` | +| POST | `/api/v1/tailscale/protect-service` | public | GENERAL (1000/15m) | `routes/tailscale.js:158` | +| DELETE | `/api/v1/tailscale/settings` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:239` | +| GET | `/api/v1/tailscale/settings` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:172` | +| PUT | `/api/v1/tailscale/settings` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:188` | +| POST | `/api/v1/tailscale/settings/test` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:246` | | GET | `/api/v1/tailscale/status` | public | GENERAL (1000/15m) | `routes/tailscale.js:36` | -| POST | `/api/v1/tailscale/sync` | public | GENERAL (1000/15m) | `routes/tailscale.js:287` | - -## Backups - -_19 routes_ - -| Method | Full path | Auth | Rate limit | Defined in | -|---|---|---|---|---| -| POST | `/api/v1/backups/backup/:appId` | protected | GENERAL (1000/15m) | `routes/backups.js:161` | -| POST | `/api/v1/backups/compare/:filename` | protected | GENERAL (1000/15m) | `routes/backups.js:373` | -| GET | `/api/v1/backups/config` | protected | GENERAL (1000/15m) | `routes/backups.js:480` | -| POST | `/api/v1/backups/config` | protected | GENERAL (1000/15m) | `routes/backups.js:486` | -| DELETE | `/api/v1/backups/credentials/:provider` | protected | GENERAL (1000/15m) | `routes/backups.js:631` | -| GET | `/api/v1/backups/credentials/:provider` | protected | GENERAL (1000/15m) | `routes/backups.js:558` | -| POST | `/api/v1/backups/credentials/:provider` | protected | GENERAL (1000/15m) | `routes/backups.js:590` | -| POST | `/api/v1/backups/execute` | protected | GENERAL (1000/15m) | `routes/backups.js:492` | -| GET | `/api/v1/backups/files` | protected | GENERAL (1000/15m) | `routes/backups.js:118` | -| GET | `/api/v1/backups/files/:appId` | protected | GENERAL (1000/15m) | `routes/backups.js:189` | -| GET | `/api/v1/backups/history` | protected | GENERAL (1000/15m) | `routes/backups.js:498` | -| POST | `/api/v1/backups/restore-file/:filename` | protected | GENERAL (1000/15m) | `routes/backups.js:237` | -| POST | `/api/v1/backups/restore/:backupId` | protected | GENERAL (1000/15m) | `routes/backups.js:538` | -| GET | `/api/v1/backups/schedule` | protected | GENERAL (1000/15m) | `routes/backups.js:29` | -| POST | `/api/v1/backups/schedule` | protected | GENERAL (1000/15m) | `routes/backups.js:59` | -| POST | `/api/v1/backups/schedule` | protected | GENERAL (1000/15m) | `routes/backups.js:511` | -| DELETE | `/api/v1/backups/schedule/:appId` | protected | GENERAL (1000/15m) | `routes/backups.js:102` | -| GET | `/api/v1/backups/storage-info` | protected | GENERAL (1000/15m) | `routes/backups.js:505` | -| POST | `/api/v1/backups/test-destination` | protected | GENERAL (1000/15m) | `routes/backups.js:546` | - -## DNS - -_19 routes_ - -| Method | Full path | Auth | Rate limit | Defined in | -|---|---|---|---|---| -| GET | `/api/v1/dns/check-update` | protected | GENERAL (1000/15m) | `routes/dns.js:669` | -| DELETE | `/api/v1/dns/credentials` | protected | GENERAL (1000/15m) | `routes/dns.js:597` | -| POST | `/api/v1/dns/credentials` | protected | GENERAL (1000/15m) | `routes/dns.js:490` | -| GET | `/api/v1/dns/logs` | protected | GENERAL (1000/15m) | `routes/dns.js:337` | -| GET | `/api/v1/dns/propagation` | protected | GENERAL (1000/15m) | `routes/dns.js:802` | -| GET | `/api/v1/dns/propagation/:domain` | protected | GENERAL (1000/15m) | `routes/dns.js:847` | -| POST | `/api/v1/dns/propagation/verify` | protected | GENERAL (1000/15m) | `routes/dns.js:815` | -| GET | `/api/v1/dns/provider/status` | protected | GENERAL (1000/15m) | `routes/dns.js:55` | -| GET | `/api/v1/dns/providers` | protected | GENERAL (1000/15m) | `routes/dns.js:48` | -| DELETE | `/api/v1/dns/record` | protected | GENERAL (1000/15m) | `routes/dns.js:176` | -| POST | `/api/v1/dns/record` | protected | GENERAL (1000/15m) | `routes/dns.js:225` | -| POST | `/api/v1/dns/refresh-token` | protected | GENERAL (1000/15m) | `routes/dns.js:655` | -| GET | `/api/v1/dns/resolve` | protected | GENERAL (1000/15m) | `routes/dns.js:292` | -| POST | `/api/v1/dns/restart/:dnsId` | protected | GENERAL (1000/15m) | `routes/dns.js:621` | -| GET | `/api/v1/dns/token-status` | protected | GENERAL (1000/15m) | `routes/dns.js:474` | -| DELETE | `/api/v1/dns/universal/record` | protected | GENERAL (1000/15m) | `routes/dns.js:119` | -| POST | `/api/v1/dns/universal/record` | protected | GENERAL (1000/15m) | `routes/dns.js:71` | -| GET | `/api/v1/dns/universal/resolve` | protected | GENERAL (1000/15m) | `routes/dns.js:145` | -| POST | `/api/v1/dns/update` | protected | GENERAL (1000/15m) | `routes/dns.js:732` | +| POST | `/api/v1/tailscale/sync` | public | GENERAL (1000/15m) | `routes/tailscale.js:302` | ## Monitoring @@ -195,6 +346,55 @@ _19 routes_ | GET | `/api/v1/stats/container/:id` | protected | GENERAL (1000/15m) | `routes/monitoring.js:240` | | GET | `/api/v1/stats/containers` | protected | GENERAL (1000/15m) | `routes/monitoring.js:182` | +## Backups + +_18 routes_ + +| Method | Full path | Auth | Rate limit | Defined in | +|---|---|---|---|---| +| POST | `/api/v1/backups/backup/:appId` | protected | GENERAL (1000/15m) | `routes/backups.js:165` | +| POST | `/api/v1/backups/compare/:filename` | protected | GENERAL (1000/15m) | `routes/backups.js:377` | +| GET | `/api/v1/backups/config` | protected | GENERAL (1000/15m) | `routes/backups.js:484` | +| POST | `/api/v1/backups/config` | protected | GENERAL (1000/15m) | `routes/backups.js:490` | +| DELETE | `/api/v1/backups/credentials/:provider` | protected | GENERAL (1000/15m) | `routes/backups.js:615` | +| GET | `/api/v1/backups/credentials/:provider` | protected | GENERAL (1000/15m) | `routes/backups.js:542` | +| POST | `/api/v1/backups/credentials/:provider` | protected | GENERAL (1000/15m) | `routes/backups.js:574` | +| POST | `/api/v1/backups/execute` | protected | GENERAL (1000/15m) | `routes/backups.js:503` | +| GET | `/api/v1/backups/files` | protected | GENERAL (1000/15m) | `routes/backups.js:122` | +| GET | `/api/v1/backups/files/:appId` | protected | GENERAL (1000/15m) | `routes/backups.js:193` | +| GET | `/api/v1/backups/history` | protected | GENERAL (1000/15m) | `routes/backups.js:509` | +| POST | `/api/v1/backups/restore-file/:filename` | protected | GENERAL (1000/15m) | `routes/backups.js:241` | +| POST | `/api/v1/backups/restore/:backupId` | protected | GENERAL (1000/15m) | `routes/backups.js:522` | +| GET | `/api/v1/backups/schedule` | protected | GENERAL (1000/15m) | `routes/backups.js:37` | +| POST | `/api/v1/backups/schedule` | protected | GENERAL (1000/15m) | `routes/backups.js:67` | +| DELETE | `/api/v1/backups/schedule/:appId` | protected | GENERAL (1000/15m) | `routes/backups.js:106` | +| GET | `/api/v1/backups/storage-info` | protected | GENERAL (1000/15m) | `routes/backups.js:516` | +| POST | `/api/v1/backups/test-destination` | protected | GENERAL (1000/15m) | `routes/backups.js:530` | + +## Logs + +_17 routes_ + +| Method | Full path | Auth | Rate limit | Defined in | +|---|---|---|---|---| +| DELETE | `/api/v1/error-logs` | protected | GENERAL (1000/15m) | `routes/errorlogs.js:216` | +| GET | `/api/v1/error-logs` | protected | GENERAL (1000/15m) | `routes/errorlogs.js:143` | +| GET | `/api/v1/error-logs/contexts` | protected | GENERAL (1000/15m) | `routes/errorlogs.js:125` | +| GET | `/api/v1/logs/container/:id` | protected | GENERAL (1000/15m) | `routes/logs.js:48` | +| GET | `/api/v1/logs/containers` | protected | GENERAL (1000/15m) | `routes/logs.js:32` | +| GET | `/api/v1/logs/digest/:date` | protected | GENERAL (1000/15m) | `routes/logs.js:197` | +| POST | `/api/v1/logs/digest/generate` | protected | GENERAL (1000/15m) | `routes/logs.js:185` | +| GET | `/api/v1/logs/digest/history` | protected | GENERAL (1000/15m) | `routes/logs.js:178` | +| GET | `/api/v1/logs/digest/latest` | protected | GENERAL (1000/15m) | `routes/logs.js:161` | +| GET | `/api/v1/logs/digest/live` | protected | GENERAL (1000/15m) | `routes/logs.js:171` | +| GET | `/api/v1/logs/docker-disk` | protected | GENERAL (1000/15m) | `routes/logs.js:216` | +| POST | `/api/v1/logs/docker-maintenance` | protected | GENERAL (1000/15m) | `routes/logs.js:224` | +| GET | `/api/v1/logs/file` | protected | GENERAL (1000/15m) | `routes/logs.js:320` | +| GET | `/api/v1/logs/journal` | protected | GENERAL (1000/15m) | `routes/logs.js:245` | +| GET | `/api/v1/logs/journal/stream` | protected | GENERAL (1000/15m) | `routes/logs.js:260` | +| GET | `/api/v1/logs/journal/units` | protected | GENERAL (1000/15m) | `routes/logs.js:236` | +| GET | `/api/v1/logs/stream/:id` | protected | GENERAL (1000/15m) | `routes/logs.js:102` | + ## Updates _16 routes_ @@ -218,73 +418,29 @@ _16 routes_ | POST | `/api/v1/updates/schedule/:containerId` | protected | GENERAL (1000/15m) | `routes/updates.js:71` | | POST | `/api/v1/updates/update/:containerId` | protected | GENERAL (1000/15m) | `routes/updates.js:37` | -## Authentication - -_15 routes_ - -| Method | Full path | Auth | Rate limit | Defined in | -|---|---|---|---|---| -| GET | `/api/v1/auth/app-token/:serviceId` | public | STRICT (20/15m) | `routes/auth/sso-gate.js:104` | -| GET | `/api/v1/auth/gate/:serviceId` | public | STRICT (20/15m) | `routes/auth/sso-gate.js:26` | -| POST | `/api/v1/auth/jwt` | protected | STRICT (20/15m) | `routes/auth/keys.js:103` | -| GET | `/api/v1/auth/keys` | protected | STRICT (20/15m) | `routes/auth/keys.js:36` | -| POST | `/api/v1/auth/keys` | protected | STRICT (20/15m) | `routes/auth/keys.js:47` | -| DELETE | `/api/v1/auth/keys/:keyId` | protected | STRICT (20/15m) | `routes/auth/keys.js:81` | -| GET | `/api/v1/auth/login-page` | public | GENERAL (1000/15m) | `routes/auth/sso-gate.js:206` | -| GET | `/api/v1/totp/check-session` | public | TOTP (10/15m) | `routes/auth/totp.js:228` | -| GET | `/api/v1/totp/config` | public | TOTP (10/15m) | `routes/auth/totp.js:30` | -| POST | `/api/v1/totp/config` | public | TOTP (10/15m) | `routes/auth/totp.js:286` | -| POST | `/api/v1/totp/disable` | protected | TOTP (10/15m) | `routes/auth/totp.js:253` | -| GET | `/api/v1/totp/recovery-info` | public | TOTP (10/15m) | `routes/auth/totp.js:56` | -| POST | `/api/v1/totp/setup` | public | TOTP (10/15m) | `routes/auth/totp.js:116` | -| POST | `/api/v1/totp/verify` | public | TOTP (10/15m) | `routes/auth/totp.js:194` | -| POST | `/api/v1/totp/verify-setup` | public | TOTP (10/15m) | `routes/auth/totp.js:157` | - -## Logs - -_15 routes_ - -| Method | Full path | Auth | Rate limit | Defined in | -|---|---|---|---|---| -| DELETE | `/api/v1/audit-logs` | protected | GENERAL (1000/15m) | `routes/errorlogs.js:71` | -| GET | `/api/v1/audit-logs` | protected | GENERAL (1000/15m) | `routes/errorlogs.js:55` | -| DELETE | `/api/v1/error-logs` | protected | GENERAL (1000/15m) | `routes/errorlogs.js:47` | -| GET | `/api/v1/error-logs` | protected | GENERAL (1000/15m) | `routes/errorlogs.js:20` | -| GET | `/api/v1/logs/container/:id` | protected | GENERAL (1000/15m) | `routes/logs.js:39` | -| GET | `/api/v1/logs/containers` | protected | GENERAL (1000/15m) | `routes/logs.js:23` | -| GET | `/api/v1/logs/digest/:date` | protected | GENERAL (1000/15m) | `routes/logs.js:184` | -| POST | `/api/v1/logs/digest/generate` | protected | GENERAL (1000/15m) | `routes/logs.js:176` | -| GET | `/api/v1/logs/digest/history` | protected | GENERAL (1000/15m) | `routes/logs.js:169` | -| GET | `/api/v1/logs/digest/latest` | protected | GENERAL (1000/15m) | `routes/logs.js:152` | -| GET | `/api/v1/logs/digest/live` | protected | GENERAL (1000/15m) | `routes/logs.js:162` | -| GET | `/api/v1/logs/docker-disk` | protected | GENERAL (1000/15m) | `routes/logs.js:203` | -| POST | `/api/v1/logs/docker-maintenance` | protected | GENERAL (1000/15m) | `routes/logs.js:211` | -| GET | `/api/v1/logs/file` | protected | GENERAL (1000/15m) | `routes/logs.js:218` | -| GET | `/api/v1/logs/stream/:id` | protected | GENERAL (1000/15m) | `routes/logs.js:93` | - ## Configuration _13 routes_ | Method | Full path | Auth | Rate limit | Defined in | |---|---|---|---|---| -| POST | `/api/v1/assets/upload` | protected | GENERAL (1000/15m) | `routes/config/assets.js:33` | +| POST | `/api/v1/assets/upload` | protected | GENERAL (1000/15m) | `routes/config/assets.js:58` | | GET | `/api/v1/backup/export` | protected | GENERAL (1000/15m) | `routes/config/backup.js:51` | | POST | `/api/v1/backup/preview` | protected | GENERAL (1000/15m) | `routes/config/backup.js:153` | | POST | `/api/v1/backup/restore` | protected | GENERAL (1000/15m) | `routes/config/backup.js:218` | -| DELETE | `/api/v1/config` | public | GENERAL (1000/15m) | `routes/config/settings.js:78` | +| DELETE | `/api/v1/config` | public | GENERAL (1000/15m) | `routes/config/settings.js:86` | | GET | `/api/v1/config` | public | GENERAL (1000/15m) | `routes/config/settings.js:26` | -| POST | `/api/v1/config` | public | GENERAL (1000/15m) | `routes/config/settings.js:35` | -| DELETE | `/api/v1/favicon` | public | GENERAL (1000/15m) | `routes/config/assets.js:272` | -| GET | `/api/v1/favicon` | public | GENERAL (1000/15m) | `routes/config/assets.js:203` | -| POST | `/api/v1/favicon` | public | GENERAL (1000/15m) | `routes/config/assets.js:212` | -| DELETE | `/api/v1/logo` | public | GENERAL (1000/15m) | `routes/config/assets.js:170` | -| GET | `/api/v1/logo` | public | GENERAL (1000/15m) | `routes/config/assets.js:77` | -| POST | `/api/v1/logo` | public | GENERAL (1000/15m) | `routes/config/assets.js:112` | +| POST | `/api/v1/config` | public | GENERAL (1000/15m) | `routes/config/settings.js:43` | +| DELETE | `/api/v1/favicon` | public | GENERAL (1000/15m) | `routes/config/assets.js:280` | +| GET | `/api/v1/favicon` | public | GENERAL (1000/15m) | `routes/config/assets.js:216` | +| POST | `/api/v1/favicon` | public | GENERAL (1000/15m) | `routes/config/assets.js:225` | +| DELETE | `/api/v1/logo` | public | GENERAL (1000/15m) | `routes/config/assets.js:183` | +| GET | `/api/v1/logo` | public | GENERAL (1000/15m) | `routes/config/assets.js:94` | +| POST | `/api/v1/logo` | public | GENERAL (1000/15m) | `routes/config/assets.js:129` | ## Health -_12 routes_ +_13 routes_ | Method | Full path | Auth | Rate limit | Defined in | |---|---|---|---|---| @@ -300,6 +456,7 @@ _12 routes_ | GET | `/api/v1/health/pylon` | protected | GENERAL (1000/15m) | `routes/health.js:245` | | GET | `/api/v1/health/service/:id` | protected | GENERAL (1000/15m) | `routes/health.js:188` | | GET | `/api/v1/health/services` | protected | GENERAL (1000/15m) | `routes/health.js:109` | +| GET | `/api/v1/system/health` | public | GENERAL (1000/15m) | `routes/health.js:383` | ## Services @@ -307,35 +464,36 @@ _12 routes_ | Method | Full path | Auth | Rate limit | Defined in | |---|---|---|---|---| -| DELETE | `/api/v1/seedhost-creds` | protected | GENERAL (1000/15m) | `routes/services.js:310` | -| GET | `/api/v1/seedhost-creds` | protected | GENERAL (1000/15m) | `routes/services.js:289` | -| POST | `/api/v1/seedhost-creds` | protected | GENERAL (1000/15m) | `routes/services.js:272` | -| GET | `/api/v1/services` | public | GENERAL (1000/15m) | `routes/services.js:374` | -| POST | `/api/v1/services` | public | GENERAL (1000/15m) | `routes/services.js:389` | -| PUT | `/api/v1/services` | public | GENERAL (1000/15m) | `routes/services.js:434` | -| DELETE | `/api/v1/services/:id` | protected | GENERAL (1000/15m) | `routes/services.js:462` | -| DELETE | `/api/v1/services/:serviceId/credentials` | protected | GENERAL (1000/15m) | `routes/services.js:237` | -| GET | `/api/v1/services/:serviceId/credentials` | protected | GENERAL (1000/15m) | `routes/services.js:252` | -| POST | `/api/v1/services/:serviceId/credentials` | protected | GENERAL (1000/15m) | `routes/services.js:213` | -| GET | `/api/v1/services/status` | public | GENERAL (1000/15m) | `routes/services.js:327` | -| POST | `/api/v1/services/update` | protected | GENERAL (1000/15m) | `routes/services.js:486` | +| DELETE | `/api/v1/seedhost-creds` | protected | GENERAL (1000/15m) | `routes/services.js:315` | +| GET | `/api/v1/seedhost-creds` | protected | GENERAL (1000/15m) | `routes/services.js:294` | +| POST | `/api/v1/seedhost-creds` | protected | GENERAL (1000/15m) | `routes/services.js:277` | +| GET | `/api/v1/services` | public | GENERAL (1000/15m) | `routes/services.js:379` | +| POST | `/api/v1/services` | public | GENERAL (1000/15m) | `routes/services.js:394` | +| PUT | `/api/v1/services` | public | GENERAL (1000/15m) | `routes/services.js:441` | +| DELETE | `/api/v1/services/:id` | protected | GENERAL (1000/15m) | `routes/services.js:470` | +| DELETE | `/api/v1/services/:serviceId/credentials` | protected | GENERAL (1000/15m) | `routes/services.js:241` | +| GET | `/api/v1/services/:serviceId/credentials` | protected | GENERAL (1000/15m) | `routes/services.js:256` | +| POST | `/api/v1/services/:serviceId/credentials` | protected | GENERAL (1000/15m) | `routes/services.js:217` | +| GET | `/api/v1/services/status` | public | GENERAL (1000/15m) | `routes/services.js:332` | +| POST | `/api/v1/services/update` | protected | GENERAL (1000/15m) | `routes/services.js:495` | ## Containers (lifecycle) -_10 routes_ +_11 routes_ | Method | Full path | Auth | Rate limit | Defined in | |---|---|---|---|---| -| DELETE | `/api/v1/containers/:id` | protected | GENERAL (1000/15m) | `routes/containers.js:235` | -| GET | `/api/v1/containers/:id/check-update` | protected | GENERAL (1000/15m) | `routes/containers.js:155` | -| GET | `/api/v1/containers/:id/logs` | protected | GENERAL (1000/15m) | `routes/containers.js:193` | -| GET | `/api/v1/containers/:id/resources` | protected | GENERAL (1000/15m) | `routes/containers.js:223` | -| PUT | `/api/v1/containers/:id/resources` | protected | GENERAL (1000/15m) | `routes/containers.js:205` | -| POST | `/api/v1/containers/:id/restart` | protected | GENERAL (1000/15m) | `routes/containers.js:48` | -| POST | `/api/v1/containers/:id/start` | protected | GENERAL (1000/15m) | `routes/containers.js:34` | -| POST | `/api/v1/containers/:id/stop` | protected | GENERAL (1000/15m) | `routes/containers.js:41` | -| POST | `/api/v1/containers/:id/update` | protected | GENERAL (1000/15m) | `routes/containers.js:55` | -| GET | `/api/v1/containers/discover` | protected | GENERAL (1000/15m) | `routes/containers.js:242` | +| GET | `/api/v1/containers/` | protected | GENERAL (1000/15m) | `routes/containers.js:76` | +| DELETE | `/api/v1/containers/:id` | protected | GENERAL (1000/15m) | `routes/containers.js:295` | +| GET | `/api/v1/containers/:id/check-update` | protected | GENERAL (1000/15m) | `routes/containers.js:211` | +| GET | `/api/v1/containers/:id/logs` | protected | GENERAL (1000/15m) | `routes/containers.js:249` | +| GET | `/api/v1/containers/:id/resources` | protected | GENERAL (1000/15m) | `routes/containers.js:283` | +| PUT | `/api/v1/containers/:id/resources` | protected | GENERAL (1000/15m) | `routes/containers.js:261` | +| POST | `/api/v1/containers/:id/restart` | protected | GENERAL (1000/15m) | `routes/containers.js:104` | +| POST | `/api/v1/containers/:id/start` | protected | GENERAL (1000/15m) | `routes/containers.js:90` | +| POST | `/api/v1/containers/:id/stop` | protected | GENERAL (1000/15m) | `routes/containers.js:97` | +| POST | `/api/v1/containers/:id/update` | protected | GENERAL (1000/15m) | `routes/containers.js:111` | +| GET | `/api/v1/containers/discover` | protected | GENERAL (1000/15m) | `routes/containers.js:302` | ## Core / system @@ -343,15 +501,15 @@ _9 routes_ | Method | Full path | Auth | Rate limit | Defined in | |---|---|---|---|---| -| GET | `/api/v1/docs` | protected | GENERAL (1000/15m) | `src/app.js:925` | -| GET | `/api/v1/docs/spec` | protected | GENERAL (1000/15m) | `src/app.js:943` | -| GET | `/api/v1/network/ips` | protected | GENERAL (1000/15m) | `src/app.js:899` | -| GET | `/health` | public | GENERAL (1000/15m) | `src/app.js:777` | -| GET | `/health/live` | public | GENERAL (1000/15m) | `src/app.js:778` | -| GET | `/health/ready` | public | GENERAL (1000/15m) | `src/app.js:782` | -| GET | `/healthz` | public | GENERAL (1000/15m) | `src/app.js:779` | -| GET | `/probe/:id` | public | GENERAL (1000/15m) | `src/app.js:786` | -| GET | `/readyz` | public | GENERAL (1000/15m) | `src/app.js:783` | +| GET | `/api/v1/docs` | protected | GENERAL (1000/15m) | `src/app.js:1358` | +| GET | `/api/v1/docs/spec` | protected | GENERAL (1000/15m) | `src/app.js:1376` | +| GET | `/api/v1/network/ips` | protected | GENERAL (1000/15m) | `src/app.js:1332` | +| GET | `/health` | public | GENERAL (1000/15m) | `src/app.js:1210` | +| GET | `/health/live` | public | GENERAL (1000/15m) | `src/app.js:1211` | +| GET | `/health/ready` | public | GENERAL (1000/15m) | `src/app.js:1215` | +| GET | `/healthz` | public | GENERAL (1000/15m) | `src/app.js:1212` | +| GET | `/probe/:id` | public | GENERAL (1000/15m) | `src/app.js:1219` | +| GET | `/readyz` | public | GENERAL (1000/15m) | `src/app.js:1216` | ## Dependencies @@ -359,14 +517,14 @@ _8 routes_ | Method | Full path | Auth | Rate limit | Defined in | |---|---|---|---|---| -| DELETE | `/api/v1/dependencies/:serviceId` | protected | GENERAL (1000/15m) | `routes/dependencies.js:166` | -| GET | `/api/v1/dependencies/:serviceId` | protected | GENERAL (1000/15m) | `routes/dependencies.js:80` | -| POST | `/api/v1/dependencies/:serviceId` | protected | GENERAL (1000/15m) | `routes/dependencies.js:123` | -| GET | `/api/v1/dependencies/:serviceId/chain` | protected | GENERAL (1000/15m) | `routes/dependencies.js:105` | -| POST | `/api/v1/dependencies/:serviceId/restart` | protected | GENERAL (1000/15m) | `routes/dependencies.js:198` | -| GET | `/api/v1/dependencies/:serviceId/status` | protected | GENERAL (1000/15m) | `routes/dependencies.js:114` | -| GET | `/api/v1/dependencies/graph` | protected | GENERAL (1000/15m) | `routes/dependencies.js:48` | -| GET | `/api/v1/dependencies/validate` | protected | GENERAL (1000/15m) | `routes/dependencies.js:56` | +| DELETE | `/api/v1/dependencies/:serviceId` | protected | GENERAL (1000/15m) | `routes/dependencies.js:199` | +| GET | `/api/v1/dependencies/:serviceId` | protected | GENERAL (1000/15m) | `routes/dependencies.js:108` | +| POST | `/api/v1/dependencies/:serviceId` | protected | GENERAL (1000/15m) | `routes/dependencies.js:151` | +| GET | `/api/v1/dependencies/:serviceId/chain` | protected | GENERAL (1000/15m) | `routes/dependencies.js:133` | +| POST | `/api/v1/dependencies/:serviceId/restart` | protected | GENERAL (1000/15m) | `routes/dependencies.js:233` | +| GET | `/api/v1/dependencies/:serviceId/status` | protected | GENERAL (1000/15m) | `routes/dependencies.js:142` | +| GET | `/api/v1/dependencies/graph` | protected | GENERAL (1000/15m) | `routes/dependencies.js:76` | +| GET | `/api/v1/dependencies/validate` | protected | GENERAL (1000/15m) | `routes/dependencies.js:84` | ## Notifications @@ -375,13 +533,13 @@ _8 routes_ | Method | Full path | Auth | Rate limit | Defined in | |---|---|---|---|---| | GET | `/api/v1/notifications/config` | protected | GENERAL (1000/15m) | `routes/notifications.js:20` | -| POST | `/api/v1/notifications/config` | protected | GENERAL (1000/15m) | `routes/notifications.js:53` | -| POST | `/api/v1/notifications/health-check` | protected | GENERAL (1000/15m) | `routes/notifications.js:214` | -| DELETE | `/api/v1/notifications/history` | protected | GENERAL (1000/15m) | `routes/notifications.js:208` | -| GET | `/api/v1/notifications/history` | protected | GENERAL (1000/15m) | `routes/notifications.js:192` | -| POST | `/api/v1/notifications/send` | protected | GENERAL (1000/15m) | `routes/notifications.js:246` | -| GET | `/api/v1/notifications/status` | protected | GENERAL (1000/15m) | `routes/notifications.js:224` | -| POST | `/api/v1/notifications/test` | protected | GENERAL (1000/15m) | `routes/notifications.js:159` | +| POST | `/api/v1/notifications/config` | protected | GENERAL (1000/15m) | `routes/notifications.js:61` | +| POST | `/api/v1/notifications/health-check` | protected | GENERAL (1000/15m) | `routes/notifications.js:300` | +| DELETE | `/api/v1/notifications/history` | protected | GENERAL (1000/15m) | `routes/notifications.js:294` | +| GET | `/api/v1/notifications/history` | protected | GENERAL (1000/15m) | `routes/notifications.js:278` | +| POST | `/api/v1/notifications/send` | protected | GENERAL (1000/15m) | `routes/notifications.js:332` | +| GET | `/api/v1/notifications/status` | protected | GENERAL (1000/15m) | `routes/notifications.js:310` | +| POST | `/api/v1/notifications/test` | protected | GENERAL (1000/15m) | `routes/notifications.js:245` | ## App recipes @@ -389,14 +547,14 @@ _8 routes_ | Method | Full path | Auth | Rate limit | Defined in | |---|---|---|---|---| -| DELETE | `/api/v1/:recipeId` | protected | GENERAL (1000/15m) | `routes/recipes/manage.js:197` | -| POST | `/api/v1/:recipeId/restart` | protected | GENERAL (1000/15m) | `routes/recipes/manage.js:171` | -| POST | `/api/v1/:recipeId/start` | protected | GENERAL (1000/15m) | `routes/recipes/manage.js:108` | -| POST | `/api/v1/:recipeId/stop` | protected | GENERAL (1000/15m) | `routes/recipes/manage.js:139` | -| POST | `/api/v1/deploy` | protected | GENERAL (1000/15m) | `routes/recipes/deploy.js:29` | -| GET | `/api/v1/deployed` | protected | GENERAL (1000/15m) | `routes/recipes/manage.js:24` | -| GET | `/api/v1/templates` | protected | GENERAL (1000/15m) | `routes/recipes/index.js:34` | -| GET | `/api/v1/templates/:recipeId` | protected | GENERAL (1000/15m) | `routes/recipes/index.js:63` | +| DELETE | `/api/v1/recipes/:recipeId` | protected | GENERAL (1000/15m) | `routes/recipes/manage.js:215` | +| POST | `/api/v1/recipes/:recipeId/restart` | protected | GENERAL (1000/15m) | `routes/recipes/manage.js:188` | +| POST | `/api/v1/recipes/:recipeId/start` | protected | GENERAL (1000/15m) | `routes/recipes/manage.js:123` | +| POST | `/api/v1/recipes/:recipeId/stop` | protected | GENERAL (1000/15m) | `routes/recipes/manage.js:155` | +| POST | `/api/v1/recipes/deploy` | protected | GENERAL (1000/15m) | `routes/recipes/deploy.js:29` | +| GET | `/api/v1/recipes/deployed` | protected | GENERAL (1000/15m) | `routes/recipes/manage.js:39` | +| GET | `/api/v1/recipes/templates` | protected | GENERAL (1000/15m) | `routes/recipes/index.js:34` | +| GET | `/api/v1/recipes/templates/:recipeId` | protected | GENERAL (1000/15m) | `routes/recipes/index.js:63` | ## Docker resources @@ -418,13 +576,26 @@ _7 routes_ | Method | Full path | Auth | Rate limit | Defined in | |---|---|---|---|---| -| GET | `/api/v1/caddy/cas` | protected | GENERAL (1000/15m) | `routes/sites.js:57` | -| GET | `/api/v1/caddy/config` | protected | GENERAL (1000/15m) | `routes/sites.js:31` | -| POST | `/api/v1/caddy/reload` | protected | GENERAL (1000/15m) | `routes/sites.js:38` | -| GET | `/api/v1/caddyfile` | protected | GENERAL (1000/15m) | `routes/sites.js:25` | -| POST | `/api/v1/site` | protected | GENERAL (1000/15m) | `routes/sites.js:160` | -| DELETE | `/api/v1/site/:domain` | protected | GENERAL (1000/15m) | `routes/sites.js:135` | -| POST | `/api/v1/site/external` | protected | GENERAL (1000/15m) | `routes/sites.js:188` | +| GET | `/api/v1/caddy/cas` | protected | GENERAL (1000/15m) | `routes/sites.js:60` | +| GET | `/api/v1/caddy/config` | protected | GENERAL (1000/15m) | `routes/sites.js:34` | +| POST | `/api/v1/caddy/reload` | protected | GENERAL (1000/15m) | `routes/sites.js:41` | +| GET | `/api/v1/caddyfile` | protected | GENERAL (1000/15m) | `routes/sites.js:28` | +| POST | `/api/v1/site` | protected | GENERAL (1000/15m) | `routes/sites.js:167` | +| DELETE | `/api/v1/site/:domain` | protected | GENERAL (1000/15m) | `routes/sites.js:138` | +| POST | `/api/v1/site/external` | protected | GENERAL (1000/15m) | `routes/sites.js:212` | + +## Certificate authority + +_6 routes_ + +| Method | Full path | Auth | Rate limit | Defined in | +|---|---|---|---|---| +| POST | `/api/v1/ca/backup` | protected | GENERAL (1000/15m) | `routes/ca.js:402` | +| GET | `/api/v1/ca/cert/:domain` | protected | GENERAL (1000/15m) | `routes/ca.js:176` | +| GET | `/api/v1/ca/certs` | protected | GENERAL (1000/15m) | `routes/ca.js:354` | +| GET | `/api/v1/ca/info` | public | GENERAL (1000/15m) | `routes/ca.js:15` | +| GET | `/api/v1/ca/install-script` | public | GENERAL (1000/15m) | `routes/ca.js:63` | +| GET | `/api/v1/ca/root.crt` | public | GENERAL (1000/15m) | `routes/ca.js:45` | ## Updates / workflows @@ -432,12 +603,12 @@ _6 routes_ | Method | Full path | Auth | Rate limit | Defined in | |---|---|---|---|---| -| GET | `/api/v1/workflows/workflows` | protected | GENERAL (1000/15m) | `routes/workflows.js:22` | -| POST | `/api/v1/workflows/workflows/:workflowId/disable` | protected | GENERAL (1000/15m) | `routes/workflows.js:35` | -| POST | `/api/v1/workflows/workflows/:workflowId/enable` | protected | GENERAL (1000/15m) | `routes/workflows.js:28` | -| GET | `/api/v1/workflows/workflows/:workflowId/history` | protected | GENERAL (1000/15m) | `routes/workflows.js:52` | -| POST | `/api/v1/workflows/workflows/:workflowId/run` | protected | GENERAL (1000/15m) | `routes/workflows.js:42` | -| GET | `/api/v1/workflows/workflows/history` | protected | GENERAL (1000/15m) | `routes/workflows.js:60` | +| GET | `/api/v1/workflows/` | protected | GENERAL (1000/15m) | `routes/workflows.js:44` | +| POST | `/api/v1/workflows/:workflowId/disable` | protected | GENERAL (1000/15m) | `routes/workflows.js:58` | +| POST | `/api/v1/workflows/:workflowId/enable` | protected | GENERAL (1000/15m) | `routes/workflows.js:50` | +| GET | `/api/v1/workflows/:workflowId/history` | protected | GENERAL (1000/15m) | `routes/workflows.js:86` | +| POST | `/api/v1/workflows/:workflowId/run` | protected | GENERAL (1000/15m) | `routes/workflows.js:66` | +| GET | `/api/v1/workflows/history` | protected | GENERAL (1000/15m) | `routes/workflows.js:79` | ## Auto-restart @@ -451,29 +622,17 @@ _5 routes_ | POST | `/api/v1/policies/:serviceId` | protected | GENERAL (1000/15m) | `routes/auto-restart.js:60` | | POST | `/api/v1/policies/:serviceId/test` | protected | GENERAL (1000/15m) | `routes/auto-restart.js:123` | -## Certificate authority - -_5 routes_ - -| Method | Full path | Auth | Rate limit | Defined in | -|---|---|---|---|---| -| GET | `/api/v1/ca/cert/:domain` | public | GENERAL (1000/15m) | `routes/ca.js:127` | -| GET | `/api/v1/ca/certs` | public | GENERAL (1000/15m) | `routes/ca.js:242` | -| GET | `/api/v1/ca/info` | public | GENERAL (1000/15m) | `routes/ca.js:15` | -| GET | `/api/v1/ca/install-script` | public | GENERAL (1000/15m) | `routes/ca.js:63` | -| GET | `/api/v1/ca/root.crt` | public | GENERAL (1000/15m) | `routes/ca.js:45` | - ## OpenClaw integration _5 routes_ | Method | Full path | Auth | Rate limit | Defined in | |---|---|---|---|---| -| DELETE | `/api/v1/openclaw/` | protected | GENERAL (1000/15m) | `routes/openclaw.js:244` | -| POST | `/api/v1/openclaw/deploy` | protected | GENERAL (1000/15m) | `routes/openclaw.js:150` | -| GET | `/api/v1/openclaw/proxy/*` | protected | GENERAL (1000/15m) | `routes/openclaw.js:216` | -| POST | `/api/v1/openclaw/proxy/*` | protected | GENERAL (1000/15m) | `routes/openclaw.js:230` | -| GET | `/api/v1/openclaw/status` | protected | GENERAL (1000/15m) | `routes/openclaw.js:116` | +| DELETE | `/api/v1/openclaw/` | protected | GENERAL (1000/15m) | `routes/openclaw.js:467` | +| POST | `/api/v1/openclaw/deploy` | protected | GENERAL (1000/15m) | `routes/openclaw.js:373` | +| GET | `/api/v1/openclaw/proxy/*` | protected | GENERAL (1000/15m) | `routes/openclaw.js:439` | +| POST | `/api/v1/openclaw/proxy/*` | protected | GENERAL (1000/15m) | `routes/openclaw.js:453` | +| GET | `/api/v1/openclaw/status` | protected | GENERAL (1000/15m) | `routes/openclaw.js:339` | ## Config drift @@ -492,10 +651,10 @@ _4 routes_ | Method | Full path | Auth | Rate limit | Defined in | |---|---|---|---|---| -| POST | `/api/v1/license/activate` | protected | GENERAL (1000/15m) | `routes/license.js:16` | -| POST | `/api/v1/license/deactivate` | protected | GENERAL (1000/15m) | `routes/license.js:41` | -| GET | `/api/v1/license/feature/:feature` | public | GENERAL (1000/15m) | `routes/license.js:52` | -| GET | `/api/v1/license/status` | public | GENERAL (1000/15m) | `routes/license.js:35` | +| POST | `/api/v1/license/activate` | protected | GENERAL (1000/15m) | `routes/license.js:29` | +| POST | `/api/v1/license/deactivate` | protected | GENERAL (1000/15m) | `routes/license.js:55` | +| GET | `/api/v1/license/feature/:feature` | public | GENERAL (1000/15m) | `routes/license.js:66` | +| GET | `/api/v1/license/status` | public | GENERAL (1000/15m) | `routes/license.js:48` | ## File browser @@ -535,38 +694,50 @@ _2 routes_ | GET | `/api/v1/events/clients` | protected | GENERAL (1000/15m) | `routes/events.js:154` | | GET | `/api/v1/events/stream` | protected | GENERAL (1000/15m) | `routes/events.js:126` | -## Internal helpers - -_1 routes_ - -| Method | Full path | Auth | Rate limit | Defined in | -|---|---|---|---|---| -| GET | `/api/v1/status` | protected | GENERAL (1000/15m) | `routes/context.js:8` | - ## Mount Point Map -How `src/app.js` wires route files to URL prefixes (via `apiRouter.use`): - | Route file(s) | Mounted at | |---|---| +| `routes/billing` | `/api/v1/billing` | | `routes/ca` | `/api/v1/ca` | +| `routes/calendar` | `/api/v1/calendar` | | `routes/containers` | `/api/v1/containers` | | `routes/dependencies` | `/api/v1/dependencies` | +| `routes/deploys` | `/api/v1/deploys` | +| `routes/disk-space` | `/api/v1/disk` | | `routes/dns` | `/api/v1/dns` | | `routes/docker-resources` | `/api/v1/docker` | | `routes/events` | `/api/v1/events` | | `routes/license` | `/api/v1/license` | +| `routes/mcp` | `/api/v1/mcp` | +| `routes/network-lifecycle` | `/api/v1/network` | +| `routes/network-access` | `/api/v1/network/access` | +| `routes/network-groups` | `/api/v1/network/groups` | +| `routes/network-invites` | `/api/v1/network/invites` | +| `routes/network-people` | `/api/v1/network/people` | +| `routes/network-resources` | `/api/v1/network/resources` | +| `routes/network-wizard` | `/api/v1/network/wizard` | | `routes/notifications` | `/api/v1/notifications` | | `routes/openclaw` | `/api/v1/openclaw` | | `routes/recipes/` | `/api/v1/recipes` | +| `routes/security` | `/api/v1/security` | | `routes/tailscale` | `/api/v1/tailscale` | | `routes/tailscale-admin` | `/api/v1/tailscale` | | `routes/workflows` | `/api/v1/workflows` | | `routes/auth/` | `/api/v1 (root)` | | `routes/config/` | `/api/v1 (root)` | +| `routes/share` | `/api/v1 (root)` | | `routes/services` | `/api/v1 (root)` | | `routes/health` | `/api/v1 (root)` | | `routes/monitoring` | `/api/v1 (root)` | +| `routes/i18n` | `/api/v1 (root)` | +| `routes/discover` | `/api/v1 (root)` | +| `routes/discover-adopt` | `/api/v1 (root)` | +| `routes/catalog` | `/api/v1 (root)` | +| `routes/wizard` | `/api/v1 (root)` | +| `routes/disaster-recovery` | `/api/v1 (root)` | +| `routes/caddycode` | `/api/v1 (root)` | +| `routes/fleet` | `/api/v1 (root)` | | `routes/updates` | `/api/v1 (root)` | | `routes/sites` | `/api/v1 (root)` | | `routes/credentials` | `/api/v1 (root)` | @@ -577,130 +748,91 @@ How `src/app.js` wires route files to URL prefixes (via `apiRouter.use`): | `routes/browse` | `/api/v1 (root)` | | `routes/errorlogs` | `/api/v1 (root)` | | `routes/themes` | `/api/v1 (root)` | +| `routes/ai-intent` | `/api/v1 (root)` | +| `routes/log-insights` | `/api/v1 (root)` | +| `routes/audit-log` | `/api/v1 (root)` | | `routes/auto-restart` | `/api/v1 (root)` | | `routes/config-drift` | `/api/v1 (root)` | | `routes/ssl-monitor` | `/api/v1 (root)` | - -## PUBLIC_ROUTES Allowlist - -Source: `src/utilities/middleware.js:310-364` (42 entries) - -| Method | Path | Match | -|---|---|---| -| ANY | `/health` | exact | -| ANY | `/health/live` | exact | -| ANY | `/health/ready` | exact | -| ANY | `/healthz` | exact | -| ANY | `/readyz` | exact | -| ANY | `/probe/` | prefix | -| ANY | `/api/v1/tailscale/` | prefix | -| ANY | `/api/v1/totp/config` | exact | -| ANY | `/api/v1/totp/recovery-info` | exact | -| ANY | `/api/v1/totp/verify` | exact | -| ANY | `/api/v1/totp/setup` | exact | -| ANY | `/api/v1/totp/verify-setup` | exact | -| ANY | `/api/v1/totp/check-session` | exact | -| ANY | `/api/v1/auth/gate/` | prefix | -| ANY | `/api/v1/auth/app-token/` | prefix | -| ANY | `/api/v1/auth/login-page` | exact | -| ANY | `/api/v1/services` | exact | -| ANY | `/api/v1/ca/info` | exact | -| ANY | `/api/v1/ca/root.crt` | exact | -| ANY | `/api/v1/ca/install-script` | exact | -| ANY | `/api/v1/health/ca` | exact | -| GET | `/api/v1/ca/cert/` | prefix | -| ANY | `/api/v1/ca/certs` | exact | -| ANY | `/api/v1/csrf-token` | exact | -| ANY | `/api/v1/logo` | exact | -| ANY | `/api/v1/favicon` | exact | -| ANY | `/api/v1/themes` | exact | -| ANY | `/api/v1/license/status` | exact | -| GET | `/api/v1/license/feature/` | prefix | -| ANY | `/api/v1/config` | exact | -| ANY | `/api/v1/services/status` | exact | -| ANY | `/api/v1/health-checks/status` | exact | -| ANY | `/api/v1/monitoring/stats` | exact | -| ANY | `/api/v1/system/version` | exact | -| ANY | `/api/v1/system/update-status` | exact | -| ANY | `/api/v1/system/update-history` | exact | -| ANY | `/api/v1/system/update-check` | exact | -| ANY | `/api/v1/updates/available` | exact | -| ANY | `/api/v1/system/update-notify` | exact | -| ANY | `/api/v1/monitoring/stats` | exact | -| ANY | `/api/v1/health-checks/status` | exact | -| ANY | `/api/v1/version` | exact | - -## Root-Level Endpoints (defined directly in src/app.js) - -| Method | Path | Auth | Purpose | -|---|---|---|---| -| GET | `/health` | public | Liveness (alias for `/health/live`) | -| GET | `/health/live` | public | Process-only check, no I/O | -| GET | `/health/ready` | public | Checks config + services + Docker + Caddy-admin (3s timeout each) | -| GET | `/healthz` | public | k8s alias for `/health/live` | -| GET | `/readyz` | public | k8s alias for `/health/ready` | -| GET | `/probe/:id` | public | Per-service health probe, sets `X-DashCaddy-HealthCheck: 1` | -| GET | `/api/v1/network/ips` | protected | Detected network interfaces + IPs (cached) | -| GET | `/api/v1/docs` | protected | Interactive Swagger UI | -| GET | `/api/v1/docs/spec` | protected | Raw OpenAPI 3.0.3 spec | -| GET | `/api/v1/version` | public (per PUBLIC_ROUTES) | API version | +| `routes/caddy-upstreams` | `/api/v1 (root)` | ## OpenAPI Spec Cross-Check -- Routes defined in code: **236** -- Paths in `openapi.yaml`: **112** +- Unique paths in code: **367** +- Unique (method, path) operations in code: **440** +- Handler rows exceed unique operations when the same (method, path) is registered more than once (e.g. GET /api/v1/auth/me, GET+POST /api/v1/config appear in more than one route file). +- Paths in openapi.yaml: **290** +- Note: openapi.yaml lags the code (spec documents the 2026-07 surface); code-derived rows above reflect the declared router (static extraction). -### In code but NOT documented in OpenAPI (142) +### In code but NOT documented (100) -- `/api/v1/:appId` -- `/api/v1/:appId/backup-points` -- `/api/v1/:appId/restore` -- `/api/v1/:appId/revert/:filename` -- `/api/v1/:recipeId` -- `/api/v1/:recipeId/restart` -- `/api/v1/:recipeId/start` -- `/api/v1/:recipeId/stop` -- `/api/v1/arr/quality-profiles` -- `/api/v1/audit-logs` -- `/api/v1/auth/jwt` -- `/api/v1/auth/keys` -- `/api/v1/auth/keys/:keyId` -- `/api/v1/auth/login-page` -- `/api/v1/backups/backup/:appId` -- `/api/v1/backups/compare/:filename` -- `/api/v1/backups/credentials/:provider` -- `/api/v1/backups/files` -- `/api/v1/backups/files/:appId` -- `/api/v1/backups/restore-file/:filename` -- `/api/v1/backups/schedule` -- `/api/v1/backups/schedule/:appId` -- `/api/v1/backups/storage-info` -- `/api/v1/backups/test-destination` -- `/api/v1/browse/directories` -- `/api/v1/ca/cert/:domain` -- `/api/v1/ca/certs` -- `/api/v1/ca/info` -- `/api/v1/ca/install-script` -- `/api/v1/ca/root.crt` -- ... and 112 more +- `/api/v1/audit-logs/actions` +- `/api/v1/auth/sso-handoff` +- `/api/v1/ca/backup` +- `/api/v1/caddy/upstreams` +- `/api/v1/caddy/upstreams/:host/mute` +- `/api/v1/caddy/upstreams/:host/unmute` +- `/api/v1/caddy/upstreams/incidents` +- `/api/v1/caddy/upstreams/mute` +- `/api/v1/caddycode/generate` +- `/api/v1/caddycode/templates` +- `/api/v1/caddycode/validate` +- `/api/v1/catalog` +- `/api/v1/catalog/:appId` +- `/api/v1/catalog/search` +- `/api/v1/certificates` +- `/api/v1/certificates/:serviceId` +- `/api/v1/check` +- `/api/v1/check/:serviceId` +- `/api/v1/containers/` +- `/api/v1/deploys/catalog/apps` +- `/api/v1/deploys/catalog/deploy` +- `/api/v1/deploys/deploy` +- `/api/v1/deploys/gitea-repos` +- `/api/v1/deploys/install` +- `/api/v1/deploys/journal` +- `/api/v1/deploys/repos` +- `/api/v1/deploys/rollback` +- `/api/v1/deploys/services` +- `/api/v1/deploys/status` +- `/api/v1/disaster/backup` +- ... and 70 more -### Documented but NOT in code (18) +### Documented but NOT in code (23) -- `/api/v1/apps/:appId` -- `/api/v1/apps/check-existing` -- `/api/v1/apps/check-port/:port` -- `/api/v1/apps/deploy` -- `/api/v1/apps/suggest-port/:basePort` -- `/api/v1/apps/templates` -- `/api/v1/apps/templates/:appId` -- `/api/v1/apps/update-subdomain` -- `/api/v1/audit-log` -- `/api/v1/browse/dir` -- `/api/v1/caddy/get-cas` -- `/api/v1/health` -- `/api/v1/health-check/configure/:serviceId` -- `/api/v1/health-check/incidents` -- `/api/v1/health-check/incidents/history` -- `/api/v1/health-check/stats/:serviceId` -- `/api/v1/health-check/status` -- `/api/v1/service-creds/:serviceId` +- `/api/v1/auto-restart/policies` +- `/api/v1/auto-restart/policies/:serviceId` +- `/api/v1/auto-restart/policies/:serviceId/test` +- `/api/v1/config-drift/fix` +- `/api/v1/config-drift/last` +- `/api/v1/config-drift/polling` +- `/api/v1/config-drift/report` +- `/api/v1/csrf-token` +- `/api/v1/disk` +- `/api/v1/mcp` +- `/api/v1/metrics` +- `/api/v1/openclaw` +- `/api/v1/ssl/certificates` +- `/api/v1/ssl/certificates/:serviceId` +- `/api/v1/ssl/check` +- `/api/v1/ssl/check/:serviceId` +- `/api/v1/ssl/config` +- `/api/v1/workflows/workflows` +- `/api/v1/workflows/workflows/:workflowId/disable` +- `/api/v1/workflows/workflows/:workflowId/enable` +- `/api/v1/workflows/workflows/:workflowId/history` +- `/api/v1/workflows/workflows/:workflowId/run` +- `/api/v1/workflows/workflows/history` + + +## Known Limitations +- Literal single-line `router.('path', ...)` declarations only; + multiline, computed, chained, `router.route()`, and `app.all()` + registrations may be omitted. Mount resolution relies on the + maintained composition map above. Treat this file as a + high-coverage declarative inventory, not a guaranteed-complete listing. +- Duplicate registrations (3): the handler-row total exceeds + unique operations because of these: + - GET /api/v1/auth/me: routes/auth/admin.js:112, routes/auth/index.js:158 + - GET /api/v1/config: routes/config/settings.js:26, routes/ssl-monitor.js:80 + - POST /api/v1/config: routes/config/settings.js:43, routes/ssl-monitor.js:90 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b546ad..b860f5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,143 +6,63 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] - ### Added -- **DC-168 judge: grade B (round 16)** urn:ump:ejzlob2bde6m6ivxfpgidmwu5a62pvl4wddm4iav7mwfuyfogirq -- **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: chip colors derive via - color-mix from --accent (lighten on dark surfaces via html.light-bg, - darken on light) — 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 on refetch; chip always renders - from the now-window even with a navigated viewMonth. 23 calendar tests. -- **DC-167 judge: grade B (round 11)** urn:ump:kfn7m6vzd4ias2u5ghg62hvoug3j2y2gmturnytdz4g4od35hx4a +- **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. Discovered while connecting the - owner's DNS3 Radicale (appt.sami) as a DashCaddy calendar source (owner - request: connect DNS3 calendar to DNS2 DashCaddy). 9-test ns suite - (extraction incl. mixed doc; empty multistatus; namespace-aware fixture - parse; undeclared element+attribute prefix rejection, both quote styles; - positive declared-attr case). Ops: NODE_EXTRA_CA_CERTS drop-in for the Sami - Home CA; source registered via authenticated API at the collection URL. -- **DC-165 judge: grade B (round 5)** urn:ump:oqgczb6zv5bemekbxko7gi5rvfle6yoshwgfwsx4mbbj4xmg6paq -- **DC-165: product/site uniformity + LLM-readable capabilities.** Site-vs-product - audit reconciliation (owner: "advertise as Premium", "It should be LLM readable"): - 'fleet' added to PREMIUM_FEATURES and `requirePremium('fleet')` mounted at the - /fleet ROUTER prefix (app.js) so every sub-route present and future inherits the - gate — proven by 4 real-boot composition-root e2e tests (free 403 envelope - contract, per-feature check, with-fleet gate cleared, hosts/status/deploy - inheritance). 'workflows' + 'auto-backup' (gated in routes forever, missing from - the constant so the offline path could never grant them) listed with - source-quoted descriptions; marketing pages now advertise them (manual backup + - Disaster Recovery stay free — copy matches code exactly). Offline activations - forward-migrate on load (append-only union, online untouched — server stays - authoritative). GET /api/v1/capabilities: public machine-readable manifest of the - RUNNING build — six measured fields (version, template count/categories, MCP - tools, transport identity triple, premium list, language count) derived at request - time from the runtime modules (single source: MCP_RUNTIME exported from - routes/mcp.js, read by /info and capabilities alike), static metadata fields - honestly labeled; PUBLIC_ROUTES + drift test + OpenAPI 200-only contract; - no-secrets negative test. License-server grant list extended to - sso/recipes/swarm/fleet/workflows/auto-backup with existing records backfilled - (ops). Marketing repo (dashcaddy.net, separate commits): counts 77→78 everywhere, - internal-identifier sweep (~85 private-TLD/IP/user/path hits → .home/example - style), radicale install guide (78th), calendar/Connect-AI advertised, MCP claim - softened to real scope + capabilities pointer, llms.txt → runtime ground truth, - robots LLMs-Txt line. 124 targeted tests (9 dc165 + 4 fleet-e2e + 76 license + - 21 tier + 14 drift); 73 MCP tests re-green after refactor. -- **DC-164 judge: grade B (round 6)** urn:ump:5vuahbn4znf7hrrshjvu2pcbqln5m6qcrpkkn3mx6y2rvvmdtqoa -- **DC-164: operator env overrides + license-material untracking.** start.sh - forwards `-e` flags from /etc/dashcaddy/operator.env (root-owned KEY=VALUE, - 0600) into the container via scripts/operator-flags.js: one pinned fd - (lstat pre-check → open O_RDONLY|O_NOFOLLOW|O_NONBLOCK → fstat+read on the - same descriptor) so symlinks, FIFOs/devices, group/other-readable or - special-bit modes, and foreign ownership are refused (exit 3, fail closed: - warn + forward nothing; any other helper failure ABORTS startup, fail - loud). Canonical flag ALLOW_LIFETIME_LICENSE=true — creator hosts only; - it is a deployment rail, not the boundary: offline lifetime codes must - HMAC-verify against the signing secret which is never shipped to customers - (installs carry .license-secret-hash). 17 executed tests (FIFO non-block - regression, O_NOFOLLOW symlink-to-valid-target, mode/owner matrix, - injection-inert values). Root-cause fix for the 2026-10-08 incident: - data/services.json, .license-secret, .license-counter UNTRACKED + - .gitignore'd — deploy checkouts can no longer clobber live runtime state. -- **DC-163 judge: grade B (round 2)** urn:ump:l2tg66wmchkmd6fde4k3ojwc56zpxqh657spe37bu6dpvduswalq -- **DC-163: AI assistant that actually works — LLM intent engine + fixed - chat panel.** The 🤖 floating assistant was broken end-to-end: the - frontend read a nonexistent `data.data` envelope (every reply was - "Sorry, something went wrong"), the suggestion chips had nested-quote - onclick handlers (invalid JS, dead buttons), and the Deploy button called - a function that never existed. All three fixed. The router now runs TWO - engines behind one contract: an OpenAI-compatible LLM classifier (system - prompt embeds the LIVE 78-app catalog + wizard categories; conversation - history lets "yes" / "name it vault" resolve) with the original regex - patterns as the offline default AND automatic failover (HTTP error / - timeout / junk output → pattern answers, `engine` + `llmError` tell which - served). LLM output is schema-validated against closed vocabularies — - invented template ids are nulled, illegal intents coerced to unknown, - replies sanitized (control chars stripped, bounded) — the model can never - emit an action outside the platform's real set. Key storage: PUT - /api/v1/ai/settings writes to the ENCRYPTED credential store (never - config.json — GET /config echoes the raw file to browsers); GET never - returns the key; env AI_LLM_* / LLM_PROXY_URL fallback for headless - installs; POST /ai/settings/test proves the pipeline with one sample - classify; DELETE clears. Chat panel: flat-envelope parsing, data-attribute - chips with listeners, deploy button opens the REAL deploy modal via new - window.openDeployConfigFor(id) in app-selector.js, engine badge in the - header, ⚙ settings sub-panel. "Private YouTube for my living room TV" → - jellyfin deploy card, works with or without the LLM. 20 new tests - (engine selection, failover incl. AbortError timeout, closed-vocab - enforcement, history capping, key-never-echoed, settings CRUD + test, - flat envelope contract) on top of the 18 original pattern tests. - OpenAPI: 4 AI path entries. dist bundle rebuilt. -- **DC-162 rev2: "Connect your AI" — spec Streamable HTTP MCP + one-click keys.** - New primary-row button 🤖 Connect AI on the dashboard opens a modal that - mints a dedicated `dk_` key and returns the standard MCP Streamable-HTTP - connection card ({url, transport, headers} + per-client notes) for - Claude / ChatGPT / generic clients. Transport implements MCP 2025-03-10 - Streamable HTTP: POST answers application/json (valid notifications → - 202; malformed → JSON-RPC -32600 with the spec's no-reply-for-broken- - notifications rule; batching removed in this revision → rejected), GET/ - DELETE return 405 + Allow: POST (stateless request/response server — - spec-permitted; clients needing server-initiated streams use the stdio - bridge). Credential TYPE preservation: apikey callers proxy x-api-key - downstream, jwt callers proxy Authorization Bearer — key scopes apply - identically at the loopback API. JSON-RPC validation: -32600 structural - (jsonrpc/method/params/id), -32602 invalid tool params, Origin DNS- - rebinding guard, MCP-Protocol-Version header check, Accept negotiation. - GET /mcp/connections lists only "AI connection (…)" keys. Tool path - params URL-encoded. Backend: routes/mcp.js (POST, GET/DELETE 405, /info, - /connections, /config), src/mcp/mcp-core.js shared by stdio + HTTP, - stale 1.15.0 version literal removed (reads package.json). - 73 route+core tests (incl. 16-case JSON-RPC boundary matrix) + - composition-root e2e proves the mount boots. - OpenAPI: 4 MCP path entries. status/dist + sw.js regenerated in-tree. -- **DC-162 judge: grade B (round 3)** urn:ump:or2qqhcytp6lfzlyi7xru7hdqy3uar2hbjllsrrbxrbray3c5kpa -- **DC-161: Calendar widget + Radicale auto-wiring.** New top-bar calendar widget - (`status/js/calendar-widget.js`) showing upcoming appointments; settings modal for - adding CalDAV / ICS-feed sources with a Test-connection probe. Backend: - `routes/calendar.js` (`GET/POST/PUT/DELETE /api/v1/calendar/sources`, - `GET /api/v1/calendar/events`, `POST /api/v1/calendar/radicale/provision`, - `POST /api/v1/calendar/discover`), dependency-free RFC 5545 ICS parser with - bounded RRULE expansion (`src/calendar/ics-parser.js`), CalDAV REPORT client - over node:https (`src/calendar/caldav-client.js`). When Radicale is deployed - through the App Selector, the deploy hook auto-provisions htpasswd credentials - (pure-Node APR1 hash, verified byte-for-byte against `openssl passwd -apr1`) - and registers the calendar source — the widget connects with zero user input. - Manual bring-your-own sources (Radicale/Baikal/Nextcloud hosted elsewhere, - iCloud/Fastmail CalDAV, plain ICS feeds) wire in through the settings modal. - Codex judge: **grade B (round 5)** — - urn:ump:znykdi6ufm53sgnhkep7ghlda7aoo55nipghjtstlkpqqmxuv2zq. + ; 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 (ticketed, pending): DNS provider plug-in parity** — capability + reporting at provider-choice time (RFC2136 is push-only: create/resolve yes, + listing is protocol-impossible); BIND 9 near-parity via the RFC2136 adapter + plus AXFR-based listing; PowerDNS/deSEC/AdGuard Home adapters on the roadmap. + +### 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 @@ -178,9 +98,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 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. -## [Unreleased] - -### Production-Grade Hardening Sprint (2026-08-12) +### 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. @@ -398,6 +316,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### 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 @@ -424,28 +364,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- -## [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.1.5] - 2026-03-23 ### Added @@ -503,7 +421,8 @@ Initial release of DashCaddy. Unified dashboard for Docker container management, Caddy reverse proxy configuration, DNS automation, and SSL certificate provisioning. -[Unreleased]: ../../compare/v1.5.0...HEAD +[Unreleased]: ../../compare/v1.17.0...HEAD +[1.17.0]: ../../compare/v1.14.6-patched...v1.17.0 [1.5.0]: ../../compare/v1.4.10...v1.5.0 [1.4.10]: ../../compare/v1.4.9...v1.4.10 [1.4.9]: ../../compare/v1.4.8...v1.4.9 @@ -522,3 +441,6 @@ certificate provisioning. [1.1.5]: ../../compare/v1.1.1...v1.1.5 [1.1.1]: ../../compare/v1.0.0...v1.1.1 [1.0.0]: ../../releases/tag/v1.0.0 +[1.15.0]: ../../compare/v1.14.0...v1.15.0 +[1.14.0]: ../../compare/v1.13.4...v1.14.0 +[1.13.4]: ../../compare/v1.13.3...v1.13.4 diff --git a/README.md b/README.md index a17f2c2..656fe46 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,14 @@ **Self-hosted dashboard for managing Docker apps with automatic SSL, DNS, and reverse proxy configuration.** -![Version](https://img.shields.io/badge/version-1.15.0-blue) +![Version](https://img.shields.io/badge/version-1.17.0-blue) ![License](https://img.shields.io/badge/license-Proprietary-red) ## What is DashCaddy? DashCaddy is an all-in-one solution for self-hosting Docker applications. It combines: - 🎨 **Beautiful Dashboard** - Monitor all your services in one place -- 🐳 **Docker Management** - Deploy 50+ pre-configured apps with one click +- 🐳 **Docker Management** - Deploy 78 pre-configured apps with one click - 🔒 **Automatic SSL** - Internal CA with automatic certificate generation - 🌐 **DNS Integration** - Automatic DNS record creation (Technitium DNS) - 🔄 **Reverse Proxy** - Caddy configuration managed automatically @@ -32,7 +32,7 @@ DashCaddy is an all-in-one solution for self-hosting Docker applications. It com - Import/export configuration ### App Deployment -- 50+ pre-configured app templates +- 78 pre-configured app templates - One-click deployment - Automatic DNS + SSL + reverse proxy setup - Container health checking @@ -64,13 +64,13 @@ DashCaddy is an all-in-one solution for self-hosting Docker applications. It com 1. **Clone the repository** ```bash -git clone https://github.com/yourusername/dashcaddy.git +git clone https://github.com/sami7777/dashcaddy.git cd dashcaddy ``` 2. **Install dependencies** ```bash -cd caddy-api +cd dashcaddy-api npm install ``` @@ -152,7 +152,7 @@ Both endpoints return JSON. Liveness is cheap (no I/O, no deps). Readiness touch ### Environment Variables -Create a `.env` file in the `caddy-api` directory: +Create a `.env` file in the `dashcaddy-api` directory: ```env # Caddy Configuration @@ -244,7 +244,7 @@ The script is idempotent, never touches byte-identical files (mtime preserved), ## App Templates -DashCaddy includes 50+ pre-configured templates: +DashCaddy includes 78 pre-configured templates: ### Media & Entertainment - Plex, Jellyfin, Emby @@ -282,27 +282,38 @@ DashCaddy includes 50+ pre-configured templates: ## API Endpoints ### Services -- `GET /api/services` - List all services -- `POST /api/services` - Add service -- `PUT /api/services` - Bulk import services -- `DELETE /api/services/:id` - Remove service +- `GET /api/v1/services` - List all services +- `POST /api/v1/services` - Add service +- `PUT /api/v1/services` - Bulk import services +- `DELETE /api/v1/services/:id` - Remove service ### App Deployment -- `GET /api/apps/templates` - List app templates -- `POST /api/apps/deploy` - Deploy new app -- `DELETE /api/apps/:id` - Remove deployed app +- `GET /api/v1/apps/templates` - List app templates +- `POST /api/v1/apps/deploy` - Deploy new app +- `DELETE /api/v1/apps/:appId` - Remove deployed app ### Error Logs -- `GET /api/error-logs` - Get error logs -- `DELETE /api/error-logs` - Clear error logs +- `GET /api/v1/error-logs` - Get error logs +- `DELETE /api/v1/error-logs` - Clear error logs ### DNS Management -- `POST /api/dns/record` - Create DNS record -- `DELETE /api/dns/record` - Delete DNS record +- `POST /api/v1/dns/record` - Create DNS record +- `DELETE /api/v1/dns/record` - Delete DNS record +- `GET /api/v1/dns/records` - List DNS records (active provider) -### Caddy Management -- `GET /api/caddy/config` - Get Caddyfile content -- `POST /api/caddy/reload` - Reload Caddy configuration +### Reverse Proxy & Caddy Management +- `GET /api/v1/caddy/config` - Get Caddyfile content +- `POST /api/v1/caddy/reload` - Reload Caddy configuration +- `GET /api/v1/caddy/cas` - List upstream Caddy servers +- `POST /api/v1/caddycode/generate` - Generate a Caddyfile site block +- `POST /api/v1/caddycode/validate` - Validate a site-block config +- `GET /api/v1/caddycode/templates` - Preset configs for common patterns + +> Endpoint accuracy: the MCP route-existence drift gate +> (`__tests__/mcp/mcp-core-dc166-integration.test.js`) verifies every MCP +> tool's handler-issued API call against the declared routes; the entries +> above were additionally spot-checked against the router, and the full +> generated inventory lives in [API-SURFACE.md](API-SURFACE.md). ## Troubleshooting @@ -401,9 +412,9 @@ Contributions are welcome! Please: - [ ] Service groups/categories - [ ] Container log viewer -- [ ] DNS management UI -- [ ] Backup automation -- [ ] Multi-user support +- [x] DNS management UI (DC-166) +- [x] Backup automation (schedules + point-in-time restore) +- [x] Multi-user support (DC-048 — opt-in via `siteConfig.authProviders.email.enabled`; bootstrap admin, allowlist, invites) - [ ] Mobile app - [ ] Analytics dashboard - [ ] Template marketplace @@ -420,9 +431,9 @@ Proprietary software. All rights reserved. See [LICENSE](LICENSE) for the End-Us ## Support -- **Issues**: [GitHub Issues](https://github.com/yourusername/dashcaddy/issues) -- **Discussions**: [GitHub Discussions](https://github.com/yourusername/dashcaddy/discussions) -- **Documentation**: [Wiki](https://github.com/yourusername/dashcaddy/wiki) +- **Issues**: [GitHub Issues](https://github.com/sami7777/dashcaddy/issues) +- **Discussions**: [GitHub Discussions](https://github.com/sami7777/dashcaddy/discussions) +- **Documentation**: [Wiki](https://github.com/sami7777/dashcaddy/wiki) ## Acknowledgments diff --git a/RELEASE-EVIDENCE-internal.md b/RELEASE-EVIDENCE-internal.md new file mode 100644 index 0000000..8df295a --- /dev/null +++ b/RELEASE-EVIDENCE-internal.md @@ -0,0 +1,13 @@ +# Internal release evidence (NOT customer-facing) + +Judge grades and ledger URNs per change. Kept out of CHANGELOG.md +(DC-170 docs batch r3: the distributable changelog must stay clean). + +Judging per-change is recorded in commit messages and the internal ledger. Grade lines consolidated here: + +- **DC-168 judge: grade B (round 16)** urn:ump:ejzlob2bde6m6ivxfpgidmwu5a62pvl4wddm4iav7mwfuyfogirq +- **DC-167 judge: grade B (round 11)** urn:ump:kfn7m6vzd4ias2u5ghg62hvoug3j2y2gmturnytdz4g4od35hx4a +- **DC-165 judge: grade B (round 5)** urn:ump:oqgczb6zv5bemekbxko7gi5rvfle6yoshwgfwsx4mbbj4xmg6paq +- **DC-164 judge: grade B (round 6)** urn:ump:5vuahbn4znf7hrrshjvu2pcbqln5m6qcrpkkn3mx6y2rvvmdtqoa +- **DC-163 judge: grade B (round 2)** urn:ump:l2tg66wmchkmd6fde4k3ojwc56zpxqh657spe37bu6dpvduswalq +- **DC-162 judge: grade B (round 3)** urn:ump:or2qqhcytp6lfzlyi7xru7hdqy3uar2hbjllsrrbxrbray3c5kpa diff --git a/VERIFY-OUTPUT.txt b/VERIFY-OUTPUT.txt new file mode 100644 index 0000000..4963e9a --- /dev/null +++ b/VERIFY-OUTPUT.txt @@ -0,0 +1,30 @@ +# Reproducible verification — DC-170 docs batch r9 rev2 (2026-10-29 09:27 UTC) +# Every line below is verbatim: the exact command run and its exact output, +# captured in one shell session against this repository. +# NOTE: dashcaddy-api/VERSION was observed reverted to 1.15.0 at 09:26 UTC +# (mtime 2026-10-29 01:48:13, writer unidentified) and was re-set to 1.17.0 +# with: echo 1.17.0 > dashcaddy-api/VERSION + +$ cd /root/dashcaddy +$ cat dashcaddy-api/VERSION +1.17.0 + +$ grep "\"version\"" dashcaddy-api/package.json + "version": "1.17.0", + +$ grep "version:" dashcaddy-api/openapi.yaml | head -1 + version: 1.17.0 + +$ grep "version-" README.md | head -1 +![Version](https://img.shields.io/badge/version-1.17.0-blue) + +$ node -e 'const {APP_TEMPLATES}=require("./dashcaddy-api/src/docker/app-templates"); console.log("templates:", Object.keys(APP_TEMPLATES).length)' +templates: 78 + +$ python3 /root/.hermes/skills/software-development/dashcaddy/scripts/api-surface-extract.py --root /root/dashcaddy --out /root/dashcaddy/API-SURFACE.md +[ok] 443 handler rows, 59 PUBLIC_ROUTES allowlist entries +[ok] wrote /root/dashcaddy/API-SURFACE.md (54340 bytes) +$ wc -c API-SURFACE.md +54340 API-SURFACE.md +$ sha256sum API-SURFACE.md +6ee6b2f0da244cd6103be14f0977b95f1a9af3d453bf2401bb4c222d59b6c621 API-SURFACE.md diff --git a/dashcaddy-api/VERSION b/dashcaddy-api/VERSION index b7c4395..092afa1 100644 --- a/dashcaddy-api/VERSION +++ b/dashcaddy-api/VERSION @@ -1 +1 @@ -70e252c +1.17.0 diff --git a/dashcaddy-api/openapi.yaml b/dashcaddy-api/openapi.yaml index 935863d..978b7b6 100644 --- a/dashcaddy-api/openapi.yaml +++ b/dashcaddy-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: DashCaddy API - version: 1.15.0 + version: 1.17.0 description: > Unified management API for Docker, Caddy, DNS, and Tailscale services. Covers container lifecycle, reverse proxy configuration, DNS record