ante-erp-cli 1.7.1 ā 1.7.2
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/ssl-enable.js +30 -14
- package/src/utils/nginx.js +138 -1
- package/src/utils/ssl.js +55 -95
package/package.json
CHANGED
|
@@ -196,28 +196,19 @@ export async function sslEnable(options) {
|
|
|
196
196
|
|
|
197
197
|
console.log(chalk.bold('\nš Starting SSL Configuration...\n'));
|
|
198
198
|
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
console.log(chalk.cyan(`\nš Configuring SSL for ${domainConfig.domain}...\n`));
|
|
199
|
+
// Step 1: Obtain SSL certificates for all domains
|
|
200
|
+
console.log(chalk.cyan('š Obtaining SSL certificates...\n'));
|
|
202
201
|
|
|
202
|
+
for (const domainConfig of domains) {
|
|
203
203
|
try {
|
|
204
|
-
|
|
204
|
+
console.log(chalk.gray(` Obtaining certificate for ${domainConfig.domain}...`));
|
|
205
205
|
await obtainSSLCertificate({
|
|
206
206
|
domain: domainConfig.domain,
|
|
207
207
|
email: options.email,
|
|
208
208
|
staging: options.staging || false
|
|
209
209
|
});
|
|
210
|
-
|
|
211
|
-
// Step 2: Update NGINX configuration
|
|
212
|
-
await updateNginxForSSL({
|
|
213
|
-
domain: domainConfig.domain,
|
|
214
|
-
port: domainConfig.port
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
console.log(chalk.green(`ā SSL configured for ${domainConfig.domain}\n`));
|
|
218
|
-
|
|
219
210
|
} catch (error) {
|
|
220
|
-
console.log(chalk.red(
|
|
211
|
+
console.log(chalk.red(`\nā Failed to obtain SSL certificate for ${domainConfig.domain}:`), error.message);
|
|
221
212
|
console.log(chalk.yellow('\nā Troubleshooting:'));
|
|
222
213
|
console.log(chalk.gray(' 1. Verify DNS points to this server'));
|
|
223
214
|
console.log(chalk.gray(' 2. Ensure ports 80 and 443 are open'));
|
|
@@ -227,6 +218,31 @@ export async function sslEnable(options) {
|
|
|
227
218
|
}
|
|
228
219
|
}
|
|
229
220
|
|
|
221
|
+
console.log(chalk.green('\nā All SSL certificates obtained\n'));
|
|
222
|
+
|
|
223
|
+
// Step 2: Update NGINX configuration with SSL
|
|
224
|
+
console.log(chalk.cyan('š§ Configuring NGINX for HTTPS...\n'));
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
// Extract frontend and API ports
|
|
228
|
+
const frontendPort = frontendUrl.match(/:(\d+)/) ? parseInt(frontendUrl.match(/:(\d+)/)[1]) : 8080;
|
|
229
|
+
const apiPort = apiUrl.match(/:(\d+)/) ? parseInt(apiUrl.match(/:(\d+)/)[1]) : 3001;
|
|
230
|
+
|
|
231
|
+
await updateNginxForSSL({
|
|
232
|
+
frontendDomain: frontendUrl,
|
|
233
|
+
apiDomain: apiUrl,
|
|
234
|
+
frontendPort,
|
|
235
|
+
apiPort
|
|
236
|
+
});
|
|
237
|
+
} catch (error) {
|
|
238
|
+
console.log(chalk.red('\nā Failed to configure NGINX:'), error.message);
|
|
239
|
+
console.log(chalk.yellow('\nā Troubleshooting:'));
|
|
240
|
+
console.log(chalk.gray(' 1. Check NGINX configuration: nginx -t'));
|
|
241
|
+
console.log(chalk.gray(' 2. View NGINX logs: journalctl -u nginx'));
|
|
242
|
+
console.log(chalk.gray(' 3. Restore backup: cp /etc/nginx/sites-enabled/ante.backup /etc/nginx/sites-enabled/ante\n'));
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
|
|
230
246
|
// Setup automatic renewal
|
|
231
247
|
console.log(chalk.cyan('\nš Setting up automatic certificate renewal...\n'));
|
|
232
248
|
await setupAutoRenewal();
|
package/src/utils/nginx.js
CHANGED
|
@@ -50,15 +50,20 @@ export async function installNginx(spinner) {
|
|
|
50
50
|
* @param {string} config.apiDomain - API domain (e.g., api.ante.example.com)
|
|
51
51
|
* @param {number} config.frontendPort - Frontend Docker port (default: 8080)
|
|
52
52
|
* @param {number} config.apiPort - API Docker port (default: 3001)
|
|
53
|
+
* @param {boolean} config.ssl - Enable SSL configuration (default: false)
|
|
53
54
|
* @returns {string} NGINX configuration content
|
|
54
55
|
*/
|
|
55
56
|
export function generateNginxConfig(config) {
|
|
56
|
-
const { frontendDomain, apiDomain, frontendPort = 8080, apiPort = 3001 } = config;
|
|
57
|
+
const { frontendDomain, apiDomain, frontendPort = 8080, apiPort = 3001, ssl = false } = config;
|
|
57
58
|
|
|
58
59
|
// Extract domain without protocol
|
|
59
60
|
const frontendHost = frontendDomain.replace(/^https?:\/\//, '').replace(/:\d+$/, '');
|
|
60
61
|
const apiHost = apiDomain.replace(/^https?:\/\//, '').replace(/:\d+$/, '');
|
|
61
62
|
|
|
63
|
+
if (ssl) {
|
|
64
|
+
return generateSslNginxConfig({ frontendHost, apiHost, frontendPort, apiPort });
|
|
65
|
+
}
|
|
66
|
+
|
|
62
67
|
return `# ANTE Frontend Configuration
|
|
63
68
|
server {
|
|
64
69
|
listen 80;
|
|
@@ -127,6 +132,138 @@ server {
|
|
|
127
132
|
`;
|
|
128
133
|
}
|
|
129
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Generate NGINX configuration with SSL support
|
|
137
|
+
* @param {Object} config - Configuration object
|
|
138
|
+
* @param {string} config.frontendHost - Frontend hostname
|
|
139
|
+
* @param {string} config.apiHost - API hostname
|
|
140
|
+
* @param {number} config.frontendPort - Frontend port
|
|
141
|
+
* @param {number} config.apiPort - API port
|
|
142
|
+
* @returns {string} NGINX configuration with SSL
|
|
143
|
+
*/
|
|
144
|
+
function generateSslNginxConfig(config) {
|
|
145
|
+
const { frontendHost, apiHost, frontendPort, apiPort } = config;
|
|
146
|
+
|
|
147
|
+
return `# ANTE Frontend Configuration (HTTPS)
|
|
148
|
+
server {
|
|
149
|
+
listen 443 ssl;
|
|
150
|
+
listen [::]:443 ssl;
|
|
151
|
+
http2 on;
|
|
152
|
+
server_name ${frontendHost};
|
|
153
|
+
|
|
154
|
+
# SSL Certificate paths
|
|
155
|
+
ssl_certificate /etc/letsencrypt/live/${frontendHost}/fullchain.pem;
|
|
156
|
+
ssl_certificate_key /etc/letsencrypt/live/${frontendHost}/privkey.pem;
|
|
157
|
+
|
|
158
|
+
# SSL Configuration
|
|
159
|
+
ssl_protocols TLSv1.2 TLSv1.3;
|
|
160
|
+
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
161
|
+
ssl_prefer_server_ciphers on;
|
|
162
|
+
ssl_session_cache shared:SSL:10m;
|
|
163
|
+
ssl_session_timeout 10m;
|
|
164
|
+
|
|
165
|
+
# Security headers
|
|
166
|
+
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
167
|
+
add_header X-Frame-Options SAMEORIGIN always;
|
|
168
|
+
add_header X-Content-Type-Options nosniff always;
|
|
169
|
+
add_header X-XSS-Protection "1; mode=block" always;
|
|
170
|
+
|
|
171
|
+
# Increase buffer sizes for large headers
|
|
172
|
+
client_header_buffer_size 16k;
|
|
173
|
+
large_client_header_buffers 4 16k;
|
|
174
|
+
|
|
175
|
+
# Increase body size for file uploads
|
|
176
|
+
client_max_body_size 100M;
|
|
177
|
+
|
|
178
|
+
location / {
|
|
179
|
+
proxy_pass http://localhost:${frontendPort};
|
|
180
|
+
proxy_http_version 1.1;
|
|
181
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
182
|
+
proxy_set_header Connection 'upgrade';
|
|
183
|
+
proxy_set_header Host $host;
|
|
184
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
185
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
186
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
187
|
+
proxy_cache_bypass $http_upgrade;
|
|
188
|
+
|
|
189
|
+
# Timeout settings
|
|
190
|
+
proxy_connect_timeout 60s;
|
|
191
|
+
proxy_send_timeout 60s;
|
|
192
|
+
proxy_read_timeout 60s;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# HTTP to HTTPS redirect for ${frontendHost}
|
|
197
|
+
server {
|
|
198
|
+
listen 80;
|
|
199
|
+
listen [::]:80;
|
|
200
|
+
server_name ${frontendHost};
|
|
201
|
+
return 301 https://$server_name$request_uri;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
# ANTE API Configuration (HTTPS)
|
|
205
|
+
server {
|
|
206
|
+
listen 443 ssl;
|
|
207
|
+
listen [::]:443 ssl;
|
|
208
|
+
http2 on;
|
|
209
|
+
server_name ${apiHost};
|
|
210
|
+
|
|
211
|
+
# SSL Certificate paths
|
|
212
|
+
ssl_certificate /etc/letsencrypt/live/${apiHost}/fullchain.pem;
|
|
213
|
+
ssl_certificate_key /etc/letsencrypt/live/${apiHost}/privkey.pem;
|
|
214
|
+
|
|
215
|
+
# SSL Configuration
|
|
216
|
+
ssl_protocols TLSv1.2 TLSv1.3;
|
|
217
|
+
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
218
|
+
ssl_prefer_server_ciphers on;
|
|
219
|
+
ssl_session_cache shared:SSL:10m;
|
|
220
|
+
ssl_session_timeout 10m;
|
|
221
|
+
|
|
222
|
+
# Security headers
|
|
223
|
+
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
224
|
+
add_header X-Frame-Options SAMEORIGIN always;
|
|
225
|
+
add_header X-Content-Type-Options nosniff always;
|
|
226
|
+
add_header X-XSS-Protection "1; mode=block" always;
|
|
227
|
+
|
|
228
|
+
# Increase buffer sizes for large headers
|
|
229
|
+
client_header_buffer_size 16k;
|
|
230
|
+
large_client_header_buffers 4 16k;
|
|
231
|
+
|
|
232
|
+
# Increase body size for file uploads
|
|
233
|
+
client_max_body_size 100M;
|
|
234
|
+
|
|
235
|
+
location / {
|
|
236
|
+
proxy_pass http://localhost:${apiPort};
|
|
237
|
+
proxy_http_version 1.1;
|
|
238
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
239
|
+
proxy_set_header Connection 'upgrade';
|
|
240
|
+
proxy_set_header Host $host;
|
|
241
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
242
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
243
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
244
|
+
proxy_cache_bypass $http_upgrade;
|
|
245
|
+
|
|
246
|
+
# Timeout settings
|
|
247
|
+
proxy_connect_timeout 60s;
|
|
248
|
+
proxy_send_timeout 60s;
|
|
249
|
+
proxy_read_timeout 60s;
|
|
250
|
+
|
|
251
|
+
# WebSocket support
|
|
252
|
+
proxy_set_header X-Forwarded-Host $host;
|
|
253
|
+
proxy_set_header X-Forwarded-Server $host;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
# HTTP to HTTPS redirect for ${apiHost}
|
|
258
|
+
server {
|
|
259
|
+
listen 80;
|
|
260
|
+
listen [::]:80;
|
|
261
|
+
server_name ${apiHost};
|
|
262
|
+
return 301 https://$server_name$request_uri;
|
|
263
|
+
}
|
|
264
|
+
`;
|
|
265
|
+
}
|
|
266
|
+
|
|
130
267
|
/**
|
|
131
268
|
* Configure NGINX for ANTE domains
|
|
132
269
|
* @param {Object} config - Configuration object
|
package/src/utils/ssl.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import { execa } from 'execa';
|
|
4
|
-
import {
|
|
4
|
+
import { writeFileSync, existsSync, copyFileSync } from 'fs';
|
|
5
|
+
import { generateNginxConfig } from './nginx.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Check if certbot is installed
|
|
@@ -121,16 +122,19 @@ export async function obtainSSLCertificate(config) {
|
|
|
121
122
|
/**
|
|
122
123
|
* Update NGINX configuration to use SSL
|
|
123
124
|
* @param {Object} config - Configuration object
|
|
124
|
-
* @param {string} config.
|
|
125
|
-
* @param {
|
|
125
|
+
* @param {string} config.frontendDomain - Frontend domain URL
|
|
126
|
+
* @param {string} config.apiDomain - API domain URL
|
|
127
|
+
* @param {number} [config.frontendPort=8080] - Frontend port
|
|
128
|
+
* @param {number} [config.apiPort=3001] - API port
|
|
126
129
|
* @returns {Promise<void>}
|
|
127
130
|
*/
|
|
128
131
|
export async function updateNginxForSSL(config) {
|
|
129
|
-
const {
|
|
130
|
-
const spinner = ora(
|
|
132
|
+
const { frontendDomain, apiDomain, frontendPort = 8080, apiPort = 3001 } = config;
|
|
133
|
+
const spinner = ora('Updating NGINX configuration for HTTPS...').start();
|
|
131
134
|
|
|
132
135
|
try {
|
|
133
136
|
const configPath = '/etc/nginx/sites-enabled/ante';
|
|
137
|
+
const backupPath = `${configPath}.backup`;
|
|
134
138
|
|
|
135
139
|
// Check if config exists
|
|
136
140
|
if (!existsSync(configPath)) {
|
|
@@ -138,112 +142,68 @@ export async function updateNginxForSSL(config) {
|
|
|
138
142
|
throw new Error('Please run "ante set-domain" first to configure NGINX');
|
|
139
143
|
}
|
|
140
144
|
|
|
141
|
-
//
|
|
142
|
-
|
|
145
|
+
// Backup existing configuration
|
|
146
|
+
spinner.text = 'Backing up current NGINX configuration...';
|
|
147
|
+
copyFileSync(configPath, backupPath);
|
|
143
148
|
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
// Generate new SSL-enabled configuration
|
|
150
|
+
spinner.text = 'Generating SSL configuration...';
|
|
151
|
+
const sslConfig = generateNginxConfig({
|
|
152
|
+
frontendDomain,
|
|
153
|
+
apiDomain,
|
|
154
|
+
frontendPort,
|
|
155
|
+
apiPort,
|
|
156
|
+
ssl: true
|
|
157
|
+
});
|
|
149
158
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
spinner.fail(`No NGINX configuration found for ${domain}`);
|
|
153
|
-
throw new Error(`Domain ${domain} not found in NGINX configuration`);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const oldServerBlock = match[0];
|
|
157
|
-
|
|
158
|
-
// Create HTTPS server block
|
|
159
|
-
const httpsServerBlock = `# HTTPS Configuration for ${domain}
|
|
160
|
-
server {
|
|
161
|
-
listen 443 ssl http2;
|
|
162
|
-
listen [::]:443 ssl http2;
|
|
163
|
-
server_name ${domain};
|
|
164
|
-
|
|
165
|
-
# SSL Certificate paths
|
|
166
|
-
ssl_certificate /etc/letsencrypt/live/${domain}/fullchain.pem;
|
|
167
|
-
ssl_certificate_key /etc/letsencrypt/live/${domain}/privkey.pem;
|
|
168
|
-
|
|
169
|
-
# SSL Configuration
|
|
170
|
-
ssl_protocols TLSv1.2 TLSv1.3;
|
|
171
|
-
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
172
|
-
ssl_prefer_server_ciphers on;
|
|
173
|
-
ssl_session_cache shared:SSL:10m;
|
|
174
|
-
ssl_session_timeout 10m;
|
|
175
|
-
|
|
176
|
-
# Security headers
|
|
177
|
-
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
178
|
-
add_header X-Frame-Options SAMEORIGIN always;
|
|
179
|
-
add_header X-Content-Type-Options nosniff always;
|
|
180
|
-
add_header X-XSS-Protection "1; mode=block" always;
|
|
181
|
-
|
|
182
|
-
# Increase buffer sizes for large headers
|
|
183
|
-
client_header_buffer_size 16k;
|
|
184
|
-
large_client_header_buffers 4 16k;
|
|
185
|
-
|
|
186
|
-
# Increase body size for file uploads
|
|
187
|
-
client_max_body_size 100M;
|
|
188
|
-
|
|
189
|
-
location / {
|
|
190
|
-
proxy_pass http://localhost:${port};
|
|
191
|
-
proxy_http_version 1.1;
|
|
192
|
-
proxy_set_header Upgrade $http_upgrade;
|
|
193
|
-
proxy_set_header Connection 'upgrade';
|
|
194
|
-
proxy_set_header Host $host;
|
|
195
|
-
proxy_set_header X-Real-IP $remote_addr;
|
|
196
|
-
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
197
|
-
proxy_set_header X-Forwarded-Proto $scheme;
|
|
198
|
-
proxy_cache_bypass $http_upgrade;
|
|
199
|
-
|
|
200
|
-
# Timeout settings
|
|
201
|
-
proxy_connect_timeout 60s;
|
|
202
|
-
proxy_send_timeout 60s;
|
|
203
|
-
proxy_read_timeout 60s;
|
|
204
|
-
|
|
205
|
-
# WebSocket support
|
|
206
|
-
proxy_set_header X-Forwarded-Host $host;
|
|
207
|
-
proxy_set_header X-Forwarded-Server $host;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
# HTTP to HTTPS redirect for ${domain}
|
|
212
|
-
server {
|
|
213
|
-
listen 80;
|
|
214
|
-
listen [::]:80;
|
|
215
|
-
server_name ${domain};
|
|
216
|
-
|
|
217
|
-
# Redirect all HTTP requests to HTTPS
|
|
218
|
-
return 301 https://$server_name$request_uri;
|
|
219
|
-
}`;
|
|
220
|
-
|
|
221
|
-
// Replace the old server block with HTTPS configuration
|
|
222
|
-
nginxConfig = nginxConfig.replace(oldServerBlock, httpsServerBlock);
|
|
223
|
-
|
|
224
|
-
// Write updated configuration
|
|
225
|
-
writeFileSync(configPath, nginxConfig);
|
|
226
|
-
|
|
227
|
-
spinner.text = 'Testing NGINX configuration...';
|
|
159
|
+
// Write new configuration
|
|
160
|
+
writeFileSync(configPath, sslConfig);
|
|
228
161
|
|
|
229
162
|
// Test NGINX configuration
|
|
163
|
+
spinner.text = 'Testing NGINX configuration...';
|
|
230
164
|
try {
|
|
231
|
-
await execa('nginx', ['-t'], { stdio: 'pipe' });
|
|
165
|
+
const { stderr } = await execa('nginx', ['-t'], { stdio: 'pipe' });
|
|
166
|
+
|
|
167
|
+
// Check for warnings
|
|
168
|
+
if (stderr && !stderr.includes('syntax is ok')) {
|
|
169
|
+
console.log(chalk.yellow('\nā NGINX warnings:'));
|
|
170
|
+
console.log(chalk.gray(stderr));
|
|
171
|
+
}
|
|
232
172
|
} catch (error) {
|
|
233
173
|
spinner.fail('NGINX configuration test failed');
|
|
234
|
-
throw new Error(`Invalid NGINX configuration: ${error.message}`);
|
|
235
|
-
}
|
|
236
174
|
|
|
237
|
-
|
|
175
|
+
// Restore backup
|
|
176
|
+
console.log(chalk.yellow('\nā Restoring previous configuration...'));
|
|
177
|
+
copyFileSync(backupPath, configPath);
|
|
178
|
+
|
|
179
|
+
throw new Error(`Invalid NGINX configuration: ${error.stderr || error.message}`);
|
|
180
|
+
}
|
|
238
181
|
|
|
239
182
|
// Reload NGINX
|
|
183
|
+
spinner.text = 'Reloading NGINX...';
|
|
240
184
|
try {
|
|
241
185
|
await execa('systemctl', ['reload', 'nginx'], { stdio: 'pipe' });
|
|
242
186
|
} catch {
|
|
187
|
+
// If reload fails, try restart
|
|
243
188
|
await execa('systemctl', ['restart', 'nginx'], { stdio: 'pipe' });
|
|
244
189
|
}
|
|
245
190
|
|
|
246
|
-
|
|
191
|
+
// Verify NGINX is running
|
|
192
|
+
const { stdout } = await execa('systemctl', ['is-active', 'nginx'], { stdio: 'pipe' });
|
|
193
|
+
if (stdout.trim() !== 'active') {
|
|
194
|
+
throw new Error('NGINX failed to start after configuration update');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
spinner.succeed('NGINX configured for HTTPS');
|
|
198
|
+
|
|
199
|
+
// Show configuration info
|
|
200
|
+
const frontendHost = frontendDomain.replace(/^https?:\/\//, '').replace(/:\d+$/, '');
|
|
201
|
+
const apiHost = apiDomain.replace(/^https?:\/\//, '').replace(/:\d+$/, '');
|
|
202
|
+
console.log(chalk.gray('\nš NGINX Configuration:'));
|
|
203
|
+
console.log(chalk.gray(` Frontend: https://${frontendHost} ā localhost:${frontendPort}`));
|
|
204
|
+
console.log(chalk.gray(` API: https://${apiHost} ā localhost:${apiPort}`));
|
|
205
|
+
console.log(chalk.gray(` HTTP redirects to HTTPS: Enabled\n`));
|
|
206
|
+
|
|
247
207
|
} catch (error) {
|
|
248
208
|
spinner.fail('Failed to update NGINX configuration');
|
|
249
209
|
throw error;
|