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:
@@ -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
@@ -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
|
||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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] || '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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` });
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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));
|
||||||
|
|||||||
Reference in New Issue
Block a user