feat: add backup and restore with encrypted secrets
BackupService exports servers/profiles/projects to JSON bundle. RestoreService imports with skip/overwrite/fail conflict strategies. AES-256-GCM encryption for sensitive env vars via scrypt-derived keys. REST endpoints and CLI commands for backup/restore operations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
60
src/mcpd/src/routes/backup.ts
Normal file
60
src/mcpd/src/routes/backup.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import type { BackupService } from '../services/backup/backup-service.js';
|
||||
import type { RestoreService } from '../services/backup/restore-service.js';
|
||||
import type { BackupBundle, BackupOptions } from '../services/backup/backup-service.js';
|
||||
import type { ConflictStrategy, RestoreOptions } from '../services/backup/restore-service.js';
|
||||
|
||||
export interface BackupDeps {
|
||||
backupService: BackupService;
|
||||
restoreService: RestoreService;
|
||||
}
|
||||
|
||||
export function registerBackupRoutes(app: FastifyInstance, deps: BackupDeps): void {
|
||||
app.post<{
|
||||
Body: {
|
||||
password?: string;
|
||||
resources?: Array<'servers' | 'profiles' | 'projects'>;
|
||||
};
|
||||
}>('/api/v1/backup', async (request) => {
|
||||
const opts: BackupOptions = {};
|
||||
if (request.body?.password) {
|
||||
opts.password = request.body.password;
|
||||
}
|
||||
if (request.body?.resources) {
|
||||
opts.resources = request.body.resources;
|
||||
}
|
||||
const bundle = await deps.backupService.createBackup(opts);
|
||||
return bundle;
|
||||
});
|
||||
|
||||
app.post<{
|
||||
Body: {
|
||||
bundle: BackupBundle;
|
||||
password?: string;
|
||||
conflictStrategy?: ConflictStrategy;
|
||||
};
|
||||
}>('/api/v1/restore', async (request, reply) => {
|
||||
const { bundle, password, conflictStrategy } = request.body;
|
||||
|
||||
if (!deps.restoreService.validateBundle(bundle)) {
|
||||
reply.code(400);
|
||||
return { error: 'Invalid backup bundle format', statusCode: 400 };
|
||||
}
|
||||
|
||||
const restoreOpts: RestoreOptions = {};
|
||||
if (password) {
|
||||
restoreOpts.password = password;
|
||||
}
|
||||
if (conflictStrategy) {
|
||||
restoreOpts.conflictStrategy = conflictStrategy;
|
||||
}
|
||||
|
||||
const result = await deps.restoreService.restore(bundle, restoreOpts);
|
||||
|
||||
if (result.errors.length > 0 && result.serversCreated === 0 && result.profilesCreated === 0 && result.projectsCreated === 0) {
|
||||
reply.code(422);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user