@zincapp/znvault-cli 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +134 -0
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/backup/config.d.ts +10 -0
- package/dist/commands/backup/config.d.ts.map +1 -0
- package/dist/commands/backup/config.js +246 -0
- package/dist/commands/backup/config.js.map +1 -0
- package/dist/commands/backup/helpers.d.ts +8 -0
- package/dist/commands/backup/helpers.d.ts.map +1 -0
- package/dist/commands/backup/helpers.js +81 -0
- package/dist/commands/backup/helpers.js.map +1 -0
- package/dist/commands/{backup.d.ts → backup/index.d.ts} +2 -1
- package/dist/commands/backup/index.d.ts.map +1 -0
- package/dist/commands/backup/index.js +129 -0
- package/dist/commands/backup/index.js.map +1 -0
- package/dist/commands/backup/operations.d.ts +15 -0
- package/dist/commands/backup/operations.d.ts.map +1 -0
- package/dist/commands/backup/operations.js +390 -0
- package/dist/commands/backup/operations.js.map +1 -0
- package/dist/commands/backup/types.d.ts +144 -0
- package/dist/commands/backup/types.d.ts.map +1 -0
- package/dist/commands/backup/types.js +4 -0
- package/dist/commands/backup/types.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/commands/backup.d.ts.map +0 -1
- package/dist/commands/backup.js +0 -646
- package/dist/commands/backup.js.map +0 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Path: znvault-cli/src/commands/backup/helpers.ts
|
|
2
|
+
// Helper functions for backup CLI commands
|
|
3
|
+
export function formatDate(dateStr) {
|
|
4
|
+
if (!dateStr)
|
|
5
|
+
return '-';
|
|
6
|
+
return new Date(dateStr).toLocaleString();
|
|
7
|
+
}
|
|
8
|
+
export function formatBytes(bytes) {
|
|
9
|
+
if (bytes < 1024)
|
|
10
|
+
return `${bytes} B`;
|
|
11
|
+
if (bytes < 1024 * 1024)
|
|
12
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
13
|
+
if (bytes < 1024 * 1024 * 1024)
|
|
14
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
15
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
16
|
+
}
|
|
17
|
+
export function formatDuration(ms) {
|
|
18
|
+
if (!ms)
|
|
19
|
+
return '-';
|
|
20
|
+
if (ms < 1000)
|
|
21
|
+
return `${ms}ms`;
|
|
22
|
+
if (ms < 60000)
|
|
23
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
24
|
+
return `${(ms / 60000).toFixed(1)}m`;
|
|
25
|
+
}
|
|
26
|
+
export function formatStatus(status) {
|
|
27
|
+
const statusMap = {
|
|
28
|
+
'pending': 'Pending',
|
|
29
|
+
'completed': 'Completed',
|
|
30
|
+
'failed': 'Failed',
|
|
31
|
+
'verified': 'Verified',
|
|
32
|
+
};
|
|
33
|
+
return statusMap[status] || status;
|
|
34
|
+
}
|
|
35
|
+
export function formatAge(dateStr) {
|
|
36
|
+
if (!dateStr)
|
|
37
|
+
return '-';
|
|
38
|
+
const date = new Date(dateStr);
|
|
39
|
+
const now = new Date();
|
|
40
|
+
const diffMs = now.getTime() - date.getTime();
|
|
41
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
42
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
43
|
+
if (diffDays > 0)
|
|
44
|
+
return `${diffDays}d ago`;
|
|
45
|
+
if (diffHours > 0)
|
|
46
|
+
return `${diffHours}h ago`;
|
|
47
|
+
return 'Just now';
|
|
48
|
+
}
|
|
49
|
+
export function formatInterval(ms) {
|
|
50
|
+
const hours = Math.floor(ms / (1000 * 60 * 60));
|
|
51
|
+
const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
|
|
52
|
+
if (hours > 0 && minutes > 0)
|
|
53
|
+
return `${hours}h ${minutes}m`;
|
|
54
|
+
if (hours > 0)
|
|
55
|
+
return `${hours}h`;
|
|
56
|
+
if (minutes > 0)
|
|
57
|
+
return `${minutes}m`;
|
|
58
|
+
return `${ms}ms`;
|
|
59
|
+
}
|
|
60
|
+
export function parseInterval(interval) {
|
|
61
|
+
// Support formats: 1h, 30m, 1h30m, 3600000 (ms)
|
|
62
|
+
const regex = /^(?:(\d+)h)?(?:(\d+)m)?$/i;
|
|
63
|
+
const match = regex.exec(interval);
|
|
64
|
+
if (match !== null) {
|
|
65
|
+
// Capture groups are undefined when not matched (runtime behavior)
|
|
66
|
+
const hoursStr = match[1];
|
|
67
|
+
const minutesStr = match[2];
|
|
68
|
+
// Both groups are optional - at least one must be present for a valid interval
|
|
69
|
+
if (hoursStr ?? minutesStr) {
|
|
70
|
+
const hours = hoursStr ? parseInt(hoursStr, 10) : 0;
|
|
71
|
+
const minutes = minutesStr ? parseInt(minutesStr, 10) : 0;
|
|
72
|
+
return (hours * 60 + minutes) * 60 * 1000;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Try parsing as milliseconds
|
|
76
|
+
const ms = parseInt(interval, 10);
|
|
77
|
+
if (!isNaN(ms))
|
|
78
|
+
return ms;
|
|
79
|
+
throw new Error('Invalid interval format. Use: 1h, 30m, 1h30m, or milliseconds');
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../../src/commands/backup/helpers.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,2CAA2C;AAE3C,MAAM,UAAU,UAAU,CAAC,OAA2B;IACpD,IAAI,CAAC,OAAO;QAAE,OAAO,GAAG,CAAC;IACzB,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,cAAc,EAAE,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,GAAG,KAAK,IAAI,CAAC;IACtC,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAClE,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAClF,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,EAAW;IACxC,IAAI,CAAC,EAAE;QAAE,OAAO,GAAG,CAAC;IACpB,IAAI,EAAE,GAAG,IAAI;QAAE,OAAO,GAAG,EAAE,IAAI,CAAC;IAChC,IAAI,EAAE,GAAG,KAAK;QAAE,OAAO,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IACpD,OAAO,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,SAAS,GAA2B;QACxC,SAAS,EAAE,SAAS;QACpB,WAAW,EAAE,WAAW;QACxB,QAAQ,EAAE,QAAQ;QAClB,UAAU,EAAE,UAAU;KACvB,CAAC;IACF,OAAO,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAgB;IACxC,IAAI,CAAC,OAAO;QAAE,OAAO,GAAG,CAAC;IACzB,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;IAC9C,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,EAAE,CAAC,CAAC;IAE5C,IAAI,QAAQ,GAAG,CAAC;QAAE,OAAO,GAAG,QAAQ,OAAO,CAAC;IAC5C,IAAI,SAAS,GAAG,CAAC;QAAE,OAAO,GAAG,SAAS,OAAO,CAAC;IAC9C,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,EAAU;IACvC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC;IAElE,IAAI,KAAK,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC;QAAE,OAAO,GAAG,KAAK,KAAK,OAAO,GAAG,CAAC;IAC7D,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,GAAG,KAAK,GAAG,CAAC;IAClC,IAAI,OAAO,GAAG,CAAC;QAAE,OAAO,GAAG,OAAO,GAAG,CAAC;IACtC,OAAO,GAAG,EAAE,IAAI,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,gDAAgD;IAChD,MAAM,KAAK,GAAG,2BAA2B,CAAC;IAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,mEAAmE;QACnE,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAuB,CAAC;QAChD,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAuB,CAAC;QAClD,+EAA+E;QAC/E,IAAI,QAAQ,IAAI,UAAU,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACpD,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC1D,OAAO,CAAC,KAAK,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;QAC5C,CAAC;IACH,CAAC;IACD,8BAA8B;IAC9B,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1B,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;AACnF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/backup/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBzC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAyI7D;AAGD,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Path: znvault-cli/src/commands/backup/index.ts
|
|
2
|
+
// Command registration for backup CLI
|
|
3
|
+
import { listBackups, getBackup, createBackup, generateKey, verifyBackup, deleteBackup, restoreBackup, showStats, showHealth, } from './operations.js';
|
|
4
|
+
import { showConfig, updateConfig, configureS3Storage, configureLocalStorage, configureEncryption, testStorage, } from './config.js';
|
|
5
|
+
export function registerBackupCommands(program) {
|
|
6
|
+
const backup = program
|
|
7
|
+
.command('backup')
|
|
8
|
+
.description('Backup management');
|
|
9
|
+
// List backups
|
|
10
|
+
backup
|
|
11
|
+
.command('list')
|
|
12
|
+
.description('List all backups')
|
|
13
|
+
.option('--status <status>', 'Filter by status (pending, completed, failed, verified)')
|
|
14
|
+
.option('--limit <n>', 'Maximum number of backups to list')
|
|
15
|
+
.option('--json', 'Output as JSON')
|
|
16
|
+
.action(listBackups);
|
|
17
|
+
// Get backup
|
|
18
|
+
backup
|
|
19
|
+
.command('get <id>')
|
|
20
|
+
.description('Get backup details')
|
|
21
|
+
.option('--json', 'Output as JSON')
|
|
22
|
+
.action(getBackup);
|
|
23
|
+
// Create backup
|
|
24
|
+
backup
|
|
25
|
+
.command('create')
|
|
26
|
+
.description('Create a new backup')
|
|
27
|
+
.option('--user-key <key>', 'Base64-encoded user master key for encryption')
|
|
28
|
+
.option('--user-key-file <path>', 'Path to file containing the user master key')
|
|
29
|
+
.action(createBackup);
|
|
30
|
+
// Generate master key
|
|
31
|
+
backup
|
|
32
|
+
.command('generate-key')
|
|
33
|
+
.description('Generate a new master key for user-key backup encryption')
|
|
34
|
+
.option('--output <path>', 'Save key to file')
|
|
35
|
+
.option('--json', 'Output as JSON')
|
|
36
|
+
.action(generateKey);
|
|
37
|
+
// Verify backup
|
|
38
|
+
backup
|
|
39
|
+
.command('verify <id>')
|
|
40
|
+
.description('Verify backup integrity')
|
|
41
|
+
.action(verifyBackup);
|
|
42
|
+
// Delete backup
|
|
43
|
+
backup
|
|
44
|
+
.command('delete <id>')
|
|
45
|
+
.description('Delete a backup')
|
|
46
|
+
.option('-f, --force', 'Skip confirmation')
|
|
47
|
+
.action(deleteBackup);
|
|
48
|
+
// Restore backup
|
|
49
|
+
backup
|
|
50
|
+
.command('restore <id>')
|
|
51
|
+
.description('Restore from a backup (DESTRUCTIVE - will overwrite current database)')
|
|
52
|
+
.option('--user-key <key>', 'Base64-encoded user master key (for user-key encrypted backups)')
|
|
53
|
+
.option('--user-key-file <path>', 'Path to file containing the user master key')
|
|
54
|
+
.option('--password <password>', 'Encryption password (for password-encrypted backups)')
|
|
55
|
+
.option('--password-file <path>', 'Path to file containing the encryption password')
|
|
56
|
+
.option('--restore-lmk', 'Also restore the LMK (DANGEROUS - changes master encryption key)')
|
|
57
|
+
.option('--no-pre-backup', 'Skip creating a pre-restore backup (not recommended)')
|
|
58
|
+
.option('-f, --force', 'Skip confirmation prompts')
|
|
59
|
+
.action(restoreBackup);
|
|
60
|
+
// Show stats
|
|
61
|
+
backup
|
|
62
|
+
.command('stats')
|
|
63
|
+
.description('Show backup statistics')
|
|
64
|
+
.option('--json', 'Output as JSON')
|
|
65
|
+
.action(showStats);
|
|
66
|
+
// Show health
|
|
67
|
+
backup
|
|
68
|
+
.command('health')
|
|
69
|
+
.description('Check backup system health')
|
|
70
|
+
.option('--json', 'Output as JSON')
|
|
71
|
+
.action(showHealth);
|
|
72
|
+
// Show config
|
|
73
|
+
backup
|
|
74
|
+
.command('config')
|
|
75
|
+
.description('Show backup configuration')
|
|
76
|
+
.option('--json', 'Output as JSON')
|
|
77
|
+
.action(showConfig);
|
|
78
|
+
// Update general config
|
|
79
|
+
backup
|
|
80
|
+
.command('config-update')
|
|
81
|
+
.description('Update general backup settings')
|
|
82
|
+
.option('--enabled', 'Enable automatic backups')
|
|
83
|
+
.option('--no-enabled', 'Disable automatic backups')
|
|
84
|
+
.option('--interval <interval>', 'Backup interval (e.g., 1h, 30m, 1h30m)')
|
|
85
|
+
.option('--retention-days <n>', 'Maximum age of backups in days')
|
|
86
|
+
.option('--retention-count <n>', 'Maximum number of backups to retain')
|
|
87
|
+
.option('--json', 'Output as JSON')
|
|
88
|
+
.action(updateConfig);
|
|
89
|
+
// Storage Configuration Subcommands
|
|
90
|
+
const storage = backup
|
|
91
|
+
.command('storage')
|
|
92
|
+
.description('Configure backup storage backend');
|
|
93
|
+
// Configure S3 storage
|
|
94
|
+
storage
|
|
95
|
+
.command('s3')
|
|
96
|
+
.description('Configure S3 or S3-compatible storage (MinIO, DigitalOcean Spaces, etc.)')
|
|
97
|
+
.requiredOption('--bucket <bucket>', 'S3 bucket name')
|
|
98
|
+
.option('--region <region>', 'AWS region (default: us-east-1)')
|
|
99
|
+
.option('--prefix <prefix>', 'Key prefix for backups (default: backups/)')
|
|
100
|
+
.option('--endpoint <url>', 'Custom endpoint URL for S3-compatible storage')
|
|
101
|
+
.option('--access-key-id <key>', 'AWS access key ID (omit to use IAM role)')
|
|
102
|
+
.option('--secret-access-key <secret>', 'AWS secret access key')
|
|
103
|
+
.option('--json', 'Output as JSON')
|
|
104
|
+
.action(configureS3Storage);
|
|
105
|
+
// Configure local storage
|
|
106
|
+
storage
|
|
107
|
+
.command('local')
|
|
108
|
+
.description('Configure local filesystem storage')
|
|
109
|
+
.requiredOption('--path <path>', 'Directory path for backup storage')
|
|
110
|
+
.option('--json', 'Output as JSON')
|
|
111
|
+
.action(configureLocalStorage);
|
|
112
|
+
// Test storage connectivity
|
|
113
|
+
storage
|
|
114
|
+
.command('test')
|
|
115
|
+
.description('Test storage backend connectivity')
|
|
116
|
+
.action(testStorage);
|
|
117
|
+
// Encryption Configuration
|
|
118
|
+
backup
|
|
119
|
+
.command('encryption')
|
|
120
|
+
.description('Configure backup encryption')
|
|
121
|
+
.option('--enable', 'Enable backup encryption')
|
|
122
|
+
.option('--disable', 'Disable backup encryption')
|
|
123
|
+
.option('--password-file <path>', 'Path to file containing encryption password')
|
|
124
|
+
.option('--json', 'Output as JSON')
|
|
125
|
+
.action(configureEncryption);
|
|
126
|
+
}
|
|
127
|
+
// Re-export types for external use
|
|
128
|
+
export * from './types.js';
|
|
129
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/backup/index.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,sCAAsC;AAGtC,OAAO,EACL,WAAW,EACX,SAAS,EACT,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,SAAS,EACT,UAAU,GACX,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,UAAU,EACV,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,WAAW,GACZ,MAAM,aAAa,CAAC;AAErB,MAAM,UAAU,sBAAsB,CAAC,OAAgB;IACrD,MAAM,MAAM,GAAG,OAAO;SACnB,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,mBAAmB,CAAC,CAAC;IAEpC,eAAe;IACf,MAAM;SACH,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,kBAAkB,CAAC;SAC/B,MAAM,CAAC,mBAAmB,EAAE,yDAAyD,CAAC;SACtF,MAAM,CAAC,aAAa,EAAE,mCAAmC,CAAC;SAC1D,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,WAAW,CAAC,CAAC;IAEvB,aAAa;IACb,MAAM;SACH,OAAO,CAAC,UAAU,CAAC;SACnB,WAAW,CAAC,oBAAoB,CAAC;SACjC,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,SAAS,CAAC,CAAC;IAErB,gBAAgB;IAChB,MAAM;SACH,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,qBAAqB,CAAC;SAClC,MAAM,CAAC,kBAAkB,EAAE,+CAA+C,CAAC;SAC3E,MAAM,CAAC,wBAAwB,EAAE,6CAA6C,CAAC;SAC/E,MAAM,CAAC,YAAY,CAAC,CAAC;IAExB,sBAAsB;IACtB,MAAM;SACH,OAAO,CAAC,cAAc,CAAC;SACvB,WAAW,CAAC,0DAA0D,CAAC;SACvE,MAAM,CAAC,iBAAiB,EAAE,kBAAkB,CAAC;SAC7C,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,WAAW,CAAC,CAAC;IAEvB,gBAAgB;IAChB,MAAM;SACH,OAAO,CAAC,aAAa,CAAC;SACtB,WAAW,CAAC,yBAAyB,CAAC;SACtC,MAAM,CAAC,YAAY,CAAC,CAAC;IAExB,gBAAgB;IAChB,MAAM;SACH,OAAO,CAAC,aAAa,CAAC;SACtB,WAAW,CAAC,iBAAiB,CAAC;SAC9B,MAAM,CAAC,aAAa,EAAE,mBAAmB,CAAC;SAC1C,MAAM,CAAC,YAAY,CAAC,CAAC;IAExB,iBAAiB;IACjB,MAAM;SACH,OAAO,CAAC,cAAc,CAAC;SACvB,WAAW,CAAC,uEAAuE,CAAC;SACpF,MAAM,CAAC,kBAAkB,EAAE,iEAAiE,CAAC;SAC7F,MAAM,CAAC,wBAAwB,EAAE,6CAA6C,CAAC;SAC/E,MAAM,CAAC,uBAAuB,EAAE,sDAAsD,CAAC;SACvF,MAAM,CAAC,wBAAwB,EAAE,iDAAiD,CAAC;SACnF,MAAM,CAAC,eAAe,EAAE,kEAAkE,CAAC;SAC3F,MAAM,CAAC,iBAAiB,EAAE,sDAAsD,CAAC;SACjF,MAAM,CAAC,aAAa,EAAE,2BAA2B,CAAC;SAClD,MAAM,CAAC,aAAa,CAAC,CAAC;IAEzB,aAAa;IACb,MAAM;SACH,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,wBAAwB,CAAC;SACrC,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,SAAS,CAAC,CAAC;IAErB,cAAc;IACd,MAAM;SACH,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,4BAA4B,CAAC;SACzC,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,UAAU,CAAC,CAAC;IAEtB,cAAc;IACd,MAAM;SACH,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,2BAA2B,CAAC;SACxC,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,UAAU,CAAC,CAAC;IAEtB,wBAAwB;IACxB,MAAM;SACH,OAAO,CAAC,eAAe,CAAC;SACxB,WAAW,CAAC,gCAAgC,CAAC;SAC7C,MAAM,CAAC,WAAW,EAAE,0BAA0B,CAAC;SAC/C,MAAM,CAAC,cAAc,EAAE,2BAA2B,CAAC;SACnD,MAAM,CAAC,uBAAuB,EAAE,wCAAwC,CAAC;SACzE,MAAM,CAAC,sBAAsB,EAAE,gCAAgC,CAAC;SAChE,MAAM,CAAC,uBAAuB,EAAE,qCAAqC,CAAC;SACtE,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,YAAY,CAAC,CAAC;IAExB,oCAAoC;IACpC,MAAM,OAAO,GAAG,MAAM;SACnB,OAAO,CAAC,SAAS,CAAC;SAClB,WAAW,CAAC,kCAAkC,CAAC,CAAC;IAEnD,uBAAuB;IACvB,OAAO;SACJ,OAAO,CAAC,IAAI,CAAC;SACb,WAAW,CAAC,0EAA0E,CAAC;SACvF,cAAc,CAAC,mBAAmB,EAAE,gBAAgB,CAAC;SACrD,MAAM,CAAC,mBAAmB,EAAE,iCAAiC,CAAC;SAC9D,MAAM,CAAC,mBAAmB,EAAE,4CAA4C,CAAC;SACzE,MAAM,CAAC,kBAAkB,EAAE,+CAA+C,CAAC;SAC3E,MAAM,CAAC,uBAAuB,EAAE,0CAA0C,CAAC;SAC3E,MAAM,CAAC,8BAA8B,EAAE,uBAAuB,CAAC;SAC/D,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,kBAAkB,CAAC,CAAC;IAE9B,0BAA0B;IAC1B,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,oCAAoC,CAAC;SACjD,cAAc,CAAC,eAAe,EAAE,mCAAmC,CAAC;SACpE,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,qBAAqB,CAAC,CAAC;IAEjC,4BAA4B;IAC5B,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,mCAAmC,CAAC;SAChD,MAAM,CAAC,WAAW,CAAC,CAAC;IAEvB,2BAA2B;IAC3B,MAAM;SACH,OAAO,CAAC,YAAY,CAAC;SACrB,WAAW,CAAC,6BAA6B,CAAC;SAC1C,MAAM,CAAC,UAAU,EAAE,0BAA0B,CAAC;SAC9C,MAAM,CAAC,WAAW,EAAE,2BAA2B,CAAC;SAChD,MAAM,CAAC,wBAAwB,EAAE,6CAA6C,CAAC;SAC/E,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,mBAAmB,CAAC,CAAC;AACjC,CAAC;AAED,mCAAmC;AACnC,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ListOptions, GetOptions, DeleteOptions, RestoreOptions, CreateBackupOptions, GenerateKeyOptions } from './types.js';
|
|
2
|
+
export declare function listBackups(options: ListOptions): Promise<void>;
|
|
3
|
+
export declare function getBackup(id: string, options: GetOptions): Promise<void>;
|
|
4
|
+
export declare function createBackup(options: CreateBackupOptions): Promise<void>;
|
|
5
|
+
export declare function generateKey(options: GenerateKeyOptions): Promise<void>;
|
|
6
|
+
export declare function verifyBackup(id: string): Promise<void>;
|
|
7
|
+
export declare function deleteBackup(id: string, options: DeleteOptions): Promise<void>;
|
|
8
|
+
export declare function restoreBackup(id: string, options: RestoreOptions): Promise<void>;
|
|
9
|
+
export declare function showStats(options: {
|
|
10
|
+
json?: boolean;
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
export declare function showHealth(options: {
|
|
13
|
+
json?: boolean;
|
|
14
|
+
}): Promise<void>;
|
|
15
|
+
//# sourceMappingURL=operations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../../src/commands/backup/operations.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAKV,WAAW,EACX,UAAU,EACV,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAGnB,MAAM,YAAY,CAAC;AAEpB,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA6CrE;AAED,wBAAsB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA2C9E;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmD9E;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiC5E;AAED,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB5D;AAED,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCpF;AAED,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA2ItF;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA+B1E;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAiC3E"}
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
// Path: znvault-cli/src/commands/backup/operations.ts
|
|
2
|
+
// Backup CRUD operations
|
|
3
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import Table from 'cli-table3';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import { client } from '../../lib/client.js';
|
|
8
|
+
import * as output from '../../lib/output.js';
|
|
9
|
+
import { formatDate, formatBytes, formatDuration, formatStatus, formatAge, } from './helpers.js';
|
|
10
|
+
export async function listBackups(options) {
|
|
11
|
+
const spinner = ora('Fetching backups...').start();
|
|
12
|
+
try {
|
|
13
|
+
const query = {};
|
|
14
|
+
if (options.status)
|
|
15
|
+
query.status = options.status;
|
|
16
|
+
if (options.limit)
|
|
17
|
+
query.limit = options.limit;
|
|
18
|
+
const response = await client.get('/v1/admin/backups?' + new URLSearchParams(query).toString());
|
|
19
|
+
spinner.stop();
|
|
20
|
+
if (options.json) {
|
|
21
|
+
output.json(response.items);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (response.items.length === 0) {
|
|
25
|
+
output.info('No backups found');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const table = new Table({
|
|
29
|
+
head: ['ID', 'Status', 'Size', 'Type', 'Initiated', 'Created', 'Duration'],
|
|
30
|
+
colWidths: [38, 12, 12, 12, 12, 20, 10],
|
|
31
|
+
});
|
|
32
|
+
for (const backup of response.items) {
|
|
33
|
+
table.push([
|
|
34
|
+
backup.id,
|
|
35
|
+
formatStatus(backup.status),
|
|
36
|
+
formatBytes(backup.backupSizeBytes),
|
|
37
|
+
backup.storageType,
|
|
38
|
+
backup.initiatedBy,
|
|
39
|
+
formatAge(backup.createdAt),
|
|
40
|
+
formatDuration(backup.metadata?.duration),
|
|
41
|
+
]);
|
|
42
|
+
}
|
|
43
|
+
console.log(table.toString());
|
|
44
|
+
output.info(`Total: ${response.total} backup(s)`);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
spinner.fail('Failed to list backups');
|
|
48
|
+
output.error(error.message);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export async function getBackup(id, options) {
|
|
53
|
+
const spinner = ora('Fetching backup...').start();
|
|
54
|
+
try {
|
|
55
|
+
const backup = await client.get(`/v1/admin/backups/${id}`);
|
|
56
|
+
spinner.stop();
|
|
57
|
+
if (options.json) {
|
|
58
|
+
output.json(backup);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const table = new Table({
|
|
62
|
+
colWidths: [20, 50],
|
|
63
|
+
});
|
|
64
|
+
table.push(['ID', backup.id], ['Filename', backup.filename], ['Status', formatStatus(backup.status)], ['Storage Type', backup.storageType], ['Storage ID', backup.storageIdentifier], ['DB Size', formatBytes(backup.dbSizeBytes)], ['Backup Size', formatBytes(backup.backupSizeBytes)], ['Compression', backup.metadata?.compressionRatio ? `${(backup.metadata.compressionRatio * 100).toFixed(1)}%` : '-'], ['Encrypted', backup.encrypted ? 'Yes' : 'No'], ['Checksum', backup.checksum ?? '-'], ['Initiated By', backup.initiatedBy], ['Created', formatDate(backup.createdAt)], ['Completed', formatDate(backup.completedAt)], ['Duration', formatDuration(backup.metadata?.duration)]);
|
|
65
|
+
if (backup.verifiedAt) {
|
|
66
|
+
table.push(['Verified At', formatDate(backup.verifiedAt)]);
|
|
67
|
+
}
|
|
68
|
+
console.log(table.toString());
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
spinner.fail('Failed to get backup');
|
|
72
|
+
output.error(error.message);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export async function createBackup(options) {
|
|
77
|
+
// Get user key if provided
|
|
78
|
+
let userKey;
|
|
79
|
+
if (options.userKey) {
|
|
80
|
+
userKey = options.userKey;
|
|
81
|
+
}
|
|
82
|
+
else if (options.userKeyFile) {
|
|
83
|
+
try {
|
|
84
|
+
const keyContent = await readFile(options.userKeyFile, 'utf-8');
|
|
85
|
+
userKey = keyContent.trim();
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
output.error(`Failed to read user key file: ${error.message}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const encryptionMode = userKey ? 'user-key encrypted' : 'default';
|
|
93
|
+
const { confirm } = await inquirer.prompt([
|
|
94
|
+
{
|
|
95
|
+
type: 'confirm',
|
|
96
|
+
name: 'confirm',
|
|
97
|
+
message: `Create a new ${encryptionMode} backup now?`,
|
|
98
|
+
default: true,
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
if (!confirm) {
|
|
102
|
+
output.info('Backup cancelled');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const spinner = ora('Creating backup...').start();
|
|
106
|
+
try {
|
|
107
|
+
const body = userKey ? { userKey } : {};
|
|
108
|
+
const result = await client.post('/v1/admin/backups', body);
|
|
109
|
+
spinner.stop();
|
|
110
|
+
output.success('Backup created successfully!');
|
|
111
|
+
console.log(` ID: ${result.backup.id}`);
|
|
112
|
+
console.log(` Filename: ${result.backup.filename}`);
|
|
113
|
+
console.log(` Size: ${formatBytes(result.backup.backupSizeBytes)}`);
|
|
114
|
+
console.log(` Encrypted: ${result.backup.encrypted ? 'Yes' : 'No'}`);
|
|
115
|
+
if (result.encryptionMode) {
|
|
116
|
+
console.log(` Encryption: ${result.encryptionMode}`);
|
|
117
|
+
}
|
|
118
|
+
console.log(` Duration: ${formatDuration(result.backup.metadata?.duration)}`);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
spinner.fail('Failed to create backup');
|
|
122
|
+
output.error(error.message);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
export async function generateKey(options) {
|
|
127
|
+
const spinner = ora('Generating master key...').start();
|
|
128
|
+
try {
|
|
129
|
+
const result = await client.post('/v1/admin/backups/generate-key', {});
|
|
130
|
+
spinner.stop();
|
|
131
|
+
if (options.json) {
|
|
132
|
+
output.json(result);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// Save to file if requested
|
|
136
|
+
if (options.output) {
|
|
137
|
+
await writeFile(options.output, result.base64, 'utf-8');
|
|
138
|
+
output.success(`Master key saved to: ${options.output}`);
|
|
139
|
+
}
|
|
140
|
+
output.success('Master key generated successfully!');
|
|
141
|
+
console.log('');
|
|
142
|
+
console.log(' Key ID: ', result.keyId);
|
|
143
|
+
console.log(' Base64: ', result.base64);
|
|
144
|
+
console.log(' Hex: ', result.hex);
|
|
145
|
+
console.log('');
|
|
146
|
+
console.log(' IMPORTANT: Store this key securely in a password manager!');
|
|
147
|
+
console.log(' You will need it to restore backups encrypted with user-key mode.');
|
|
148
|
+
console.log(' If you lose this key, encrypted backups cannot be recovered.');
|
|
149
|
+
console.log('');
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
spinner.fail('Failed to generate key');
|
|
153
|
+
output.error(error.message);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
export async function verifyBackup(id) {
|
|
158
|
+
const spinner = ora('Verifying backup...').start();
|
|
159
|
+
try {
|
|
160
|
+
const result = await client.post(`/v1/admin/backups/${id}/verify`, {});
|
|
161
|
+
spinner.stop();
|
|
162
|
+
if (result.valid) {
|
|
163
|
+
output.success('Backup verification passed!');
|
|
164
|
+
console.log(` Checksum: ${result.checksum}`);
|
|
165
|
+
console.log(` Integrity: ${result.integrityCheck}`);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
output.error('Backup verification failed!');
|
|
169
|
+
console.log(` Message: ${result.message}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
spinner.fail('Failed to verify backup');
|
|
174
|
+
output.error(error.message);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
export async function deleteBackup(id, options) {
|
|
179
|
+
if (!options.force) {
|
|
180
|
+
const spinner = ora('Fetching backup...').start();
|
|
181
|
+
try {
|
|
182
|
+
const backup = await client.get(`/v1/admin/backups/${id}`);
|
|
183
|
+
spinner.stop();
|
|
184
|
+
const { confirm } = await inquirer.prompt([
|
|
185
|
+
{
|
|
186
|
+
type: 'confirm',
|
|
187
|
+
name: 'confirm',
|
|
188
|
+
message: `Delete backup "${backup.filename}" (${formatBytes(backup.backupSizeBytes)})? This cannot be undone.`,
|
|
189
|
+
default: false,
|
|
190
|
+
},
|
|
191
|
+
]);
|
|
192
|
+
if (!confirm) {
|
|
193
|
+
output.info('Deletion cancelled');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
spinner.fail('Failed to fetch backup');
|
|
199
|
+
output.error(error.message);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const deleteSpinner = ora('Deleting backup...').start();
|
|
204
|
+
try {
|
|
205
|
+
await client.delete(`/v1/admin/backups/${id}`);
|
|
206
|
+
deleteSpinner.stop();
|
|
207
|
+
output.success('Backup deleted successfully');
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
deleteSpinner.fail('Failed to delete backup');
|
|
211
|
+
output.error(error.message);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
export async function restoreBackup(id, options) {
|
|
216
|
+
// Fetch backup details first
|
|
217
|
+
const fetchSpinner = ora('Fetching backup details...').start();
|
|
218
|
+
let backup;
|
|
219
|
+
try {
|
|
220
|
+
backup = await client.get(`/v1/admin/backups/${id}`);
|
|
221
|
+
fetchSpinner.stop();
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
fetchSpinner.fail('Failed to fetch backup');
|
|
225
|
+
output.error(error.message);
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
// Get user key if provided
|
|
229
|
+
let userKey;
|
|
230
|
+
if (options.userKey) {
|
|
231
|
+
userKey = options.userKey;
|
|
232
|
+
}
|
|
233
|
+
else if (options.userKeyFile) {
|
|
234
|
+
try {
|
|
235
|
+
const keyContent = await readFile(options.userKeyFile, 'utf-8');
|
|
236
|
+
userKey = keyContent.trim();
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
output.error(`Failed to read user key file: ${error.message}`);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Get password if provided
|
|
244
|
+
let password;
|
|
245
|
+
if (options.password) {
|
|
246
|
+
password = options.password;
|
|
247
|
+
}
|
|
248
|
+
else if (options.passwordFile) {
|
|
249
|
+
try {
|
|
250
|
+
const passContent = await readFile(options.passwordFile, 'utf-8');
|
|
251
|
+
password = passContent.trim();
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
output.error(`Failed to read password file: ${error.message}`);
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Show warnings and get confirmation
|
|
259
|
+
if (!options.force) {
|
|
260
|
+
console.log('\n');
|
|
261
|
+
output.warn('WARNING: This is a DESTRUCTIVE operation!');
|
|
262
|
+
console.log('');
|
|
263
|
+
console.log(' Backup to restore:');
|
|
264
|
+
console.log(` ID: ${backup.id}`);
|
|
265
|
+
console.log(` Filename: ${backup.filename}`);
|
|
266
|
+
console.log(` Size: ${formatBytes(backup.backupSizeBytes)}`);
|
|
267
|
+
console.log(` Created: ${formatDate(backup.createdAt)}`);
|
|
268
|
+
console.log(` Encrypted: ${backup.encrypted ? 'Yes' : 'No'}`);
|
|
269
|
+
console.log('');
|
|
270
|
+
if (options.restoreLmk) {
|
|
271
|
+
output.warn('LMK RESTORE ENABLED: This will change the master encryption key!');
|
|
272
|
+
output.warn('A server restart will be REQUIRED after restore.');
|
|
273
|
+
console.log('');
|
|
274
|
+
}
|
|
275
|
+
if (!options.noPreBackup) {
|
|
276
|
+
console.log(' A pre-restore backup will be created before restoring.');
|
|
277
|
+
console.log('');
|
|
278
|
+
}
|
|
279
|
+
const confirmPhrase = `RESTORE-${backup.id.slice(0, 8)}`;
|
|
280
|
+
console.log(` To proceed, type: ${confirmPhrase}`);
|
|
281
|
+
console.log('');
|
|
282
|
+
const { confirmation } = await inquirer.prompt([
|
|
283
|
+
{
|
|
284
|
+
type: 'input',
|
|
285
|
+
name: 'confirmation',
|
|
286
|
+
message: 'Confirmation phrase:',
|
|
287
|
+
},
|
|
288
|
+
]);
|
|
289
|
+
if (confirmation !== confirmPhrase) {
|
|
290
|
+
output.error('Confirmation phrase does not match. Restore cancelled.');
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const restoreSpinner = ora('Restoring backup...').start();
|
|
295
|
+
try {
|
|
296
|
+
const confirmPhrase = `RESTORE-${backup.id.slice(0, 8)}`;
|
|
297
|
+
const body = {
|
|
298
|
+
confirmPhrase,
|
|
299
|
+
createPreRestoreBackup: !options.noPreBackup,
|
|
300
|
+
};
|
|
301
|
+
if (userKey) {
|
|
302
|
+
body.userKey = userKey;
|
|
303
|
+
}
|
|
304
|
+
if (password) {
|
|
305
|
+
body.password = password;
|
|
306
|
+
}
|
|
307
|
+
if (options.restoreLmk) {
|
|
308
|
+
body.options = { restoreLmk: true };
|
|
309
|
+
}
|
|
310
|
+
const result = await client.post(`/v1/admin/backups/${id}/restore`, body);
|
|
311
|
+
restoreSpinner.stop();
|
|
312
|
+
output.success('Backup restored successfully!');
|
|
313
|
+
console.log('');
|
|
314
|
+
console.log(` Tables restored: ${result.tablesRestored}`);
|
|
315
|
+
console.log(` LMK restored: ${result.lmkRestored ? 'Yes' : 'No'}`);
|
|
316
|
+
console.log(` Duration: ${formatDuration(result.duration)}`);
|
|
317
|
+
if (result.preRestoreBackupId) {
|
|
318
|
+
console.log(` Pre-restore backup: ${result.preRestoreBackupId}`);
|
|
319
|
+
}
|
|
320
|
+
if (result.warnings.length > 0) {
|
|
321
|
+
console.log('');
|
|
322
|
+
output.warn('Warnings:');
|
|
323
|
+
for (const warning of result.warnings) {
|
|
324
|
+
console.log(` - ${warning}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (result.lmkRestored) {
|
|
328
|
+
console.log('');
|
|
329
|
+
output.warn('IMPORTANT: Server restart is REQUIRED due to LMK change!');
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
restoreSpinner.fail('Failed to restore backup');
|
|
334
|
+
output.error(error.message);
|
|
335
|
+
process.exit(1);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
export async function showStats(options) {
|
|
339
|
+
const spinner = ora('Fetching backup stats...').start();
|
|
340
|
+
try {
|
|
341
|
+
const stats = await client.get('/v1/admin/backups/stats');
|
|
342
|
+
spinner.stop();
|
|
343
|
+
if (options.json) {
|
|
344
|
+
output.json(stats);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const table = new Table({
|
|
348
|
+
colWidths: [25, 40],
|
|
349
|
+
});
|
|
350
|
+
table.push(['Total Backups', String(stats.totalBackups)], ['Total Size', formatBytes(stats.totalSizeBytes)], ['Last Backup', formatDate(stats.lastBackup)], ['Last Successful', formatDate(stats.lastSuccessful)], ['Verified Count', String(stats.verifiedCount)], ['Failed Count', String(stats.failedCount)]);
|
|
351
|
+
console.log(table.toString());
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
spinner.fail('Failed to fetch stats');
|
|
355
|
+
output.error(error.message);
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
export async function showHealth(options) {
|
|
360
|
+
const spinner = ora('Checking backup health...').start();
|
|
361
|
+
try {
|
|
362
|
+
const health = await client.get('/v1/admin/backups/health');
|
|
363
|
+
spinner.stop();
|
|
364
|
+
if (options.json) {
|
|
365
|
+
output.json(health);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (health.healthy) {
|
|
369
|
+
output.success('Backup system is healthy');
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
output.warn('Backup system has issues');
|
|
373
|
+
}
|
|
374
|
+
console.log(` Last Backup Age: ${health.lastBackupAge ? `${health.lastBackupAge}h` : 'N/A'}`);
|
|
375
|
+
console.log(` Last Backup Status: ${health.lastBackupStatus ?? 'N/A'}`);
|
|
376
|
+
console.log(` Storage Accessible: ${health.storageAccessible ? 'Yes' : 'No'}`);
|
|
377
|
+
if (health.warnings && health.warnings.length > 0) {
|
|
378
|
+
console.log('\nWarnings:');
|
|
379
|
+
for (const warning of health.warnings) {
|
|
380
|
+
console.log(` - ${warning}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
spinner.fail('Failed to check health');
|
|
386
|
+
output.error(error.message);
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
//# sourceMappingURL=operations.js.map
|