@zincapp/znvault-cli 2.3.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/apikey.d.ts.map +1 -1
- package/dist/commands/apikey.js +350 -0
- package/dist/commands/apikey.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/dist/lib/client.d.ts +11 -1
- package/dist/lib/client.d.ts.map +1 -1
- package/dist/lib/client.js +203 -0
- package/dist/lib/client.js.map +1 -1
- package/dist/types/index.d.ts +72 -0
- package/dist/types/index.d.ts.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
package/dist/commands/backup.js
DELETED
|
@@ -1,646 +0,0 @@
|
|
|
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
|