feat: 1.5.0 prep — API v1 cutover, LICENSE, CHANGELOG, CI

- Remove legacy /api/ mount; all routes now under /api/v1/ only
- Update path matchers (CSRF excludes, public routes, audit log, rate limits)
- Move standalone routes (/api/network/ips, /api/docs, /api/docs/spec) to v1
- Update openapi.yaml (110 paths), CA pages, and 4 lingering frontend files
- Add LICENSE (proprietary EULA), CHANGELOG.md (Keep a Changelog format)
- Add .gitea/workflows/ci.yml (test+lint and security audit jobs)
- Fix 9 pre-existing no-empty lint errors so CI starts green
- Drop ad-hoc scratch reports and *.bak files from repo root

All 739 jest tests pass. Lint is clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sami
2026-05-17 11:38:45 -07:00
co-authored by Claude Opus 4.7
parent cd8ccaba2b
commit d36705bd90
18 changed files with 663 additions and 252 deletions
+71
View File
@@ -0,0 +1,71 @@
name: CI
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
jobs:
test:
name: Test & Lint
runs-on: ubuntu-latest
defaults:
run:
working-directory: dashcaddy-api
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: dashcaddy-api/package-lock.json
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Test (CI mode + coverage)
run: npm run test:ci
- name: Upload coverage artifact
if: always()
uses: actions/upload-artifact@v3
with:
name: coverage-${{ github.sha }}
path: dashcaddy-api/coverage/
retention-days: 14
security:
name: Security audit
runs-on: ubuntu-latest
defaults:
run:
working-directory: dashcaddy-api
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: dashcaddy-api/package-lock.json
- name: Install dependencies
run: npm ci
- name: npm audit (production deps, high+ severity)
run: npm audit --production --audit-level=high
continue-on-error: true
- name: Run security-focused test suite
run: npm run test:security
+220
View File
@@ -0,0 +1,220 @@
# Changelog
All notable changes to DashCaddy are documented in this file.
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]
## [1.5.0] - 2026-05-17
### Changed (BREAKING)
- API routes now mounted exclusively under `/api/v1/`. The legacy un-versioned
`/api/` mount has been removed. Frontend, OpenAPI spec, DashCA pages, and
all internal path matchers (CSRF exclusions, auth public routes, audit log,
rate-limit mounts) updated accordingly. **Existing integrations that hit
`/api/...` directly must update to `/api/v1/...`.** Held at minor bump
(1.5.0) rather than major (2.0.0) — DashCaddy is still pre-1.0-API-stable.
### Added
- `LICENSE` (proprietary EULA) at repo root.
- `CHANGELOG.md` (this file) — Keep a Changelog format.
- Gitea Actions workflow ([.gitea/workflows/ci.yml](.gitea/workflows/ci.yml))
that runs `npm test` (with coverage) and `npm run lint` on every push to
`main`/`master` and on PRs, plus a `security` job running `npm audit` and
the security-focused test subset.
### Fixed
- 9 pre-existing `no-empty` ESLint errors in `backup-manager.js` and
`routes/backups.js` (intentional ignore-failure catches now annotated).
### Removed
- Stale files at repo root: `*.bak`, `server-old.js`, and ad-hoc
deployment/migration/test reports (`DEPLOYMENT-SUCCESS.md`,
`FINAL-DEPLOYMENT-REPORT.md`, `DESLOPIFICATION-ROADMAP.md`,
`error-handling-*.md`, `WHAT-IS-DASHCADDY.md`, etc.). Already gitignored —
disk-only cleanup.
---
## [1.4.10] - 2026-05-17
### Fixed
- `release.sh` now stages build-rewritten files (`sw.js`, `index.html`) so
they're included in the published tarball.
## [1.4.9] - 2026-05-17
### Fixed
- Container-logs feature was misusing `wireModal`, which short-circuited the
rest of `features.js` and broke unrelated dashboard features.
## [1.4.8] - 2026-05-17
### Fixed
- CSP hash now computed on LF-normalized `index.html` so Windows and Linux
builds produce identical hashes.
## [1.4.7] - 2026-05-17
### Fixed
- Dashboard unbroken: corrected bundle order, closed dangling IIFE, removed
duplicate `const` declaration.
## [1.4.6] - 2026-05-17
### Fixed
- `sw.js` cache tag now derived from bundle content hash, so service worker
invalidates correctly when bundle content changes.
## [1.4.5] - 2026-05-17
### Fixed
- Frontend deploy routed through the host-side updater (matches the API
container's own update path).
## [1.4.4] - 2026-05-16
### Fixed
- `notify` endpoint exempted from CSRF (it's called by the host-side updater,
not the browser).
- `release.sh` JSON parsing made portable (no longer assumes GNU `jq`
semantics on every host).
## [1.4.3] - 2026-05-16
### Added
- Seamless release flow: push-notify endpoint, VERSION file copy into
release tarball, robust SSH mirror handling on port 22022.
## [1.4.2] - 2026-05-16
## [1.4.1] - 2026-05-16
### Changed
- Version bump only — packaging plumbing for the 1.4.x release line.
## [1.4.0] - 2026-05-06
### Added
- `scripts/release.sh` — one-command release cutting and publishing.
---
## [1.3.1] - 2026-05-06
### Fixed
- Installer: added `src/` directory to the deploy manifest; dropped
`MakeDirectory=yes` from the systemd updater path unit.
- Self-updater: copies `src/`, replaces `routes/` in place instead of
nesting it inside the existing tree.
## [1.3.0] - 2026-05-06
### Added
- Self-updater supports `DASHCADDY_API_SOURCE_DIR` env override for
non-standard deploy layouts.
### Fixed
- Self-updater now clears *all* pending history entries, not just one.
---
## [1.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
- Pylon health relay for remote service health checks (with relay
fallback on `/probe/:id`).
- Host-side auto-updater for zero-touch API container rebuilds.
### Fixed
- Service edit preserves service ID on subdomain change; accepts
`localhost` as a valid IP.
- Taxi theme accent color now distinct from text.
- Prevents encryption key conflicts; adds license backup on rotation.
## [1.1.1] - 2026-03-23
### Fixed
- Service edit, CSRF token stability, and license restore.
---
## [1.0.x] - 2026-03-05 → 2026-03-22
Initial release line. Highlights from work between v1.0 and v1.1:
### Added
- Cross-platform path support (Windows + Linux deployments).
- Subdirectory routing mode for public-domain deployments.
- Auto-update system for DashCaddy instances.
- Batched status endpoint (frontend performance).
- Install-wide onboarding tour (no longer per-browser).
- Daily log digest and Docker hygiene/maintenance.
- Unified backup/restore v2.0 with full state capture.
- DNS uptime bars and fully-dynamic DNS server config.
### Changed
- Phase 1-3 refactor: extracted config/context/utils into `src/`, split
monolithic `server.js`, standardized all 25+ route files with explicit
dependency injection.
- Unified error handling system (throw-based, migrated 25 route files).
- ESLint + Prettier baseline with auto-fixes.
### Security
- 7 critical + 16 high/medium API security bugs fixed.
- 7 frontend security vulnerabilities fixed (4 critical, 3 high).
- Logger sanitization to prevent log injection.
### Tests
- Comprehensive test suite reaching 80%+ coverage threshold.
- `docker-security` test suite (41 tests).
- `auth-manager` and `credential-manager` test suites.
## [1.0.0] - 2026-03-05
Initial release of DashCaddy. Unified dashboard for Docker container
management, Caddy reverse proxy configuration, DNS automation, and SSL
certificate provisioning.
[Unreleased]: ../../compare/v1.5.0...HEAD
[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
[1.4.8]: ../../compare/v1.4.7...v1.4.8
[1.4.7]: ../../compare/v1.4.6...v1.4.7
[1.4.6]: ../../compare/v1.4.5...v1.4.6
[1.4.5]: ../../compare/v1.4.4...v1.4.5
[1.4.4]: ../../compare/v1.4.3...v1.4.4
[1.4.3]: ../../compare/v1.4.2...v1.4.3
[1.4.2]: ../../compare/v1.4.1...v1.4.2
[1.4.1]: ../../compare/v1.4.0...v1.4.1
[1.4.0]: ../../compare/v1.3.1...v1.4.0
[1.3.1]: ../../compare/v1.3.0...v1.3.1
[1.3.0]: ../../compare/v1.2.0...v1.3.0
[1.2.0]: ../../compare/v1.1.5...v1.2.0
[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
+125
View File
@@ -0,0 +1,125 @@
DashCaddy End-User License Agreement (EULA)
=============================================
Copyright (c) 2024-2026 Sami Ahmed. All rights reserved.
This software and its source code (the "Software") are proprietary and
confidential. By installing, copying, accessing, or otherwise using the
Software, you ("Licensee") agree to be bound by the terms of this License.
If you do not agree, do not install, copy, or use the Software.
1. GRANT OF LICENSE
-------------------
Subject to the terms of this License and the purchase of a valid license
key where required, Licensor grants Licensee a non-exclusive,
non-transferable, revocable license to install and use the Software on
hardware that Licensee owns or controls, solely for Licensee's internal
purposes.
A separate license key is required for each production deployment. Use
of the Software without a valid license key is permitted only for
personal, non-commercial evaluation on a single host, for up to 30 days.
2. RESTRICTIONS
---------------
Licensee shall NOT:
(a) sell, rent, lease, sublicense, distribute, publish, or otherwise
transfer the Software or any portion thereof to any third party;
(b) modify, adapt, translate, or create derivative works based on the
Software, except as expressly permitted in Section 3;
(c) reverse engineer, decompile, or disassemble the Software, except
to the extent that such activity is expressly permitted by
applicable law notwithstanding this limitation;
(d) remove, alter, or obscure any copyright, trademark, or other
proprietary notices contained in the Software;
(e) use the Software to operate a hosted or managed service that
makes the Software's functionality available to third parties,
without a separate commercial agreement with Licensor;
(f) use the Software in any manner that violates applicable law.
3. SOURCE AVAILABILITY
----------------------
The Software's source code is made available for the purposes of
transparency, security review, and self-hosted deployment. Source
availability does NOT constitute a grant of open-source rights.
Modifications made by Licensee for internal use only are permitted,
provided they are not redistributed.
4. OWNERSHIP
------------
The Software is licensed, not sold. Licensor retains all right, title,
and interest in and to the Software, including all intellectual property
rights therein. No rights are granted to Licensee other than those
expressly set forth in this License.
5. UPDATES
----------
Licensor may, at its sole discretion, provide updates, patches, or new
versions of the Software. Any such updates are subject to the terms of
this License unless accompanied by a separate license agreement.
6. TERMINATION
--------------
This License is effective until terminated. Licensor may terminate this
License immediately upon any breach by Licensee. Upon termination,
Licensee shall cease all use of the Software and destroy all copies in
its possession or control.
7. WARRANTY DISCLAIMER
----------------------
THE SOFTWARE IS PROVIDED "AS IS" AND "AS AVAILABLE", WITHOUT WARRANTY
OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
TITLE, AND NON-INFRINGEMENT. LICENSEE BEARS THE ENTIRE RISK ARISING
OUT OF THE USE OR PERFORMANCE OF THE SOFTWARE.
8. LIMITATION OF LIABILITY
--------------------------
IN NO EVENT SHALL LICENSOR BE LIABLE FOR ANY INDIRECT, INCIDENTAL,
SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, OR FOR ANY LOSS OF
PROFITS, REVENUE, DATA, OR USE, ARISING OUT OF OR RELATED TO THIS
LICENSE OR THE SOFTWARE, EVEN IF LICENSOR HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES. LICENSOR'S TOTAL CUMULATIVE LIABILITY
SHALL NOT EXCEED THE AMOUNT PAID BY LICENSEE FOR THE SOFTWARE IN THE
TWELVE (12) MONTHS PRECEDING THE EVENT GIVING RISE TO LIABILITY, OR
ONE HUNDRED U.S. DOLLARS (USD $100), WHICHEVER IS GREATER.
9. THIRD-PARTY COMPONENTS
-------------------------
The Software incorporates third-party open-source components, each
governed by its own license. A list of such components and their
licenses is available in the project's `node_modules/` directory or
on request. This License does not modify the terms of any third-party
component license.
10. GOVERNING LAW
-----------------
This License shall be governed by and construed in accordance with the
laws of the jurisdiction in which Licensor resides, without regard to
its conflict of laws principles.
11. ENTIRE AGREEMENT
--------------------
This License constitutes the entire agreement between the parties with
respect to the Software and supersedes all prior or contemporaneous
understandings, whether written or oral.
For licensing inquiries, contact: ahmed.sami@gmail.com
@@ -169,7 +169,7 @@ describe('CSRF Protection', () => {
const origEnv = process.env.NODE_ENV; const origEnv = process.env.NODE_ENV;
process.env.NODE_ENV = 'production'; process.env.NODE_ENV = 'production';
const excludedPaths = ['/api/totp/verify', '/api/totp/setup', '/health', '/api/health']; const excludedPaths = ['/api/v1/totp/verify', '/api/v1/totp/setup', '/health', '/api/v1/health'];
for (const excludedPath of excludedPaths) { for (const excludedPath of excludedPaths) {
const { req, res, next } = createMockReqRes({ method: 'POST', path: excludedPath }); const { req, res, next } = createMockReqRes({ method: 'POST', path: excludedPath });
csrfValidationMiddleware(req, res, next); csrfValidationMiddleware(req, res, next);
@@ -184,7 +184,7 @@ describe('CSRF Protection', () => {
process.env.NODE_ENV = 'production'; process.env.NODE_ENV = 'production';
const { req, res, next } = createMockReqRes({ const { req, res, next } = createMockReqRes({
method: 'POST', path: '/api/auth/gate/plex' method: 'POST', path: '/api/v1/auth/gate/plex'
}); });
csrfValidationMiddleware(req, res, next); csrfValidationMiddleware(req, res, next);
expect(next).toHaveBeenCalled(); expect(next).toHaveBeenCalled();
@@ -296,7 +296,7 @@ describe('CSRF Protection', () => {
process.env.NODE_ENV = origEnv; process.env.NODE_ENV = origEnv;
}); });
it('normalizes /api/v1/ prefix for exclusion matching', () => { it('excludes /api/v1/ paths directly', () => {
const origEnv = process.env.NODE_ENV; const origEnv = process.env.NODE_ENV;
process.env.NODE_ENV = 'production'; process.env.NODE_ENV = 'production';
+56 -56
View File
@@ -7,65 +7,65 @@ const MAX_ENTRIES = parseInt(process.env.AUDIT_MAX_ENTRIES || '1000', 10);
// Route path → readable action mapping // Route path → readable action mapping
const ACTION_MAP = { const ACTION_MAP = {
'POST /api/services/update': 'service.reorder', 'POST /api/v1/services/update': 'service.reorder',
'POST /api/services': 'service.create', 'POST /api/v1/services': 'service.create',
'PUT /api/services': 'service.update', 'PUT /api/v1/services': 'service.update',
'DELETE /api/services/': 'service.delete', 'DELETE /api/v1/services/': 'service.delete',
'POST /api/site': 'caddy.add-site', 'POST /api/v1/site': 'caddy.add-site',
'POST /api/site/external': 'caddy.add-external', 'POST /api/v1/site/external': 'caddy.add-external',
'DELETE /api/site/': 'caddy.remove-site', 'DELETE /api/v1/site/': 'caddy.remove-site',
'POST /api/caddy/reload': 'caddy.reload', 'POST /api/v1/caddy/reload': 'caddy.reload',
'POST /api/dns/record': 'dns.add-record', 'POST /api/v1/dns/record': 'dns.add-record',
'DELETE /api/dns/record': 'dns.delete-record', 'DELETE /api/v1/dns/record': 'dns.delete-record',
'POST /api/dns/credentials': 'dns.save-credentials', 'POST /api/v1/dns/credentials': 'dns.save-credentials',
'DELETE /api/dns/credentials': 'dns.delete-credentials', 'DELETE /api/v1/dns/credentials': 'dns.delete-credentials',
'POST /api/dns/refresh-token': 'dns.refresh-token', 'POST /api/v1/dns/refresh-token': 'dns.refresh-token',
'POST /api/dns/update': 'dns.update-server', 'POST /api/v1/dns/update': 'dns.update-server',
'POST /api/containers/': 'container.action', 'POST /api/v1/containers/': 'container.action',
'DELETE /api/containers/': 'container.delete', 'DELETE /api/v1/containers/': 'container.delete',
'POST /api/apps/deploy': 'container.deploy', 'POST /api/v1/apps/deploy': 'container.deploy',
'DELETE /api/apps/': 'container.undeploy', 'DELETE /api/v1/apps/': 'container.undeploy',
'POST /api/backups/execute': 'backup.execute', 'POST /api/v1/backups/execute': 'backup.execute',
'POST /api/backups/restore/': 'backup.restore', 'POST /api/v1/backups/restore/': 'backup.restore',
'POST /api/backups/config': 'backup.config', 'POST /api/v1/backups/config': 'backup.config',
'POST /api/config': 'config.update', 'POST /api/v1/config': 'config.update',
'DELETE /api/config': 'config.reset', 'DELETE /api/v1/config': 'config.reset',
'POST /api/notifications/config': 'config.notifications', 'POST /api/v1/notifications/config': 'config.notifications',
'POST /api/totp/setup': 'auth.totp-setup', 'POST /api/v1/totp/setup': 'auth.totp-setup',
'POST /api/totp/verify-setup': 'auth.totp-activate', 'POST /api/v1/totp/verify-setup': 'auth.totp-activate',
'POST /api/totp/disable': 'auth.totp-disable', 'POST /api/v1/totp/disable': 'auth.totp-disable',
'POST /api/totp/config': 'auth.totp-config', 'POST /api/v1/totp/config': 'auth.totp-config',
'POST /api/credentials/rotate-key': 'config.rotate-key', 'POST /api/v1/credentials/rotate-key': 'config.rotate-key',
'POST /api/updates/update/': 'container.update', 'POST /api/v1/updates/update/': 'container.update',
'POST /api/updates/rollback/': 'container.rollback', 'POST /api/v1/updates/rollback/': 'container.rollback',
'POST /api/updates/auto-update/': 'container.auto-update', 'POST /api/v1/updates/auto-update/': 'container.auto-update',
'POST /api/updates/check': 'container.check-updates', 'POST /api/v1/updates/check': 'container.check-updates',
'POST /api/health-checks/': 'config.health-check', 'POST /api/v1/health-checks/': 'config.health-check',
'DELETE /api/health-checks/': 'config.health-check-delete', 'DELETE /api/v1/health-checks/': 'config.health-check-delete',
'POST /api/monitoring/alerts/': 'config.monitoring-alert', 'POST /api/v1/monitoring/alerts/': 'config.monitoring-alert',
'DELETE /api/monitoring/alerts/': 'config.monitoring-alert-delete', 'DELETE /api/v1/monitoring/alerts/': 'config.monitoring-alert-delete',
'POST /api/arr/smart-connect': 'service.arr-connect', 'POST /api/v1/arr/smart-connect': 'service.arr-connect',
'POST /api/arr/credentials': 'config.arr-credentials', 'POST /api/v1/arr/credentials': 'config.arr-credentials',
'DELETE /api/arr/credentials/': 'config.arr-credentials-delete', 'DELETE /api/v1/arr/credentials/': 'config.arr-credentials-delete',
'POST /api/logo': 'config.logo-upload', 'POST /api/v1/logo': 'config.logo-upload',
'DELETE /api/logo': 'config.logo-delete', 'DELETE /api/v1/logo': 'config.logo-delete',
'POST /api/favicon': 'config.favicon-upload', 'POST /api/v1/favicon': 'config.favicon-upload',
'DELETE /api/favicon': 'config.favicon-delete', 'DELETE /api/v1/favicon': 'config.favicon-delete',
'POST /api/tailscale/config': 'config.tailscale', 'POST /api/v1/tailscale/config': 'config.tailscale',
'POST /api/tailscale/protect-service': 'config.tailscale-protect', 'POST /api/v1/tailscale/protect-service': 'config.tailscale-protect',
}; };
// Paths to skip logging (noisy or internal) // Paths to skip logging (noisy or internal)
const SKIP_PATHS = [ const SKIP_PATHS = [
'/api/totp/verify', '/api/v1/totp/verify',
'/api/totp/check-session', '/api/v1/totp/check-session',
'/api/auth/gate/', '/api/v1/auth/gate/',
'/api/auth/app-token/', '/api/v1/auth/app-token/',
'/api/audit-logs', '/api/v1/audit-logs',
'/api/health', '/api/v1/health',
'/health', '/health',
'/api/notifications/test', '/api/v1/notifications/test',
'/api/notifications/health-check', '/api/v1/notifications/health-check',
]; ];
class AuditLogger { class AuditLogger {
@@ -82,14 +82,14 @@ class AuditLogger {
if (key.startsWith(pattern)) return action; if (key.startsWith(pattern)) return action;
} }
// Fallback: derive from path // Fallback: derive from path
const parts = urlPath.replace('/api/', '').split('/'); const parts = urlPath.replace('/api/v1/', '').split('/');
const category = parts[0] || 'unknown'; const category = parts[0] || 'unknown';
return `${category}.${method.toLowerCase()}`; return `${category}.${method.toLowerCase()}`;
} }
extractResource(urlPath) { extractResource(urlPath) {
// Pull a meaningful resource identifier from the URL path // Pull a meaningful resource identifier from the URL path
const parts = urlPath.replace('/api/', '').split('/'); const parts = urlPath.replace('/api/v1/', '').split('/');
if (parts.length >= 2) return parts.slice(1).join('/'); if (parts.length >= 2) return parts.slice(1).join('/');
return parts[0] || ''; return parts[0] || '';
} }
+8 -8
View File
@@ -740,7 +740,7 @@ class BackupManager extends EventEmitter {
size: data.length size: data.length
}; };
} finally { } finally {
try { await client.end(); } catch (_) {} try { await client.end(); } catch (_) { /* ignore */ }
} }
} }
@@ -759,7 +759,7 @@ class BackupManager extends EventEmitter {
const buffer = await client.get(location.path); const buffer = await client.get(location.path);
return Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer); return Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
} finally { } finally {
try { await client.end(); } catch (_) {} try { await client.end(); } catch (_) { /* ignore */ }
} }
} }
@@ -786,7 +786,7 @@ class BackupManager extends EventEmitter {
// Delete the probe // Delete the probe
try { try {
await this._deleteFromDestination(location); await this._deleteFromDestination(location);
} catch (_) {} } catch (_) { /* ignore */ }
const elapsed = Date.now() - start; const elapsed = Date.now() - start;
return { return {
@@ -817,14 +817,14 @@ class BackupManager extends EventEmitter {
const { Dropbox } = require('dropbox'); const { Dropbox } = require('dropbox');
const creds = await this._getCloudCredentials('dropbox'); const creds = await this._getCloudCredentials('dropbox');
const dbx = new Dropbox({ accessToken: creds.token }); const dbx = new Dropbox({ accessToken: creds.token });
try { await dbx.filesDeleteV2({ path: location.path }); } catch (_) {} try { await dbx.filesDeleteV2({ path: location.path }); } catch (_) { /* ignore */ }
return; return;
} }
if (location.type === 'webdav') { if (location.type === 'webdav') {
const { createClient } = require('webdav'); const { createClient } = require('webdav');
const creds = await this._getCloudCredentials('webdav'); const creds = await this._getCloudCredentials('webdav');
const client = createClient(creds.url, { username: creds.username, password: creds.password }); const client = createClient(creds.url, { username: creds.username, password: creds.password });
try { await client.deleteFile(location.path); } catch (_) {} try { await client.deleteFile(location.path); } catch (_) { /* ignore */ }
return; return;
} }
if (location.type === 'sftp') { if (location.type === 'sftp') {
@@ -839,9 +839,9 @@ class BackupManager extends EventEmitter {
password: creds.password || undefined, password: creds.password || undefined,
privateKey: creds.privateKey || undefined privateKey: creds.privateKey || undefined
}); });
try { await client.delete(location.path); } catch (_) {} try { await client.delete(location.path); } catch (_) { /* ignore */ }
} finally { } finally {
try { await client.end(); } catch (_) {} try { await client.end(); } catch (_) { /* ignore */ }
} }
return; return;
} }
@@ -895,7 +895,7 @@ class BackupManager extends EventEmitter {
recovered = true; recovered = true;
console.log(`[BackupManager] Loaded backup from fallback location ${backup.locations[i].type}`); console.log(`[BackupManager] Loaded backup from fallback location ${backup.locations[i].type}`);
break; break;
} catch (_) {} } catch (_) { /* ignore */ }
} }
if (!recovered) throw loadErr; if (!recovered) throw loadErr;
} }
+2 -2
View File
@@ -16,7 +16,7 @@ DashCA now provides automatic SSL certificate generation for services on your ne
### 1. Generate/Download Certificate ### 1. Generate/Download Certificate
**GET** `/api/ca/cert/:domain` **GET** `/api/v1/ca/cert/:domain`
Generate and download an SSL certificate for the specified domain. Generate and download an SSL certificate for the specified domain.
@@ -52,7 +52,7 @@ Invoke-WebRequest -Uri "https://ca.sami/api/ca/cert/dns1.sami?format=pfx" -OutFi
### 2. List Generated Certificates ### 2. List Generated Certificates
**GET** `/api/ca/certs` **GET** `/api/v1/ca/certs`
List all generated certificates with their status and expiration information. List all generated certificates with their status and expiration information.
+2 -2
View File
@@ -113,7 +113,7 @@ The landing page (`index.html`) includes:
- **DashCaddy Theme:** Dark mode with Sami Grotesk font - **DashCaddy Theme:** Dark mode with Sami Grotesk font
**API Integration:** **API Integration:**
- Loads certificate info from `/api/ca/info` endpoint - Loads certificate info from `/api/v1/ca/info` endpoint
- Falls back to static info if API unavailable - Falls back to static info if API unavailable
## Development Workflow ## Development Workflow
@@ -234,7 +234,7 @@ Returns CA expiration health status:
**Symptom:** Certificate info stuck on loading state **Symptom:** Certificate info stuck on loading state
**Cause:** API endpoint not accessible **Cause:** API endpoint not accessible
**Solution:** Check that dashcaddy-api server is running and `/api/ca/info` responds **Solution:** Check that dashcaddy-api server is running and `/api/v1/ca/info` responds
## Testing Checklist ## Testing Checklist
+5 -5
View File
@@ -952,7 +952,7 @@
async function loadCertInfo() { async function loadCertInfo() {
try { try {
// Add cache-busting parameter // Add cache-busting parameter
const response = await fetch('/api/ca/info?t=' + Date.now()); const response = await fetch('/api/v1/ca/info?t=' + Date.now());
if (!response.ok) { if (!response.ok) {
console.error('API response not OK:', response.status, response.statusText); console.error('API response not OK:', response.status, response.statusText);
throw new Error('Failed to fetch certificate info'); throw new Error('Failed to fetch certificate info');
@@ -1151,7 +1151,7 @@
// Load service certificates // Load service certificates
async function loadServiceCertificates() { async function loadServiceCertificates() {
try { try {
const response = await fetch('/api/ca/certs'); const response = await fetch('/api/v1/ca/certs');
if (!response.ok) { if (!response.ok) {
throw new Error('Failed to fetch certificates'); throw new Error('Failed to fetch certificates');
} }
@@ -1205,17 +1205,17 @@
<strong>Fingerprint:</strong><br>${cert.fingerprint} <strong>Fingerprint:</strong><br>${cert.fingerprint}
</div> </div>
<div class="cert-actions"> <div class="cert-actions">
<a href="/api/ca/cert/${cert.domain}?format=pfx&password=dashcaddy" <a href="/api/v1/ca/cert/${cert.domain}?format=pfx&password=dashcaddy"
class="cert-btn cert-btn-primary" class="cert-btn cert-btn-primary"
download="${cert.domain}.pfx"> download="${cert.domain}.pfx">
Download PFX Download PFX
</a> </a>
<a href="/api/ca/cert/${cert.domain}?format=pem" <a href="/api/v1/ca/cert/${cert.domain}?format=pem"
class="cert-btn" class="cert-btn"
download="${cert.domain}.pem"> download="${cert.domain}.pem">
PEM PEM
</a> </a>
<a href="/api/ca/cert/${cert.domain}?format=crt" <a href="/api/v1/ca/cert/${cert.domain}?format=crt"
class="cert-btn" class="cert-btn"
download="${cert.domain}.crt"> download="${cert.domain}.crt">
CRT CRT
+7 -9
View File
@@ -118,20 +118,18 @@ function csrfValidationMiddleware(req, res, next) {
// Excluded paths that don't require CSRF validation // Excluded paths that don't require CSRF validation
const excludedPaths = [ const excludedPaths = [
'/api/totp/verify', '/api/v1/totp/verify',
'/api/totp/verify-setup', '/api/v1/totp/verify-setup',
'/api/totp/setup', '/api/v1/totp/setup',
'/health', '/health',
'/api/health', '/api/v1/health',
// Machine-to-machine: publishing host POSTs here with its own shared-secret // Machine-to-machine: publishing host POSTs here with its own shared-secret
// header (X-DashCaddy-Notify-Secret) — browsers never reach this endpoint. // header (X-DashCaddy-Notify-Secret) — browsers never reach this endpoint.
'/api/system/update-notify' '/api/v1/system/update-notify'
]; ];
// Normalize /api/v1/... to /api/... so exclusions work with both prefixes const isExcluded = excludedPaths.some(path => req.path === path) ||
const normalizedPath = req.path.replace(/^\/api\/v1\//, '/api/'); req.path.startsWith('/api/v1/auth/gate/');
const isExcluded = excludedPaths.some(path => normalizedPath === path) ||
normalizedPath.startsWith('/api/auth/gate/');
if (isExcluded) { if (isExcluded) {
return next(); return next();
+37 -39
View File
@@ -92,7 +92,7 @@ module.exports = function configureMiddleware(app, {
res.on('finish', () => { res.on('finish', () => {
const duration = Date.now() - start; const duration = Date.now() - start;
metrics.recordRequest(req.method, req.path, res.statusCode, duration); metrics.recordRequest(req.method, req.path, res.statusCode, duration);
if (req.path !== '/health' && req.path !== '/api/health') { if (req.path !== '/health' && req.path !== '/api/v1/health') {
const level = res.statusCode >= 500 ? 'error' : res.statusCode >= 400 ? 'warn' : 'debug'; const level = res.statusCode >= 500 ? 'error' : res.statusCode >= 400 ? 'warn' : 'debug';
log[level]('http', `${req.method} ${req.path} ${res.statusCode}`, { log[level]('http', `${req.method} ${req.path} ${res.statusCode}`, {
ms: duration, ip: req.ip, id: req.id ms: duration, ip: req.ip, id: req.id
@@ -108,11 +108,11 @@ module.exports = function configureMiddleware(app, {
return next(); return next();
} }
if (req.path === '/health' || req.path === '/api/health' || req.path.startsWith('/probe/')) { if (req.path === '/health' || req.path === '/api/v1/health' || req.path.startsWith('/probe/')) {
return next(); return next();
} }
if (req.path.startsWith('/api/tailscale/')) { if (req.path.startsWith('/api/v1/tailscale/')) {
return next(); return next();
} }
@@ -274,40 +274,38 @@ module.exports = function configureMiddleware(app, {
// ── Public routes (bypass TOTP and JWT auth) ── // ── Public routes (bypass TOTP and JWT auth) ──
const PUBLIC_ROUTES = [ const PUBLIC_ROUTES = [
{ path: '/health', exact: true }, { path: '/health', exact: true },
{ path: '/api/health', exact: true }, { path: '/api/v1/health', exact: true },
{ path: '/probe/', prefix: true }, { path: '/probe/', prefix: true },
{ path: '/api/tailscale/', prefix: true }, { path: '/api/v1/tailscale/', prefix: true },
{ path: '/api/totp/config', exact: true, method: 'GET' }, { path: '/api/v1/totp/config', exact: true, method: 'GET' },
{ path: '/api/totp/verify', exact: true }, { path: '/api/v1/totp/verify', exact: true },
{ path: '/api/totp/setup', exact: true, method: 'POST' }, { path: '/api/v1/totp/setup', exact: true, method: 'POST' },
{ path: '/api/totp/verify-setup', exact: true, method: 'POST' }, { path: '/api/v1/totp/verify-setup', exact: true, method: 'POST' },
{ path: '/api/totp/check-session', exact: true }, { path: '/api/v1/totp/check-session', exact: true },
{ path: '/api/auth/gate/', prefix: true }, { path: '/api/v1/auth/gate/', prefix: true },
{ path: '/api/auth/app-token/', prefix: true }, { path: '/api/v1/auth/app-token/', prefix: true },
{ path: '/api/services', exact: true, method: 'GET' }, { path: '/api/v1/services', exact: true, method: 'GET' },
{ path: '/api/ca/info', exact: true, method: 'GET' }, { path: '/api/v1/ca/info', exact: true, method: 'GET' },
{ path: '/api/ca/root.crt', exact: true, method: 'GET' }, { path: '/api/v1/ca/root.crt', exact: true, method: 'GET' },
{ path: '/api/ca/install-script', exact: true, method: 'GET' }, { path: '/api/v1/ca/install-script', exact: true, method: 'GET' },
{ path: '/api/health/ca', exact: true, method: 'GET' }, { path: '/api/v1/health/ca', exact: true, method: 'GET' },
{ path: '/api/ca/cert/', prefix: true, method: 'GET' }, { path: '/api/v1/ca/cert/', prefix: true, method: 'GET' },
{ path: '/api/ca/certs', exact: true, method: 'GET' }, { path: '/api/v1/ca/certs', exact: true, method: 'GET' },
{ path: '/api/csrf-token', exact: true, method: 'GET' }, { path: '/api/v1/csrf-token', exact: true, method: 'GET' },
{ path: '/api/logo', exact: true, method: 'GET' }, { path: '/api/v1/logo', exact: true, method: 'GET' },
{ path: '/api/favicon', exact: true, method: 'GET' }, { path: '/api/v1/favicon', exact: true, method: 'GET' },
{ path: '/api/themes', exact: true, method: 'GET' }, { path: '/api/v1/themes', exact: true, method: 'GET' },
{ path: '/api/license/status', exact: true, method: 'GET' }, { path: '/api/v1/license/status', exact: true, method: 'GET' },
{ path: '/api/license/feature/', prefix: true, method: 'GET' }, { path: '/api/v1/license/feature/', prefix: true, method: 'GET' },
{ path: '/api/config', exact: true, method: 'GET' }, { path: '/api/v1/config', exact: true, method: 'GET' },
{ path: '/api/services/status', exact: true, method: 'GET' }, { path: '/api/v1/services/status', exact: true, method: 'GET' },
{ path: '/api/system/update-notify', exact: true, method: 'POST' }, { path: '/api/v1/system/update-notify', exact: true, method: 'POST' },
]; ];
function isPublicRoute(req) { function isPublicRoute(req) {
// Normalize /api/v1/... to /api/... so public routes work with both
const p = req.path.replace(/^\/api\/v1\//, '/api/');
return PUBLIC_ROUTES.some(r => { return PUBLIC_ROUTES.some(r => {
if (r.method && req.method !== r.method) return false; if (r.method && req.method !== r.method) return false;
return r.prefix ? p.startsWith(r.path) : p === r.path; return r.prefix ? req.path.startsWith(r.path) : req.path === r.path;
}); });
} }
@@ -389,7 +387,7 @@ module.exports = function configureMiddleware(app, {
...RATE_LIMITS.GENERAL, ...RATE_LIMITS.GENERAL,
standardHeaders: true, standardHeaders: true,
legacyHeaders: false, legacyHeaders: false,
skip: (req) => isTest || req.path === '/health' || req.path === '/api/health' || req.path.startsWith('/probe/') || req.path.startsWith('/api/auth/gate/') || req.path === '/api/totp/check-session' || req.path.endsWith('/health-checks/status') || req.path.endsWith('/csrf-token') || req.path === '/api/v1/dns/logs' || req.path === '/api/license/status' || req.path.startsWith('/api/license/feature/') || req.path === '/api/services' || req.path === '/api/config', skip: (req) => isTest || req.path === '/health' || req.path === '/api/v1/health' || req.path.startsWith('/probe/') || req.path.startsWith('/api/v1/auth/gate/') || req.path === '/api/v1/totp/check-session' || req.path.endsWith('/health-checks/status') || req.path.endsWith('/csrf-token') || req.path === '/api/v1/dns/logs' || req.path === '/api/v1/license/status' || req.path.startsWith('/api/v1/license/feature/') || req.path === '/api/v1/services' || req.path === '/api/v1/config',
message: { success: false, error: 'Too many requests, please try again later' } message: { success: false, error: 'Too many requests, please try again later' }
}); });
@@ -402,11 +400,11 @@ module.exports = function configureMiddleware(app, {
}); });
app.use(generalLimiter); app.use(generalLimiter);
app.use('/api/dns/credentials', strictLimiter); app.use('/api/v1/dns/credentials', strictLimiter);
app.use('/api/apps/deploy', strictLimiter); app.use('/api/v1/apps/deploy', strictLimiter);
app.use('/api/backup/restore', strictLimiter); app.use('/api/v1/backup/restore', strictLimiter);
app.use('/api/site', strictLimiter); app.use('/api/v1/site', strictLimiter);
app.use('/api/credentials/rotate-key', strictLimiter); app.use('/api/v1/credentials/rotate-key', strictLimiter);
const totpLimiter = rateLimit({ const totpLimiter = rateLimit({
...RATE_LIMITS.TOTP, ...RATE_LIMITS.TOTP,
@@ -414,8 +412,8 @@ module.exports = function configureMiddleware(app, {
legacyHeaders: false, legacyHeaders: false,
message: { success: false, error: 'Too many TOTP attempts, please try again later' } message: { success: false, error: 'Too many TOTP attempts, please try again later' }
}); });
app.use('/api/totp/verify', totpLimiter); app.use('/api/v1/totp/verify', totpLimiter);
app.use('/api/totp/verify-setup', totpLimiter); app.use('/api/v1/totp/verify-setup', totpLimiter);
// ── Audit logging middleware (logs non-GET API requests) ── // ── Audit logging middleware (logs non-GET API requests) ──
app.use(auditLogger.middleware()); app.use(auditLogger.middleware());
+110 -110
View File
@@ -83,7 +83,7 @@ paths:
type: string type: string
example: ok example: ok
/api/health: /api/v1/health:
get: get:
tags: [Health & Status] tags: [Health & Status]
summary: API health check summary: API health check
@@ -118,7 +118,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/network/ips: /api/v1/network/ips:
get: get:
tags: [Health & Status] tags: [Health & Status]
summary: Get network interface IPs summary: Get network interface IPs
@@ -145,7 +145,7 @@ paths:
type: string type: string
# TOTP Authentication # TOTP Authentication
/api/totp/config: /api/v1/totp/config:
get: get:
tags: [TOTP Authentication] tags: [TOTP Authentication]
summary: Get TOTP configuration summary: Get TOTP configuration
@@ -184,7 +184,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/totp/setup: /api/v1/totp/setup:
post: post:
tags: [TOTP Authentication] tags: [TOTP Authentication]
summary: Generate TOTP secret summary: Generate TOTP secret
@@ -206,7 +206,7 @@ paths:
otpAuthUrl: otpAuthUrl:
type: string type: string
/api/totp/verify-setup: /api/v1/totp/verify-setup:
post: post:
tags: [TOTP Authentication] tags: [TOTP Authentication]
summary: Verify and activate TOTP summary: Verify and activate TOTP
@@ -229,7 +229,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/totp/verify: /api/v1/totp/verify:
post: post:
tags: [TOTP Authentication] tags: [TOTP Authentication]
summary: Verify TOTP code and create session summary: Verify TOTP code and create session
@@ -259,7 +259,7 @@ paths:
type: string type: string
format: date-time format: date-time
/api/totp/check-session: /api/v1/totp/check-session:
get: get:
tags: [TOTP Authentication] tags: [TOTP Authentication]
summary: Check if session is valid summary: Check if session is valid
@@ -276,7 +276,7 @@ paths:
valid: valid:
type: boolean type: boolean
/api/totp/disable: /api/v1/totp/disable:
post: post:
tags: [TOTP Authentication] tags: [TOTP Authentication]
summary: Disable TOTP summary: Disable TOTP
@@ -299,7 +299,7 @@ paths:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
# SSO Auth Gate # SSO Auth Gate
/api/auth/gate/{serviceId}: /api/v1/auth/gate/{serviceId}:
get: get:
tags: [SSO Auth Gate] tags: [SSO Auth Gate]
summary: Forward auth endpoint for Caddy summary: Forward auth endpoint for Caddy
@@ -315,7 +315,7 @@ paths:
'401': '401':
description: Auth failed description: Auth failed
/api/auth/app-token/{serviceId}: /api/v1/auth/app-token/{serviceId}:
get: get:
tags: [SSO Auth Gate] tags: [SSO Auth Gate]
summary: Get app-specific session token summary: Get app-specific session token
@@ -339,7 +339,7 @@ paths:
type: string type: string
# Service Credentials # Service Credentials
/api/service-creds/{serviceId}: /api/v1/service-creds/{serviceId}:
post: post:
tags: [Service Credentials] tags: [Service Credentials]
summary: Store service credentials summary: Store service credentials
@@ -408,7 +408,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/seedhost-creds: /api/v1/seedhost-creds:
post: post:
tags: [Service Credentials] tags: [Service Credentials]
summary: Store seedhost credentials summary: Store seedhost credentials
@@ -459,7 +459,7 @@ paths:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
# Tailscale # Tailscale
/api/tailscale/status: /api/v1/tailscale/status:
get: get:
tags: [Tailscale] tags: [Tailscale]
summary: Get Tailscale status summary: Get Tailscale status
@@ -482,7 +482,7 @@ paths:
hostname: hostname:
type: string type: string
/api/tailscale/config: /api/v1/tailscale/config:
post: post:
tags: [Tailscale] tags: [Tailscale]
summary: Update Tailscale config summary: Update Tailscale config
@@ -504,7 +504,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/tailscale/check-connection: /api/v1/tailscale/check-connection:
get: get:
tags: [Tailscale] tags: [Tailscale]
summary: Check if request is from Tailscale summary: Check if request is from Tailscale
@@ -521,7 +521,7 @@ paths:
isTailscale: isTailscale:
type: boolean type: boolean
/api/tailscale/devices: /api/v1/tailscale/devices:
get: get:
tags: [Tailscale] tags: [Tailscale]
summary: List Tailscale devices summary: List Tailscale devices
@@ -540,7 +540,7 @@ paths:
items: items:
type: object type: object
/api/tailscale/protect-service: /api/v1/tailscale/protect-service:
post: post:
tags: [Tailscale] tags: [Tailscale]
summary: Add Tailscale ACLs summary: Add Tailscale ACLs
@@ -563,7 +563,7 @@ paths:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
# Caddy Management # Caddy Management
/api/caddyfile: /api/v1/caddyfile:
get: get:
tags: [Caddy Management] tags: [Caddy Management]
summary: Read Caddyfile summary: Read Caddyfile
@@ -580,7 +580,7 @@ paths:
content: content:
type: string type: string
/api/caddy/config: /api/v1/caddy/config:
get: get:
tags: [Caddy Management] tags: [Caddy Management]
summary: Get Caddy admin config summary: Get Caddy admin config
@@ -597,7 +597,7 @@ paths:
config: config:
type: object type: object
/api/caddy/reload: /api/v1/caddy/reload:
post: post:
tags: [Caddy Management] tags: [Caddy Management]
summary: Reload Caddy summary: Reload Caddy
@@ -609,7 +609,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/caddy/get-cas: /api/v1/caddy/get-cas:
get: get:
tags: [Caddy Management] tags: [Caddy Management]
summary: Get certificate authorities summary: Get certificate authorities
@@ -629,7 +629,7 @@ paths:
type: object type: object
# Site Management # Site Management
/api/site: /api/v1/site:
post: post:
tags: [Site Management] tags: [Site Management]
summary: Add site to Caddyfile summary: Add site to Caddyfile
@@ -655,7 +655,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/site/external: /api/v1/site/external:
post: post:
tags: [Site Management] tags: [Site Management]
summary: Add external service proxy summary: Add external service proxy
@@ -683,7 +683,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/site/{domain}: /api/v1/site/{domain}:
delete: delete:
tags: [Site Management] tags: [Site Management]
summary: Remove site from Caddyfile summary: Remove site from Caddyfile
@@ -702,7 +702,7 @@ paths:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
# DNS Management # DNS Management
/api/dns/record: /api/v1/dns/record:
post: post:
tags: [DNS Management] tags: [DNS Management]
summary: Create DNS record summary: Create DNS record
@@ -757,7 +757,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/dns/resolve: /api/v1/dns/resolve:
get: get:
tags: [DNS Management] tags: [DNS Management]
summary: Resolve DNS summary: Resolve DNS
@@ -790,7 +790,7 @@ paths:
items: items:
type: object type: object
/api/dns/logs: /api/v1/dns/logs:
get: get:
tags: [DNS Management] tags: [DNS Management]
summary: Get DNS query logs summary: Get DNS query logs
@@ -826,7 +826,7 @@ paths:
items: items:
type: object type: object
/api/dns/token-status: /api/v1/dns/token-status:
get: get:
tags: [DNS Management] tags: [DNS Management]
summary: Check DNS token status summary: Check DNS token status
@@ -843,7 +843,7 @@ paths:
valid: valid:
type: boolean type: boolean
/api/dns/credentials: /api/v1/dns/credentials:
post: post:
tags: [DNS Management] tags: [DNS Management]
summary: Store DNS credentials summary: Store DNS credentials
@@ -894,7 +894,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/dns/refresh-token: /api/v1/dns/refresh-token:
post: post:
tags: [DNS Management] tags: [DNS Management]
summary: Refresh DNS API token summary: Refresh DNS API token
@@ -906,7 +906,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/dns/check-update: /api/v1/dns/check-update:
get: get:
tags: [DNS Management] tags: [DNS Management]
summary: Check for DNS server updates summary: Check for DNS server updates
@@ -923,7 +923,7 @@ paths:
updateAvailable: updateAvailable:
type: boolean type: boolean
/api/dns/update: /api/v1/dns/update:
post: post:
tags: [DNS Management] tags: [DNS Management]
summary: Update DNS server summary: Update DNS server
@@ -936,7 +936,7 @@ paths:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
# Services Dashboard # Services Dashboard
/api/services: /api/v1/services:
get: get:
tags: [Services Dashboard] tags: [Services Dashboard]
summary: List all services summary: List all services
@@ -992,7 +992,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/services/{id}: /api/v1/services/{id}:
delete: delete:
tags: [Services Dashboard] tags: [Services Dashboard]
summary: Delete service summary: Delete service
@@ -1010,7 +1010,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/services/update: /api/v1/services/update:
post: post:
tags: [Services Dashboard] tags: [Services Dashboard]
summary: Reorder services summary: Reorder services
@@ -1034,7 +1034,7 @@ paths:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
# Assets & Branding # Assets & Branding
/api/assets/upload: /api/v1/assets/upload:
post: post:
tags: [Assets & Branding] tags: [Assets & Branding]
summary: Upload asset file summary: Upload asset file
@@ -1061,7 +1061,7 @@ paths:
filename: filename:
type: string type: string
/api/logo: /api/v1/logo:
get: get:
tags: [Assets & Branding] tags: [Assets & Branding]
summary: Get custom logo summary: Get custom logo
@@ -1104,7 +1104,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/favicon: /api/v1/favicon:
get: get:
tags: [Assets & Branding] tags: [Assets & Branding]
summary: Get custom favicon summary: Get custom favicon
@@ -1148,7 +1148,7 @@ paths:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
# Configuration # Configuration
/api/config: /api/v1/config:
get: get:
tags: [Configuration] tags: [Configuration]
summary: Get DashCaddy config summary: Get DashCaddy config
@@ -1192,7 +1192,7 @@ paths:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
# Backup & Restore # Backup & Restore
/api/backup/export: /api/v1/backup/export:
get: get:
tags: [Backup & Restore] tags: [Backup & Restore]
summary: Export full backup summary: Export full backup
@@ -1209,7 +1209,7 @@ paths:
backup: backup:
type: object type: object
/api/backup/preview: /api/v1/backup/preview:
post: post:
tags: [Backup & Restore] tags: [Backup & Restore]
summary: Preview backup contents summary: Preview backup contents
@@ -1235,7 +1235,7 @@ paths:
preview: preview:
type: object type: object
/api/backup/restore: /api/v1/backup/restore:
post: post:
tags: [Backup & Restore] tags: [Backup & Restore]
summary: Restore from backup summary: Restore from backup
@@ -1257,7 +1257,7 @@ paths:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
# Credential Management # Credential Management
/api/credentials/list: /api/v1/credentials/list:
get: get:
tags: [Credential Management] tags: [Credential Management]
summary: List all stored credentials summary: List all stored credentials
@@ -1276,7 +1276,7 @@ paths:
items: items:
type: string type: string
/api/credentials/rotate-key: /api/v1/credentials/rotate-key:
post: post:
tags: [Credential Management] tags: [Credential Management]
summary: Rotate encryption key summary: Rotate encryption key
@@ -1289,7 +1289,7 @@ paths:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
# Arr Stack Integration # Arr Stack Integration
/api/arr/detect: /api/v1/arr/detect:
get: get:
tags: [Arr Stack Integration] tags: [Arr Stack Integration]
summary: Detect installed Arr apps summary: Detect installed Arr apps
@@ -1308,7 +1308,7 @@ paths:
items: items:
type: object type: object
/api/arr/configure-overseerr: /api/v1/arr/configure-overseerr:
post: post:
tags: [Arr Stack Integration] tags: [Arr Stack Integration]
summary: Configure Overseerr summary: Configure Overseerr
@@ -1330,7 +1330,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/arr/test-connection: /api/v1/arr/test-connection:
post: post:
tags: [Arr Stack Integration] tags: [Arr Stack Integration]
summary: Test Arr service connection summary: Test Arr service connection
@@ -1359,7 +1359,7 @@ paths:
connected: connected:
type: boolean type: boolean
/api/arr/auto-setup: /api/v1/arr/auto-setup:
post: post:
tags: [Arr Stack Integration] tags: [Arr Stack Integration]
summary: Automatic Arr stack setup summary: Automatic Arr stack setup
@@ -1371,7 +1371,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/arr/credentials: /api/v1/arr/credentials:
post: post:
tags: [Arr Stack Integration] tags: [Arr Stack Integration]
summary: Store Arr credentials summary: Store Arr credentials
@@ -1409,7 +1409,7 @@ paths:
credentials: credentials:
type: object type: object
/api/arr/credentials/{service}: /api/v1/arr/credentials/{service}:
delete: delete:
tags: [Arr Stack Integration] tags: [Arr Stack Integration]
summary: Delete Arr credentials summary: Delete Arr credentials
@@ -1427,7 +1427,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/arr/smart-detect: /api/v1/arr/smart-detect:
get: get:
tags: [Arr Stack Integration] tags: [Arr Stack Integration]
summary: Smart detection of Arr services summary: Smart detection of Arr services
@@ -1444,7 +1444,7 @@ paths:
detected: detected:
type: object type: object
/api/arr/smart-connect: /api/v1/arr/smart-connect:
post: post:
tags: [Arr Stack Integration] tags: [Arr Stack Integration]
summary: Smart connect Arr stack summary: Smart connect Arr stack
@@ -1471,7 +1471,7 @@ paths:
type: object type: object
# Plex # Plex
/api/plex/libraries: /api/v1/plex/libraries:
get: get:
tags: [Plex] tags: [Plex]
summary: Get Plex libraries summary: Get Plex libraries
@@ -1491,7 +1491,7 @@ paths:
type: object type: object
# Docker App Deployment # Docker App Deployment
/api/apps/templates: /api/v1/apps/templates:
get: get:
tags: [Docker App Deployment] tags: [Docker App Deployment]
summary: Get all app templates summary: Get all app templates
@@ -1510,7 +1510,7 @@ paths:
items: items:
$ref: '#/components/schemas/AppTemplate' $ref: '#/components/schemas/AppTemplate'
/api/apps/templates/{appId}: /api/v1/apps/templates/{appId}:
get: get:
tags: [Docker App Deployment] tags: [Docker App Deployment]
summary: Get specific template summary: Get specific template
@@ -1533,7 +1533,7 @@ paths:
template: template:
$ref: '#/components/schemas/AppTemplate' $ref: '#/components/schemas/AppTemplate'
/api/apps/check-port/{port}: /api/v1/apps/check-port/{port}:
get: get:
tags: [Docker App Deployment] tags: [Docker App Deployment]
summary: Check port availability summary: Check port availability
@@ -1556,7 +1556,7 @@ paths:
available: available:
type: boolean type: boolean
/api/apps/suggest-port/{basePort}: /api/v1/apps/suggest-port/{basePort}:
get: get:
tags: [Docker App Deployment] tags: [Docker App Deployment]
summary: Suggest next available port summary: Suggest next available port
@@ -1579,7 +1579,7 @@ paths:
port: port:
type: integer type: integer
/api/apps/check-existing: /api/v1/apps/check-existing:
post: post:
tags: [Docker App Deployment] tags: [Docker App Deployment]
summary: Check if app deployed summary: Check if app deployed
@@ -1605,7 +1605,7 @@ paths:
exists: exists:
type: boolean type: boolean
/api/apps/deploy: /api/v1/apps/deploy:
post: post:
tags: [Docker App Deployment] tags: [Docker App Deployment]
summary: Deploy Docker app summary: Deploy Docker app
@@ -1646,7 +1646,7 @@ paths:
url: url:
type: string type: string
/api/apps/{appId}: /api/v1/apps/{appId}:
delete: delete:
tags: [Docker App Deployment] tags: [Docker App Deployment]
summary: Delete deployed app summary: Delete deployed app
@@ -1664,7 +1664,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/apps/update-subdomain: /api/v1/apps/update-subdomain:
post: post:
tags: [Docker App Deployment] tags: [Docker App Deployment]
summary: Update app subdomain summary: Update app subdomain
@@ -1689,7 +1689,7 @@ paths:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
# Container Management # Container Management
/api/containers/{id}/start: /api/v1/containers/{id}/start:
post: post:
tags: [Container Management] tags: [Container Management]
summary: Start container summary: Start container
@@ -1707,7 +1707,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/containers/{id}/stop: /api/v1/containers/{id}/stop:
post: post:
tags: [Container Management] tags: [Container Management]
summary: Stop container summary: Stop container
@@ -1725,7 +1725,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/containers/{id}/restart: /api/v1/containers/{id}/restart:
post: post:
tags: [Container Management] tags: [Container Management]
summary: Restart container summary: Restart container
@@ -1743,7 +1743,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/containers/{id}/update: /api/v1/containers/{id}/update:
post: post:
tags: [Container Management] tags: [Container Management]
summary: Update container image summary: Update container image
@@ -1761,7 +1761,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/containers/{id}/check-update: /api/v1/containers/{id}/check-update:
get: get:
tags: [Container Management] tags: [Container Management]
summary: Check for container updates summary: Check for container updates
@@ -1784,7 +1784,7 @@ paths:
updateAvailable: updateAvailable:
type: boolean type: boolean
/api/containers/{id}/logs: /api/v1/containers/{id}/logs:
get: get:
tags: [Container Management] tags: [Container Management]
summary: Get container logs summary: Get container logs
@@ -1815,7 +1815,7 @@ paths:
logs: logs:
type: string type: string
/api/containers/{id}: /api/v1/containers/{id}:
delete: delete:
tags: [Container Management] tags: [Container Management]
summary: Delete container summary: Delete container
@@ -1833,7 +1833,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/containers/discover: /api/v1/containers/discover:
get: get:
tags: [Container Management] tags: [Container Management]
summary: Discover unmanaged containers summary: Discover unmanaged containers
@@ -1853,7 +1853,7 @@ paths:
type: object type: object
# Notifications # Notifications
/api/notifications/config: /api/v1/notifications/config:
get: get:
tags: [Notifications] tags: [Notifications]
summary: Get notification config summary: Get notification config
@@ -1891,7 +1891,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/notifications/test: /api/v1/notifications/test:
post: post:
tags: [Notifications] tags: [Notifications]
summary: Send test notification summary: Send test notification
@@ -1903,7 +1903,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/notifications/history: /api/v1/notifications/history:
get: get:
tags: [Notifications] tags: [Notifications]
summary: Get notification history summary: Get notification history
@@ -1932,7 +1932,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/notifications/health-check: /api/v1/notifications/health-check:
post: post:
tags: [Notifications] tags: [Notifications]
summary: Trigger health check notification summary: Trigger health check notification
@@ -1945,7 +1945,7 @@ paths:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
# Container Stats & Logs # Container Stats & Logs
/api/stats/containers: /api/v1/stats/containers:
get: get:
tags: [Container Stats & Logs] tags: [Container Stats & Logs]
summary: Get all container stats summary: Get all container stats
@@ -1964,7 +1964,7 @@ paths:
items: items:
$ref: '#/components/schemas/ContainerStats' $ref: '#/components/schemas/ContainerStats'
/api/stats/container/{id}: /api/v1/stats/container/{id}:
get: get:
tags: [Container Stats & Logs] tags: [Container Stats & Logs]
summary: Get specific container stats summary: Get specific container stats
@@ -1987,7 +1987,7 @@ paths:
stats: stats:
$ref: '#/components/schemas/ContainerStats' $ref: '#/components/schemas/ContainerStats'
/api/logs/containers: /api/v1/logs/containers:
get: get:
tags: [Container Stats & Logs] tags: [Container Stats & Logs]
summary: List containers with logs summary: List containers with logs
@@ -2006,7 +2006,7 @@ paths:
items: items:
type: object type: object
/api/logs/container/{id}: /api/v1/logs/container/{id}:
get: get:
tags: [Container Stats & Logs] tags: [Container Stats & Logs]
summary: Get container log entries summary: Get container log entries
@@ -2031,7 +2031,7 @@ paths:
items: items:
type: string type: string
/api/logs/stream/{id}: /api/v1/logs/stream/{id}:
get: get:
tags: [Container Stats & Logs] tags: [Container Stats & Logs]
summary: Stream container logs (SSE) summary: Stream container logs (SSE)
@@ -2049,7 +2049,7 @@ paths:
schema: schema:
type: string type: string
/api/logs/file: /api/v1/logs/file:
get: get:
tags: [Container Stats & Logs] tags: [Container Stats & Logs]
summary: Read native log file summary: Read native log file
@@ -2077,7 +2077,7 @@ paths:
type: string type: string
# Service Health # Service Health
/api/health/services: /api/v1/health/services:
get: get:
tags: [Service Health] tags: [Service Health]
summary: Full health check for all services summary: Full health check for all services
@@ -2096,7 +2096,7 @@ paths:
items: items:
type: object type: object
/api/health/cached: /api/v1/health/cached:
get: get:
tags: [Service Health] tags: [Service Health]
summary: Cached health results summary: Cached health results
@@ -2113,7 +2113,7 @@ paths:
cached: cached:
type: object type: object
/api/health/service/{id}: /api/v1/health/service/{id}:
get: get:
tags: [Service Health] tags: [Service Health]
summary: Health for specific service summary: Health for specific service
@@ -2137,7 +2137,7 @@ paths:
type: boolean type: boolean
# Resource Monitoring # Resource Monitoring
/api/monitoring/stats: /api/v1/monitoring/stats:
get: get:
tags: [Resource Monitoring] tags: [Resource Monitoring]
summary: All container resource stats summary: All container resource stats
@@ -2156,7 +2156,7 @@ paths:
items: items:
$ref: '#/components/schemas/ContainerStats' $ref: '#/components/schemas/ContainerStats'
/api/monitoring/stats/{containerId}: /api/v1/monitoring/stats/{containerId}:
get: get:
tags: [Resource Monitoring] tags: [Resource Monitoring]
summary: Specific container stats summary: Specific container stats
@@ -2179,7 +2179,7 @@ paths:
stats: stats:
$ref: '#/components/schemas/ContainerStats' $ref: '#/components/schemas/ContainerStats'
/api/monitoring/history/{containerId}: /api/v1/monitoring/history/{containerId}:
get: get:
tags: [Resource Monitoring] tags: [Resource Monitoring]
summary: Historical stats summary: Historical stats
@@ -2208,7 +2208,7 @@ paths:
items: items:
type: object type: object
/api/monitoring/aggregated/{containerId}: /api/v1/monitoring/aggregated/{containerId}:
get: get:
tags: [Resource Monitoring] tags: [Resource Monitoring]
summary: Aggregated stats summary: Aggregated stats
@@ -2235,7 +2235,7 @@ paths:
aggregated: aggregated:
type: object type: object
/api/monitoring/alerts/{containerId}: /api/v1/monitoring/alerts/{containerId}:
post: post:
tags: [Resource Monitoring] tags: [Resource Monitoring]
summary: Configure alerts summary: Configure alerts
@@ -2304,7 +2304,7 @@ paths:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
# Automated Backups # Automated Backups
/api/backups/config: /api/v1/backups/config:
get: get:
tags: [Automated Backups] tags: [Automated Backups]
summary: Get backup config summary: Get backup config
@@ -2344,7 +2344,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/backups/execute: /api/v1/backups/execute:
post: post:
tags: [Automated Backups] tags: [Automated Backups]
summary: Run manual backup summary: Run manual backup
@@ -2361,7 +2361,7 @@ paths:
backupId: backupId:
type: string type: string
/api/backups/history: /api/v1/backups/history:
get: get:
tags: [Automated Backups] tags: [Automated Backups]
summary: Get backup history summary: Get backup history
@@ -2380,7 +2380,7 @@ paths:
items: items:
type: object type: object
/api/backups/restore/{backupId}: /api/v1/backups/restore/{backupId}:
post: post:
tags: [Automated Backups] tags: [Automated Backups]
summary: Restore from backup summary: Restore from backup
@@ -2399,7 +2399,7 @@ paths:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
# Health Checks # Health Checks
/api/health-check/status: /api/v1/health-check/status:
get: get:
tags: [Health Checks] tags: [Health Checks]
summary: All service health status summary: All service health status
@@ -2418,7 +2418,7 @@ paths:
items: items:
type: object type: object
/api/health-check/stats/{serviceId}: /api/v1/health-check/stats/{serviceId}:
get: get:
tags: [Health Checks] tags: [Health Checks]
summary: Detailed service stats summary: Detailed service stats
@@ -2445,7 +2445,7 @@ paths:
stats: stats:
type: object type: object
/api/health-check/configure/{serviceId}: /api/v1/health-check/configure/{serviceId}:
post: post:
tags: [Health Checks] tags: [Health Checks]
summary: Configure health check summary: Configure health check
@@ -2492,7 +2492,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/health-check/incidents: /api/v1/health-check/incidents:
get: get:
tags: [Health Checks] tags: [Health Checks]
summary: Open incidents summary: Open incidents
@@ -2511,7 +2511,7 @@ paths:
items: items:
type: object type: object
/api/health-check/incidents/history: /api/v1/health-check/incidents/history:
get: get:
tags: [Health Checks] tags: [Health Checks]
summary: Incident history summary: Incident history
@@ -2536,7 +2536,7 @@ paths:
type: object type: object
# Update Management # Update Management
/api/updates/check: /api/v1/updates/check:
post: post:
tags: [Update Management] tags: [Update Management]
summary: Check for updates summary: Check for updates
@@ -2555,7 +2555,7 @@ paths:
items: items:
type: object type: object
/api/updates/available: /api/v1/updates/available:
get: get:
tags: [Update Management] tags: [Update Management]
summary: Get available updates summary: Get available updates
@@ -2574,7 +2574,7 @@ paths:
items: items:
type: object type: object
/api/updates/update/{containerId}: /api/v1/updates/update/{containerId}:
post: post:
tags: [Update Management] tags: [Update Management]
summary: Update container summary: Update container
@@ -2592,7 +2592,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/updates/rollback/{containerId}: /api/v1/updates/rollback/{containerId}:
post: post:
tags: [Update Management] tags: [Update Management]
summary: Rollback container summary: Rollback container
@@ -2610,7 +2610,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/updates/history: /api/v1/updates/history:
get: get:
tags: [Update Management] tags: [Update Management]
summary: Get update history summary: Get update history
@@ -2634,7 +2634,7 @@ paths:
items: items:
type: object type: object
/api/updates/auto-update/{containerId}: /api/v1/updates/auto-update/{containerId}:
post: post:
tags: [Update Management] tags: [Update Management]
summary: Configure auto-update summary: Configure auto-update
@@ -2661,7 +2661,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
/api/updates/schedule/{containerId}: /api/v1/updates/schedule/{containerId}:
post: post:
tags: [Update Management] tags: [Update Management]
summary: Schedule update summary: Schedule update
@@ -2690,7 +2690,7 @@ paths:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
# Error Logs # Error Logs
/api/error-logs: /api/v1/error-logs:
get: get:
tags: [Error Logs] tags: [Error Logs]
summary: View error logs summary: View error logs
@@ -2720,7 +2720,7 @@ paths:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
# Filesystem Browser # Filesystem Browser
/api/browse/roots: /api/v1/browse/roots:
get: get:
tags: [Filesystem Browser] tags: [Filesystem Browser]
summary: Get browseable roots summary: Get browseable roots
@@ -2739,7 +2739,7 @@ paths:
items: items:
type: string type: string
/api/browse/dir: /api/v1/browse/dir:
get: get:
tags: [Filesystem Browser] tags: [Filesystem Browser]
summary: Browse directory summary: Browse directory
@@ -2771,7 +2771,7 @@ paths:
size: size:
type: integer type: integer
/api/media/detected-mounts: /api/v1/media/detected-mounts:
get: get:
tags: [Filesystem Browser] tags: [Filesystem Browser]
summary: Detect media mounts summary: Detect media mounts
@@ -2791,7 +2791,7 @@ paths:
type: object type: object
# Audit Log # Audit Log
/api/audit-log: /api/v1/audit-log:
get: get:
tags: [Audit Log] tags: [Audit Log]
summary: Query audit log summary: Query audit log
@@ -2844,7 +2844,7 @@ paths:
$ref: '#/components/schemas/SuccessResponse' $ref: '#/components/schemas/SuccessResponse'
# API Documentation # API Documentation
/api/docs: /api/v1/docs:
get: get:
tags: [API Documentation] tags: [API Documentation]
summary: API docs UI summary: API docs UI
@@ -2856,7 +2856,7 @@ paths:
schema: schema:
type: string type: string
/api/docs/spec: /api/v1/docs/spec:
get: get:
tags: [API Documentation] tags: [API Documentation]
summary: OpenAPI spec summary: OpenAPI spec
+1 -1
View File
@@ -146,7 +146,7 @@ module.exports = function({ backupManager, asyncHandler }) {
}; };
for (const k of keys[provider]) { for (const k of keys[provider]) {
try { await credentialManager.delete(k); } catch (_) {} try { await credentialManager.delete(k); } catch (_) { /* ignore */ }
} }
success(res, { message: `${provider} credentials deleted` }); success(res, { message: `${provider} credentials deleted` });
+5 -6
View File
@@ -446,9 +446,8 @@ async function createApp() {
res.json({ success: true, metrics: metrics.getSummary() }); res.json({ success: true, metrics: metrics.getSummary() });
}); });
// Mount at /api/v1 (canonical) and /api (legacy) // Mount at /api/v1 (canonical, single version)
app.use('/api/v1', apiRouter); app.use('/api/v1', apiRouter);
app.use('/api', apiRouter);
// Root-level health check // Root-level health check
app.get('/health', (req, res) => { app.get('/health', (req, res) => {
@@ -548,7 +547,7 @@ async function createApp() {
}, 'probe')); }, 'probe'));
// Network IPs endpoint // Network IPs endpoint
app.get('/api/network/ips', (req, res) => { app.get('/api/v1/network/ips', (req, res) => {
try { try {
const os = require('os'); const os = require('os');
const envLan = process.env.HOST_LAN_IP; const envLan = process.env.HOST_LAN_IP;
@@ -585,7 +584,7 @@ async function createApp() {
}); });
// API Documentation // API Documentation
app.get('/api/docs', (req, res) => { app.get('/api/v1/docs', (req, res) => {
res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline' https://unpkg.com; style-src 'self' 'unsafe-inline' https://unpkg.com; img-src 'self' data: https:; connect-src 'self'; font-src 'self' data: https://unpkg.com;"); res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline' https://unpkg.com; style-src 'self' 'unsafe-inline' https://unpkg.com; img-src 'self' data: https:; connect-src 'self'; font-src 'self' data: https://unpkg.com;");
res.send(`<!DOCTYPE html> res.send(`<!DOCTYPE html>
<html lang="en"> <html lang="en">
@@ -598,12 +597,12 @@ async function createApp() {
<body> <body>
<div id="swagger-ui"></div> <div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script> <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
<script>SwaggerUIBundle({url:'/api/docs/spec',dom_id:'#swagger-ui',deepLinking:true})</script> <script>SwaggerUIBundle({url:'/api/v1/docs/spec',dom_id:'#swagger-ui',deepLinking:true})</script>
</body> </body>
</html>`); </html>`);
}); });
app.get('/api/docs/spec', boundAsyncHandler(async (req, res) => { app.get('/api/v1/docs/spec', boundAsyncHandler(async (req, res) => {
const path = require('path'); const path = require('path');
const { exists } = require('../fs-helpers'); const { exists } = require('../fs-helpers');
const fsp = require('fs').promises; const fsp = require('fs').promises;
+5 -5
View File
@@ -59,7 +59,7 @@ async function makeRequest(url, options = {}) {
} }
// Get current Caddy configuration // Get current Caddy configuration
app.get('/api/caddy/config', async (req, res) => { app.get('/api/v1/caddy/config', async (req, res) => {
try { try {
const response = await makeRequest(`${CADDY_ADMIN_API}/config/`); const response = await makeRequest(`${CADDY_ADMIN_API}/config/`);
@@ -85,7 +85,7 @@ app.get('/api/caddy/config', async (req, res) => {
}); });
// Get list of services (from apps.json + custom apps) // Get list of services (from apps.json + custom apps)
app.get('/api/services', async (req, res) => { app.get('/api/v1/services', async (req, res) => {
try { try {
const servicesPath = path.join(__dirname, '../apps.json'); const servicesPath = path.join(__dirname, '../apps.json');
@@ -184,7 +184,7 @@ async function addCaddyRoute(domain, upstreamUrl, useTls = true) {
} }
// Deploy app endpoint - handles DNS and Caddy configuration via APIs // Deploy app endpoint - handles DNS and Caddy configuration via APIs
app.post('/api/apps/deploy', async (req, res) => { app.post('/api/v1/apps/deploy', async (req, res) => {
try { try {
const { appId, config } = req.body; const { appId, config } = req.body;
const { subdomain, ip, createDns, port, sslType, dnsType } = config; const { subdomain, ip, createDns, port, sslType, dnsType } = config;
@@ -264,7 +264,7 @@ app.post('/api/apps/deploy', async (req, res) => {
}); });
// Delete app endpoint - removes DNS and Caddy configuration // Delete app endpoint - removes DNS and Caddy configuration
app.post('/api/apps/delete', async (req, res) => { app.post('/api/v1/apps/delete', async (req, res) => {
try { try {
const { domain, ip } = req.body; const { domain, ip } = req.body;
@@ -324,7 +324,7 @@ app.post('/api/apps/delete', async (req, res) => {
}); });
// Test endpoint // Test endpoint
app.get('/api/caddy/test', (req, res) => { app.get('/api/v1/caddy/test', (req, res) => {
res.json({ res.json({
status: 'success', status: 'success',
message: 'Caddy API is running', message: 'Caddy API is running',
+2 -2
View File
@@ -38,7 +38,7 @@ async function runTests() {
// Test 2: API Test Endpoint // Test 2: API Test Endpoint
console.log('\n2. Testing API test endpoint...'); console.log('\n2. Testing API test endpoint...');
try { try {
const test = await makeRequest('/api/caddy/test'); const test = await makeRequest('/api/v1/caddy/test');
if (test.status === 200) { if (test.status === 200) {
console.log(' ✓ API test passed'); console.log(' ✓ API test passed');
console.log(' Platform:', test.data.platform); console.log(' Platform:', test.data.platform);
@@ -55,7 +55,7 @@ async function runTests() {
// Test 3: Services Endpoint // Test 3: Services Endpoint
console.log('\n3. Testing services endpoint...'); console.log('\n3. Testing services endpoint...');
try { try {
const services = await makeRequest('/api/services'); const services = await makeRequest('/api/v1/services');
if (services.status === 200) { if (services.status === 200) {
console.log(' ✓ Services endpoint passed'); console.log(' ✓ Services endpoint passed');
console.log(' Found', services.data.services.length, 'services'); console.log(' Found', services.data.services.length, 'services');
+1 -1
View File
@@ -793,7 +793,7 @@
if (apiDocsBtn) { if (apiDocsBtn) {
apiDocsBtn.addEventListener('click', function() { apiDocsBtn.addEventListener('click', function() {
window.open('/api/docs', '_blank'); window.open('/api/v1/docs', '_blank');
}); });
} }
+1 -1
View File
@@ -105,7 +105,7 @@ self.addEventListener('fetch', (event) => {
const url = new URL(request.url); const url = new URL(request.url);
if (url.origin !== self.location.origin) return; if (url.origin !== self.location.origin) return;
if (url.pathname.startsWith('/api/') || url.pathname.startsWith('/probe/')) return; if (url.pathname.startsWith('/api/v1/') || url.pathname.startsWith('/probe/')) return;
if (isNavigationRequest(request) || url.pathname === '/' || url.pathname.endsWith('/index.html')) { if (isNavigationRequest(request) || url.pathname === '/' || url.pathname.endsWith('/index.html')) {
event.respondWith(networkFirst(request, event.preloadResponse)); event.respondWith(networkFirst(request, event.preloadResponse));