feat(backup): Add backup system: BackupManager, DB schema, API endpoints and UI support

Introduce a complete service backup/restore subsystem with encrypted archives, database records and REST endpoints. Implements BackupManager with export/import for service config, platform resources (MongoDB, MinIO, ClickHouse), and Docker images; adds BackupRepository and migrations for backups table and include_image_in_backup; integrates backup flows into the HTTP API and the UI client; exposes backup password management and restore modes (restore/import/clone). Wire BackupManager into Onebox initialization.
This commit is contained in:
2025-11-27 13:48:11 +00:00
parent e7ade45097
commit 5cd7e7c252
13 changed files with 2013 additions and 2 deletions

View File

@@ -18,6 +18,7 @@ import type {
IDomain,
ICertificate,
ICertRequirement,
IBackup,
} from '../types.ts';
import type { TBindValue } from './types.ts';
import { logger } from '../logging.ts';
@@ -31,6 +32,7 @@ import {
AuthRepository,
MetricsRepository,
PlatformRepository,
BackupRepository,
} from './repositories/index.ts';
export class OneboxDatabase {
@@ -44,6 +46,7 @@ export class OneboxDatabase {
private authRepo!: AuthRepository;
private metricsRepo!: MetricsRepository;
private platformRepo!: PlatformRepository;
private backupRepo!: BackupRepository;
constructor(dbPath = './.nogit/onebox.db') {
this.dbPath = dbPath;
@@ -76,6 +79,7 @@ export class OneboxDatabase {
this.authRepo = new AuthRepository(queryFn);
this.metricsRepo = new MetricsRepository(queryFn);
this.platformRepo = new PlatformRepository(queryFn);
this.backupRepo = new BackupRepository(queryFn);
} catch (error) {
logger.error(`Failed to initialize database: ${getErrorMessage(error)}`);
throw error;
@@ -705,6 +709,37 @@ export class OneboxDatabase {
this.setMigrationVersion(8);
logger.success('Migration 8 completed: Certificates table now stores PEM content');
}
// Migration 9: Backup system tables
const version9 = this.getMigrationVersion();
if (version9 < 9) {
logger.info('Running migration 9: Creating backup system tables...');
// Add include_image_in_backup column to services table
this.query(`ALTER TABLE services ADD COLUMN include_image_in_backup INTEGER DEFAULT 1`);
// Create backups table
this.query(`
CREATE TABLE backups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
service_id INTEGER NOT NULL,
service_name TEXT NOT NULL,
filename TEXT NOT NULL,
size_bytes INTEGER NOT NULL,
created_at REAL NOT NULL,
includes_image INTEGER NOT NULL,
platform_resources TEXT NOT NULL DEFAULT '[]',
checksum TEXT NOT NULL,
FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE
)
`);
this.query('CREATE INDEX IF NOT EXISTS idx_backups_service ON backups(service_id)');
this.query('CREATE INDEX IF NOT EXISTS idx_backups_created ON backups(created_at DESC)');
this.setMigrationVersion(9);
logger.success('Migration 9 completed: Backup system tables created');
}
} catch (error) {
logger.error(`Migration failed: ${getErrorMessage(error)}`);
if (error instanceof Error && error.stack) {
@@ -1078,4 +1113,30 @@ export class OneboxDatabase {
deletePlatformResourcesByService(serviceId: number): void {
this.platformRepo.deletePlatformResourcesByService(serviceId);
}
// ============ Backups (delegated to repository) ============
createBackup(backup: Omit<IBackup, 'id'>): IBackup {
return this.backupRepo.create(backup);
}
getBackupById(id: number): IBackup | null {
return this.backupRepo.getById(id);
}
getBackupsByService(serviceId: number): IBackup[] {
return this.backupRepo.getByService(serviceId);
}
getAllBackups(): IBackup[] {
return this.backupRepo.getAll();
}
deleteBackup(id: number): void {
this.backupRepo.delete(id);
}
deleteBackupsByService(serviceId: number): void {
this.backupRepo.deleteByService(serviceId);
}
}