OpenAPI spec at /api/docs with a Scalar viewer, opt-in via FEAT_API_DOCS
This commit is contained in:
251 files changed
+32497
-66
No files matched your search
@@ -36,6 +36,14 @@ function prepareDestination(dest: any, opts: { includeEnvVars: boolean }): any {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/backup/destinations - List backup destinations
|
||||
*
|
||||
* @openapi
|
||||
* summary: List all backup destinations (restic repositories); the password is stripped and cloud-credential env vars are omitted from the list view
|
||||
* description: Permission denial (403, "backups:view") is produced by the shared requireBackups route guard.
|
||||
* resp-200: Array of backup destination objects without secrets (no password, no envVars)
|
||||
*/
|
||||
export const GET: RequestHandler = async ({ cookies }) => {
|
||||
const auth = await authorize(cookies);
|
||||
const denied = await requireBackups(auth, 'view');
|
||||
@@ -46,6 +54,19 @@ export const GET: RequestHandler = async ({ cookies }) => {
|
||||
return json(destinations.map(d => prepareDestination(d, { includeEnvVars: false })));
|
||||
};
|
||||
|
||||
/**
|
||||
* POST /api/backup/destinations - Create a backup destination
|
||||
*
|
||||
* @openapi
|
||||
* summary: Create a restic backup destination, auto-initialize and test the repository, and register its default maintenance schedules
|
||||
* description: Permission denial (403, "backups:manage") is produced by the shared requireBackups route guard.
|
||||
* body: {name:string!, repository:string!, password:string!, envVars:{}, flags:string, hostPath:string, policies:string}
|
||||
* body-example: {"name":"S3 Offsite","repository":"s3:s3.amazonaws.com/my-bucket/restic","password":"***","envVars":{"AWS_ACCESS_KEY_ID":"***","AWS_SECRET_ACCESS_KEY":"***"}}
|
||||
* resp-201: The created backup destination object (includes decrypted envVars since the caller just supplied them; password is stripped)
|
||||
* resp-400: Invalid input — missing name/repository/password, unsupported/SSRF-blocked repository, invalid restic flags, or an invalid cron schedule in the policies
|
||||
* resp-409: A destination with this name already exists
|
||||
* resp-500: Failed to create the destination (persistence error)
|
||||
*/
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
const { request, cookies } = event;
|
||||
const auth = await authorize(cookies);
|
||||
|
||||
@@ -37,6 +37,17 @@ function prepareDestination(dest: any, includeSecrets: boolean): any {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/backup/destinations/{id} - Get a single backup destination
|
||||
*
|
||||
* @openapi
|
||||
* summary: Fetch a single backup destination; decrypted cloud-credential env vars are only included for callers who can manage backups, and the password is always stripped
|
||||
* description: Permission denial (403, "backups:view") is produced by the shared requireBackups route guard.
|
||||
* path: id:integer! Backup destination id (from GET /api/backup/destinations)
|
||||
* resp-200: The backup destination object (envVars included only for "backups:manage" callers; password always stripped)
|
||||
* resp-400: Invalid id (not a number)
|
||||
* resp-404: Destination not found
|
||||
*/
|
||||
export const GET: RequestHandler = async ({ params, cookies }) => {
|
||||
const auth = await authorize(cookies);
|
||||
const denied = await requireBackups(auth, 'view');
|
||||
@@ -54,6 +65,21 @@ export const GET: RequestHandler = async ({ params, cookies }) => {
|
||||
return json(prepareDestination(destination, canManage));
|
||||
};
|
||||
|
||||
/**
|
||||
* PUT /api/backup/destinations/{id} - Update a backup destination
|
||||
*
|
||||
* @openapi
|
||||
* summary: Update a backup destination, re-validating repository and flags when supplied and re-registering maintenance schedules when policies change
|
||||
* description: Permission denial (403, "backups:manage") is produced by the shared requireBackups route guard.
|
||||
* path: id:integer! Backup destination id (from GET /api/backup/destinations)
|
||||
* body: {name:string, repository:string, password:string, envVars:{}, flags:string, hostPath:string, policies:string}
|
||||
* body-example: {"name":"S3 Offsite (renamed)","policies":"{\"pruneEnabled\":true,\"pruneSchedule\":\"0 0 1 * *\"}"}
|
||||
* resp-200: The updated backup destination object (password stripped, envVars echoed back to the managing caller)
|
||||
* resp-400: Invalid input — invalid id, unsupported/SSRF-blocked repository, invalid restic flags, invalid policy cron, or switching to a local repository used by a remote-environment config
|
||||
* resp-404: Destination not found
|
||||
* resp-409: A backup using this destination is currently running, or a destination with the new name already exists
|
||||
* resp-500: Update failed (persistence error)
|
||||
*/
|
||||
export const PUT: RequestHandler = async (event) => {
|
||||
const { params, request, cookies } = event;
|
||||
const auth = await authorize(cookies);
|
||||
@@ -140,6 +166,19 @@ export const PUT: RequestHandler = async (event) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DELETE /api/backup/destinations/{id} - Delete a backup destination
|
||||
*
|
||||
* @openapi
|
||||
* summary: Delete a backup destination and unregister all its maintenance and dependent backup-config schedules
|
||||
* description: Permission denial (403, "backups:manage") is produced by the shared requireBackups route guard.
|
||||
* path: id:integer! Backup destination id (from GET /api/backup/destinations)
|
||||
* resp-200: Returns { success: true } once the destination is deleted
|
||||
* resp-200-example: {"success":true}
|
||||
* resp-400: Invalid id (not a number)
|
||||
* resp-404: Destination not found
|
||||
* resp-409: A backup using this destination is currently running — try again once it finishes
|
||||
*/
|
||||
export const DELETE: RequestHandler = async (event) => {
|
||||
const { params, cookies } = event;
|
||||
const auth = await authorize(cookies);
|
||||
|
||||
@@ -6,6 +6,19 @@ import { auditBackupDestination } from '$lib/server/audit';
|
||||
import { getBackupDestination, updateBackupDestinationTestStatus } from '$lib/server/db';
|
||||
import { initRepository } from '$lib/server/backups';
|
||||
|
||||
/**
|
||||
* POST /api/backup/destinations/{id}/init - Initialize the restic repository
|
||||
*
|
||||
* @openapi
|
||||
* summary: Initialize the restic repository for a destination and record the resulting test status
|
||||
* description: Permission denial (403, "backups:manage") is produced by the shared requireBackups route guard.
|
||||
* path: id:integer! Backup destination id (from GET /api/backup/destinations)
|
||||
* resp-200: Returns { success: true, message } when the repository is initialized
|
||||
* resp-200-example: {"success":true,"message":"Repository initialized"}
|
||||
* resp-400: Invalid id (not a number)
|
||||
* resp-404: Destination not found
|
||||
* resp-500: Repository initialization failed; returns { success: false, error }
|
||||
*/
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
const { params, cookies } = event;
|
||||
const auth = await authorize(cookies);
|
||||
|
||||
@@ -9,14 +9,18 @@ import { rotateDestinationPassword } from '$lib/server/backups';
|
||||
/**
|
||||
* Rotate the restic repository password for a destination.
|
||||
*
|
||||
* Body: { currentPassword: string, newPassword: string }
|
||||
*
|
||||
* 200 — rotated, DB updated
|
||||
* 400 — input invalid or current password wrong
|
||||
* 404 — destination not found
|
||||
* 409 — restic rotated but DB write failed (manual recovery needed; details
|
||||
* include dbOutOfSync: true)
|
||||
* 500 — restic call failed for an unrelated reason
|
||||
* @openapi
|
||||
* summary: Rotate the restic repository password for a destination and persist the new password
|
||||
* description: Permission denial (403, "backups:manage") is produced by the shared requireBackups route guard.
|
||||
* path: id:integer! Backup destination id (from GET /api/backup/destinations)
|
||||
* body: {currentPassword:string!, newPassword:string!}
|
||||
* body-example: {"currentPassword":"***","newPassword":"***"}
|
||||
* resp-200: Returns { success: true } once the password is rotated and the database is updated
|
||||
* resp-200-example: {"success":true}
|
||||
* resp-400: Invalid input — invalid id, missing passwords, or the current password is incorrect
|
||||
* resp-404: Destination not found
|
||||
* resp-409: restic rotated the key but the database write failed (manual recovery needed; the response includes dbOutOfSync:true)
|
||||
* resp-500: restic call failed for an unrelated reason
|
||||
*/
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
const { params, request, cookies } = event;
|
||||
|
||||
@@ -7,6 +7,20 @@ import { getBackupDestination, getBackupConfigs } from '$lib/server/db';
|
||||
import { runRepoTask } from '$lib/server/backups';
|
||||
import { createJobResponse } from '$lib/server/sse';
|
||||
|
||||
/**
|
||||
* POST /api/backup/destinations/{id}/task - Run a repository maintenance task
|
||||
*
|
||||
* @openapi
|
||||
* summary: Run a restic repository maintenance task (unlock, check, prune, stats, repair-index or repair-snapshots) against a destination
|
||||
* description: Permission denial (403, "backups:manage") is produced by the shared requireBackups route guard. Streamed via createJobResponse — a task failure never surfaces as an HTTP 500; it comes back as a 200 with a failed job result (JSON-preferring callers) or a job/SSE 'result' event with success:false (streaming callers).
|
||||
* path: id:integer! Backup destination id (from GET /api/backup/destinations)
|
||||
* body: {task:string!}
|
||||
* body-example: {"task":"check"}
|
||||
* resp-200: The task result object — { success: true, ... } on success, or { success: false, error } when the task itself fails
|
||||
* resp-400: Invalid input — invalid destination id, or an unknown task name (must be one of unlock, check, prune, stats, repair-index, repair-snapshots)
|
||||
* resp-403: No access to an environment whose configs use this destination (enterprise)
|
||||
* resp-404: Destination not found
|
||||
*/
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
const { params, request, cookies } = event;
|
||||
const auth = await authorize(cookies);
|
||||
|
||||
@@ -8,6 +8,18 @@ import {
|
||||
} from '$lib/server/db';
|
||||
import { testRepository } from '$lib/server/backups';
|
||||
|
||||
/**
|
||||
* POST /api/backup/destinations/{id}/test - Test a saved backup destination
|
||||
*
|
||||
* @openapi
|
||||
* summary: Test connectivity to a saved backup destination's repository and update its stored test status
|
||||
* description: Permission denial (403, "backups:manage") is produced by the shared requireBackups route guard.
|
||||
* path: id:integer! Backup destination id (from GET /api/backup/destinations)
|
||||
* resp-200: Test result — { success: true, status: "success" } when reachable, or { success: false, status: "needs_init" | "failed", error } otherwise
|
||||
* resp-200-example: {"success":true,"status":"success"}
|
||||
* resp-400: Invalid id (not a number)
|
||||
* resp-404: Destination not found
|
||||
*/
|
||||
export const POST: RequestHandler = async ({ params, cookies }) => {
|
||||
const auth = await authorize(cookies);
|
||||
const denied = await requireBackups(auth, 'manage');
|
||||
|
||||
@@ -7,6 +7,18 @@ import { getBackupDestination } from '$lib/server/db';
|
||||
import { verifyBackup } from '$lib/server/backups';
|
||||
import { createJobResponse } from '$lib/server/sse';
|
||||
|
||||
/**
|
||||
* POST /api/backup/destinations/{id}/verify - Verify backup integrity (streamed job)
|
||||
*
|
||||
* @openapi
|
||||
* summary: Verify the integrity of a destination's backups by reading a data subset, streaming progress as a Server-Sent Events job
|
||||
* description: Returns a text/event-stream that emits `progress` events and a final `result` event. Permission denial (403, "backups:manage") is produced by the shared requireBackups route guard.
|
||||
* path: id:integer! Backup destination id (from GET /api/backup/destinations)
|
||||
* body: {dataSubset:string}
|
||||
* body-example: {"dataSubset":"5%"}
|
||||
* resp-200: Server-Sent Events stream of progress and a final result event ({ success, ... })
|
||||
* resp-400: Invalid destination id (not a number)
|
||||
*/
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
const { params, request, cookies } = event;
|
||||
const auth = await authorize(cookies);
|
||||
|
||||
@@ -11,6 +11,16 @@ import { withGcsCredFile } from '$lib/server/backups/restic';
|
||||
* Test a backup destination configuration.
|
||||
* - With `destinationId`: uses saved credentials from DB
|
||||
* - Without: uses inline credentials from request body (for testing before save)
|
||||
*
|
||||
* @openapi
|
||||
* summary: Test a backup repository — either a saved destination (by destinationId) or inline credentials supplied for a not-yet-saved destination
|
||||
* description: Provide `destinationId` to test a saved destination using its stored credentials, or omit it and supply `repository`/`password`/`envVars` to test before saving. Permission denial (403, "backups:manage") is produced by the shared requireBackups route guard. destinationId from GET /api/backup/destinations.
|
||||
* body: {destinationId:integer, repository:string, password:string, envVars:{}}
|
||||
* body-example: {"repository":"s3:s3.amazonaws.com/my-bucket/restic","password":"***","envVars":{"AWS_ACCESS_KEY_ID":"***","AWS_SECRET_ACCESS_KEY":"***"}}
|
||||
* resp-200: Test result — { success: true } when the repository is reachable, or { success: false, needsInit?, error } otherwise
|
||||
* resp-200-example: {"success":true}
|
||||
* resp-400: Invalid input — missing repository/password for an inline test, or an unsupported/SSRF-blocked repository
|
||||
* resp-404: Destination not found (when testing a saved destinationId)
|
||||
*/
|
||||
export const POST: RequestHandler = async ({ request, cookies }) => {
|
||||
const auth = await authorize(cookies);
|
||||
|
||||
Reference in new issue
Block a user