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 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.27",
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
+ });