@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.
@@ -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"}
@@ -1,3 +1,4 @@
1
1
  import { type Command } from 'commander';
2
2
  export declare function registerBackupCommands(program: Command): void;
3
- //# sourceMappingURL=backup.d.ts.map
3
+ export * from './types.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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