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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ante-erp-cli",
3
- "version": "1.11.46",
3
+ "version": "1.11.50",
4
4
  "description": "Comprehensive CLI tool for managing ANTE ERP self-hosted installations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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(process.cwd(), 'cli/src/commands/daily-backup-runner.js');
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
 
@@ -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
@@ -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'));
@@ -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;
@@ -42,7 +42,7 @@ services:
42
42
  networks:
43
43
  - ante-network
44
44
  healthcheck:
45
- test: ["CMD-SHELL", "pg_isready -U ante"]
45
+ test: ["CMD-SHELL", "pg_isready -U ante -d ante_db"]
46
46
  interval: 10s
47
47
  timeout: 5s
48
48
  retries: 5
@@ -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
+ }