mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-11 03:06:54 +00:00
feat(cloud-backup): mirror fleet snapshots to S3-compatible storage (#782)
* feat(cloud-backup): mirror fleet snapshots to S3-compatible storage
Add an Admiral-tier Cloud Backup feature that replicates every fleet
snapshot to off-site storage, with two provider modes that share the
same `@aws-sdk/client-s3` code path:
- Sencho Cloud Backup: zero-config, 500 MB allowance backed by
Cloudflare R2, provisioned via the sencho.io worker against the
user's Lemon Squeezy license.
- Custom S3 (BYOB): any S3-compatible bucket (AWS, MinIO, Backblaze
B2, Wasabi, R2 with own keys), with credentials encrypted via
`CryptoService` before storage.
API-triggered snapshots upload fire-and-forget so the UI returns
immediately; scheduled snapshots block on the upload so the task's
success/failure reflects cloud durability. Object keys include the
instance_id segment to prevent collisions when the same Admiral
license is activated on multiple Sencho instances.
* fix(cloud-backup): drop ES2022-only Error cause arg breaking ES2020 build
The backend tsconfig pins lib to ES2020. The two-argument
`Error(message, { cause })` form requires ES2022, so tsc rejected it
with TS2554. Revert to single-argument throw to match the
convention used elsewhere in the backend services.
This commit is contained in:
@@ -15,6 +15,7 @@ import { NotificationService } from './NotificationService';
|
||||
import TrivyService from './TrivyService';
|
||||
import type { ScanAllNodeImagesResult } from './TrivyService';
|
||||
import TrivyInstaller from './TrivyInstaller';
|
||||
import { CloudBackupService } from './CloudBackupService';
|
||||
|
||||
const TRIVY_UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
||||
const TRIVY_UPDATE_CHECK_STARTUP_DELAY_MS = 5 * 60 * 1000;
|
||||
@@ -501,11 +502,25 @@ export class SchedulerService {
|
||||
db.insertSnapshotFiles(snapshotId, allFiles);
|
||||
}
|
||||
|
||||
if (isDebugEnabled()) {
|
||||
console.debug(`[SchedulerService:debug] Snapshot task ${task.id}: captured ${capturedNodes.length} node(s), ${totalStacks} stack(s), ${allFiles.length} file(s), skipped ${skippedNodes.length}`);
|
||||
let cloudUploadNote = '';
|
||||
const cloudSvc = CloudBackupService.getInstance();
|
||||
if (cloudSvc.isEnabled() && cloudSvc.isAutoUploadOn()) {
|
||||
try {
|
||||
await cloudSvc.uploadSnapshot(snapshotId);
|
||||
cloudUploadNote = ', cloud upload OK';
|
||||
} catch (err) {
|
||||
const message = getErrorMessage(err, 'Cloud upload failed');
|
||||
console.error('[SchedulerService] Cloud upload failed:', message);
|
||||
this.safeDispatch('warning', 'system', `Cloud backup failed for scheduled snapshot ${snapshotId}: ${message}`);
|
||||
cloudUploadNote = ', cloud upload FAILED';
|
||||
}
|
||||
}
|
||||
|
||||
return `Fleet snapshot created (id=${snapshotId}, ${capturedNodes.length} node(s), ${totalStacks} stack(s)${skippedNodes.length > 0 ? `, ${skippedNodes.length} skipped` : ''})`;
|
||||
if (isDebugEnabled()) {
|
||||
console.debug(`[SchedulerService:debug] Snapshot task ${task.id}: captured ${capturedNodes.length} node(s), ${totalStacks} stack(s), ${allFiles.length} file(s), skipped ${skippedNodes.length}${cloudUploadNote}`);
|
||||
}
|
||||
|
||||
return `Fleet snapshot created (id=${snapshotId}, ${capturedNodes.length} node(s), ${totalStacks} stack(s)${skippedNodes.length > 0 ? `, ${skippedNodes.length} skipped` : ''}${cloudUploadNote})`;
|
||||
}
|
||||
|
||||
private async executePrune(task: ScheduledTask): Promise<string> {
|
||||
|
||||
Reference in New Issue
Block a user