ante-erp-cli 1.11.46 → 1.11.50
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/package.json
CHANGED
|
@@ -3,8 +3,12 @@ import chalk from 'chalk';
|
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import { execa } from 'execa';
|
|
5
5
|
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
6
|
-
import { join } from 'path';
|
|
6
|
+
import { join, dirname } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
7
8
|
import os from 'os';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
8
12
|
import Conf from 'conf';
|
|
9
13
|
import Table from 'cli-table3';
|
|
10
14
|
import { backup } from './backup.js';
|
|
@@ -522,8 +526,8 @@ async function setupCronJob(schedule) {
|
|
|
522
526
|
.split('\n')
|
|
523
527
|
.filter(line => !line.includes('ante-daily-backup-runner'));
|
|
524
528
|
|
|
525
|
-
// Add new cron job
|
|
526
|
-
const runnerPath = join(
|
|
529
|
+
// Add new cron job - use the actual module location, not process.cwd()
|
|
530
|
+
const runnerPath = join(__dirname, 'daily-backup-runner.js');
|
|
527
531
|
const logFile = join(os.homedir(), '.ante-cli-backup.log');
|
|
528
532
|
const cronLine = `${schedule} /usr/bin/node ${runnerPath} >> ${logFile} 2>&1`;
|
|
529
533
|
|
package/src/commands/install.js
CHANGED
|
@@ -15,6 +15,7 @@ import { generateDockerCompose } from '../templates/docker-compose.yml.js';
|
|
|
15
15
|
import { generateEnv } from '../templates/env.js';
|
|
16
16
|
import { detectPublicIPWithFeedback, buildURL } from '../utils/network.js';
|
|
17
17
|
import { configureNginx, requiresNginx } from '../utils/nginx.js';
|
|
18
|
+
import { applySysctlSettings } from '../utils/system-config.js';
|
|
18
19
|
|
|
19
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
20
21
|
const __dirname = dirname(__filename);
|
|
@@ -302,6 +303,10 @@ export async function install(options) {
|
|
|
302
303
|
}
|
|
303
304
|
|
|
304
305
|
console.log(chalk.green(`✓ ${formatStepTitle(stepSystemCheck, totalSteps, 'System requirements met')}\n`));
|
|
306
|
+
|
|
307
|
+
// Configure system kernel parameters for Redis
|
|
308
|
+
console.log(chalk.cyan(' Configuring system kernel parameters...'));
|
|
309
|
+
await applySysctlSettings();
|
|
305
310
|
}
|
|
306
311
|
|
|
307
312
|
// Step: Network detection
|
package/src/commands/restore.js
CHANGED
|
@@ -6,7 +6,7 @@ import { existsSync, mkdirSync, readdirSync, statSync, readFileSync, createWrite
|
|
|
6
6
|
import { execa } from 'execa';
|
|
7
7
|
import Conf from 'conf';
|
|
8
8
|
import { getInstallDir } from '../utils/config.js';
|
|
9
|
-
import { execInContainer, stopServices, startServices, waitForHealthy } from '../utils/docker.js';
|
|
9
|
+
import { execInContainer, stopServices, startServices, waitForHealthy, runMigrations } from '../utils/docker.js';
|
|
10
10
|
import { createS3Client, listS3Backups } from '../utils/s3-client.js';
|
|
11
11
|
import { decryptFields } from '../utils/crypto.js';
|
|
12
12
|
import { GetObjectCommand } from '@aws-sdk/client-s3';
|
|
@@ -368,6 +368,19 @@ export async function restore(backupFile, options = {}) {
|
|
|
368
368
|
spinner.text = 'Starting all services...';
|
|
369
369
|
await startServices(composeFile);
|
|
370
370
|
|
|
371
|
+
// Wait for backend to be healthy before running migrations
|
|
372
|
+
spinner.text = 'Waiting for backend service...';
|
|
373
|
+
await waitForHealthy(composeFile, ['backend'], 120);
|
|
374
|
+
|
|
375
|
+
// Run database migrations to sync schema with current code
|
|
376
|
+
spinner.text = 'Running database migrations...';
|
|
377
|
+
const migrationResult = await runMigrations(composeFile);
|
|
378
|
+
|
|
379
|
+
if (!migrationResult.success) {
|
|
380
|
+
spinner.warn(chalk.yellow('Migrations completed with warnings'));
|
|
381
|
+
console.log(chalk.gray(migrationResult.output));
|
|
382
|
+
}
|
|
383
|
+
|
|
371
384
|
// Cleanup
|
|
372
385
|
spinner.text = 'Cleaning up...';
|
|
373
386
|
await execa('rm', ['-rf', tempDir]);
|
|
@@ -382,6 +395,7 @@ export async function restore(backupFile, options = {}) {
|
|
|
382
395
|
|
|
383
396
|
console.log(chalk.white('\n✓ PostgreSQL database restored'));
|
|
384
397
|
console.log(chalk.white('✓ MongoDB database restored'));
|
|
398
|
+
console.log(chalk.white('✓ Database migrations applied'));
|
|
385
399
|
console.log(chalk.white('✓ All services started'));
|
|
386
400
|
|
|
387
401
|
console.log(chalk.cyan('\nANTE is now running with restored database data'));
|
package/src/commands/update.js
CHANGED
|
@@ -9,6 +9,7 @@ import { backup } from './backup.js';
|
|
|
9
9
|
import { getCurrentVersion, getLatestVersion } from './update-cli.js';
|
|
10
10
|
import { generateDockerCompose } from '../templates/docker-compose.yml.js';
|
|
11
11
|
import { generateSecurePassword } from '../utils/password.js';
|
|
12
|
+
import { verifySysctlSettings, applySysctlSettings } from '../utils/system-config.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Check if gate-app, guardian-app, facial-web, or pos-app is installed by checking docker-compose.yml
|
|
@@ -461,6 +462,16 @@ export async function update(options) {
|
|
|
461
462
|
refreshDockerCompose(composeFile, envFile);
|
|
462
463
|
}
|
|
463
464
|
|
|
465
|
+
// Verify and apply sysctl settings if needed (for Redis optimization)
|
|
466
|
+
console.log(chalk.gray('Verifying system kernel parameters...'));
|
|
467
|
+
const sysctlCheck = await verifySysctlSettings();
|
|
468
|
+
if (!sysctlCheck.ok) {
|
|
469
|
+
console.log(chalk.yellow(' Reapplying system kernel parameters...'));
|
|
470
|
+
await applySysctlSettings();
|
|
471
|
+
} else {
|
|
472
|
+
console.log(chalk.gray(' System kernel parameters OK'));
|
|
473
|
+
}
|
|
474
|
+
|
|
464
475
|
// Pre-calculate step numbers for each task (fixes step numbering bug)
|
|
465
476
|
let currentStep = 0;
|
|
466
477
|
const stepPreStart = !options.skipBackup ? ++currentStep : null;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import { writeFileSync } from 'fs';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
const SYSCTL_CONFIG_FILE = '/etc/sysctl.d/ante-redis.conf';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Required sysctl settings for optimal Redis performance
|
|
9
|
+
* vm.overcommit_memory=1 prevents Redis background save failures under low memory
|
|
10
|
+
*/
|
|
11
|
+
const REQUIRED_SETTINGS = {
|
|
12
|
+
'vm.overcommit_memory': '1'
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if a sysctl setting is currently applied
|
|
17
|
+
* @param {string} key - The sysctl key (e.g., 'vm.overcommit_memory')
|
|
18
|
+
* @returns {Promise<string|null>} Current value or null if error
|
|
19
|
+
*/
|
|
20
|
+
export async function checkSysctlSetting(key) {
|
|
21
|
+
try {
|
|
22
|
+
const { stdout } = await execa('sysctl', ['-n', key]);
|
|
23
|
+
return stdout.trim();
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Apply a sysctl setting immediately
|
|
31
|
+
* @param {string} key - The sysctl key
|
|
32
|
+
* @param {string} value - The value to set
|
|
33
|
+
* @returns {Promise<boolean>} Success status
|
|
34
|
+
*/
|
|
35
|
+
export async function applySysctlSetting(key, value) {
|
|
36
|
+
try {
|
|
37
|
+
await execa('sudo', ['sysctl', '-w', `${key}=${value}`], { stdio: 'pipe' });
|
|
38
|
+
return true;
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Persist sysctl settings to config file for reboot survival
|
|
46
|
+
* @returns {Promise<boolean>} Success status
|
|
47
|
+
*/
|
|
48
|
+
export async function persistSysctlSettings() {
|
|
49
|
+
const lines = Object.entries(REQUIRED_SETTINGS)
|
|
50
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
51
|
+
.join('\n');
|
|
52
|
+
|
|
53
|
+
const content = `# ANTE ERP Redis Optimization
|
|
54
|
+
# Generated by ante-erp-cli
|
|
55
|
+
# See: https://redis.io/docs/getting-started/faq/#background-saving-fails-with-a-fork-error-on-linux
|
|
56
|
+
${lines}
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// Write to temp file then move with sudo
|
|
61
|
+
const tempFile = '/tmp/ante-sysctl.conf';
|
|
62
|
+
writeFileSync(tempFile, content);
|
|
63
|
+
await execa('sudo', ['mv', tempFile, SYSCTL_CONFIG_FILE], { stdio: 'pipe' });
|
|
64
|
+
await execa('sudo', ['chmod', '644', SYSCTL_CONFIG_FILE], { stdio: 'pipe' });
|
|
65
|
+
return true;
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Apply and persist all required sysctl settings
|
|
73
|
+
* @param {boolean} silent - Suppress console output
|
|
74
|
+
* @returns {Promise<{applied: string[], failed: string[], skipped: string[]}>}
|
|
75
|
+
*/
|
|
76
|
+
export async function applySysctlSettings(silent = false) {
|
|
77
|
+
const results = { applied: [], failed: [], skipped: [] };
|
|
78
|
+
|
|
79
|
+
for (const [key, value] of Object.entries(REQUIRED_SETTINGS)) {
|
|
80
|
+
const current = await checkSysctlSetting(key);
|
|
81
|
+
|
|
82
|
+
if (current === value) {
|
|
83
|
+
results.skipped.push(key);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const success = await applySysctlSetting(key, value);
|
|
88
|
+
if (success) {
|
|
89
|
+
results.applied.push(key);
|
|
90
|
+
} else {
|
|
91
|
+
results.failed.push(key);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Persist to config file for reboot survival
|
|
96
|
+
if (results.applied.length > 0) {
|
|
97
|
+
await persistSysctlSettings();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!silent) {
|
|
101
|
+
if (results.applied.length > 0) {
|
|
102
|
+
console.log(chalk.green(` ✓ Applied sysctl settings: ${results.applied.join(', ')}`));
|
|
103
|
+
}
|
|
104
|
+
if (results.skipped.length > 0) {
|
|
105
|
+
console.log(chalk.gray(` Sysctl settings already configured: ${results.skipped.join(', ')}`));
|
|
106
|
+
}
|
|
107
|
+
if (results.failed.length > 0) {
|
|
108
|
+
console.log(chalk.yellow(` ⚠ Failed to apply: ${results.failed.join(', ')} (may require root privileges)`));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return results;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Verify sysctl settings are applied (for update command)
|
|
117
|
+
* @returns {Promise<{ok: boolean, missing: Array<{key: string, expected: string, current: string|null}>}>}
|
|
118
|
+
*/
|
|
119
|
+
export async function verifySysctlSettings() {
|
|
120
|
+
const missing = [];
|
|
121
|
+
|
|
122
|
+
for (const [key, value] of Object.entries(REQUIRED_SETTINGS)) {
|
|
123
|
+
const current = await checkSysctlSetting(key);
|
|
124
|
+
if (current !== value) {
|
|
125
|
+
missing.push({ key, expected: value, current });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { ok: missing.length === 0, missing };
|
|
130
|
+
}
|