@zincapp/znvault-cli 2.1.2 → 2.3.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.
Files changed (184) hide show
  1. package/README.md +84 -0
  2. package/dist/commands/agent.d.ts +1 -1
  3. package/dist/commands/agent.d.ts.map +1 -1
  4. package/dist/commands/agent.js +71 -71
  5. package/dist/commands/agent.js.map +1 -1
  6. package/dist/commands/apikey.d.ts +1 -1
  7. package/dist/commands/apikey.d.ts.map +1 -1
  8. package/dist/commands/apikey.js +47 -85
  9. package/dist/commands/apikey.js.map +1 -1
  10. package/dist/commands/audit.d.ts +1 -1
  11. package/dist/commands/audit.d.ts.map +1 -1
  12. package/dist/commands/audit.js +2 -2
  13. package/dist/commands/audit.js.map +1 -1
  14. package/dist/commands/auth.d.ts +1 -1
  15. package/dist/commands/auth.d.ts.map +1 -1
  16. package/dist/commands/auth.js +82 -15
  17. package/dist/commands/auth.js.map +1 -1
  18. package/dist/commands/backup.d.ts +3 -0
  19. package/dist/commands/backup.d.ts.map +1 -0
  20. package/dist/commands/backup.js +646 -0
  21. package/dist/commands/backup.js.map +1 -0
  22. package/dist/commands/cert.d.ts +1 -1
  23. package/dist/commands/cert.d.ts.map +1 -1
  24. package/dist/commands/cert.js +2 -2
  25. package/dist/commands/cert.js.map +1 -1
  26. package/dist/commands/cluster.d.ts +1 -1
  27. package/dist/commands/cluster.d.ts.map +1 -1
  28. package/dist/commands/cluster.js +2 -2
  29. package/dist/commands/cluster.js.map +1 -1
  30. package/dist/commands/emergency.d.ts +1 -1
  31. package/dist/commands/emergency.d.ts.map +1 -1
  32. package/dist/commands/emergency.js +19 -18
  33. package/dist/commands/emergency.js.map +1 -1
  34. package/dist/commands/health.d.ts +1 -1
  35. package/dist/commands/health.d.ts.map +1 -1
  36. package/dist/commands/health.js +75 -77
  37. package/dist/commands/health.js.map +1 -1
  38. package/dist/commands/kms.d.ts +3 -0
  39. package/dist/commands/kms.d.ts.map +1 -0
  40. package/dist/commands/kms.js +536 -0
  41. package/dist/commands/kms.js.map +1 -0
  42. package/dist/commands/lockdown.d.ts +1 -1
  43. package/dist/commands/lockdown.d.ts.map +1 -1
  44. package/dist/commands/lockdown.js +6 -6
  45. package/dist/commands/lockdown.js.map +1 -1
  46. package/dist/commands/notification.d.ts +3 -0
  47. package/dist/commands/notification.d.ts.map +1 -0
  48. package/dist/commands/notification.js +394 -0
  49. package/dist/commands/notification.js.map +1 -0
  50. package/dist/commands/permissions.d.ts +1 -1
  51. package/dist/commands/permissions.d.ts.map +1 -1
  52. package/dist/commands/permissions.js +7 -7
  53. package/dist/commands/permissions.js.map +1 -1
  54. package/dist/commands/policy.d.ts +1 -1
  55. package/dist/commands/policy.d.ts.map +1 -1
  56. package/dist/commands/policy.js +14 -14
  57. package/dist/commands/policy.js.map +1 -1
  58. package/dist/commands/role.d.ts +3 -0
  59. package/dist/commands/role.d.ts.map +1 -0
  60. package/dist/commands/role.js +400 -0
  61. package/dist/commands/role.js.map +1 -0
  62. package/dist/commands/secret.d.ts +3 -0
  63. package/dist/commands/secret.d.ts.map +1 -0
  64. package/dist/commands/secret.js +694 -0
  65. package/dist/commands/secret.js.map +1 -0
  66. package/dist/commands/self-update.d.ts +8 -0
  67. package/dist/commands/self-update.d.ts.map +1 -0
  68. package/dist/commands/self-update.js +114 -0
  69. package/dist/commands/self-update.js.map +1 -0
  70. package/dist/commands/superadmin.d.ts +1 -1
  71. package/dist/commands/superadmin.d.ts.map +1 -1
  72. package/dist/commands/superadmin.js +3 -3
  73. package/dist/commands/superadmin.js.map +1 -1
  74. package/dist/commands/tenant.d.ts +1 -1
  75. package/dist/commands/tenant.d.ts.map +1 -1
  76. package/dist/commands/tenant.js +16 -18
  77. package/dist/commands/tenant.js.map +1 -1
  78. package/dist/commands/tui.d.ts +3 -0
  79. package/dist/commands/tui.d.ts.map +1 -0
  80. package/dist/commands/tui.js +66 -0
  81. package/dist/commands/tui.js.map +1 -0
  82. package/dist/commands/update.d.ts +1 -1
  83. package/dist/commands/update.d.ts.map +1 -1
  84. package/dist/commands/update.js +8 -8
  85. package/dist/commands/update.js.map +1 -1
  86. package/dist/commands/user.d.ts +1 -1
  87. package/dist/commands/user.d.ts.map +1 -1
  88. package/dist/commands/user.js +7 -7
  89. package/dist/commands/user.js.map +1 -1
  90. package/dist/index.js +38 -4
  91. package/dist/index.js.map +1 -1
  92. package/dist/lib/cli-update.d.ts +43 -0
  93. package/dist/lib/cli-update.d.ts.map +1 -0
  94. package/dist/lib/cli-update.js +257 -0
  95. package/dist/lib/cli-update.js.map +1 -0
  96. package/dist/lib/client.d.ts +48 -61
  97. package/dist/lib/client.d.ts.map +1 -1
  98. package/dist/lib/client.js +57 -30
  99. package/dist/lib/client.js.map +1 -1
  100. package/dist/lib/config.d.ts.map +1 -1
  101. package/dist/lib/config.js +42 -37
  102. package/dist/lib/config.js.map +1 -1
  103. package/dist/lib/db.d.ts.map +1 -1
  104. package/dist/lib/db.js +23 -23
  105. package/dist/lib/db.js.map +1 -1
  106. package/dist/lib/local.d.ts.map +1 -1
  107. package/dist/lib/local.js +9 -9
  108. package/dist/lib/local.js.map +1 -1
  109. package/dist/lib/mode.d.ts +1 -1
  110. package/dist/lib/mode.d.ts.map +1 -1
  111. package/dist/lib/mode.js +4 -7
  112. package/dist/lib/mode.js.map +1 -1
  113. package/dist/lib/output-mode.d.ts +31 -0
  114. package/dist/lib/output-mode.d.ts.map +1 -0
  115. package/dist/lib/output-mode.js +81 -0
  116. package/dist/lib/output-mode.js.map +1 -0
  117. package/dist/lib/output.d.ts +60 -5
  118. package/dist/lib/output.d.ts.map +1 -1
  119. package/dist/lib/output.js +291 -30
  120. package/dist/lib/output.js.map +1 -1
  121. package/dist/lib/prompts.d.ts +2 -2
  122. package/dist/lib/prompts.d.ts.map +1 -1
  123. package/dist/lib/prompts.js.map +1 -1
  124. package/dist/lib/visual.d.ts +71 -0
  125. package/dist/lib/visual.d.ts.map +1 -0
  126. package/dist/lib/visual.js +266 -0
  127. package/dist/lib/visual.js.map +1 -0
  128. package/dist/services/auto-update-daemon.d.ts.map +1 -1
  129. package/dist/services/auto-update-daemon.js +39 -19
  130. package/dist/services/auto-update-daemon.js.map +1 -1
  131. package/dist/services/signature-verifier.d.ts +0 -1
  132. package/dist/services/signature-verifier.d.ts.map +1 -1
  133. package/dist/services/signature-verifier.js +4 -8
  134. package/dist/services/signature-verifier.js.map +1 -1
  135. package/dist/services/update-checker.d.ts.map +1 -1
  136. package/dist/services/update-checker.js +3 -3
  137. package/dist/services/update-checker.js.map +1 -1
  138. package/dist/services/update-installer.d.ts.map +1 -1
  139. package/dist/services/update-installer.js +5 -5
  140. package/dist/services/update-installer.js.map +1 -1
  141. package/dist/tui/App.d.ts +9 -0
  142. package/dist/tui/App.d.ts.map +1 -0
  143. package/dist/tui/App.js +63 -0
  144. package/dist/tui/App.js.map +1 -0
  145. package/dist/tui/ProfileManager.d.ts +8 -0
  146. package/dist/tui/ProfileManager.d.ts.map +1 -0
  147. package/dist/tui/ProfileManager.js +226 -0
  148. package/dist/tui/ProfileManager.js.map +1 -0
  149. package/dist/tui/components/Header.d.ts +9 -0
  150. package/dist/tui/components/Header.d.ts.map +1 -0
  151. package/dist/tui/components/Header.js +9 -0
  152. package/dist/tui/components/Header.js.map +1 -0
  153. package/dist/tui/components/List.d.ts +61 -0
  154. package/dist/tui/components/List.d.ts.map +1 -0
  155. package/dist/tui/components/List.js +85 -0
  156. package/dist/tui/components/List.js.map +1 -0
  157. package/dist/tui/components/ProfileList.d.ts +25 -0
  158. package/dist/tui/components/ProfileList.d.ts.map +1 -0
  159. package/dist/tui/components/ProfileList.js +28 -0
  160. package/dist/tui/components/ProfileList.js.map +1 -0
  161. package/dist/tui/components/StatusCard.d.ts +29 -0
  162. package/dist/tui/components/StatusCard.d.ts.map +1 -0
  163. package/dist/tui/components/StatusCard.js +46 -0
  164. package/dist/tui/components/StatusCard.js.map +1 -0
  165. package/dist/tui/components/Table.d.ts +29 -0
  166. package/dist/tui/components/Table.d.ts.map +1 -0
  167. package/dist/tui/components/Table.js +131 -0
  168. package/dist/tui/components/Table.js.map +1 -0
  169. package/dist/tui/components/UpdateBanner.d.ts +9 -0
  170. package/dist/tui/components/UpdateBanner.d.ts.map +1 -0
  171. package/dist/tui/components/UpdateBanner.js +6 -0
  172. package/dist/tui/components/UpdateBanner.js.map +1 -0
  173. package/dist/tui/hooks/useApi.d.ts +75 -0
  174. package/dist/tui/hooks/useApi.d.ts.map +1 -0
  175. package/dist/tui/hooks/useApi.js +79 -0
  176. package/dist/tui/hooks/useApi.js.map +1 -0
  177. package/dist/tui/screens/Dashboard.d.ts +9 -0
  178. package/dist/tui/screens/Dashboard.d.ts.map +1 -0
  179. package/dist/tui/screens/Dashboard.js +67 -0
  180. package/dist/tui/screens/Dashboard.js.map +1 -0
  181. package/dist/types/index.d.ts +5 -5
  182. package/dist/types/index.d.ts.map +1 -1
  183. package/dist/utils/platform.js +1 -1
  184. package/package.json +30 -13
@@ -0,0 +1,646 @@
1
+ // Path: znvault-cli/src/commands/backup.ts
2
+ // CLI commands for backup management
3
+ import ora from 'ora';
4
+ import Table from 'cli-table3';
5
+ import inquirer from 'inquirer';
6
+ import { client } from '../lib/client.js';
7
+ import * as output from '../lib/output.js';
8
+ // ============================================================================
9
+ // Helper Functions
10
+ // ============================================================================
11
+ function formatDate(dateStr) {
12
+ if (!dateStr)
13
+ return '-';
14
+ return new Date(dateStr).toLocaleString();
15
+ }
16
+ function formatBytes(bytes) {
17
+ if (bytes < 1024)
18
+ return `${bytes} B`;
19
+ if (bytes < 1024 * 1024)
20
+ return `${(bytes / 1024).toFixed(1)} KB`;
21
+ if (bytes < 1024 * 1024 * 1024)
22
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
23
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
24
+ }
25
+ function formatDuration(ms) {
26
+ if (!ms)
27
+ return '-';
28
+ if (ms < 1000)
29
+ return `${ms}ms`;
30
+ if (ms < 60000)
31
+ return `${(ms / 1000).toFixed(1)}s`;
32
+ return `${(ms / 60000).toFixed(1)}m`;
33
+ }
34
+ function formatStatus(status) {
35
+ const statusMap = {
36
+ 'pending': 'Pending',
37
+ 'completed': 'Completed',
38
+ 'failed': 'Failed',
39
+ 'verified': 'Verified',
40
+ };
41
+ return statusMap[status] || status;
42
+ }
43
+ function formatAge(dateStr) {
44
+ if (!dateStr)
45
+ return '-';
46
+ const date = new Date(dateStr);
47
+ const now = new Date();
48
+ const diffMs = now.getTime() - date.getTime();
49
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
50
+ const diffDays = Math.floor(diffHours / 24);
51
+ if (diffDays > 0)
52
+ return `${diffDays}d ago`;
53
+ if (diffHours > 0)
54
+ return `${diffHours}h ago`;
55
+ return 'Just now';
56
+ }
57
+ function formatInterval(ms) {
58
+ const hours = Math.floor(ms / (1000 * 60 * 60));
59
+ const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
60
+ if (hours > 0 && minutes > 0)
61
+ return `${hours}h ${minutes}m`;
62
+ if (hours > 0)
63
+ return `${hours}h`;
64
+ if (minutes > 0)
65
+ return `${minutes}m`;
66
+ return `${ms}ms`;
67
+ }
68
+ function parseInterval(interval) {
69
+ // Support formats: 1h, 30m, 1h30m, 3600000 (ms)
70
+ const regex = /^(?:(\d+)h)?(?:(\d+)m)?$/i;
71
+ const match = regex.exec(interval);
72
+ if (match !== null) {
73
+ // Capture groups are undefined when not matched (runtime behavior)
74
+ const hoursStr = match[1];
75
+ const minutesStr = match[2];
76
+ // Both groups are optional - at least one must be present for a valid interval
77
+ if (hoursStr ?? minutesStr) {
78
+ const hours = hoursStr ? parseInt(hoursStr, 10) : 0;
79
+ const minutes = minutesStr ? parseInt(minutesStr, 10) : 0;
80
+ return (hours * 60 + minutes) * 60 * 1000;
81
+ }
82
+ }
83
+ // Try parsing as milliseconds
84
+ const ms = parseInt(interval, 10);
85
+ if (!isNaN(ms))
86
+ return ms;
87
+ throw new Error('Invalid interval format. Use: 1h, 30m, 1h30m, or milliseconds');
88
+ }
89
+ // ============================================================================
90
+ // Command Implementations
91
+ // ============================================================================
92
+ async function listBackups(options) {
93
+ const spinner = ora('Fetching backups...').start();
94
+ try {
95
+ const query = {};
96
+ if (options.status)
97
+ query.status = options.status;
98
+ if (options.limit)
99
+ query.limit = options.limit;
100
+ const response = await client.get('/v1/admin/backups?' + new URLSearchParams(query).toString());
101
+ spinner.stop();
102
+ if (options.json) {
103
+ output.json(response.items);
104
+ return;
105
+ }
106
+ if (response.items.length === 0) {
107
+ output.info('No backups found');
108
+ return;
109
+ }
110
+ const table = new Table({
111
+ head: ['ID', 'Status', 'Size', 'Type', 'Initiated', 'Created', 'Duration'],
112
+ colWidths: [38, 12, 12, 12, 12, 20, 10],
113
+ });
114
+ for (const backup of response.items) {
115
+ table.push([
116
+ backup.id,
117
+ formatStatus(backup.status),
118
+ formatBytes(backup.backupSizeBytes),
119
+ backup.storageType,
120
+ backup.initiatedBy,
121
+ formatAge(backup.createdAt),
122
+ formatDuration(backup.metadata?.duration),
123
+ ]);
124
+ }
125
+ console.log(table.toString());
126
+ output.info(`Total: ${response.total} backup(s)`);
127
+ }
128
+ catch (error) {
129
+ spinner.fail('Failed to list backups');
130
+ output.error(error.message);
131
+ process.exit(1);
132
+ }
133
+ }
134
+ async function getBackup(id, options) {
135
+ const spinner = ora('Fetching backup...').start();
136
+ try {
137
+ const backup = await client.get(`/v1/admin/backups/${id}`);
138
+ spinner.stop();
139
+ if (options.json) {
140
+ output.json(backup);
141
+ return;
142
+ }
143
+ const table = new Table({
144
+ colWidths: [20, 50],
145
+ });
146
+ 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)]);
147
+ if (backup.verifiedAt) {
148
+ table.push(['Verified At', formatDate(backup.verifiedAt)]);
149
+ }
150
+ console.log(table.toString());
151
+ }
152
+ catch (error) {
153
+ spinner.fail('Failed to get backup');
154
+ output.error(error.message);
155
+ process.exit(1);
156
+ }
157
+ }
158
+ async function createBackup() {
159
+ const { confirm } = await inquirer.prompt([
160
+ {
161
+ type: 'confirm',
162
+ name: 'confirm',
163
+ message: 'Create a new backup now?',
164
+ default: true,
165
+ },
166
+ ]);
167
+ if (!confirm) {
168
+ output.info('Backup cancelled');
169
+ return;
170
+ }
171
+ const spinner = ora('Creating backup...').start();
172
+ try {
173
+ const result = await client.post('/v1/admin/backups', {});
174
+ spinner.stop();
175
+ output.success('Backup created successfully!');
176
+ console.log(` ID: ${result.backup.id}`);
177
+ console.log(` Filename: ${result.backup.filename}`);
178
+ console.log(` Size: ${formatBytes(result.backup.backupSizeBytes)}`);
179
+ console.log(` Duration: ${formatDuration(result.backup.metadata?.duration)}`);
180
+ }
181
+ catch (error) {
182
+ spinner.fail('Failed to create backup');
183
+ output.error(error.message);
184
+ process.exit(1);
185
+ }
186
+ }
187
+ async function verifyBackup(id) {
188
+ const spinner = ora('Verifying backup...').start();
189
+ try {
190
+ const result = await client.post(`/v1/admin/backups/${id}/verify`, {});
191
+ spinner.stop();
192
+ if (result.valid) {
193
+ output.success('Backup verification passed!');
194
+ console.log(` Checksum: ${result.checksum}`);
195
+ console.log(` Integrity: ${result.integrityCheck}`);
196
+ }
197
+ else {
198
+ output.error('Backup verification failed!');
199
+ console.log(` Message: ${result.message}`);
200
+ }
201
+ }
202
+ catch (error) {
203
+ spinner.fail('Failed to verify backup');
204
+ output.error(error.message);
205
+ process.exit(1);
206
+ }
207
+ }
208
+ async function deleteBackup(id, options) {
209
+ if (!options.force) {
210
+ const spinner = ora('Fetching backup...').start();
211
+ try {
212
+ const backup = await client.get(`/v1/admin/backups/${id}`);
213
+ spinner.stop();
214
+ const { confirm } = await inquirer.prompt([
215
+ {
216
+ type: 'confirm',
217
+ name: 'confirm',
218
+ message: `Delete backup "${backup.filename}" (${formatBytes(backup.backupSizeBytes)})? This cannot be undone.`,
219
+ default: false,
220
+ },
221
+ ]);
222
+ if (!confirm) {
223
+ output.info('Deletion cancelled');
224
+ return;
225
+ }
226
+ }
227
+ catch (error) {
228
+ spinner.fail('Failed to fetch backup');
229
+ output.error(error.message);
230
+ process.exit(1);
231
+ }
232
+ }
233
+ const deleteSpinner = ora('Deleting backup...').start();
234
+ try {
235
+ await client.delete(`/v1/admin/backups/${id}`);
236
+ deleteSpinner.stop();
237
+ output.success('Backup deleted successfully');
238
+ }
239
+ catch (error) {
240
+ deleteSpinner.fail('Failed to delete backup');
241
+ output.error(error.message);
242
+ process.exit(1);
243
+ }
244
+ }
245
+ async function showStats(options) {
246
+ const spinner = ora('Fetching backup stats...').start();
247
+ try {
248
+ const stats = await client.get('/v1/admin/backups/stats');
249
+ spinner.stop();
250
+ if (options.json) {
251
+ output.json(stats);
252
+ return;
253
+ }
254
+ const table = new Table({
255
+ colWidths: [25, 40],
256
+ });
257
+ 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)]);
258
+ console.log(table.toString());
259
+ }
260
+ catch (error) {
261
+ spinner.fail('Failed to fetch stats');
262
+ output.error(error.message);
263
+ process.exit(1);
264
+ }
265
+ }
266
+ async function showHealth(options) {
267
+ const spinner = ora('Checking backup health...').start();
268
+ try {
269
+ const health = await client.get('/v1/admin/backups/health');
270
+ spinner.stop();
271
+ if (options.json) {
272
+ output.json(health);
273
+ return;
274
+ }
275
+ if (health.healthy) {
276
+ output.success('Backup system is healthy');
277
+ }
278
+ else {
279
+ output.warn('Backup system has issues');
280
+ }
281
+ console.log(` Last Backup Age: ${health.lastBackupAge ? `${health.lastBackupAge}h` : 'N/A'}`);
282
+ console.log(` Last Backup Status: ${health.lastBackupStatus ?? 'N/A'}`);
283
+ console.log(` Storage Accessible: ${health.storageAccessible ? 'Yes' : 'No'}`);
284
+ if (health.warnings && health.warnings.length > 0) {
285
+ console.log('\nWarnings:');
286
+ for (const warning of health.warnings) {
287
+ console.log(` - ${warning}`);
288
+ }
289
+ }
290
+ }
291
+ catch (error) {
292
+ spinner.fail('Failed to check health');
293
+ output.error(error.message);
294
+ process.exit(1);
295
+ }
296
+ }
297
+ async function showConfig(options) {
298
+ const spinner = ora('Fetching backup config...').start();
299
+ try {
300
+ const config = await client.get('/v1/admin/backups/config');
301
+ spinner.stop();
302
+ if (options.json) {
303
+ output.json(config);
304
+ return;
305
+ }
306
+ // General settings
307
+ console.log('\n GENERAL SETTINGS\n');
308
+ const generalTable = new Table({
309
+ colWidths: [25, 45],
310
+ style: { head: [], border: [] },
311
+ });
312
+ generalTable.push(['Enabled', config.enabled ? 'Yes' : 'No'], ['Interval', formatInterval(config.intervalMs)], ['Retention (days)', config.retentionDays.toString()], ['Retention (count)', config.retentionCount.toString()]);
313
+ console.log(generalTable.toString());
314
+ // Storage settings
315
+ console.log('\n STORAGE CONFIGURATION\n');
316
+ const storageTable = new Table({
317
+ colWidths: [25, 45],
318
+ style: { head: [], border: [] },
319
+ });
320
+ const storageType = config.storage?.type ?? 'local';
321
+ storageTable.push(['Type', storageType.toUpperCase()]);
322
+ if (storageType === 'local') {
323
+ storageTable.push(['Path', config.storage?.local?.path ?? 'data/backups']);
324
+ }
325
+ else if (storageType === 's3') {
326
+ const s3 = config.storage?.s3;
327
+ storageTable.push(['Bucket', s3?.bucket ?? '-']);
328
+ storageTable.push(['Region', s3?.region ?? 'us-east-1']);
329
+ storageTable.push(['Prefix', s3?.prefix ?? 'backups/']);
330
+ if (s3?.endpoint) {
331
+ storageTable.push(['Endpoint', s3.endpoint]);
332
+ }
333
+ storageTable.push(['Credentials', s3?.hasCredentials ? 'Configured' : 'Using IAM Role']);
334
+ }
335
+ else {
336
+ // SFTP storage
337
+ const sftp = config.storage?.sftp;
338
+ storageTable.push(['Host', sftp?.host ?? '-']);
339
+ storageTable.push(['Port', sftp?.port?.toString() ?? '22']);
340
+ storageTable.push(['Username', sftp?.username ?? '-']);
341
+ storageTable.push(['Remote Path', sftp?.remotePath ?? '-']);
342
+ storageTable.push(['Credentials', sftp?.hasCredentials ? 'Configured' : 'Not configured']);
343
+ }
344
+ console.log(storageTable.toString());
345
+ // Encryption settings
346
+ console.log('\n ENCRYPTION\n');
347
+ const encryptionTable = new Table({
348
+ colWidths: [25, 45],
349
+ style: { head: [], border: [] },
350
+ });
351
+ encryptionTable.push(['Enabled', config.encryption?.enabled ? 'Yes' : 'No'], ['Password File', config.encryption?.hasPassword ? 'Configured' : 'Not configured']);
352
+ console.log(encryptionTable.toString());
353
+ console.log();
354
+ }
355
+ catch (error) {
356
+ spinner.fail('Failed to fetch config');
357
+ output.error(error.message);
358
+ process.exit(1);
359
+ }
360
+ }
361
+ async function updateConfig(options) {
362
+ const body = {};
363
+ if (options.enabled !== undefined)
364
+ body.enabled = options.enabled;
365
+ if (options.interval) {
366
+ try {
367
+ body.intervalMs = parseInterval(options.interval);
368
+ }
369
+ catch (err) {
370
+ output.error(err.message);
371
+ process.exit(1);
372
+ }
373
+ }
374
+ if (options.retentionDays)
375
+ body.retentionDays = parseInt(options.retentionDays, 10);
376
+ if (options.retentionCount)
377
+ body.retentionCount = parseInt(options.retentionCount, 10);
378
+ if (Object.keys(body).length === 0) {
379
+ output.info('No changes specified. Use --help to see available options.');
380
+ return;
381
+ }
382
+ const spinner = ora('Updating backup config...').start();
383
+ try {
384
+ const result = await client.put('/v1/admin/backups/config', body);
385
+ spinner.stop();
386
+ if (options.json) {
387
+ output.json(result.config);
388
+ return;
389
+ }
390
+ output.success('Backup configuration updated');
391
+ console.log(` Enabled: ${result.config.enabled ? 'Yes' : 'No'}`);
392
+ console.log(` Interval: ${formatInterval(result.config.intervalMs)}`);
393
+ console.log(` Retention Days: ${result.config.retentionDays}`);
394
+ console.log(` Retention Count: ${result.config.retentionCount}`);
395
+ }
396
+ catch (error) {
397
+ spinner.fail('Failed to update config');
398
+ output.error(error.message);
399
+ process.exit(1);
400
+ }
401
+ }
402
+ // ============================================================================
403
+ // Storage Configuration Commands
404
+ // ============================================================================
405
+ async function configureS3Storage(options) {
406
+ const body = {
407
+ storage: {
408
+ type: 's3',
409
+ s3: {
410
+ bucket: options.bucket,
411
+ region: options.region ?? 'us-east-1',
412
+ prefix: options.prefix ?? 'backups/',
413
+ endpoint: options.endpoint,
414
+ accessKeyId: options.accessKeyId,
415
+ secretAccessKey: options.secretAccessKey,
416
+ },
417
+ },
418
+ };
419
+ const spinner = ora('Configuring S3 storage...').start();
420
+ try {
421
+ const result = await client.put('/v1/admin/backups/config', body);
422
+ spinner.stop();
423
+ if (options.json) {
424
+ output.json(result.config);
425
+ return;
426
+ }
427
+ output.success('S3 storage configured successfully');
428
+ console.log(` Bucket: ${options.bucket}`);
429
+ console.log(` Region: ${options.region ?? 'us-east-1'}`);
430
+ console.log(` Prefix: ${options.prefix ?? 'backups/'}`);
431
+ if (options.endpoint) {
432
+ console.log(` Endpoint: ${options.endpoint}`);
433
+ }
434
+ console.log(` Credentials: ${options.accessKeyId ? 'Provided' : 'Using IAM Role'}`);
435
+ if (result.storageBackendUpdated) {
436
+ console.log('\n Storage backend updated and ready to use.');
437
+ }
438
+ }
439
+ catch (error) {
440
+ spinner.fail('Failed to configure S3 storage');
441
+ output.error(error.message);
442
+ process.exit(1);
443
+ }
444
+ }
445
+ async function configureLocalStorage(options) {
446
+ const body = {
447
+ storage: {
448
+ type: 'local',
449
+ local: {
450
+ path: options.path,
451
+ },
452
+ },
453
+ };
454
+ const spinner = ora('Configuring local storage...').start();
455
+ try {
456
+ const result = await client.put('/v1/admin/backups/config', body);
457
+ spinner.stop();
458
+ if (options.json) {
459
+ output.json(result.config);
460
+ return;
461
+ }
462
+ output.success('Local storage configured successfully');
463
+ console.log(` Path: ${options.path}`);
464
+ if (result.storageBackendUpdated) {
465
+ console.log('\n Storage backend updated and ready to use.');
466
+ }
467
+ }
468
+ catch (error) {
469
+ spinner.fail('Failed to configure local storage');
470
+ output.error(error.message);
471
+ process.exit(1);
472
+ }
473
+ }
474
+ async function configureEncryption(options) {
475
+ if (options.enable === undefined && options.disable === undefined && !options.passwordFile) {
476
+ output.info('No changes specified. Use --enable, --disable, or --password-file');
477
+ return;
478
+ }
479
+ const body = {
480
+ encryption: {},
481
+ };
482
+ if (options.enable) {
483
+ body.encryption.enabled = true;
484
+ }
485
+ else if (options.disable) {
486
+ body.encryption.enabled = false;
487
+ }
488
+ if (options.passwordFile) {
489
+ body.encryption.passwordFile = options.passwordFile;
490
+ // Enabling encryption if password file is provided
491
+ if (options.enable === undefined && options.disable === undefined) {
492
+ body.encryption.enabled = true;
493
+ }
494
+ }
495
+ const spinner = ora('Configuring encryption...').start();
496
+ try {
497
+ const result = await client.put('/v1/admin/backups/config', body);
498
+ spinner.stop();
499
+ if (options.json) {
500
+ output.json(result.config);
501
+ return;
502
+ }
503
+ output.success('Encryption configuration updated');
504
+ console.log(` Enabled: ${result.config.encryption?.enabled ? 'Yes' : 'No'}`);
505
+ console.log(` Password File: ${result.config.encryption?.hasPassword ? 'Configured' : 'Not configured'}`);
506
+ }
507
+ catch (error) {
508
+ spinner.fail('Failed to configure encryption');
509
+ output.error(error.message);
510
+ process.exit(1);
511
+ }
512
+ }
513
+ async function testStorage() {
514
+ const spinner = ora('Testing storage backend...').start();
515
+ try {
516
+ const health = await client.get('/v1/admin/backups/health');
517
+ spinner.stop();
518
+ if (health.storageAccessible) {
519
+ output.success('Storage backend is accessible and working');
520
+ }
521
+ else {
522
+ output.error('Storage backend is not accessible');
523
+ if (health.warnings && health.warnings.length > 0) {
524
+ console.log('\nIssues:');
525
+ for (const warning of health.warnings) {
526
+ console.log(` - ${warning}`);
527
+ }
528
+ }
529
+ process.exit(1);
530
+ }
531
+ }
532
+ catch (error) {
533
+ spinner.fail('Failed to test storage');
534
+ output.error(error.message);
535
+ process.exit(1);
536
+ }
537
+ }
538
+ // ============================================================================
539
+ // Command Registration
540
+ // ============================================================================
541
+ export function registerBackupCommands(program) {
542
+ const backup = program
543
+ .command('backup')
544
+ .description('Backup management');
545
+ // List backups
546
+ backup
547
+ .command('list')
548
+ .description('List all backups')
549
+ .option('--status <status>', 'Filter by status (pending, completed, failed, verified)')
550
+ .option('--limit <n>', 'Maximum number of backups to list')
551
+ .option('--json', 'Output as JSON')
552
+ .action(listBackups);
553
+ // Get backup
554
+ backup
555
+ .command('get <id>')
556
+ .description('Get backup details')
557
+ .option('--json', 'Output as JSON')
558
+ .action(getBackup);
559
+ // Create backup
560
+ backup
561
+ .command('create')
562
+ .description('Create a new backup')
563
+ .action(createBackup);
564
+ // Verify backup
565
+ backup
566
+ .command('verify <id>')
567
+ .description('Verify backup integrity')
568
+ .action(verifyBackup);
569
+ // Delete backup
570
+ backup
571
+ .command('delete <id>')
572
+ .description('Delete a backup')
573
+ .option('-f, --force', 'Skip confirmation')
574
+ .action(deleteBackup);
575
+ // Show stats
576
+ backup
577
+ .command('stats')
578
+ .description('Show backup statistics')
579
+ .option('--json', 'Output as JSON')
580
+ .action(showStats);
581
+ // Show health
582
+ backup
583
+ .command('health')
584
+ .description('Check backup system health')
585
+ .option('--json', 'Output as JSON')
586
+ .action(showHealth);
587
+ // Show config
588
+ backup
589
+ .command('config')
590
+ .description('Show backup configuration')
591
+ .option('--json', 'Output as JSON')
592
+ .action(showConfig);
593
+ // Update general config
594
+ backup
595
+ .command('config-update')
596
+ .description('Update general backup settings')
597
+ .option('--enabled', 'Enable automatic backups')
598
+ .option('--no-enabled', 'Disable automatic backups')
599
+ .option('--interval <interval>', 'Backup interval (e.g., 1h, 30m, 1h30m)')
600
+ .option('--retention-days <n>', 'Maximum age of backups in days')
601
+ .option('--retention-count <n>', 'Maximum number of backups to retain')
602
+ .option('--json', 'Output as JSON')
603
+ .action(updateConfig);
604
+ // ============================================================================
605
+ // Storage Configuration Subcommands
606
+ // ============================================================================
607
+ const storage = backup
608
+ .command('storage')
609
+ .description('Configure backup storage backend');
610
+ // Configure S3 storage
611
+ storage
612
+ .command('s3')
613
+ .description('Configure S3 or S3-compatible storage (MinIO, DigitalOcean Spaces, etc.)')
614
+ .requiredOption('--bucket <bucket>', 'S3 bucket name')
615
+ .option('--region <region>', 'AWS region (default: us-east-1)')
616
+ .option('--prefix <prefix>', 'Key prefix for backups (default: backups/)')
617
+ .option('--endpoint <url>', 'Custom endpoint URL for S3-compatible storage')
618
+ .option('--access-key-id <key>', 'AWS access key ID (omit to use IAM role)')
619
+ .option('--secret-access-key <secret>', 'AWS secret access key')
620
+ .option('--json', 'Output as JSON')
621
+ .action(configureS3Storage);
622
+ // Configure local storage
623
+ storage
624
+ .command('local')
625
+ .description('Configure local filesystem storage')
626
+ .requiredOption('--path <path>', 'Directory path for backup storage')
627
+ .option('--json', 'Output as JSON')
628
+ .action(configureLocalStorage);
629
+ // Test storage connectivity
630
+ storage
631
+ .command('test')
632
+ .description('Test storage backend connectivity')
633
+ .action(testStorage);
634
+ // ============================================================================
635
+ // Encryption Configuration
636
+ // ============================================================================
637
+ backup
638
+ .command('encryption')
639
+ .description('Configure backup encryption')
640
+ .option('--enable', 'Enable backup encryption')
641
+ .option('--disable', 'Disable backup encryption')
642
+ .option('--password-file <path>', 'Path to file containing encryption password')
643
+ .option('--json', 'Output as JSON')
644
+ .action(configureEncryption);
645
+ }
646
+ //# sourceMappingURL=backup.js.map