ante-erp-cli 1.8.5 → 1.9.1
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/CHANGELOG.md +20 -0
- package/bin/ante-cli.js +6 -0
- package/package.json +1 -1
- package/src/commands/install.js +35 -5
- package/src/commands/regenerate-compose.js +141 -0
- package/src/commands/update.js +56 -15
- package/src/templates/docker-compose.yml.js +29 -0
- package/src/templates/env.js +6 -1
- package/src/utils/nginx.js +104 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.10.0] - 2025-11-04
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Facial-web frontend app detection and installation** in `ante update` command
|
|
14
|
+
- Automatic detection of missing facial-web service during updates
|
|
15
|
+
- Interactive prompt to install facial-web when detected as missing
|
|
16
|
+
- Facial-web Docker image support: `ghcr.io/gtplusnet/ante-self-hosted-frontend-facial-web`
|
|
17
|
+
- Port 8083 default configuration for facial-web service
|
|
18
|
+
- Health checks for facial-web service after installation
|
|
19
|
+
- Automatic docker-compose.yml update to include facial-web service
|
|
20
|
+
- Environment variable configuration (FACIAL_WEB_PORT, FACIAL_WEB_URL)
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- **CLI version auto-bumping in GitHub Actions workflow**
|
|
24
|
+
- Added automatic version bumping when CLI changes are pushed to main
|
|
25
|
+
- Support for commit message flags: `[cli:minor]`, `[cli:major]`
|
|
26
|
+
- Defaults to patch version bump when no flag is specified
|
|
27
|
+
- Auto-commits version bump with `[skip ci]` to prevent infinite loops
|
|
28
|
+
- Enhanced Telegram notifications to show bump type when version is auto-bumped
|
|
29
|
+
|
|
10
30
|
## [1.8.1] - 2025-11-01
|
|
11
31
|
|
|
12
32
|
### Fixed
|
package/bin/ante-cli.js
CHANGED
|
@@ -34,6 +34,7 @@ import { migrate, seed, shell, optimize, reset as dbReset, info } from '../src/c
|
|
|
34
34
|
import { cloneDb } from '../src/commands/clone-db.js';
|
|
35
35
|
import { setDomain } from '../src/commands/set-domain.js';
|
|
36
36
|
import { sslEnable, sslStatus } from '../src/commands/ssl-enable.js';
|
|
37
|
+
import { regenerateCompose } from '../src/commands/regenerate-compose.js';
|
|
37
38
|
|
|
38
39
|
// Installation & Setup
|
|
39
40
|
program
|
|
@@ -218,6 +219,11 @@ program
|
|
|
218
219
|
.option('--no-interactive', 'Non-interactive mode')
|
|
219
220
|
.action(setDomain);
|
|
220
221
|
|
|
222
|
+
program
|
|
223
|
+
.command('regenerate-compose')
|
|
224
|
+
.description('Regenerate docker-compose.yml with correct configuration')
|
|
225
|
+
.action(regenerateCompose);
|
|
226
|
+
|
|
221
227
|
// SSL/HTTPS Management
|
|
222
228
|
const sslCmd = program
|
|
223
229
|
.command('ssl')
|
package/package.json
CHANGED
package/src/commands/install.js
CHANGED
|
@@ -115,6 +115,7 @@ function showSuccess(installDir, credentials, config) {
|
|
|
115
115
|
const apiUrl = config.apiDomain || 'http://localhost:3001';
|
|
116
116
|
const gateAppUrl = config.gateAppDomain || 'http://localhost:8081';
|
|
117
117
|
const guardianAppUrl = config.guardianAppDomain || 'http://localhost:8082';
|
|
118
|
+
const facialWebUrl = config.facialAppDomain || 'http://localhost:8083';
|
|
118
119
|
|
|
119
120
|
let accessInfo = chalk.white('Access Information:') + '\n' +
|
|
120
121
|
chalk.gray('━'.repeat(40)) + '\n' +
|
|
@@ -128,6 +129,10 @@ function showSuccess(installDir, credentials, config) {
|
|
|
128
129
|
accessInfo += chalk.cyan('Guardian App: ') + chalk.white(guardianAppUrl) + '\n';
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
if (config.installFacial) {
|
|
133
|
+
accessInfo += chalk.cyan('Facial Web: ') + chalk.white(facialWebUrl) + '\n';
|
|
134
|
+
}
|
|
135
|
+
|
|
131
136
|
accessInfo += chalk.cyan('Backend: ') + chalk.white(apiUrl) + '\n' +
|
|
132
137
|
chalk.cyan('WebSocket: ') + chalk.white(apiUrl) + '\n\n';
|
|
133
138
|
|
|
@@ -255,7 +260,8 @@ export async function install(options) {
|
|
|
255
260
|
choices: [
|
|
256
261
|
{ name: 'Main Frontend (Required)', value: 'main', checked: true, disabled: true },
|
|
257
262
|
{ name: 'Gate App (School/Gate attendance)', value: 'gate', checked: false },
|
|
258
|
-
{ name: 'Guardian App (Parent portal)', value: 'guardian', checked: false }
|
|
263
|
+
{ name: 'Guardian App (Parent portal)', value: 'guardian', checked: false },
|
|
264
|
+
{ name: 'Facial Web (Employee face recognition)', value: 'facial', checked: false }
|
|
259
265
|
],
|
|
260
266
|
validate: (answer) => {
|
|
261
267
|
if (answer.length < 1) {
|
|
@@ -269,7 +275,7 @@ export async function install(options) {
|
|
|
269
275
|
name: 'companyId',
|
|
270
276
|
message: 'Company ID (for multi-tenant apps):',
|
|
271
277
|
default: 1,
|
|
272
|
-
when: (answers) => answers.frontends.includes('gate') || answers.frontends.includes('guardian'),
|
|
278
|
+
when: (answers) => answers.frontends.includes('gate') || answers.frontends.includes('guardian') || answers.frontends.includes('facial'),
|
|
273
279
|
validate: (input) => {
|
|
274
280
|
if (input < 1) return 'Company ID must be at least 1';
|
|
275
281
|
return true;
|
|
@@ -356,26 +362,40 @@ export async function install(options) {
|
|
|
356
362
|
if (input < 1 || input > 65535) return 'Port must be between 1 and 65535';
|
|
357
363
|
return true;
|
|
358
364
|
}
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
type: 'number',
|
|
368
|
+
name: 'facialWebPort',
|
|
369
|
+
message: 'Facial Web port:',
|
|
370
|
+
default: 8083,
|
|
371
|
+
when: (answers) => answers.networkType !== 'domain' && frontendConfig.frontends.includes('facial'),
|
|
372
|
+
validate: (input) => {
|
|
373
|
+
if (input < 1 || input > 65535) return 'Port must be between 1 and 65535';
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
359
376
|
}
|
|
360
377
|
]);
|
|
361
378
|
|
|
362
379
|
// Build URLs based on configuration
|
|
363
|
-
let frontendUrl, apiUrl, gateAppUrl, guardianAppUrl;
|
|
380
|
+
let frontendUrl, apiUrl, gateAppUrl, guardianAppUrl, facialWebUrl;
|
|
364
381
|
const frontendPort = networkConfig.frontendPort || 8080;
|
|
365
382
|
const apiPort = networkConfig.apiPort || 3001;
|
|
366
383
|
const gateAppPort = networkConfig.gateAppPort || 8081;
|
|
367
384
|
const guardianAppPort = networkConfig.guardianAppPort || 8082;
|
|
385
|
+
const facialWebPort = networkConfig.facialWebPort || 8083;
|
|
368
386
|
|
|
369
387
|
if (networkConfig.networkType === 'localhost') {
|
|
370
388
|
frontendUrl = buildURL('localhost', frontendPort);
|
|
371
389
|
apiUrl = buildURL('localhost', apiPort);
|
|
372
390
|
gateAppUrl = buildURL('localhost', gateAppPort);
|
|
373
391
|
guardianAppUrl = buildURL('localhost', guardianAppPort);
|
|
392
|
+
facialWebUrl = buildURL('localhost', facialWebPort);
|
|
374
393
|
} else if (networkConfig.networkType === 'ip') {
|
|
375
394
|
frontendUrl = buildURL(networkConfig.publicIP, frontendPort);
|
|
376
395
|
apiUrl = buildURL(networkConfig.publicIP, apiPort);
|
|
377
396
|
gateAppUrl = buildURL(networkConfig.publicIP, gateAppPort);
|
|
378
397
|
guardianAppUrl = buildURL(networkConfig.publicIP, guardianAppPort);
|
|
398
|
+
facialWebUrl = buildURL(networkConfig.publicIP, facialWebPort);
|
|
379
399
|
} else {
|
|
380
400
|
// Domain - use standard HTTP/HTTPS ports (NGINX will handle reverse proxy)
|
|
381
401
|
const isHttps = await inquirer.prompt([{
|
|
@@ -403,6 +423,7 @@ export async function install(options) {
|
|
|
403
423
|
apiUrl = `${protocol}://${apiSubdomain.subdomain}.${networkConfig.domainName}`;
|
|
404
424
|
gateAppUrl = `${protocol}://gate.${networkConfig.domainName}`;
|
|
405
425
|
guardianAppUrl = `${protocol}://guardian.${networkConfig.domainName}`;
|
|
426
|
+
facialWebUrl = `${protocol}://facial.${networkConfig.domainName}`;
|
|
406
427
|
}
|
|
407
428
|
|
|
408
429
|
config = {
|
|
@@ -412,12 +433,15 @@ export async function install(options) {
|
|
|
412
433
|
apiDomain: apiUrl,
|
|
413
434
|
gateAppDomain: gateAppUrl,
|
|
414
435
|
guardianAppDomain: guardianAppUrl,
|
|
436
|
+
facialAppDomain: facialWebUrl,
|
|
415
437
|
frontendPort,
|
|
416
438
|
apiPort,
|
|
417
439
|
gateAppPort,
|
|
418
440
|
guardianAppPort,
|
|
441
|
+
facialWebPort,
|
|
419
442
|
installGate: frontendConfig.frontends.includes('gate'),
|
|
420
|
-
installGuardian: frontendConfig.frontends.includes('guardian')
|
|
443
|
+
installGuardian: frontendConfig.frontends.includes('guardian'),
|
|
444
|
+
installFacial: frontendConfig.frontends.includes('facial')
|
|
421
445
|
};
|
|
422
446
|
} else {
|
|
423
447
|
// Non-interactive mode - use options or defaults with IP detection
|
|
@@ -488,6 +512,9 @@ export async function install(options) {
|
|
|
488
512
|
if (config.installGuardian) {
|
|
489
513
|
console.log(chalk.cyan(' Guardian App:'), chalk.white(config.guardianAppDomain));
|
|
490
514
|
}
|
|
515
|
+
if (config.installFacial) {
|
|
516
|
+
console.log(chalk.cyan(' Facial Web:'), chalk.white(config.facialAppDomain));
|
|
517
|
+
}
|
|
491
518
|
console.log(chalk.cyan(' API:'), chalk.white(config.apiDomain));
|
|
492
519
|
if (config.companyId) {
|
|
493
520
|
console.log(chalk.cyan(' Company ID:'), chalk.white(config.companyId));
|
|
@@ -557,11 +584,14 @@ export async function install(options) {
|
|
|
557
584
|
backendPort: config.apiPort || 3001,
|
|
558
585
|
gateAppPort: config.gateAppPort || 8081,
|
|
559
586
|
guardianAppPort: config.guardianAppPort || 8082,
|
|
587
|
+
facialWebPort: config.facialWebPort || 8083,
|
|
560
588
|
gateAppUrl: config.gateAppDomain || 'http://localhost:8081',
|
|
561
589
|
guardianAppUrl: config.guardianAppDomain || 'http://localhost:8082',
|
|
590
|
+
facialWebUrl: config.facialWebUrl || 'http://localhost:8083',
|
|
562
591
|
companyId: config.companyId || 1,
|
|
563
592
|
installGate: config.installGate || false,
|
|
564
|
-
installGuardian: config.installGuardian || false
|
|
593
|
+
installGuardian: config.installGuardian || false,
|
|
594
|
+
installFacial: config.installFacial || false
|
|
565
595
|
});
|
|
566
596
|
|
|
567
597
|
writeFileSync(join(config.installDir, 'docker-compose.yml'), dockerCompose);
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { readFileSync, writeFileSync, renameSync, existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { getInstallDir } from '../utils/config.js';
|
|
5
|
+
import { generateDockerCompose } from '../templates/docker-compose.yml.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Parse .env file to extract configuration
|
|
9
|
+
* @param {string} envPath - Path to .env file
|
|
10
|
+
* @returns {Object} Configuration object
|
|
11
|
+
*/
|
|
12
|
+
function parseEnvFile(envPath) {
|
|
13
|
+
const envContent = readFileSync(envPath, 'utf8');
|
|
14
|
+
const config = {};
|
|
15
|
+
|
|
16
|
+
envContent.split('\n').forEach(line => {
|
|
17
|
+
const match = line.match(/^([A-Z_]+)=(.*)$/);
|
|
18
|
+
if (match) {
|
|
19
|
+
config[match[1]] = match[2];
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return config;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Detect which apps are installed from existing docker-compose.yml
|
|
28
|
+
* @param {string} composeFile - Path to docker-compose.yml
|
|
29
|
+
* @returns {Object} Installed apps
|
|
30
|
+
*/
|
|
31
|
+
function detectInstalledApps(composeFile) {
|
|
32
|
+
try {
|
|
33
|
+
const composeContent = readFileSync(composeFile, 'utf8');
|
|
34
|
+
return {
|
|
35
|
+
hasMain: composeContent.includes('frontend:') || composeContent.includes('container_name: ante-frontend'),
|
|
36
|
+
hasGateApp: composeContent.includes('gate-app:') || composeContent.includes('container_name: ante-gate-app'),
|
|
37
|
+
hasGuardianApp: composeContent.includes('guardian-app:') || composeContent.includes('container_name: ante-guardian-app')
|
|
38
|
+
};
|
|
39
|
+
} catch {
|
|
40
|
+
return { hasMain: true, hasGateApp: false, hasGuardianApp: false };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Regenerate docker-compose.yml with correct configuration
|
|
46
|
+
*/
|
|
47
|
+
export async function regenerateCompose() {
|
|
48
|
+
try {
|
|
49
|
+
console.log(chalk.bold('\n🔧 Regenerate docker-compose.yml\n'));
|
|
50
|
+
|
|
51
|
+
const installDir = getInstallDir();
|
|
52
|
+
const composeFile = join(installDir, 'docker-compose.yml');
|
|
53
|
+
const envFile = join(installDir, '.env');
|
|
54
|
+
|
|
55
|
+
console.log(chalk.gray(`Installation: ${installDir}\n`));
|
|
56
|
+
|
|
57
|
+
// Check if files exist
|
|
58
|
+
if (!existsSync(composeFile)) {
|
|
59
|
+
console.log(chalk.red('✗ docker-compose.yml not found'));
|
|
60
|
+
console.log(chalk.gray(' Run "ante install" first\n'));
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!existsSync(envFile)) {
|
|
65
|
+
console.log(chalk.red('✗ .env file not found'));
|
|
66
|
+
console.log(chalk.gray(' Run "ante install" first\n'));
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Detect installed apps
|
|
71
|
+
const installed = detectInstalledApps(composeFile);
|
|
72
|
+
console.log(chalk.cyan('Detected Configuration:'));
|
|
73
|
+
console.log(chalk.gray(` Frontend Main: ${installed.hasMain ? '✓ Installed' : '✗ Not installed'}`));
|
|
74
|
+
console.log(chalk.gray(` Gate App: ${installed.hasGateApp ? '✓ Installed' : '✗ Not installed'}`));
|
|
75
|
+
console.log(chalk.gray(` Guardian App: ${installed.hasGuardianApp ? '✓ Installed' : '✗ Not installed'}\n`));
|
|
76
|
+
|
|
77
|
+
// Parse .env file
|
|
78
|
+
const envConfig = parseEnvFile(envFile);
|
|
79
|
+
|
|
80
|
+
// Extract port configuration
|
|
81
|
+
const frontendPort = parseInt(envConfig.FRONTEND_PORT) || 8080;
|
|
82
|
+
const backendPort = parseInt(envConfig.BACKEND_PORT) || 3001;
|
|
83
|
+
const gateAppPort = parseInt(envConfig.GATE_APP_PORT) || 8081;
|
|
84
|
+
const guardianAppPort = parseInt(envConfig.GUARDIAN_APP_PORT) || 8082;
|
|
85
|
+
const companyId = parseInt(envConfig.COMPANY_ID) || 1;
|
|
86
|
+
|
|
87
|
+
console.log(chalk.cyan('Port Configuration:'));
|
|
88
|
+
console.log(chalk.gray(` Frontend: ${frontendPort}`));
|
|
89
|
+
console.log(chalk.gray(` Backend: ${backendPort}`));
|
|
90
|
+
if (installed.hasGateApp) {
|
|
91
|
+
console.log(chalk.gray(` Gate App: ${gateAppPort}`));
|
|
92
|
+
}
|
|
93
|
+
if (installed.hasGuardianApp) {
|
|
94
|
+
console.log(chalk.gray(` Guardian: ${guardianAppPort}`));
|
|
95
|
+
}
|
|
96
|
+
console.log(chalk.gray(` Company ID: ${companyId}\n`));
|
|
97
|
+
|
|
98
|
+
// Backup existing docker-compose.yml
|
|
99
|
+
const timestamp = new Date().toISOString().split('.')[0].replace(/:/g, '-').replace('T', '_');
|
|
100
|
+
const backupFile = `${composeFile}.${timestamp}.bak`;
|
|
101
|
+
|
|
102
|
+
console.log(chalk.gray('📦 Creating backup...'));
|
|
103
|
+
renameSync(composeFile, backupFile);
|
|
104
|
+
console.log(chalk.green(`✓ Backup created: ${backupFile}\n`));
|
|
105
|
+
|
|
106
|
+
// Generate new docker-compose.yml
|
|
107
|
+
console.log(chalk.gray('⚙️ Generating new docker-compose.yml...'));
|
|
108
|
+
const newCompose = generateDockerCompose({
|
|
109
|
+
frontendPort,
|
|
110
|
+
backendPort,
|
|
111
|
+
gateAppPort,
|
|
112
|
+
guardianAppPort,
|
|
113
|
+
installMain: installed.hasMain,
|
|
114
|
+
installGate: installed.hasGateApp,
|
|
115
|
+
installGuardian: installed.hasGuardianApp,
|
|
116
|
+
companyId
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Write new docker-compose.yml
|
|
120
|
+
writeFileSync(composeFile, newCompose);
|
|
121
|
+
console.log(chalk.green('✓ New docker-compose.yml generated\n'));
|
|
122
|
+
|
|
123
|
+
// Show what changed
|
|
124
|
+
console.log(chalk.bold.green('✓ Configuration regenerated successfully!\n'));
|
|
125
|
+
console.log(chalk.cyan('Next Steps:'));
|
|
126
|
+
console.log(chalk.gray(' 1. Review the changes: diff docker-compose.yml ' + backupFile.replace(installDir + '/', '')));
|
|
127
|
+
console.log(chalk.gray(' 2. Restart services: ante restart'));
|
|
128
|
+
console.log(chalk.gray(' 3. Check status: ante status\n'));
|
|
129
|
+
|
|
130
|
+
// Show important fixes
|
|
131
|
+
if (installed.hasGuardianApp) {
|
|
132
|
+
console.log(chalk.yellow('⚠ Important: Guardian App port mapping updated'));
|
|
133
|
+
console.log(chalk.gray(` Old: ${guardianAppPort}:3000 (incorrect)`));
|
|
134
|
+
console.log(chalk.gray(` New: ${guardianAppPort}:9003 (correct)\n`));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error(chalk.red('\n✗ Failed to regenerate docker-compose.yml:'), error.message);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
}
|
package/src/commands/update.js
CHANGED
|
@@ -9,32 +9,34 @@ import { backup } from './backup.js';
|
|
|
9
9
|
import { generateDockerCompose } from '../templates/docker-compose.yml.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Check if gate-app
|
|
12
|
+
* Check if gate-app, guardian-app, or facial-web is installed by checking docker-compose.yml
|
|
13
13
|
* @param {string} composeFile - Path to docker-compose.yml
|
|
14
|
-
* @returns {{hasGateApp: boolean, hasGuardianApp: boolean}}
|
|
14
|
+
* @returns {{hasGateApp: boolean, hasGuardianApp: boolean, hasFacialWeb: boolean}}
|
|
15
15
|
*/
|
|
16
16
|
function detectInstalledApps(composeFile) {
|
|
17
17
|
try {
|
|
18
18
|
const composeContent = readFileSync(composeFile, 'utf8');
|
|
19
19
|
return {
|
|
20
20
|
hasGateApp: composeContent.includes('gate-app:') || composeContent.includes('container_name: ante-gate-app'),
|
|
21
|
-
hasGuardianApp: composeContent.includes('guardian-app:') || composeContent.includes('container_name: ante-guardian-app')
|
|
21
|
+
hasGuardianApp: composeContent.includes('guardian-app:') || composeContent.includes('container_name: ante-guardian-app'),
|
|
22
|
+
hasFacialWeb: composeContent.includes('facial-web:') || composeContent.includes('container_name: ante-facial-web')
|
|
22
23
|
};
|
|
23
24
|
} catch {
|
|
24
|
-
return { hasGateApp: false, hasGuardianApp: false };
|
|
25
|
+
return { hasGateApp: false, hasGuardianApp: false, hasFacialWeb: false };
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Detect missing services that could be installed
|
|
30
31
|
* @param {string} composeFile - Path to docker-compose.yml
|
|
31
|
-
* @returns {{gateApp: boolean, guardianApp: boolean}}
|
|
32
|
+
* @returns {{gateApp: boolean, guardianApp: boolean, facialWeb: boolean}}
|
|
32
33
|
*/
|
|
33
34
|
function detectMissingServices(composeFile) {
|
|
34
35
|
const installed = detectInstalledApps(composeFile);
|
|
35
36
|
return {
|
|
36
37
|
gateApp: !installed.hasGateApp,
|
|
37
|
-
guardianApp: !installed.hasGuardianApp
|
|
38
|
+
guardianApp: !installed.hasGuardianApp,
|
|
39
|
+
facialWeb: !installed.hasFacialWeb
|
|
38
40
|
};
|
|
39
41
|
}
|
|
40
42
|
|
|
@@ -61,7 +63,7 @@ function parseEnvFile(envFile) {
|
|
|
61
63
|
* Update docker-compose.yml with new services
|
|
62
64
|
* @param {string} composeFile - Path to docker-compose.yml
|
|
63
65
|
* @param {string} envFile - Path to .env file
|
|
64
|
-
* @param {{gateApp: boolean, guardianApp: boolean}} newServices - Services to add
|
|
66
|
+
* @param {{gateApp: boolean, guardianApp: boolean, facialWeb: boolean}} newServices - Services to add
|
|
65
67
|
*/
|
|
66
68
|
function updateDockerCompose(composeFile, envFile, newServices) {
|
|
67
69
|
const envConfig = parseEnvFile(envFile);
|
|
@@ -73,9 +75,11 @@ function updateDockerCompose(composeFile, envFile, newServices) {
|
|
|
73
75
|
backendPort: parseInt(envConfig.BACKEND_PORT) || 3001,
|
|
74
76
|
gateAppPort: parseInt(envConfig.GATE_APP_PORT) || 8081,
|
|
75
77
|
guardianAppPort: parseInt(envConfig.GUARDIAN_APP_PORT) || 8082,
|
|
78
|
+
facialWebPort: parseInt(envConfig.FACIAL_WEB_PORT) || 8083,
|
|
76
79
|
installMain: true,
|
|
77
80
|
installGate: currentInstalled.hasGateApp || newServices.gateApp,
|
|
78
81
|
installGuardian: currentInstalled.hasGuardianApp || newServices.guardianApp,
|
|
82
|
+
installFacial: currentInstalled.hasFacialWeb || newServices.facialWeb,
|
|
79
83
|
companyId: parseInt(envConfig.COMPANY_ID) || 1
|
|
80
84
|
});
|
|
81
85
|
|
|
@@ -90,7 +94,7 @@ function updateDockerCompose(composeFile, envFile, newServices) {
|
|
|
90
94
|
/**
|
|
91
95
|
* Update .env file with new app configuration
|
|
92
96
|
* @param {string} envFile - Path to .env file
|
|
93
|
-
* @param {{gateApp: boolean, guardianApp: boolean}} newServices - Services to add
|
|
97
|
+
* @param {{gateApp: boolean, guardianApp: boolean, facialWeb: boolean}} newServices - Services to add
|
|
94
98
|
*/
|
|
95
99
|
function updateEnvFile(envFile, newServices) {
|
|
96
100
|
let envContent = readFileSync(envFile, 'utf8');
|
|
@@ -143,8 +147,32 @@ COMPANY_ID=1
|
|
|
143
147
|
}
|
|
144
148
|
}
|
|
145
149
|
|
|
150
|
+
// Add facial-web configuration if needed
|
|
151
|
+
if (newServices.facialWeb && !envContent.includes('FACIAL_WEB_PORT')) {
|
|
152
|
+
if (!envContent.includes('# FRONTEND APP CONFIGURATION')) {
|
|
153
|
+
envContent += `\n# ------------------------------------------------------------------------------
|
|
154
|
+
# FRONTEND APP CONFIGURATION
|
|
155
|
+
# ------------------------------------------------------------------------------
|
|
156
|
+
COMPANY_ID=1
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Replace commented line or add new
|
|
161
|
+
if (envContent.includes('# FACIAL_WEB_PORT=')) {
|
|
162
|
+
envContent = envContent.replace(/# FACIAL_WEB_PORT=\d+/, 'FACIAL_WEB_PORT=8083');
|
|
163
|
+
} else {
|
|
164
|
+
envContent += `FACIAL_WEB_PORT=8083\n`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (envContent.includes('# FACIAL_WEB_URL=')) {
|
|
168
|
+
envContent = envContent.replace(/# FACIAL_WEB_URL=.*/, 'FACIAL_WEB_URL=http://localhost:8083');
|
|
169
|
+
} else {
|
|
170
|
+
envContent += `FACIAL_WEB_URL=http://localhost:8083\n`;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
146
174
|
// Add COMPANY_ID if missing and any new service is being added
|
|
147
|
-
if ((newServices.gateApp || newServices.guardianApp) && !envContent.includes('COMPANY_ID=')) {
|
|
175
|
+
if ((newServices.gateApp || newServices.guardianApp || newServices.facialWeb) && !envContent.includes('COMPANY_ID=')) {
|
|
148
176
|
envContent += `COMPANY_ID=1\n`;
|
|
149
177
|
}
|
|
150
178
|
|
|
@@ -161,7 +189,7 @@ export async function update(options) {
|
|
|
161
189
|
const envFile = join(installDir, '.env');
|
|
162
190
|
|
|
163
191
|
// Detect which apps are installed
|
|
164
|
-
const { hasGateApp, hasGuardianApp } = detectInstalledApps(composeFile);
|
|
192
|
+
const { hasGateApp, hasGuardianApp, hasFacialWeb } = detectInstalledApps(composeFile);
|
|
165
193
|
|
|
166
194
|
console.log(chalk.bold('\n🔄 ANTE ERP Update\n'));
|
|
167
195
|
|
|
@@ -191,7 +219,7 @@ export async function update(options) {
|
|
|
191
219
|
}
|
|
192
220
|
|
|
193
221
|
// Track which services to install
|
|
194
|
-
let servicesToInstall = { gateApp: false, guardianApp: false };
|
|
222
|
+
let servicesToInstall = { gateApp: false, guardianApp: false, facialWeb: false };
|
|
195
223
|
|
|
196
224
|
const tasks = new Listr([
|
|
197
225
|
{
|
|
@@ -207,7 +235,7 @@ export async function update(options) {
|
|
|
207
235
|
task: async (ctx, task) => {
|
|
208
236
|
const missingServices = detectMissingServices(composeFile);
|
|
209
237
|
|
|
210
|
-
if (!missingServices.gateApp && !missingServices.guardianApp) {
|
|
238
|
+
if (!missingServices.gateApp && !missingServices.guardianApp && !missingServices.facialWeb) {
|
|
211
239
|
task.skip('All available services are already installed');
|
|
212
240
|
return;
|
|
213
241
|
}
|
|
@@ -215,6 +243,7 @@ export async function update(options) {
|
|
|
215
243
|
const availableServices = [];
|
|
216
244
|
if (missingServices.gateApp) availableServices.push('Gate App (School attendance)');
|
|
217
245
|
if (missingServices.guardianApp) availableServices.push('Guardian App (Parent portal)');
|
|
246
|
+
if (missingServices.facialWeb) availableServices.push('Facial Web (Face recognition)');
|
|
218
247
|
|
|
219
248
|
task.output = `Found ${availableServices.length} new service(s): ${availableServices.join(', ')}`;
|
|
220
249
|
ctx.missingServices = missingServices;
|
|
@@ -222,7 +251,7 @@ export async function update(options) {
|
|
|
222
251
|
},
|
|
223
252
|
{
|
|
224
253
|
title: 'Confirming installation of new services',
|
|
225
|
-
skip: (ctx) => !ctx.missingServices || (!ctx.missingServices.gateApp && !ctx.missingServices.guardianApp),
|
|
254
|
+
skip: (ctx) => !ctx.missingServices || (!ctx.missingServices.gateApp && !ctx.missingServices.guardianApp && !ctx.missingServices.facialWeb),
|
|
226
255
|
task: async (ctx, task) => {
|
|
227
256
|
if (options.force) {
|
|
228
257
|
servicesToInstall = ctx.missingServices;
|
|
@@ -238,12 +267,13 @@ export async function update(options) {
|
|
|
238
267
|
const availableServices = [];
|
|
239
268
|
if (ctx.missingServices.gateApp) availableServices.push('Gate App');
|
|
240
269
|
if (ctx.missingServices.guardianApp) availableServices.push('Guardian App');
|
|
270
|
+
if (ctx.missingServices.facialWeb) availableServices.push('Facial Web');
|
|
241
271
|
|
|
242
272
|
const { installNew } = await inquirer.prompt([
|
|
243
273
|
{
|
|
244
274
|
type: 'confirm',
|
|
245
275
|
name: 'installNew',
|
|
246
|
-
message: `Install ${availableServices.join('
|
|
276
|
+
message: `Install ${availableServices.join(', ')}?`,
|
|
247
277
|
default: true
|
|
248
278
|
}
|
|
249
279
|
]);
|
|
@@ -258,11 +288,12 @@ export async function update(options) {
|
|
|
258
288
|
},
|
|
259
289
|
{
|
|
260
290
|
title: 'Installing new services',
|
|
261
|
-
skip: () => !servicesToInstall.gateApp && !servicesToInstall.guardianApp,
|
|
291
|
+
skip: () => !servicesToInstall.gateApp && !servicesToInstall.guardianApp && !servicesToInstall.facialWeb,
|
|
262
292
|
task: async (ctx, task) => {
|
|
263
293
|
const servicesAdded = [];
|
|
264
294
|
if (servicesToInstall.gateApp) servicesAdded.push('Gate App');
|
|
265
295
|
if (servicesToInstall.guardianApp) servicesAdded.push('Guardian App');
|
|
296
|
+
if (servicesToInstall.facialWeb) servicesAdded.push('Facial Web');
|
|
266
297
|
|
|
267
298
|
task.output = `Adding ${servicesAdded.join(', ')} to configuration...`;
|
|
268
299
|
|
|
@@ -322,6 +353,16 @@ export async function update(options) {
|
|
|
322
353
|
}
|
|
323
354
|
}
|
|
324
355
|
},
|
|
356
|
+
{
|
|
357
|
+
title: 'Waiting for Facial Web to be healthy',
|
|
358
|
+
skip: () => !hasFacialWeb && !servicesToInstall.facialWeb,
|
|
359
|
+
task: async () => {
|
|
360
|
+
const healthy = await waitForServiceHealthy(composeFile, 'facial-web', 60);
|
|
361
|
+
if (!healthy) {
|
|
362
|
+
throw new Error('Facial Web did not become healthy within 60 seconds');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
},
|
|
325
366
|
{
|
|
326
367
|
title: 'Running database migrations',
|
|
327
368
|
task: async (ctx, task) => {
|
|
@@ -9,8 +9,10 @@ export function generateDockerCompose(options = {}) {
|
|
|
9
9
|
backendPort = 3001,
|
|
10
10
|
gateAppPort = 8081,
|
|
11
11
|
guardianAppPort = 8082,
|
|
12
|
+
facialWebPort = 8083,
|
|
12
13
|
installGate = false,
|
|
13
14
|
installGuardian = false,
|
|
15
|
+
installFacial = false,
|
|
14
16
|
companyId = 1
|
|
15
17
|
} = options;
|
|
16
18
|
|
|
@@ -240,6 +242,33 @@ ${installGate ? `
|
|
|
240
242
|
options:
|
|
241
243
|
max-size: "10m"
|
|
242
244
|
max-file: "3"
|
|
245
|
+
` : ''}${installFacial ? `
|
|
246
|
+
# ANTE Facial Web
|
|
247
|
+
facial-web:
|
|
248
|
+
image: ghcr.io/gtplusnet/ante-self-hosted-frontend-facial-web:latest
|
|
249
|
+
container_name: ante-facial-web
|
|
250
|
+
restart: unless-stopped
|
|
251
|
+
depends_on:
|
|
252
|
+
backend:
|
|
253
|
+
condition: service_healthy
|
|
254
|
+
environment:
|
|
255
|
+
- API_URL=\${API_URL:-http://localhost:${backendPort}}
|
|
256
|
+
- SOCKET_URL=\${SOCKET_URL:-ws://localhost:${backendPort}}
|
|
257
|
+
- COMPANY_ID=${companyId}
|
|
258
|
+
ports:
|
|
259
|
+
- "${facialWebPort}:5173"
|
|
260
|
+
networks:
|
|
261
|
+
- ante-network
|
|
262
|
+
healthcheck:
|
|
263
|
+
test: ["CMD", "curl", "-f", "http://localhost:5173"]
|
|
264
|
+
interval: 30s
|
|
265
|
+
timeout: 10s
|
|
266
|
+
retries: 3
|
|
267
|
+
logging:
|
|
268
|
+
driver: "json-file"
|
|
269
|
+
options:
|
|
270
|
+
max-size: "10m"
|
|
271
|
+
max-file: "3"
|
|
243
272
|
` : ''}
|
|
244
273
|
volumes:
|
|
245
274
|
postgres_data:
|
package/src/templates/env.js
CHANGED
|
@@ -13,11 +13,14 @@ export function generateEnv(credentials, options = {}) {
|
|
|
13
13
|
backendPort = 3001,
|
|
14
14
|
gateAppPort = 8081,
|
|
15
15
|
guardianAppPort = 8082,
|
|
16
|
+
facialWebPort = 8083,
|
|
16
17
|
gateAppUrl = 'http://localhost:8081',
|
|
17
18
|
guardianAppUrl = 'http://localhost:8082',
|
|
19
|
+
facialWebUrl = 'http://localhost:8083',
|
|
18
20
|
companyId = 1,
|
|
19
21
|
installGate = false,
|
|
20
22
|
installGuardian = false,
|
|
23
|
+
installFacial = false,
|
|
21
24
|
smtpHost = '',
|
|
22
25
|
smtpPort = 587,
|
|
23
26
|
smtpUsername = '',
|
|
@@ -60,14 +63,16 @@ FRONTEND_PORT=${frontendPort}
|
|
|
60
63
|
BACKEND_PORT=${backendPort}
|
|
61
64
|
${installGate ? `GATE_APP_PORT=${gateAppPort}` : '# GATE_APP_PORT=8081'}
|
|
62
65
|
${installGuardian ? `GUARDIAN_APP_PORT=${guardianAppPort}` : '# GUARDIAN_APP_PORT=8082'}
|
|
66
|
+
${installFacial ? `FACIAL_WEB_PORT=${facialWebPort}` : '# FACIAL_WEB_PORT=8083'}
|
|
63
67
|
# Note: WebSocket runs on the same port as backend (BACKEND_PORT)
|
|
64
68
|
|
|
65
|
-
${(installGate || installGuardian) ? `# ------------------------------------------------------------------------------
|
|
69
|
+
${(installGate || installGuardian || installFacial) ? `# ------------------------------------------------------------------------------
|
|
66
70
|
# FRONTEND APP CONFIGURATION
|
|
67
71
|
# ------------------------------------------------------------------------------
|
|
68
72
|
COMPANY_ID=${companyId}
|
|
69
73
|
${installGate ? `GATE_APP_URL=${gateAppUrl}` : '# GATE_APP_URL=http://localhost:8081'}
|
|
70
74
|
${installGuardian ? `GUARDIAN_APP_URL=${guardianAppUrl}` : '# GUARDIAN_APP_URL=http://localhost:8082'}
|
|
75
|
+
${installFacial ? `FACIAL_WEB_URL=${facialWebUrl}` : '# FACIAL_WEB_URL=http://localhost:8083'}
|
|
71
76
|
|
|
72
77
|
` : ''}# ------------------------------------------------------------------------------
|
|
73
78
|
# EMAIL CONFIGURATION
|
package/src/utils/nginx.js
CHANGED
|
@@ -50,10 +50,12 @@ export async function installNginx(spinner) {
|
|
|
50
50
|
* @param {string} config.apiDomain - API domain (e.g., api.ante.example.com)
|
|
51
51
|
* @param {string} [config.gateAppDomain] - Gate app domain (optional)
|
|
52
52
|
* @param {string} [config.guardianAppDomain] - Guardian app domain (optional)
|
|
53
|
+
* @param {string} [config.facialAppDomain] - Facial web app domain (optional)
|
|
53
54
|
* @param {number} config.frontendPort - Frontend Docker port (default: 8080)
|
|
54
55
|
* @param {number} config.apiPort - API Docker port (default: 3001)
|
|
55
56
|
* @param {number} [config.gateAppPort] - Gate app Docker port (default: 8081)
|
|
56
57
|
* @param {number} [config.guardianAppPort] - Guardian app Docker port (default: 8082)
|
|
58
|
+
* @param {number} [config.facialWebPort] - Facial web app Docker port (default: 8083)
|
|
57
59
|
* @param {boolean} config.ssl - Enable SSL configuration (default: false)
|
|
58
60
|
* @returns {string} NGINX configuration content
|
|
59
61
|
*/
|
|
@@ -63,10 +65,12 @@ export function generateNginxConfig(config) {
|
|
|
63
65
|
apiDomain,
|
|
64
66
|
gateAppDomain,
|
|
65
67
|
guardianAppDomain,
|
|
68
|
+
facialAppDomain,
|
|
66
69
|
frontendPort = 8080,
|
|
67
70
|
apiPort = 3001,
|
|
68
71
|
gateAppPort = 8081,
|
|
69
72
|
guardianAppPort = 8082,
|
|
73
|
+
facialWebPort = 8083,
|
|
70
74
|
ssl = false
|
|
71
75
|
} = config;
|
|
72
76
|
|
|
@@ -75,6 +79,7 @@ export function generateNginxConfig(config) {
|
|
|
75
79
|
const apiHost = apiDomain.replace(/^https?:\/\//, '').replace(/:\d+$/, '');
|
|
76
80
|
const gateAppHost = gateAppDomain ? gateAppDomain.replace(/^https?:\/\//, '').replace(/:\d+$/, '') : null;
|
|
77
81
|
const guardianAppHost = guardianAppDomain ? guardianAppDomain.replace(/^https?:\/\//, '').replace(/:\d+$/, '') : null;
|
|
82
|
+
const facialAppHost = facialAppDomain ? facialAppDomain.replace(/^https?:\/\//, '').replace(/:\d+$/, '') : null;
|
|
78
83
|
|
|
79
84
|
if (ssl) {
|
|
80
85
|
return generateSslNginxConfig({
|
|
@@ -82,10 +87,12 @@ export function generateNginxConfig(config) {
|
|
|
82
87
|
apiHost,
|
|
83
88
|
gateAppHost,
|
|
84
89
|
guardianAppHost,
|
|
90
|
+
facialAppHost,
|
|
85
91
|
frontendPort,
|
|
86
92
|
apiPort,
|
|
87
93
|
gateAppPort,
|
|
88
|
-
guardianAppPort
|
|
94
|
+
guardianAppPort,
|
|
95
|
+
facialWebPort
|
|
89
96
|
});
|
|
90
97
|
}
|
|
91
98
|
|
|
@@ -216,6 +223,37 @@ server {
|
|
|
216
223
|
proxy_read_timeout 60s;
|
|
217
224
|
}
|
|
218
225
|
}
|
|
226
|
+
` : ''}${facialAppHost ? `
|
|
227
|
+
# ANTE Facial Web App Configuration
|
|
228
|
+
server {
|
|
229
|
+
listen 80;
|
|
230
|
+
listen [::]:80;
|
|
231
|
+
server_name ${facialAppHost};
|
|
232
|
+
|
|
233
|
+
# Increase buffer sizes for large headers
|
|
234
|
+
client_header_buffer_size 16k;
|
|
235
|
+
large_client_header_buffers 4 16k;
|
|
236
|
+
|
|
237
|
+
# Increase body size for image uploads
|
|
238
|
+
client_max_body_size 50M;
|
|
239
|
+
|
|
240
|
+
location / {
|
|
241
|
+
proxy_pass http://localhost:${facialWebPort};
|
|
242
|
+
proxy_http_version 1.1;
|
|
243
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
244
|
+
proxy_set_header Connection 'upgrade';
|
|
245
|
+
proxy_set_header Host $host;
|
|
246
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
247
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
248
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
249
|
+
proxy_cache_bypass $http_upgrade;
|
|
250
|
+
|
|
251
|
+
# Timeout settings
|
|
252
|
+
proxy_connect_timeout 60s;
|
|
253
|
+
proxy_send_timeout 60s;
|
|
254
|
+
proxy_read_timeout 60s;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
219
257
|
` : ''}`;
|
|
220
258
|
}
|
|
221
259
|
|
|
@@ -226,10 +264,12 @@ server {
|
|
|
226
264
|
* @param {string} config.apiHost - API hostname
|
|
227
265
|
* @param {string} [config.gateAppHost] - Gate app hostname (optional)
|
|
228
266
|
* @param {string} [config.guardianAppHost] - Guardian app hostname (optional)
|
|
267
|
+
* @param {string} [config.facialAppHost] - Facial web app hostname (optional)
|
|
229
268
|
* @param {number} config.frontendPort - Frontend port
|
|
230
269
|
* @param {number} config.apiPort - API port
|
|
231
270
|
* @param {number} [config.gateAppPort] - Gate app port (default: 8081)
|
|
232
271
|
* @param {number} [config.guardianAppPort] - Guardian app port (default: 8082)
|
|
272
|
+
* @param {number} [config.facialWebPort] - Facial web app port (default: 8083)
|
|
233
273
|
* @returns {string} NGINX configuration with SSL
|
|
234
274
|
*/
|
|
235
275
|
function generateSslNginxConfig(config) {
|
|
@@ -238,10 +278,12 @@ function generateSslNginxConfig(config) {
|
|
|
238
278
|
apiHost,
|
|
239
279
|
gateAppHost,
|
|
240
280
|
guardianAppHost,
|
|
281
|
+
facialAppHost,
|
|
241
282
|
frontendPort,
|
|
242
283
|
apiPort,
|
|
243
284
|
gateAppPort = 8081,
|
|
244
|
-
guardianAppPort = 8082
|
|
285
|
+
guardianAppPort = 8082,
|
|
286
|
+
facialWebPort = 8083
|
|
245
287
|
} = config;
|
|
246
288
|
|
|
247
289
|
return `# ANTE Frontend Configuration (HTTPS)
|
|
@@ -475,6 +517,63 @@ server {
|
|
|
475
517
|
server_name ${guardianAppHost};
|
|
476
518
|
return 301 https://$server_name$request_uri;
|
|
477
519
|
}
|
|
520
|
+
` : ''}${facialAppHost ? `
|
|
521
|
+
# ANTE Facial Web App Configuration (HTTPS)
|
|
522
|
+
server {
|
|
523
|
+
listen 443 ssl;
|
|
524
|
+
listen [::]:443 ssl;
|
|
525
|
+
http2 on;
|
|
526
|
+
server_name ${facialAppHost};
|
|
527
|
+
|
|
528
|
+
# SSL Certificate paths
|
|
529
|
+
ssl_certificate /etc/letsencrypt/live/${facialAppHost}/fullchain.pem;
|
|
530
|
+
ssl_certificate_key /etc/letsencrypt/live/${facialAppHost}/privkey.pem;
|
|
531
|
+
|
|
532
|
+
# SSL Configuration
|
|
533
|
+
ssl_protocols TLSv1.2 TLSv1.3;
|
|
534
|
+
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
535
|
+
ssl_prefer_server_ciphers on;
|
|
536
|
+
ssl_session_cache shared:SSL:10m;
|
|
537
|
+
ssl_session_timeout 10m;
|
|
538
|
+
|
|
539
|
+
# Security headers
|
|
540
|
+
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
541
|
+
add_header X-Frame-Options SAMEORIGIN always;
|
|
542
|
+
add_header X-Content-Type-Options nosniff always;
|
|
543
|
+
add_header X-XSS-Protection "1; mode=block" always;
|
|
544
|
+
|
|
545
|
+
# Increase buffer sizes for large headers
|
|
546
|
+
client_header_buffer_size 16k;
|
|
547
|
+
large_client_header_buffers 4 16k;
|
|
548
|
+
|
|
549
|
+
# Increase body size for image uploads
|
|
550
|
+
client_max_body_size 50M;
|
|
551
|
+
|
|
552
|
+
location / {
|
|
553
|
+
proxy_pass http://localhost:${facialWebPort};
|
|
554
|
+
proxy_http_version 1.1;
|
|
555
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
556
|
+
proxy_set_header Connection 'upgrade';
|
|
557
|
+
proxy_set_header Host $host;
|
|
558
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
559
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
560
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
561
|
+
proxy_cache_bypass $http_upgrade;
|
|
562
|
+
|
|
563
|
+
# Timeout settings
|
|
564
|
+
proxy_connect_timeout 60s;
|
|
565
|
+
proxy_send_timeout 60s;
|
|
566
|
+
proxy_read_timeout 60s;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
# HTTP to HTTPS redirect for ${facialAppHost}
|
|
571
|
+
server {
|
|
572
|
+
listen 80;
|
|
573
|
+
listen [::]:80;
|
|
574
|
+
server_name ${facialAppHost};
|
|
575
|
+
return 301 https://$server_name$request_uri;
|
|
576
|
+
}
|
|
478
577
|
` : ''}`;
|
|
479
578
|
}
|
|
480
579
|
|
|
@@ -551,6 +650,9 @@ export async function configureNginx(config) {
|
|
|
551
650
|
if (config.guardianAppDomain) {
|
|
552
651
|
console.log(chalk.gray(` Guardian App: ${config.guardianAppDomain} → localhost:${config.guardianAppPort || 8082}`));
|
|
553
652
|
}
|
|
653
|
+
if (config.facialAppDomain) {
|
|
654
|
+
console.log(chalk.gray(` Facial Web: ${config.facialAppDomain} → localhost:${config.facialWebPort || 8083}`));
|
|
655
|
+
}
|
|
554
656
|
console.log(chalk.gray(` API: ${config.apiDomain} → localhost:${config.apiPort || 3001}`));
|
|
555
657
|
|
|
556
658
|
} catch (error) {
|