ante-erp-cli 1.11.27 → 1.11.28
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/bin/ante-cli.js +59 -0
- package/package.json +3 -1
- package/src/commands/daily-backup-runner.js +173 -0
- package/src/commands/daily-backup.js +600 -0
- package/src/commands/restore.js +199 -16
- package/src/utils/crypto.js +151 -0
- package/src/utils/s3-client.js +217 -0
package/bin/ante-cli.js
CHANGED
|
@@ -38,6 +38,15 @@ import { exposeDbPorts, secureDbPorts, dbPortStatus } from '../src/commands/data
|
|
|
38
38
|
import { setDomain } from '../src/commands/set-domain.js';
|
|
39
39
|
import { sslEnable, sslStatus } from '../src/commands/ssl-enable.js';
|
|
40
40
|
import { regenerateCompose } from '../src/commands/regenerate-compose.js';
|
|
41
|
+
import {
|
|
42
|
+
setDailyBackup,
|
|
43
|
+
disableDailyBackup,
|
|
44
|
+
dailyBackupStatus,
|
|
45
|
+
dailyBackupRun,
|
|
46
|
+
dailyBackupLogs,
|
|
47
|
+
dailyBackupList,
|
|
48
|
+
dailyBackupClean
|
|
49
|
+
} from '../src/commands/daily-backup.js';
|
|
41
50
|
|
|
42
51
|
// Installation & Setup
|
|
43
52
|
program
|
|
@@ -111,6 +120,56 @@ program
|
|
|
111
120
|
.option('--force', 'Skip confirmation prompts')
|
|
112
121
|
.action(restore);
|
|
113
122
|
|
|
123
|
+
// Daily Automated Backups
|
|
124
|
+
program
|
|
125
|
+
.command('set-daily-backup')
|
|
126
|
+
.description('Configure automated daily backups to S3/DigitalOcean Spaces')
|
|
127
|
+
.option('--endpoint <url>', 'S3 endpoint URL')
|
|
128
|
+
.option('--region <region>', 'S3 region', 'sgp1')
|
|
129
|
+
.option('--bucket <name>', 'S3 bucket name')
|
|
130
|
+
.option('--access-key <key>', 'S3 access key')
|
|
131
|
+
.option('--secret-key <key>', 'S3 secret key')
|
|
132
|
+
.option('--schedule <cron>', 'Cron schedule', '0 3 * * *')
|
|
133
|
+
.option('--retention <days>', 'Retention period in days', '30')
|
|
134
|
+
.option('--folder-prefix <name>', 'Custom folder prefix (default: server IP)')
|
|
135
|
+
.option('--test', 'Run test backup immediately after setup')
|
|
136
|
+
.action(setDailyBackup);
|
|
137
|
+
|
|
138
|
+
program
|
|
139
|
+
.command('disable-daily-backup')
|
|
140
|
+
.description('Disable automated daily backups')
|
|
141
|
+
.action(disableDailyBackup);
|
|
142
|
+
|
|
143
|
+
const dailyBackupCmd = program
|
|
144
|
+
.command('daily-backup')
|
|
145
|
+
.description('Manage daily backup operations');
|
|
146
|
+
|
|
147
|
+
dailyBackupCmd
|
|
148
|
+
.command('status')
|
|
149
|
+
.description('Show daily backup status and configuration')
|
|
150
|
+
.action(dailyBackupStatus);
|
|
151
|
+
|
|
152
|
+
dailyBackupCmd
|
|
153
|
+
.command('run')
|
|
154
|
+
.description('Trigger manual backup now')
|
|
155
|
+
.action(dailyBackupRun);
|
|
156
|
+
|
|
157
|
+
dailyBackupCmd
|
|
158
|
+
.command('logs')
|
|
159
|
+
.description('View backup logs')
|
|
160
|
+
.option('--lines <n>', 'Number of lines to show', '50')
|
|
161
|
+
.action(dailyBackupLogs);
|
|
162
|
+
|
|
163
|
+
dailyBackupCmd
|
|
164
|
+
.command('list')
|
|
165
|
+
.description('List remote backups on S3')
|
|
166
|
+
.action(dailyBackupList);
|
|
167
|
+
|
|
168
|
+
dailyBackupCmd
|
|
169
|
+
.command('clean')
|
|
170
|
+
.description('Clean old backups manually (keeps retention days)')
|
|
171
|
+
.action(dailyBackupClean);
|
|
172
|
+
|
|
114
173
|
// System Reset
|
|
115
174
|
program
|
|
116
175
|
.command('reset')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ante-erp-cli",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.28",
|
|
4
4
|
"description": "Comprehensive CLI tool for managing ANTE ERP self-hosted installations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
"node": ">=24.0.0"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
+
"@aws-sdk/client-s3": "^3.645.0",
|
|
46
|
+
"@aws-sdk/lib-storage": "^3.645.0",
|
|
45
47
|
"boxen": "^7.1.0",
|
|
46
48
|
"chalk": "^5.3.0",
|
|
47
49
|
"cli-table3": "^0.6.3",
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Daily Backup Runner Script
|
|
5
|
+
* Executed by cron job to run automated backups
|
|
6
|
+
* This script runs independently and logs to ~/.ante-cli-backup.log
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { writeFileSync, appendFileSync, existsSync } from 'fs';
|
|
11
|
+
import os from 'os';
|
|
12
|
+
import Conf from 'conf';
|
|
13
|
+
import { backup } from './backup.js';
|
|
14
|
+
import { createS3Client, uploadFileToS3, cleanOldS3Backups } from '../utils/s3-client.js';
|
|
15
|
+
import { decryptFields } from '../utils/crypto.js';
|
|
16
|
+
|
|
17
|
+
const config = new Conf({ projectName: 'ante-cli' });
|
|
18
|
+
const logFile = join(os.homedir(), '.ante-cli-backup.log');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Log message to file with timestamp
|
|
22
|
+
*/
|
|
23
|
+
function log(message, level = 'INFO') {
|
|
24
|
+
const timestamp = new Date().toISOString();
|
|
25
|
+
const logMessage = `[${timestamp}] [${level}] ${message}\n`;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (!existsSync(logFile)) {
|
|
29
|
+
writeFileSync(logFile, '');
|
|
30
|
+
}
|
|
31
|
+
appendFileSync(logFile, logMessage);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error('Failed to write to log file:', error.message);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Also output to console (will be captured by cron)
|
|
37
|
+
console.log(logMessage.trim());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Main backup execution function
|
|
42
|
+
*/
|
|
43
|
+
async function runDailyBackup() {
|
|
44
|
+
log('========================================');
|
|
45
|
+
log('Starting automated daily backup...');
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// Load configuration
|
|
49
|
+
const dailyBackupConfig = config.get('dailyBackup');
|
|
50
|
+
|
|
51
|
+
if (!dailyBackupConfig || !dailyBackupConfig.enabled) {
|
|
52
|
+
log('Daily backups are not enabled. Exiting.', 'WARN');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
log(`Backup configuration loaded successfully`);
|
|
57
|
+
log(`S3 Bucket: ${dailyBackupConfig.s3.bucket}`);
|
|
58
|
+
log(`Folder Prefix: ${dailyBackupConfig.folderPrefix}`);
|
|
59
|
+
log(`Retention: ${dailyBackupConfig.retention} days`);
|
|
60
|
+
|
|
61
|
+
// Step 1: Create local backup
|
|
62
|
+
log('Step 1/3: Creating local backup...');
|
|
63
|
+
const backupFile = await backup({ silent: true });
|
|
64
|
+
|
|
65
|
+
if (!backupFile) {
|
|
66
|
+
throw new Error('Failed to create local backup - no file returned');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
log(`Local backup created successfully: ${backupFile}`, 'SUCCESS');
|
|
70
|
+
|
|
71
|
+
// Step 2: Upload to S3
|
|
72
|
+
log('Step 2/3: Uploading backup to S3...');
|
|
73
|
+
|
|
74
|
+
const s3Config = decryptFields(dailyBackupConfig.s3, ['accessKey', 'secretKey']);
|
|
75
|
+
const s3Client = createS3Client(s3Config);
|
|
76
|
+
|
|
77
|
+
const fileName = backupFile.split('/').pop();
|
|
78
|
+
const s3Key = `${dailyBackupConfig.folderPrefix}/${fileName}`;
|
|
79
|
+
|
|
80
|
+
log(`S3 Key: ${s3Key}`);
|
|
81
|
+
|
|
82
|
+
let lastProgress = 0;
|
|
83
|
+
const uploadResult = await uploadFileToS3(
|
|
84
|
+
s3Client,
|
|
85
|
+
backupFile,
|
|
86
|
+
s3Config.bucket,
|
|
87
|
+
s3Key,
|
|
88
|
+
(percentage, loaded, total) => {
|
|
89
|
+
// Log progress every 25%
|
|
90
|
+
if (percentage >= lastProgress + 25) {
|
|
91
|
+
log(`Upload progress: ${percentage}%`);
|
|
92
|
+
lastProgress = percentage;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
if (!uploadResult.success) {
|
|
98
|
+
throw new Error(`S3 upload failed: ${uploadResult.error}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
log(`Backup uploaded successfully to S3`, 'SUCCESS');
|
|
102
|
+
log(`S3 URL: ${uploadResult.url}`);
|
|
103
|
+
log(`File size: ${(uploadResult.size / 1024 / 1024).toFixed(2)} MB`);
|
|
104
|
+
|
|
105
|
+
// Step 3: Clean old backups
|
|
106
|
+
log('Step 3/3: Cleaning old backups...');
|
|
107
|
+
|
|
108
|
+
const cleanResult = await cleanOldS3Backups(
|
|
109
|
+
s3Client,
|
|
110
|
+
s3Config.bucket,
|
|
111
|
+
dailyBackupConfig.folderPrefix,
|
|
112
|
+
dailyBackupConfig.retention
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (!cleanResult.success) {
|
|
116
|
+
log(`Warning: Failed to clean old backups: ${cleanResult.error}`, 'WARN');
|
|
117
|
+
} else if (cleanResult.deleted > 0) {
|
|
118
|
+
log(`Cleaned ${cleanResult.deleted} old backup(s)`, 'SUCCESS');
|
|
119
|
+
cleanResult.backups.forEach(name => {
|
|
120
|
+
log(` - Deleted: ${name}`);
|
|
121
|
+
});
|
|
122
|
+
} else {
|
|
123
|
+
log('No old backups to clean');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Update configuration
|
|
127
|
+
config.set('dailyBackup.lastBackup', new Date().toISOString());
|
|
128
|
+
config.set('dailyBackup.lastStatus', 'success');
|
|
129
|
+
|
|
130
|
+
log('Automated backup completed successfully!', 'SUCCESS');
|
|
131
|
+
log('========================================\n');
|
|
132
|
+
|
|
133
|
+
process.exit(0);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
log(`BACKUP FAILED: ${error.message}`, 'ERROR');
|
|
136
|
+
|
|
137
|
+
if (error.stack) {
|
|
138
|
+
log(`Stack trace: ${error.stack}`, 'ERROR');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Update configuration
|
|
142
|
+
config.set('dailyBackup.lastStatus', 'failed');
|
|
143
|
+
|
|
144
|
+
log('========================================\n');
|
|
145
|
+
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Handle script errors
|
|
152
|
+
*/
|
|
153
|
+
process.on('unhandledRejection', (error) => {
|
|
154
|
+
log(`Unhandled rejection: ${error.message}`, 'ERROR');
|
|
155
|
+
if (error.stack) {
|
|
156
|
+
log(`Stack trace: ${error.stack}`, 'ERROR');
|
|
157
|
+
}
|
|
158
|
+
process.exit(1);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
process.on('uncaughtException', (error) => {
|
|
162
|
+
log(`Uncaught exception: ${error.message}`, 'ERROR');
|
|
163
|
+
if (error.stack) {
|
|
164
|
+
log(`Stack trace: ${error.stack}`, 'ERROR');
|
|
165
|
+
}
|
|
166
|
+
process.exit(1);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Run the backup
|
|
170
|
+
runDailyBackup().catch((error) => {
|
|
171
|
+
log(`Fatal error: ${error.message}`, 'ERROR');
|
|
172
|
+
process.exit(1);
|
|
173
|
+
});
|