ante-erp-cli 1.11.16 → 1.11.18
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 +1 -1
- package/src/commands/install.js +172 -529
- package/src/utils/validation.js +2 -4
package/package.json
CHANGED
package/src/commands/install.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import boxen from 'boxen';
|
|
3
|
-
import inquirer from 'inquirer';
|
|
4
3
|
import ora from 'ora';
|
|
5
4
|
import { Listr } from 'listr2';
|
|
6
5
|
import { execa } from 'execa';
|
|
7
|
-
import { mkdirSync, writeFileSync, existsSync, renameSync } from 'fs';
|
|
6
|
+
import { mkdirSync, writeFileSync, existsSync, renameSync, readFileSync } from 'fs';
|
|
8
7
|
import { join, dirname } from 'path';
|
|
9
8
|
import { fileURLToPath } from 'url';
|
|
10
9
|
import { runSystemChecks, checkAndInstallDocker } from '../utils/validation.js';
|
|
11
10
|
import { generateCredentials } from '../utils/password.js';
|
|
12
11
|
import { saveInstallConfig, detectInstallation } from '../utils/config.js';
|
|
13
|
-
import {
|
|
12
|
+
import { pullImagesSilent, startServicesSilent, waitForServiceHealthy, runMigrations } from '../utils/docker.js';
|
|
14
13
|
import { generateDockerCompose } from '../templates/docker-compose.yml.js';
|
|
15
14
|
import { generateEnv } from '../templates/env.js';
|
|
16
|
-
import { detectPublicIPWithFeedback, buildURL
|
|
15
|
+
import { detectPublicIPWithFeedback, buildURL } from '../utils/network.js';
|
|
17
16
|
import { configureNginx, requiresNginx } from '../utils/nginx.js';
|
|
18
17
|
|
|
19
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -42,50 +41,14 @@ function backupEnvFile(envPath) {
|
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
/**
|
|
45
|
-
*
|
|
46
|
-
* @param {
|
|
47
|
-
* @
|
|
44
|
+
* Format step title with numbering
|
|
45
|
+
* @param {number} step - Current step number
|
|
46
|
+
* @param {number} total - Total number of steps
|
|
47
|
+
* @param {string} description - Step description
|
|
48
|
+
* @returns {string} Formatted title
|
|
48
49
|
*/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (!existsSync(envPath)) {
|
|
53
|
-
return true; // No existing config, safe to continue
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
console.log(chalk.yellow('\n⚠ Existing configuration detected!'));
|
|
57
|
-
console.log(chalk.white(` Found: ${envPath}`));
|
|
58
|
-
console.log(chalk.gray(' This file contains API URLs, credentials, and other settings.\n'));
|
|
59
|
-
|
|
60
|
-
const { action } = await inquirer.prompt([
|
|
61
|
-
{
|
|
62
|
-
type: 'list',
|
|
63
|
-
name: 'action',
|
|
64
|
-
message: 'How would you like to proceed?',
|
|
65
|
-
choices: [
|
|
66
|
-
{ name: 'Backup old config and create new (Recommended)', value: 'backup' },
|
|
67
|
-
{ name: 'Overwrite with new configuration', value: 'overwrite' },
|
|
68
|
-
{ name: 'Cancel installation', value: 'cancel' }
|
|
69
|
-
],
|
|
70
|
-
default: 'backup'
|
|
71
|
-
}
|
|
72
|
-
]);
|
|
73
|
-
|
|
74
|
-
if (action === 'cancel') {
|
|
75
|
-
console.log(chalk.gray('\nInstallation cancelled.\n'));
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (action === 'backup') {
|
|
80
|
-
const backupPath = backupEnvFile(envPath);
|
|
81
|
-
if (backupPath) {
|
|
82
|
-
console.log(chalk.green(` ✓ Configuration backed up to: ${backupPath}\n`));
|
|
83
|
-
}
|
|
84
|
-
} else {
|
|
85
|
-
console.log(chalk.yellow(' ⚠ Existing configuration will be overwritten\n'));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return true;
|
|
50
|
+
function formatStepTitle(step, total, description) {
|
|
51
|
+
return `[${step}/${total}] ${description}`;
|
|
89
52
|
}
|
|
90
53
|
|
|
91
54
|
/**
|
|
@@ -165,34 +128,43 @@ function showSuccess(installDir, credentials, config) {
|
|
|
165
128
|
}
|
|
166
129
|
|
|
167
130
|
/**
|
|
168
|
-
* Install ANTE ERP
|
|
131
|
+
* Install ANTE ERP (Non-Interactive Mode)
|
|
169
132
|
*/
|
|
170
133
|
export async function install(options) {
|
|
171
134
|
try {
|
|
172
135
|
showWelcome();
|
|
173
|
-
|
|
136
|
+
|
|
137
|
+
console.log(chalk.bold('\n🚀 ANTE ERP Installation\n'));
|
|
138
|
+
|
|
174
139
|
// Check if already installed
|
|
175
140
|
const existing = detectInstallation();
|
|
176
141
|
if (existing && !options.force) {
|
|
177
|
-
console.log(chalk.yellow('
|
|
178
|
-
|
|
179
|
-
{
|
|
180
|
-
type: 'confirm',
|
|
181
|
-
name: 'continueAnyway',
|
|
182
|
-
message: 'Install anyway?',
|
|
183
|
-
default: false
|
|
184
|
-
}
|
|
185
|
-
]);
|
|
186
|
-
|
|
187
|
-
if (!continueAnyway) {
|
|
188
|
-
console.log(chalk.gray('\nInstallation cancelled.'));
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
142
|
+
console.log(chalk.yellow('⚠ ANTE is already installed at:'), chalk.white(existing));
|
|
143
|
+
console.log(chalk.gray('Proceeding with re-installation...\n'));
|
|
191
144
|
}
|
|
192
|
-
|
|
193
|
-
//
|
|
145
|
+
|
|
146
|
+
// Calculate total steps
|
|
147
|
+
let totalSteps = 10; // Base steps
|
|
148
|
+
|
|
149
|
+
// Pre-calculate step numbers
|
|
150
|
+
let currentStep = 0;
|
|
151
|
+
const stepSystemCheck = !options.skipChecks ? ++currentStep : null;
|
|
152
|
+
const stepNetworkDetect = ++currentStep;
|
|
153
|
+
const stepGenerateCredentials = ++currentStep;
|
|
154
|
+
const stepCreateDir = ++currentStep;
|
|
155
|
+
const stepGenerateConfig = ++currentStep;
|
|
156
|
+
const stepPullImages = ++currentStep;
|
|
157
|
+
const stepStartServices = ++currentStep;
|
|
158
|
+
const stepWaitServices = ++currentStep;
|
|
159
|
+
const stepInitDatabase = ++currentStep;
|
|
160
|
+
const stepRunMigrations = ++currentStep;
|
|
161
|
+
|
|
162
|
+
// Start installation
|
|
163
|
+
let stepNum = 0;
|
|
164
|
+
|
|
165
|
+
// Step: System checks
|
|
194
166
|
if (!options.skipChecks) {
|
|
195
|
-
console.log(chalk.
|
|
167
|
+
console.log(chalk.cyan(formatStepTitle(stepSystemCheck, totalSteps, 'Checking system requirements')));
|
|
196
168
|
|
|
197
169
|
// Check Docker first and offer installation if needed
|
|
198
170
|
const dockerResult = await checkAndInstallDocker(!options.skipDockerInstall);
|
|
@@ -203,394 +175,92 @@ export async function install(options) {
|
|
|
203
175
|
// Update checks with Docker installation result
|
|
204
176
|
checks.docker = dockerResult;
|
|
205
177
|
|
|
206
|
-
// Display check results
|
|
207
|
-
console.log(chalk.
|
|
208
|
-
console.log(`${checks.docker.ok ? chalk.green('✓') : chalk.red('✗')} Docker: ${checks.docker.message}`);
|
|
178
|
+
// Display check results (compact)
|
|
179
|
+
console.log(` ${checks.docker.ok ? chalk.green('✓') : chalk.red('✗')} Docker: ${checks.docker.message}`);
|
|
209
180
|
if (checks.docker.installed) {
|
|
210
|
-
console.log(chalk.green('
|
|
181
|
+
console.log(chalk.green(' ↳ Docker was installed during this setup'));
|
|
211
182
|
}
|
|
212
|
-
console.log(
|
|
213
|
-
console.log(
|
|
214
|
-
|
|
215
|
-
console.log(chalk.bold('\nResources:'));
|
|
216
|
-
console.log(`${checks.diskSpace.ok ? chalk.green('✓') : chalk.red('✗')} Disk Space: ${checks.diskSpace.message}`);
|
|
217
|
-
console.log(`${checks.memory.ok ? chalk.green('✓') : chalk.red('✗')} Memory: ${checks.memory.message}`);
|
|
218
|
-
console.log(`${checks.cpu.ok ? chalk.green('✓') : chalk.yellow('⚠')} CPU: ${checks.cpu.message}`);
|
|
183
|
+
console.log(` ${checks.dockerCompose.ok ? chalk.green('✓') : chalk.red('✗')} Docker Compose: ${checks.dockerCompose.message}`);
|
|
184
|
+
console.log(` ${checks.node.ok ? chalk.green('✓') : chalk.red('✗')} Node.js: ${checks.node.message}`);
|
|
185
|
+
console.log(` ${checks.diskSpace.ok ? chalk.green('✓') : chalk.red('✗')} Disk Space: ${checks.diskSpace.message}`);
|
|
219
186
|
|
|
220
187
|
if (!ok || !checks.docker.ok) {
|
|
221
188
|
console.log(chalk.red('\n✗ System requirements not met. Please resolve issues above.\n'));
|
|
222
189
|
process.exit(1);
|
|
223
190
|
}
|
|
224
191
|
|
|
225
|
-
console.log(chalk.green('
|
|
226
|
-
} else {
|
|
227
|
-
console.log(chalk.yellow('\n⚠ Skipping system requirements check (--skip-checks)\n'));
|
|
192
|
+
console.log(chalk.green(`✓ ${formatStepTitle(stepSystemCheck, totalSteps, 'System requirements met')}\n`));
|
|
228
193
|
}
|
|
229
194
|
|
|
230
|
-
//
|
|
231
|
-
console.log(chalk.
|
|
232
|
-
const spinner = ora();
|
|
195
|
+
// Step: Network detection
|
|
196
|
+
console.log(chalk.cyan(formatStepTitle(stepNetworkDetect, totalSteps, 'Detecting network configuration')));
|
|
197
|
+
const spinner = ora({ text: 'Detecting public IP...', prefixText: ' ' }).start();
|
|
233
198
|
const detectedIP = await detectPublicIPWithFeedback(spinner);
|
|
234
199
|
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
{ name: 'Main Frontend (Required)', value: 'main', checked: true, disabled: true },
|
|
267
|
-
{ name: 'Gate App (School/Gate attendance)', value: 'gate', checked: false },
|
|
268
|
-
{ name: 'Guardian App (Parent portal)', value: 'guardian', checked: false },
|
|
269
|
-
{ name: 'Facial Web (Employee face recognition)', value: 'facial', checked: false },
|
|
270
|
-
{ name: 'POS App (Point of Sale)', value: 'pos', checked: false }
|
|
271
|
-
],
|
|
272
|
-
validate: (answer) => {
|
|
273
|
-
if (answer.length < 1) {
|
|
274
|
-
return 'You must select at least one frontend (Main Frontend is required).';
|
|
275
|
-
}
|
|
276
|
-
return true;
|
|
277
|
-
}
|
|
278
|
-
},
|
|
279
|
-
{
|
|
280
|
-
type: 'number',
|
|
281
|
-
name: 'companyId',
|
|
282
|
-
message: 'Company ID (for multi-tenant apps):',
|
|
283
|
-
default: 1,
|
|
284
|
-
when: (answers) => answers.frontends.includes('gate') || answers.frontends.includes('guardian') || answers.frontends.includes('facial') || answers.frontends.includes('pos'),
|
|
285
|
-
validate: (input) => {
|
|
286
|
-
if (input < 1) return 'Company ID must be at least 1';
|
|
287
|
-
return true;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
]);
|
|
291
|
-
|
|
292
|
-
// Network configuration prompts
|
|
293
|
-
const networkConfig = await inquirer.prompt([
|
|
294
|
-
{
|
|
295
|
-
type: 'list',
|
|
296
|
-
name: 'networkType',
|
|
297
|
-
message: 'How will users access this installation?',
|
|
298
|
-
choices: [
|
|
299
|
-
{ name: 'Local development (localhost)', value: 'localhost' },
|
|
300
|
-
{ name: 'Public IP address', value: 'ip' },
|
|
301
|
-
{ name: 'Domain name', value: 'domain' }
|
|
302
|
-
],
|
|
303
|
-
default: detectedIP ? 'ip' : 'localhost'
|
|
304
|
-
},
|
|
305
|
-
{
|
|
306
|
-
type: 'input',
|
|
307
|
-
name: 'publicIP',
|
|
308
|
-
message: 'Enter the public IP address:',
|
|
309
|
-
default: detectedIP || '',
|
|
310
|
-
when: (answers) => answers.networkType === 'ip',
|
|
311
|
-
validate: (input) => {
|
|
312
|
-
if (!input) return 'IP address is required';
|
|
313
|
-
if (!isValidIPv4(input)) return 'Please enter a valid IPv4 address';
|
|
314
|
-
return true;
|
|
315
|
-
}
|
|
316
|
-
},
|
|
317
|
-
{
|
|
318
|
-
type: 'input',
|
|
319
|
-
name: 'domainName',
|
|
320
|
-
message: 'Enter your domain name (e.g., erp.example.com):',
|
|
321
|
-
when: (answers) => answers.networkType === 'domain',
|
|
322
|
-
validate: (input) => {
|
|
323
|
-
if (!input) return 'Domain name is required';
|
|
324
|
-
if (!isValidDomain(input)) return 'Please enter a valid domain name';
|
|
325
|
-
return true;
|
|
326
|
-
}
|
|
327
|
-
},
|
|
328
|
-
{
|
|
329
|
-
type: 'number',
|
|
330
|
-
name: 'frontendPort',
|
|
331
|
-
message: 'Frontend port:',
|
|
332
|
-
default: 8080,
|
|
333
|
-
when: (answers) => answers.networkType !== 'domain',
|
|
334
|
-
validate: (input) => {
|
|
335
|
-
if (input < 1 || input > 65535) return 'Port must be between 1 and 65535';
|
|
336
|
-
return true;
|
|
337
|
-
}
|
|
338
|
-
},
|
|
339
|
-
{
|
|
340
|
-
type: 'number',
|
|
341
|
-
name: 'apiPort',
|
|
342
|
-
message: 'API port:',
|
|
343
|
-
default: 3001,
|
|
344
|
-
when: (answers) => answers.networkType !== 'domain',
|
|
345
|
-
validate: (input) => {
|
|
346
|
-
if (input < 1 || input > 65535) return 'Port must be between 1 and 65535';
|
|
347
|
-
return true;
|
|
348
|
-
}
|
|
349
|
-
},
|
|
350
|
-
{
|
|
351
|
-
type: 'number',
|
|
352
|
-
name: 'gateAppPort',
|
|
353
|
-
message: 'Gate App port:',
|
|
354
|
-
default: 8081,
|
|
355
|
-
when: (answers) => answers.networkType !== 'domain' && frontendConfig.frontends.includes('gate'),
|
|
356
|
-
validate: (input) => {
|
|
357
|
-
if (input < 1 || input > 65535) return 'Port must be between 1 and 65535';
|
|
358
|
-
return true;
|
|
359
|
-
}
|
|
360
|
-
},
|
|
361
|
-
{
|
|
362
|
-
type: 'number',
|
|
363
|
-
name: 'guardianAppPort',
|
|
364
|
-
message: 'Guardian App port:',
|
|
365
|
-
default: 8082,
|
|
366
|
-
when: (answers) => answers.networkType !== 'domain' && frontendConfig.frontends.includes('guardian'),
|
|
367
|
-
validate: (input) => {
|
|
368
|
-
if (input < 1 || input > 65535) return 'Port must be between 1 and 65535';
|
|
369
|
-
return true;
|
|
370
|
-
}
|
|
371
|
-
},
|
|
372
|
-
{
|
|
373
|
-
type: 'number',
|
|
374
|
-
name: 'facialWebPort',
|
|
375
|
-
message: 'Facial Web port:',
|
|
376
|
-
default: 8083,
|
|
377
|
-
when: (answers) => answers.networkType !== 'domain' && frontendConfig.frontends.includes('facial'),
|
|
378
|
-
validate: (input) => {
|
|
379
|
-
if (input < 1 || input > 65535) return 'Port must be between 1 and 65535';
|
|
380
|
-
return true;
|
|
381
|
-
}
|
|
382
|
-
},
|
|
383
|
-
{
|
|
384
|
-
type: 'number',
|
|
385
|
-
name: 'posAppPort',
|
|
386
|
-
message: 'POS App port:',
|
|
387
|
-
default: 8084,
|
|
388
|
-
when: (answers) => answers.networkType !== 'domain' && frontendConfig.frontends.includes('pos'),
|
|
389
|
-
validate: (input) => {
|
|
390
|
-
if (input < 1 || input > 65535) return 'Port must be between 1 and 65535';
|
|
391
|
-
return true;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
]);
|
|
395
|
-
|
|
396
|
-
// Build URLs based on configuration
|
|
397
|
-
let frontendUrl, apiUrl, gateAppUrl, guardianAppUrl, facialWebUrl, posAppUrl;
|
|
398
|
-
const frontendPort = networkConfig.frontendPort || 8080;
|
|
399
|
-
const apiPort = networkConfig.apiPort || 3001;
|
|
400
|
-
const gateAppPort = networkConfig.gateAppPort || 8081;
|
|
401
|
-
const guardianAppPort = networkConfig.guardianAppPort || 8082;
|
|
402
|
-
const facialWebPort = networkConfig.facialWebPort || 8083;
|
|
403
|
-
const posAppPort = networkConfig.posAppPort || 8084;
|
|
404
|
-
|
|
405
|
-
if (networkConfig.networkType === 'localhost') {
|
|
406
|
-
frontendUrl = buildURL('localhost', frontendPort);
|
|
407
|
-
apiUrl = buildURL('localhost', apiPort);
|
|
408
|
-
gateAppUrl = buildURL('localhost', gateAppPort);
|
|
409
|
-
guardianAppUrl = buildURL('localhost', guardianAppPort);
|
|
410
|
-
facialWebUrl = buildURL('localhost', facialWebPort);
|
|
411
|
-
posAppUrl = buildURL('localhost', posAppPort);
|
|
412
|
-
} else if (networkConfig.networkType === 'ip') {
|
|
413
|
-
frontendUrl = buildURL(networkConfig.publicIP, frontendPort);
|
|
414
|
-
apiUrl = buildURL(networkConfig.publicIP, apiPort);
|
|
415
|
-
gateAppUrl = buildURL(networkConfig.publicIP, gateAppPort);
|
|
416
|
-
guardianAppUrl = buildURL(networkConfig.publicIP, guardianAppPort);
|
|
417
|
-
facialWebUrl = buildURL(networkConfig.publicIP, facialWebPort);
|
|
418
|
-
posAppUrl = buildURL(networkConfig.publicIP, posAppPort);
|
|
419
|
-
} else {
|
|
420
|
-
// Domain - use standard HTTP/HTTPS ports (NGINX will handle reverse proxy)
|
|
421
|
-
const isHttps = await inquirer.prompt([{
|
|
422
|
-
type: 'confirm',
|
|
423
|
-
name: 'useHttps',
|
|
424
|
-
message: 'Is SSL/TLS configured for this domain (e.g., via Cloudflare)?',
|
|
425
|
-
default: true
|
|
426
|
-
}]);
|
|
427
|
-
|
|
428
|
-
// Ask for API subdomain
|
|
429
|
-
const apiSubdomain = await inquirer.prompt([{
|
|
430
|
-
type: 'input',
|
|
431
|
-
name: 'subdomain',
|
|
432
|
-
message: 'API subdomain (e.g., "api" for api.example.com):',
|
|
433
|
-
default: 'api',
|
|
434
|
-
validate: (input) => {
|
|
435
|
-
if (!input) return 'Subdomain is required';
|
|
436
|
-
if (!/^[a-z0-9-]+$/.test(input)) return 'Invalid subdomain format';
|
|
437
|
-
return true;
|
|
438
|
-
}
|
|
439
|
-
}]);
|
|
440
|
-
|
|
441
|
-
const protocol = isHttps.useHttps ? 'https' : 'http';
|
|
442
|
-
frontendUrl = `${protocol}://${networkConfig.domainName}`;
|
|
443
|
-
apiUrl = `${protocol}://${apiSubdomain.subdomain}.${networkConfig.domainName}`;
|
|
444
|
-
gateAppUrl = `${protocol}://gate.${networkConfig.domainName}`;
|
|
445
|
-
guardianAppUrl = `${protocol}://guardian.${networkConfig.domainName}`;
|
|
446
|
-
facialWebUrl = `${protocol}://facial.${networkConfig.domainName}`;
|
|
447
|
-
posAppUrl = `${protocol}://pos.${networkConfig.domainName}`;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
config = {
|
|
451
|
-
...baseConfig,
|
|
452
|
-
...frontendConfig,
|
|
453
|
-
frontendDomain: frontendUrl,
|
|
454
|
-
apiDomain: apiUrl,
|
|
455
|
-
gateAppDomain: gateAppUrl,
|
|
456
|
-
guardianAppDomain: guardianAppUrl,
|
|
457
|
-
facialAppDomain: facialWebUrl,
|
|
458
|
-
posAppDomain: posAppUrl,
|
|
459
|
-
frontendPort,
|
|
460
|
-
apiPort,
|
|
461
|
-
gateAppPort,
|
|
462
|
-
guardianAppPort,
|
|
463
|
-
facialWebPort,
|
|
464
|
-
posAppPort,
|
|
465
|
-
installGate: frontendConfig.frontends.includes('gate'),
|
|
466
|
-
installGuardian: frontendConfig.frontends.includes('guardian'),
|
|
467
|
-
installFacial: frontendConfig.frontends.includes('facial'),
|
|
468
|
-
installPos: frontendConfig.frontends.includes('pos')
|
|
469
|
-
};
|
|
470
|
-
} else {
|
|
471
|
-
// Non-interactive mode - use options or defaults with IP detection
|
|
472
|
-
let frontendDomain = options.frontendDomain;
|
|
473
|
-
let apiDomain = options.apiDomain;
|
|
474
|
-
|
|
475
|
-
// If no domains provided and IP detected, use detected IP
|
|
476
|
-
if (!frontendDomain && detectedIP) {
|
|
477
|
-
frontendDomain = buildURL(detectedIP, parseInt(options.port) || 8080);
|
|
478
|
-
console.log(chalk.cyan(' → Using detected IP for frontend:'), chalk.white(frontendDomain));
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
if (!apiDomain && detectedIP) {
|
|
482
|
-
apiDomain = buildURL(detectedIP, 3001);
|
|
483
|
-
console.log(chalk.cyan(' → Using detected IP for API:'), chalk.white(apiDomain));
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// If still no domain and no IP detected, prompt user
|
|
487
|
-
if (!frontendDomain || !apiDomain) {
|
|
488
|
-
console.log(chalk.yellow('\n⚠ Could not auto-detect public IP address.'));
|
|
489
|
-
console.log(chalk.white('Please provide domain/IP configuration manually:\n'));
|
|
490
|
-
|
|
491
|
-
const manualConfig = await inquirer.prompt([
|
|
492
|
-
{
|
|
493
|
-
type: 'input',
|
|
494
|
-
name: 'hostIp',
|
|
495
|
-
message: 'Enter public IP address or domain:',
|
|
496
|
-
validate: (input) => {
|
|
497
|
-
if (!input) return 'IP address or domain is required';
|
|
498
|
-
if (!isValidIPv4(input) && !isValidDomain(input)) {
|
|
499
|
-
return 'Please enter a valid IP address or domain name';
|
|
500
|
-
}
|
|
501
|
-
return true;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
]);
|
|
505
|
-
|
|
506
|
-
frontendDomain = buildURL(manualConfig.hostIp, parseInt(options.port) || 8080);
|
|
507
|
-
apiDomain = buildURL(manualConfig.hostIp, 3001);
|
|
508
|
-
}
|
|
200
|
+
// Build configuration (non-interactive, enterprise preset with all frontends)
|
|
201
|
+
const host = detectedIP || 'localhost';
|
|
202
|
+
const frontendPort = parseInt(options.port) || 8080;
|
|
203
|
+
const apiPort = 3001;
|
|
204
|
+
const gateAppPort = 8081;
|
|
205
|
+
const guardianAppPort = 8082;
|
|
206
|
+
const facialWebPort = 8083;
|
|
207
|
+
const posAppPort = 8084;
|
|
208
|
+
|
|
209
|
+
const config = {
|
|
210
|
+
installDir: options.dir || './ante-erp',
|
|
211
|
+
preset: 'enterprise', // Always install all features
|
|
212
|
+
frontendDomain: buildURL(host, frontendPort),
|
|
213
|
+
apiDomain: buildURL(host, apiPort),
|
|
214
|
+
gateAppDomain: buildURL(host, gateAppPort),
|
|
215
|
+
guardianAppDomain: buildURL(host, guardianAppPort),
|
|
216
|
+
facialAppDomain: buildURL(host, facialWebPort),
|
|
217
|
+
posAppDomain: buildURL(host, posAppPort),
|
|
218
|
+
frontendPort,
|
|
219
|
+
apiPort,
|
|
220
|
+
gateAppPort,
|
|
221
|
+
guardianAppPort,
|
|
222
|
+
facialWebPort,
|
|
223
|
+
posAppPort,
|
|
224
|
+
companyId: 1,
|
|
225
|
+
installGate: true, // Always install all frontends
|
|
226
|
+
installGuardian: true,
|
|
227
|
+
installFacial: true,
|
|
228
|
+
installPos: true,
|
|
229
|
+
frontends: ['main', 'gate', 'guardian', 'facial', 'pos']
|
|
230
|
+
};
|
|
509
231
|
|
|
510
|
-
|
|
511
|
-
const installAllFrontends = options.withAllFrontends;
|
|
512
|
-
const installGate = installAllFrontends || options.withGate;
|
|
513
|
-
const installGuardian = installAllFrontends || options.withGuardian;
|
|
514
|
-
const installFacial = installAllFrontends || options.withFacial;
|
|
515
|
-
const installPos = installAllFrontends || options.withPos;
|
|
516
|
-
|
|
517
|
-
// Build frontends array
|
|
518
|
-
const frontends = ['main'];
|
|
519
|
-
if (installGate) frontends.push('gate');
|
|
520
|
-
if (installGuardian) frontends.push('guardian');
|
|
521
|
-
if (installFacial) frontends.push('facial');
|
|
522
|
-
if (installPos) frontends.push('pos');
|
|
523
|
-
|
|
524
|
-
config = {
|
|
525
|
-
installDir: options.dir,
|
|
526
|
-
preset: options.preset || 'standard',
|
|
527
|
-
frontendDomain,
|
|
528
|
-
apiDomain,
|
|
529
|
-
frontendPort: parseInt(options.port) || 8080,
|
|
530
|
-
apiPort: 3001,
|
|
531
|
-
gateAppPort: 8081,
|
|
532
|
-
guardianAppPort: 8082,
|
|
533
|
-
facialWebPort: 8083,
|
|
534
|
-
posAppPort: 8084,
|
|
535
|
-
gateAppDomain: buildURL(detectedIP || 'localhost', 8081),
|
|
536
|
-
guardianAppDomain: buildURL(detectedIP || 'localhost', 8082),
|
|
537
|
-
facialAppDomain: buildURL(detectedIP || 'localhost', 8083),
|
|
538
|
-
posAppDomain: buildURL(detectedIP || 'localhost', 8084),
|
|
539
|
-
companyId: 1,
|
|
540
|
-
installGate,
|
|
541
|
-
installGuardian,
|
|
542
|
-
installFacial,
|
|
543
|
-
installPos,
|
|
544
|
-
frontends
|
|
545
|
-
};
|
|
546
|
-
}
|
|
232
|
+
console.log(chalk.green(`✓ ${formatStepTitle(stepNetworkDetect, totalSteps, `Network detected: ${host}`)}\n`));
|
|
547
233
|
|
|
548
|
-
// Display configuration summary
|
|
549
|
-
console.log(chalk.bold('
|
|
234
|
+
// Display configuration summary (compact)
|
|
235
|
+
console.log(chalk.bold('📋 Installation Configuration:\n'));
|
|
550
236
|
console.log(chalk.cyan(' Directory:'), chalk.white(config.installDir));
|
|
551
|
-
console.log(chalk.cyan(' Preset:'), chalk.white(
|
|
237
|
+
console.log(chalk.cyan(' Preset:'), chalk.white('Enterprise (All Features)'));
|
|
552
238
|
console.log(chalk.cyan(' Frontend Main:'), chalk.white(config.frontendDomain));
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
console.log(chalk.cyan(' Guardian App:'), chalk.white(config.guardianAppDomain));
|
|
558
|
-
}
|
|
559
|
-
if (config.installFacial) {
|
|
560
|
-
console.log(chalk.cyan(' Facial Web:'), chalk.white(config.facialAppDomain));
|
|
561
|
-
}
|
|
562
|
-
if (config.installPos) {
|
|
563
|
-
console.log(chalk.cyan(' POS App:'), chalk.white(config.posAppDomain));
|
|
564
|
-
}
|
|
239
|
+
console.log(chalk.cyan(' Gate App:'), chalk.white(config.gateAppDomain));
|
|
240
|
+
console.log(chalk.cyan(' Guardian App:'), chalk.white(config.guardianAppDomain));
|
|
241
|
+
console.log(chalk.cyan(' Facial Web:'), chalk.white(config.facialAppDomain));
|
|
242
|
+
console.log(chalk.cyan(' POS App:'), chalk.white(config.posAppDomain));
|
|
565
243
|
console.log(chalk.cyan(' API:'), chalk.white(config.apiDomain));
|
|
566
|
-
if (config.companyId) {
|
|
567
|
-
console.log(chalk.cyan(' Company ID:'), chalk.white(config.companyId));
|
|
568
|
-
}
|
|
569
244
|
console.log();
|
|
570
245
|
|
|
571
|
-
//
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
if (!canContinue) {
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
} else {
|
|
578
|
-
// Force mode: backup existing .env without prompting
|
|
579
|
-
const envPath = join(config.installDir, '.env');
|
|
246
|
+
// Auto-backup existing config if exists (no prompt)
|
|
247
|
+
const envPath = join(config.installDir, '.env');
|
|
248
|
+
if (existsSync(envPath)) {
|
|
580
249
|
const backupPath = backupEnvFile(envPath);
|
|
581
250
|
if (backupPath) {
|
|
582
|
-
console.log(chalk.yellow(`⚠
|
|
251
|
+
console.log(chalk.yellow(`⚠ Existing config backed up: ${backupPath}\n`));
|
|
583
252
|
}
|
|
584
253
|
}
|
|
585
254
|
|
|
586
|
-
// Generate credentials
|
|
587
|
-
console.log(chalk.
|
|
255
|
+
// Step: Generate credentials
|
|
256
|
+
console.log(chalk.cyan(formatStepTitle(stepGenerateCredentials, totalSteps, 'Generating secure credentials')));
|
|
588
257
|
const credentials = generateCredentials();
|
|
589
|
-
|
|
590
|
-
|
|
258
|
+
console.log(chalk.green(`✓ ${formatStepTitle(stepGenerateCredentials, totalSteps, 'Secure credentials generated')}\n`));
|
|
259
|
+
|
|
260
|
+
// Installation tasks with Listr2
|
|
591
261
|
const tasks = new Listr([
|
|
592
262
|
{
|
|
593
|
-
title: 'Creating installation directory',
|
|
263
|
+
title: formatStepTitle(stepCreateDir, totalSteps, 'Creating installation directory'),
|
|
594
264
|
task: () => {
|
|
595
265
|
mkdirSync(config.installDir, { recursive: true });
|
|
596
266
|
mkdirSync(join(config.installDir, 'backups'), { recursive: true });
|
|
@@ -598,59 +268,47 @@ export async function install(options) {
|
|
|
598
268
|
}
|
|
599
269
|
},
|
|
600
270
|
{
|
|
601
|
-
title: '
|
|
602
|
-
task: async () => {
|
|
603
|
-
try {
|
|
604
|
-
// Set system timezone to Asia/Manila
|
|
605
|
-
await execa('timedatectl', ['set-timezone', 'Asia/Manila'], { stdio: 'pipe' });
|
|
606
|
-
} catch (error) {
|
|
607
|
-
// Non-critical: log warning but continue installation
|
|
608
|
-
console.warn(' ⚠ Could not set system timezone (may require sudo)');
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
},
|
|
612
|
-
{
|
|
613
|
-
title: 'Generating configuration files',
|
|
271
|
+
title: formatStepTitle(stepGenerateConfig, totalSteps, 'Generating configuration files'),
|
|
614
272
|
task: () => {
|
|
615
273
|
const dockerCompose = generateDockerCompose({
|
|
616
|
-
frontendPort: config.frontendPort
|
|
617
|
-
backendPort: config.apiPort
|
|
618
|
-
gateAppPort: config.gateAppPort
|
|
619
|
-
guardianAppPort: config.guardianAppPort
|
|
620
|
-
facialWebPort: config.facialWebPort
|
|
621
|
-
posAppPort: config.posAppPort
|
|
274
|
+
frontendPort: config.frontendPort,
|
|
275
|
+
backendPort: config.apiPort,
|
|
276
|
+
gateAppPort: config.gateAppPort,
|
|
277
|
+
guardianAppPort: config.guardianAppPort,
|
|
278
|
+
facialWebPort: config.facialWebPort,
|
|
279
|
+
posAppPort: config.posAppPort,
|
|
622
280
|
installMain: true,
|
|
623
|
-
installGate: config.installGate
|
|
624
|
-
installGuardian: config.installGuardian
|
|
625
|
-
installFacial: config.installFacial
|
|
626
|
-
installPos: config.installPos
|
|
627
|
-
companyId: config.companyId
|
|
281
|
+
installGate: config.installGate,
|
|
282
|
+
installGuardian: config.installGuardian,
|
|
283
|
+
installFacial: config.installFacial,
|
|
284
|
+
installPos: config.installPos,
|
|
285
|
+
companyId: config.companyId
|
|
628
286
|
});
|
|
629
287
|
|
|
630
288
|
const envContent = generateEnv(credentials, {
|
|
631
|
-
frontendUrl: config.frontendDomain
|
|
632
|
-
apiUrl: config.apiDomain
|
|
633
|
-
socketUrl: config.apiDomain
|
|
634
|
-
frontendPort: config.frontendPort
|
|
635
|
-
backendPort: config.apiPort
|
|
636
|
-
gateAppPort: config.gateAppPort
|
|
637
|
-
guardianAppPort: config.guardianAppPort
|
|
638
|
-
facialWebPort: config.facialWebPort
|
|
639
|
-
posAppPort: config.posAppPort
|
|
640
|
-
gateAppUrl: config.gateAppDomain
|
|
641
|
-
guardianAppUrl: config.guardianAppDomain
|
|
642
|
-
facialWebUrl: config.facialAppDomain
|
|
643
|
-
posAppUrl: config.posAppDomain
|
|
644
|
-
companyId: config.companyId
|
|
645
|
-
installGate: config.installGate
|
|
646
|
-
installGuardian: config.installGuardian
|
|
647
|
-
installFacial: config.installFacial
|
|
648
|
-
installPos: config.installPos
|
|
289
|
+
frontendUrl: config.frontendDomain,
|
|
290
|
+
apiUrl: config.apiDomain,
|
|
291
|
+
socketUrl: config.apiDomain,
|
|
292
|
+
frontendPort: config.frontendPort,
|
|
293
|
+
backendPort: config.apiPort,
|
|
294
|
+
gateAppPort: config.gateAppPort,
|
|
295
|
+
guardianAppPort: config.guardianAppPort,
|
|
296
|
+
facialWebPort: config.facialWebPort,
|
|
297
|
+
posAppPort: config.posAppPort,
|
|
298
|
+
gateAppUrl: config.gateAppDomain,
|
|
299
|
+
guardianAppUrl: config.guardianAppDomain,
|
|
300
|
+
facialWebUrl: config.facialAppDomain,
|
|
301
|
+
posAppUrl: config.posAppDomain,
|
|
302
|
+
companyId: config.companyId,
|
|
303
|
+
installGate: config.installGate,
|
|
304
|
+
installGuardian: config.installGuardian,
|
|
305
|
+
installFacial: config.installFacial,
|
|
306
|
+
installPos: config.installPos
|
|
649
307
|
});
|
|
650
|
-
|
|
308
|
+
|
|
651
309
|
writeFileSync(join(config.installDir, 'docker-compose.yml'), dockerCompose);
|
|
652
310
|
writeFileSync(join(config.installDir, '.env'), envContent);
|
|
653
|
-
|
|
311
|
+
|
|
654
312
|
// Save credentials
|
|
655
313
|
const credentialsText = `ANTE ERP Installation Credentials
|
|
656
314
|
Generated: ${new Date().toISOString()}
|
|
@@ -671,9 +329,9 @@ Encryption Key: ${credentials.encryptionKey}
|
|
|
671
329
|
|
|
672
330
|
Access Information:
|
|
673
331
|
━━━━━━━━━━━━━━━━━━━
|
|
674
|
-
Frontend: ${config.frontendDomain
|
|
675
|
-
Backend: ${config.apiDomain
|
|
676
|
-
WebSocket: ${config.apiDomain
|
|
332
|
+
Frontend: ${config.frontendDomain}
|
|
333
|
+
Backend: ${config.apiDomain}
|
|
334
|
+
WebSocket: ${config.apiDomain}
|
|
677
335
|
|
|
678
336
|
Next Steps:
|
|
679
337
|
━━━━━━━━━━
|
|
@@ -686,50 +344,49 @@ Next Steps:
|
|
|
686
344
|
Documentation: https://docs.ante.ph
|
|
687
345
|
Support: support@ante.ph
|
|
688
346
|
`;
|
|
689
|
-
|
|
347
|
+
|
|
690
348
|
writeFileSync(join(config.installDir, 'installation-credentials.txt'), credentialsText);
|
|
691
349
|
}
|
|
692
350
|
},
|
|
693
351
|
{
|
|
694
|
-
title: 'Pulling Docker images',
|
|
352
|
+
title: formatStepTitle(stepPullImages, totalSteps, 'Pulling Docker images'),
|
|
695
353
|
task: async () => {
|
|
696
354
|
const composeFile = join(config.installDir, 'docker-compose.yml');
|
|
697
|
-
await
|
|
355
|
+
await pullImagesSilent(composeFile);
|
|
698
356
|
}
|
|
699
357
|
},
|
|
700
358
|
{
|
|
701
|
-
title: 'Starting services',
|
|
359
|
+
title: formatStepTitle(stepStartServices, totalSteps, 'Starting services'),
|
|
702
360
|
task: async () => {
|
|
703
361
|
const composeFile = join(config.installDir, 'docker-compose.yml');
|
|
704
|
-
await
|
|
362
|
+
await startServicesSilent(composeFile);
|
|
705
363
|
}
|
|
706
364
|
},
|
|
707
365
|
{
|
|
708
|
-
title: 'Waiting for services to be ready',
|
|
366
|
+
title: formatStepTitle(stepWaitServices, totalSteps, 'Waiting for services to be ready'),
|
|
709
367
|
task: async () => {
|
|
710
368
|
const composeFile = join(config.installDir, 'docker-compose.yml');
|
|
711
|
-
|
|
369
|
+
|
|
712
370
|
// Wait for backend to be healthy
|
|
713
371
|
const backendHealthy = await waitForServiceHealthy(composeFile, 'backend', 120);
|
|
714
372
|
if (!backendHealthy) {
|
|
715
373
|
throw new Error('Backend service failed to start');
|
|
716
374
|
}
|
|
717
|
-
|
|
718
|
-
// Give it a few more seconds
|
|
375
|
+
|
|
376
|
+
// Give it a few more seconds for full initialization
|
|
719
377
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
720
378
|
}
|
|
721
379
|
},
|
|
722
380
|
{
|
|
723
|
-
title: 'Initializing database
|
|
381
|
+
title: formatStepTitle(stepInitDatabase, totalSteps, 'Initializing database'),
|
|
724
382
|
task: async () => {
|
|
725
383
|
const composeFile = join(config.installDir, 'docker-compose.yml');
|
|
726
384
|
const schemaFile = join(__dirname, '../templates/init-schema.sql');
|
|
727
385
|
|
|
728
386
|
try {
|
|
729
|
-
const { readFileSync } = await import('fs');
|
|
730
387
|
const schemaSQL = readFileSync(schemaFile, 'utf8');
|
|
731
388
|
|
|
732
|
-
// Execute schema SQL in postgres container
|
|
389
|
+
// Execute schema SQL in postgres container
|
|
733
390
|
await execa('docker', [
|
|
734
391
|
'compose',
|
|
735
392
|
'-f', composeFile,
|
|
@@ -740,21 +397,12 @@ Support: support@ante.ph
|
|
|
740
397
|
'-U', 'ante',
|
|
741
398
|
'-d', 'ante_db'
|
|
742
399
|
], {
|
|
743
|
-
input: schemaSQL
|
|
400
|
+
input: schemaSQL,
|
|
401
|
+
stdout: 'pipe',
|
|
402
|
+
stderr: 'pipe'
|
|
744
403
|
});
|
|
745
|
-
} catch (error) {
|
|
746
|
-
throw new Error(`Schema initialization failed: ${error.message}`);
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
},
|
|
750
|
-
{
|
|
751
|
-
title: 'Seeding initial data',
|
|
752
|
-
task: async () => {
|
|
753
|
-
const composeFile = join(config.installDir, 'docker-compose.yml');
|
|
754
404
|
|
|
755
|
-
|
|
756
|
-
// Run production seed command in the backend container
|
|
757
|
-
// Uses seed:prod which compiles TypeScript and runs with node (production-safe)
|
|
405
|
+
// Run production seed command
|
|
758
406
|
await execa('docker', [
|
|
759
407
|
'compose',
|
|
760
408
|
'-f', composeFile,
|
|
@@ -764,51 +412,52 @@ Support: support@ante.ph
|
|
|
764
412
|
'npm',
|
|
765
413
|
'run',
|
|
766
414
|
'seed:prod'
|
|
767
|
-
]
|
|
415
|
+
], {
|
|
416
|
+
stdout: 'pipe',
|
|
417
|
+
stderr: 'pipe'
|
|
418
|
+
});
|
|
768
419
|
} catch (error) {
|
|
769
|
-
|
|
770
|
-
console.log(chalk.yellow(' ⚠ Seeding skipped (optional)'));
|
|
420
|
+
throw new Error(`Database initialization failed: ${error.message}`);
|
|
771
421
|
}
|
|
772
422
|
}
|
|
773
423
|
},
|
|
774
424
|
{
|
|
775
|
-
title: 'Running database migrations',
|
|
776
|
-
task: async (
|
|
425
|
+
title: formatStepTitle(stepRunMigrations, totalSteps, 'Running database migrations'),
|
|
426
|
+
task: async () => {
|
|
777
427
|
const composeFile = join(config.installDir, 'docker-compose.yml');
|
|
778
428
|
const result = await runMigrations(composeFile);
|
|
779
429
|
|
|
780
430
|
if (!result.success) {
|
|
781
431
|
throw new Error(`Migration failed:\n${result.output}`);
|
|
782
432
|
}
|
|
783
|
-
|
|
784
|
-
// Show migration output if there were migrations run
|
|
785
|
-
if (result.output && result.output.trim()) {
|
|
786
|
-
task.output = result.output;
|
|
787
|
-
}
|
|
788
433
|
}
|
|
789
434
|
}
|
|
790
435
|
], {
|
|
436
|
+
renderer: 'default',
|
|
791
437
|
rendererOptions: {
|
|
792
|
-
|
|
793
|
-
|
|
438
|
+
collapse: false,
|
|
439
|
+
showSubtasks: false,
|
|
440
|
+
clearOutput: false,
|
|
441
|
+
showTimer: false,
|
|
442
|
+
removeEmptyLines: true
|
|
443
|
+
},
|
|
444
|
+
concurrent: false,
|
|
445
|
+
exitOnError: true
|
|
794
446
|
});
|
|
795
|
-
|
|
796
|
-
await tasks.run();
|
|
797
447
|
|
|
798
|
-
|
|
799
|
-
if (requiresNginx(config.frontendDomain || 'http://localhost:8080', config.apiDomain || 'http://localhost:3001')) {
|
|
800
|
-
console.log(chalk.gray('\n🔧 Setting up reverse proxy...\n'));
|
|
448
|
+
await tasks.run();
|
|
801
449
|
|
|
450
|
+
// Configure NGINX if needed (silent)
|
|
451
|
+
if (requiresNginx(config.frontendDomain, config.apiDomain)) {
|
|
802
452
|
try {
|
|
803
453
|
await configureNginx({
|
|
804
|
-
frontendDomain: config.frontendDomain
|
|
805
|
-
apiDomain: config.apiDomain
|
|
806
|
-
frontendPort: config.frontendPort
|
|
807
|
-
apiPort: config.apiPort
|
|
454
|
+
frontendDomain: config.frontendDomain,
|
|
455
|
+
apiDomain: config.apiDomain,
|
|
456
|
+
frontendPort: config.frontendPort,
|
|
457
|
+
apiPort: config.apiPort
|
|
808
458
|
});
|
|
809
459
|
} catch (error) {
|
|
810
|
-
console.log(chalk.yellow('
|
|
811
|
-
console.log(chalk.gray('You may need to configure reverse proxy manually\n'));
|
|
460
|
+
console.log(chalk.yellow('⚠ NGINX configuration skipped (manual setup may be required)'));
|
|
812
461
|
}
|
|
813
462
|
}
|
|
814
463
|
|
|
@@ -818,20 +467,14 @@ Support: support@ante.ph
|
|
|
818
467
|
version: '1.0.0'
|
|
819
468
|
};
|
|
820
469
|
|
|
821
|
-
// Only include domain if it exists
|
|
822
|
-
if (config.domain) {
|
|
823
|
-
installConfig.domain = config.domain;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
470
|
saveInstallConfig(installConfig);
|
|
827
471
|
|
|
828
472
|
// Show success message
|
|
829
473
|
showSuccess(config.installDir, credentials, config);
|
|
830
|
-
|
|
474
|
+
|
|
831
475
|
} catch (error) {
|
|
832
476
|
console.error(chalk.red('\n✗ Installation failed:'), error.message);
|
|
833
477
|
console.error(chalk.gray('\nFor help, visit: https://docs.ante.ph/self-hosting/troubleshooting\n'));
|
|
834
478
|
process.exit(1);
|
|
835
479
|
}
|
|
836
480
|
}
|
|
837
|
-
|
package/src/utils/validation.js
CHANGED
|
@@ -300,13 +300,11 @@ export async function runSystemChecks() {
|
|
|
300
300
|
dockerCompose: await checkDockerCompose(),
|
|
301
301
|
node: checkNode(),
|
|
302
302
|
diskSpace: checkDiskSpace(),
|
|
303
|
-
memory: checkMemory(),
|
|
304
|
-
cpu: checkCPU(),
|
|
305
303
|
ports: await checkPorts()
|
|
306
304
|
};
|
|
307
|
-
|
|
305
|
+
|
|
308
306
|
const ok = Object.values(checks).every(check => check.ok);
|
|
309
|
-
|
|
307
|
+
|
|
310
308
|
return { ok, checks };
|
|
311
309
|
}
|
|
312
310
|
|