mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 15:51:26 +00:00
feat(databases): S3-compatible backup targets — offsite archives + remote restore
Phase D3. Managed databases can ship backup archives to a configured S3-compatible destination (AWS S3, MinIO, Garage, …) while the local copy remains intact. - backup_targets table: owner-scoped endpoint/bucket/region/prefix + encrypted access/secret keys (enc:v1:, never returned, has_credentials) - create/update probe bucket access before storing; empty keys on update keep stored credentials; delete refused (409) while referenced - databases.backup_target_id assigns a target; external DBs rejected - backup pipeline uploads via a holder container over docker cp streams; remote failure keeps the local archive (logged), row gets remote:true - restore prefers the local archive; on a missing local copy it downloads the remote object into the backup volume first - holder containers must be started before archive ops — docker cp does not resolve volume mounts on never-started (Created) containers, and removing the source mid-stream stalls the reader without EOF - CLI: backup-targets list|add|update|remove|test + databases update --backup-target; MCP: 5 new tools; Settings UI card + per-database target picker + offsite badge on backups; OpenAPI + llms.txt + CLI.md Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
b7131965b1
commit
6f43bbf8f6
25 files changed
+1901
-32
No files matched your search
@@ -1773,6 +1773,40 @@ export async function testDatabaseConnectionByID(id: string): Promise<{ ok: bool
|
||||
return requestJson(`/databases/${encodeURIComponent(id)}/test-connection`, { method: 'POST' });
|
||||
}
|
||||
|
||||
export type BackupTarget = components['schemas']['BackupTarget'];
|
||||
export type BackupTargetInput = components['schemas']['BackupTargetRequest'];
|
||||
|
||||
export async function listBackupTargets(): Promise<BackupTarget[]> {
|
||||
const payload = await requestJson<{ backup_targets?: BackupTarget[] }>('/backup-targets');
|
||||
return payload.backup_targets ?? [];
|
||||
}
|
||||
|
||||
export async function createBackupTarget(input: BackupTargetInput): Promise<BackupTarget> {
|
||||
const payload = await requestJson<{ backup_target?: BackupTarget }>('/backup-targets', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
if (!payload.backup_target) {
|
||||
throw new ApiError('Backup target response is invalid', 500);
|
||||
}
|
||||
return payload.backup_target;
|
||||
}
|
||||
|
||||
export async function updateBackupTarget(id: string, input: BackupTargetInput): Promise<void> {
|
||||
await requestJson(`/backup-targets/${encodeURIComponent(id)}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteBackupTarget(id: string): Promise<void> {
|
||||
await requestJson(`/backup-targets/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
export async function testBackupTarget(id: string): Promise<{ ok: boolean; latency_ms?: number; error?: string }> {
|
||||
return requestJson(`/backup-targets/${encodeURIComponent(id)}/test`, { method: 'POST' });
|
||||
}
|
||||
|
||||
export type ConnectGitRepositoryInput = components['schemas']['ConnectGitRepoRequest'];
|
||||
export type CreateGitWebhookInput = components['schemas']['CreateWebhookRequest'];
|
||||
export type GitWebhookEntity = components['schemas']['GitWebhook'];
|
||||
|
||||
Reference in new issue
Block a user