@akash-chowdhury-24/deployhub 2.0.3 → 2.0.4
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/README.md +41 -0
- package/package.json +1 -1
- package/src/commands/doctor.js +154 -0
- package/src/deployment/providers/ssh.js +75 -10
- package/src/utils/nginx.js +60 -3
package/README.md
CHANGED
|
@@ -604,9 +604,38 @@ DeployHub supports six deployment targets. Pick based on what infrastructure you
|
|
|
604
604
|
|
|
605
605
|
Each method below follows the same structure: **prerequisites** (before `deployhub init`), **what DeployHub automates**, **after init** (matches terminal output), and a **variable reference**.
|
|
606
606
|
|
|
607
|
+
### One-time server setup (before your first deploy)
|
|
608
|
+
|
|
609
|
+
SSH-based methods (SSH, EC2, Azure VM, GCP VM) require a few one-time steps on the server **before your first deploy**. DeployHub does not silently change ownership or sudo policy for you.
|
|
610
|
+
|
|
611
|
+
SSH into your server once and run:
|
|
612
|
+
|
|
613
|
+
```bash
|
|
614
|
+
sudo mkdir -p /var/www/your-app-name
|
|
615
|
+
sudo chown your-ssh-user:your-ssh-user /var/www/your-app-name
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
Replace `/var/www/your-app-name` with your actual deploy path and `your-ssh-user` with your configured `SSH_USER` (e.g. `ec2-user` on Amazon Linux, `ubuntu` on Ubuntu). Without this, DeployHub cannot write your build output — `deployhub doctor` will catch it and show the exact fix.
|
|
619
|
+
|
|
620
|
+
**Frontend deploys** that auto-activate `nginx.conf` also need **passwordless sudo** for Nginx test/reload (and `cp` into `/etc/nginx/`). After installing Nginx, run `sudo visudo` and add a line like:
|
|
621
|
+
|
|
622
|
+
```bash
|
|
623
|
+
your-ssh-user ALL=(ALL) NOPASSWD: /usr/sbin/nginx, /bin/cp, /usr/bin/cp, /bin/systemctl, /usr/bin/systemctl
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
> **Security note:** This example grants broad privileges — unrestricted `cp` (any source/destination) and `systemctl` (any unit/action), not just Nginx. That keeps setup simple but is a significant trust boundary. For production, prefer a dedicated deploy user and a **narrow wrapper script** (e.g. `/usr/local/bin/deployhub-nginx-reload` that only copies to your project's config path and runs `nginx -t` + reload), then grant `NOPASSWD` only for that script. The line above is a starting point for dev/test servers; tighten it before production.
|
|
627
|
+
|
|
628
|
+
Install Nginx if it is not already present:
|
|
629
|
+
|
|
630
|
+
- **Amazon Linux / RHEL:** `sudo yum install -y nginx && sudo systemctl enable --now nginx`
|
|
631
|
+
- **Ubuntu / Debian:** `sudo apt install -y nginx`
|
|
632
|
+
|
|
633
|
+
DeployHub detects whether the server uses Debian-style `sites-available` or RHEL-style `conf.d` at deploy time and writes a **uniquely named** config file for your project only — it does not overwrite unrelated Nginx configs.
|
|
634
|
+
|
|
607
635
|
### SSH
|
|
608
636
|
|
|
609
637
|
**Prerequisites (before `deployhub init`):**
|
|
638
|
+
- [ ] Complete **[one-time server setup](#one-time-server-setup-before-your-first-deploy)** (deploy path ownership + Nginx/sudo for frontends)
|
|
610
639
|
- [ ] A Linux server with SSH enabled
|
|
611
640
|
- [ ] Private SSH key file (.pem/.key) and public key in `authorized_keys`
|
|
612
641
|
- [ ] Port 22 open in firewall for your IP
|
|
@@ -618,6 +647,9 @@ Each method below follows the same structure: **prerequisites** (before `deployh
|
|
|
618
647
|
- SSH key permission check (offers to `chmod 600`)
|
|
619
648
|
- SSH connectivity test during `init`
|
|
620
649
|
- Deploy path write-permission check during `deployhub doctor`
|
|
650
|
+
- Nginx layout detection (Debian `sites-available` vs RHEL `conf.d`) at deploy time
|
|
651
|
+
- Nginx config test (`nginx -t`) before reload
|
|
652
|
+
- Passwordless sudo and Nginx checks during `deployhub doctor` (frontend)
|
|
621
653
|
- Artifact upload, extract, app restart (PM2, gunicorn, etc.)
|
|
622
654
|
|
|
623
655
|
**After `init`:**
|
|
@@ -669,6 +701,7 @@ Each method below follows the same structure: **prerequisites** (before `deployh
|
|
|
669
701
|
### AWS EC2
|
|
670
702
|
|
|
671
703
|
**Prerequisites:**
|
|
704
|
+
- [ ] Complete **[one-time server setup](#one-time-server-setup-before-your-first-deploy)** (`ec2-user` on Amazon Linux)
|
|
672
705
|
- [ ] EC2 instance launched in AWS Console (DeployHub does not create it)
|
|
673
706
|
- [ ] Key pair `.pem` downloaded at launch
|
|
674
707
|
- [ ] Security group: inbound SSH (22) from your IP
|
|
@@ -679,6 +712,8 @@ Each method below follows the same structure: **prerequisites** (before `deployh
|
|
|
679
712
|
- EC2-specific `.env.example` (SSH + optional AWS API vars)
|
|
680
713
|
- SSH key validation and connectivity test
|
|
681
714
|
- Deploy path write-permission check during `deployhub doctor`
|
|
715
|
+
- Nginx layout detection and config test before reload (frontend)
|
|
716
|
+
- Passwordless sudo and Nginx checks during `deployhub doctor` (frontend)
|
|
682
717
|
- OS user suggestion from AMI hint (ubuntu, ec2-user)
|
|
683
718
|
- Optional public IP lookup via `EC2_INSTANCE_ID` + AWS CLI
|
|
684
719
|
|
|
@@ -702,6 +737,7 @@ Each method below follows the same structure: **prerequisites** (before `deployh
|
|
|
702
737
|
### Azure VM
|
|
703
738
|
|
|
704
739
|
**Prerequisites:**
|
|
740
|
+
- [ ] Complete **[one-time server setup](#one-time-server-setup-before-your-first-deploy)** (`azureuser` or your VM login user)
|
|
705
741
|
- [ ] Azure VM created in Portal (DeployHub does not provision it)
|
|
706
742
|
- [ ] NSG rule allowing inbound SSH (port 22)
|
|
707
743
|
- [ ] SSH public key on the VM
|
|
@@ -713,6 +749,8 @@ Each method below follows the same structure: **prerequisites** (before `deployh
|
|
|
713
749
|
- Auto-detects subscription ID via `az` CLI if logged in
|
|
714
750
|
- SSH key validation and connectivity test
|
|
715
751
|
- Deploy path write-permission check during `deployhub doctor`
|
|
752
|
+
- Nginx layout detection and config test before reload (frontend)
|
|
753
|
+
- Passwordless sudo and Nginx checks during `deployhub doctor` (frontend)
|
|
716
754
|
|
|
717
755
|
**After `init`:**
|
|
718
756
|
1. Azure Portal → VM → Networking → allow SSH (22) from your IP
|
|
@@ -733,6 +771,7 @@ Each method below follows the same structure: **prerequisites** (before `deployh
|
|
|
733
771
|
### GCP VM
|
|
734
772
|
|
|
735
773
|
**Prerequisites:**
|
|
774
|
+
- [ ] Complete **[one-time server setup](#one-time-server-setup-before-your-first-deploy)** (your GCP SSH username)
|
|
736
775
|
- [ ] Compute Engine VM created (DeployHub does not create it)
|
|
737
776
|
- [ ] Firewall rule allowing `tcp:22` (default `default-allow-ssh` may exist)
|
|
738
777
|
- [ ] SSH public key in **Metadata → SSH Keys** (GCP uses metadata keys, not launch key pairs like AWS)
|
|
@@ -744,6 +783,8 @@ Each method below follows the same structure: **prerequisites** (before `deployh
|
|
|
744
783
|
- Auto-detects project ID via `gcloud` if authenticated
|
|
745
784
|
- SSH key validation and connectivity test
|
|
746
785
|
- Deploy path write-permission check during `deployhub doctor`
|
|
786
|
+
- Nginx layout detection and config test before reload (frontend)
|
|
787
|
+
- Passwordless sudo and Nginx checks during `deployhub doctor` (frontend)
|
|
747
788
|
|
|
748
789
|
**After `init`:**
|
|
749
790
|
1. GCP Console → VPC → Firewall → ensure SSH (tcp:22) allowed from your IP
|
package/package.json
CHANGED
package/src/commands/doctor.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
buildDeployPathWriteTestCommand,
|
|
19
19
|
formatDeployPathWriteFailure,
|
|
20
20
|
} from '../utils/shell-quote.js';
|
|
21
|
+
import { formatPasswordlessSudoGuidance } from '../utils/nginx.js';
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* @typedef {{ name: string, pass: boolean, message: string }} CheckResult
|
|
@@ -82,6 +83,16 @@ function resolveSshDeployPaths(config, envConfig) {
|
|
|
82
83
|
return [...new Set(paths.filter(Boolean))];
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
/**
|
|
87
|
+
* @param {import('../core/config.js').DeployHubConfig} config
|
|
88
|
+
* @returns {boolean}
|
|
89
|
+
*/
|
|
90
|
+
function needsNginxActivationForDeploy(config) {
|
|
91
|
+
if (config.projectType === 'backend') return false;
|
|
92
|
+
if (config.projectType === 'frontend' && config.framework === 'nextjs') return false;
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
|
|
85
96
|
/**
|
|
86
97
|
* @param {import('../core/config.js').DeployHubConfig} config
|
|
87
98
|
* @param {string} envName
|
|
@@ -207,6 +218,149 @@ async function runDeploymentChecks(config, envName, envConfig) {
|
|
|
207
218
|
);
|
|
208
219
|
}
|
|
209
220
|
|
|
221
|
+
if (needsNginxActivationForDeploy(config)) {
|
|
222
|
+
checks.push(
|
|
223
|
+
await runCheck('Nginx installed', async () => {
|
|
224
|
+
if (!host || !user) {
|
|
225
|
+
return {
|
|
226
|
+
name: 'Nginx installed',
|
|
227
|
+
pass: false,
|
|
228
|
+
message: 'SSH_HOST and SSH_USER are required to check Nginx on the server.',
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (!keyPath && !process.env.SSH_KEY) {
|
|
232
|
+
return {
|
|
233
|
+
name: 'Nginx installed',
|
|
234
|
+
pass: false,
|
|
235
|
+
message: 'SSH_KEY_PATH or SSH_KEY is required to check Nginx on the server.',
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const provider = getDeploymentProvider(deployType, config, envName);
|
|
240
|
+
if (!provider.runRemoteCheck) {
|
|
241
|
+
return {
|
|
242
|
+
name: 'Nginx installed',
|
|
243
|
+
pass: false,
|
|
244
|
+
message: 'Deploy provider does not support remote checks.',
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const result = await provider.runRemoteCheck('command -v nginx >/dev/null 2>&1 && echo yes');
|
|
249
|
+
if (result.pass && result.message.includes('yes')) {
|
|
250
|
+
return { name: 'Nginx installed', pass: true, message: 'Nginx installed on server' };
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
name: 'Nginx installed',
|
|
254
|
+
pass: false,
|
|
255
|
+
message:
|
|
256
|
+
'Nginx not found on server — install it first (e.g. sudo yum install nginx on Amazon Linux, or sudo apt install nginx on Ubuntu).',
|
|
257
|
+
};
|
|
258
|
+
})
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
checks.push(
|
|
262
|
+
await runCheck('Passwordless sudo', async () => {
|
|
263
|
+
if (!host || !user) {
|
|
264
|
+
return {
|
|
265
|
+
name: 'Passwordless sudo',
|
|
266
|
+
pass: false,
|
|
267
|
+
message: 'SSH_HOST and SSH_USER are required for sudo check.',
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (!keyPath && !process.env.SSH_KEY) {
|
|
271
|
+
return {
|
|
272
|
+
name: 'Passwordless sudo',
|
|
273
|
+
pass: false,
|
|
274
|
+
message: 'SSH_KEY_PATH or SSH_KEY is required for sudo check.',
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const provider = getDeploymentProvider(deployType, config, envName);
|
|
279
|
+
if (!provider.runRemoteCheck) {
|
|
280
|
+
return {
|
|
281
|
+
name: 'Passwordless sudo',
|
|
282
|
+
pass: false,
|
|
283
|
+
message: 'Deploy provider does not support remote checks.',
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const result = await provider.runRemoteCheck('sudo -n true');
|
|
288
|
+
if (result.pass) {
|
|
289
|
+
return {
|
|
290
|
+
name: 'Passwordless sudo',
|
|
291
|
+
pass: true,
|
|
292
|
+
message: 'Non-interactive sudo available (required for Nginx config activation)',
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
name: 'Passwordless sudo',
|
|
297
|
+
pass: false,
|
|
298
|
+
message: formatPasswordlessSudoGuidance(sshUser),
|
|
299
|
+
};
|
|
300
|
+
})
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
checks.push(
|
|
304
|
+
await runCheck('Nginx sudo access', async () => {
|
|
305
|
+
if (!host || !user) {
|
|
306
|
+
return {
|
|
307
|
+
name: 'Nginx sudo access',
|
|
308
|
+
pass: false,
|
|
309
|
+
message: 'SSH_HOST and SSH_USER are required for Nginx sudo check.',
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
if (!keyPath && !process.env.SSH_KEY) {
|
|
313
|
+
return {
|
|
314
|
+
name: 'Nginx sudo access',
|
|
315
|
+
pass: false,
|
|
316
|
+
message: 'SSH_KEY_PATH or SSH_KEY is required for Nginx sudo check.',
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const provider = getDeploymentProvider(deployType, config, envName);
|
|
321
|
+
if (!provider.runRemoteCheck) {
|
|
322
|
+
return {
|
|
323
|
+
name: 'Nginx sudo access',
|
|
324
|
+
pass: false,
|
|
325
|
+
message: 'Deploy provider does not support remote checks.',
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const nginxInstalled = await provider.runRemoteCheck('command -v nginx >/dev/null 2>&1 && echo yes');
|
|
330
|
+
if (!nginxInstalled.pass || !nginxInstalled.message.includes('yes')) {
|
|
331
|
+
return {
|
|
332
|
+
name: 'Nginx sudo access',
|
|
333
|
+
pass: false,
|
|
334
|
+
message: 'Skipped — install Nginx first.',
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const sudoOk = await provider.runRemoteCheck('sudo -n true');
|
|
339
|
+
if (!sudoOk.pass) {
|
|
340
|
+
return {
|
|
341
|
+
name: 'Nginx sudo access',
|
|
342
|
+
pass: false,
|
|
343
|
+
message: 'Skipped — configure passwordless sudo first.',
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const result = await provider.runRemoteCheck('sudo -n nginx -t 2>&1');
|
|
348
|
+
if (result.pass) {
|
|
349
|
+
return {
|
|
350
|
+
name: 'Nginx sudo access',
|
|
351
|
+
pass: true,
|
|
352
|
+
message: 'sudo nginx -t OK (can test config before reload)',
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
name: 'Nginx sudo access',
|
|
357
|
+
pass: false,
|
|
358
|
+
message: `sudo nginx -t failed — ${result.message}. Check Nginx install and sudoers (see README one-time server setup).`,
|
|
359
|
+
};
|
|
360
|
+
})
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
210
364
|
const isBackend = config.projectType === 'backend' || config.projectType === 'both';
|
|
211
365
|
if (isBackend) {
|
|
212
366
|
const backendChecks = await runBackendProcessChecks(config, envName, deployType);
|
|
@@ -3,7 +3,11 @@ import fs from 'fs-extra';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import { createLogger } from '../../logger/index.js';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getNginxSitesAvailablePath,
|
|
8
|
+
getNginxSitesEnabledPath,
|
|
9
|
+
getNginxConfDPath,
|
|
10
|
+
} from '../../utils/nginx.js';
|
|
7
11
|
import { shellQuote, formatRemoteCommandFailure } from '../../utils/shell-quote.js';
|
|
8
12
|
|
|
9
13
|
/** @type {Set<string>} */
|
|
@@ -240,24 +244,85 @@ export function createSshProvider(config, envName, env = process.env) {
|
|
|
240
244
|
await exec(ssh, 'pm2 save');
|
|
241
245
|
}
|
|
242
246
|
|
|
247
|
+
/**
|
|
248
|
+
* @param {import('node-ssh').NodeSSH} ssh
|
|
249
|
+
* @param {string} remotePath
|
|
250
|
+
* @param {'f'|'d'} [kind='f']
|
|
251
|
+
*/
|
|
252
|
+
async function remotePathExists(ssh, remotePath, kind = 'f') {
|
|
253
|
+
const flag = kind === 'd' ? '-d' : '-f';
|
|
254
|
+
const result = await ssh.execCommand(`test ${flag} ${sh(remotePath)} && echo yes`);
|
|
255
|
+
return result.code === 0 && result.stdout.trim() === 'yes';
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* @param {import('node-ssh').NodeSSH} ssh
|
|
260
|
+
* @param {string} command
|
|
261
|
+
*/
|
|
262
|
+
async function remoteCommandExists(ssh, command) {
|
|
263
|
+
const result = await ssh.execCommand(`command -v ${sh(command)} >/dev/null 2>&1 && echo yes`);
|
|
264
|
+
return result.code === 0 && result.stdout.trim() === 'yes';
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @param {import('node-ssh').NodeSSH} ssh
|
|
269
|
+
* @returns {Promise<'debian'|'rhel'>}
|
|
270
|
+
*/
|
|
271
|
+
async function detectNginxLayout(ssh) {
|
|
272
|
+
if (await remotePathExists(ssh, '/etc/nginx/sites-available', 'd')) {
|
|
273
|
+
return 'debian';
|
|
274
|
+
}
|
|
275
|
+
return 'rhel';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* @param {import('node-ssh').NodeSSH} ssh
|
|
280
|
+
*/
|
|
281
|
+
async function reloadNginx(ssh) {
|
|
282
|
+
await exec(ssh, 'sudo systemctl reload nginx 2>/dev/null || sudo nginx -s reload');
|
|
283
|
+
}
|
|
284
|
+
|
|
243
285
|
/**
|
|
244
286
|
* @param {import('node-ssh').NodeSSH} ssh
|
|
245
287
|
* @param {string} targetPath
|
|
246
288
|
*/
|
|
247
289
|
async function setupNginx(ssh, targetPath) {
|
|
248
|
-
const sitePath = getNginxSitePath(config.project);
|
|
249
290
|
const nginxConfRemote = `${targetPath}/nginx.conf`;
|
|
250
291
|
|
|
251
|
-
await
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
292
|
+
if (!(await remoteCommandExists(ssh, 'nginx'))) {
|
|
293
|
+
throw new Error(
|
|
294
|
+
'Nginx is not installed on the server. Install it first (e.g. sudo yum install nginx on Amazon Linux, or sudo apt install nginx on Ubuntu), then re-run deploy.'
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (!(await remotePathExists(ssh, nginxConfRemote))) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
`Nginx config not found at ${nginxConfRemote} — artifact may be missing nginx.conf.`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const layout = await detectNginxLayout(ssh);
|
|
305
|
+
log.info(
|
|
306
|
+
layout === 'debian'
|
|
307
|
+
? 'Detected Nginx layout: Debian/Ubuntu (sites-available)'
|
|
308
|
+
: 'Detected Nginx layout: RHEL/Amazon Linux (conf.d)'
|
|
258
309
|
);
|
|
310
|
+
|
|
311
|
+
if (layout === 'debian') {
|
|
312
|
+
const sitePath = getNginxSitesAvailablePath(config.project);
|
|
313
|
+
const enabledPath = getNginxSitesEnabledPath(config.project);
|
|
314
|
+
await exec(ssh, `sudo cp ${sh(nginxConfRemote)} ${sh(sitePath)}`);
|
|
315
|
+
await exec(ssh, `sudo ln -sf ${sh(sitePath)} ${sh(enabledPath)}`);
|
|
316
|
+
log.info(`Nginx config installed: ${sitePath}`);
|
|
317
|
+
} else {
|
|
318
|
+
const confPath = getNginxConfDPath(config.project);
|
|
319
|
+
await exec(ssh, `sudo cp ${sh(nginxConfRemote)} ${sh(confPath)}`);
|
|
320
|
+
log.info(`Nginx config installed: ${confPath}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
259
323
|
await exec(ssh, 'sudo nginx -t');
|
|
260
|
-
await
|
|
324
|
+
await reloadNginx(ssh);
|
|
325
|
+
log.success('Nginx config tested and reloaded');
|
|
261
326
|
}
|
|
262
327
|
|
|
263
328
|
/**
|
package/src/utils/nginx.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitize a project name for use in Nginx config file paths.
|
|
3
|
+
* @param {string} projectName
|
|
4
|
+
* @returns {string}
|
|
5
|
+
*/
|
|
6
|
+
export function sanitizeNginxProjectName(projectName) {
|
|
7
|
+
return projectName.replace(/[^a-zA-Z0-9_-]/g, '-');
|
|
8
|
+
}
|
|
9
|
+
|
|
1
10
|
/**
|
|
2
11
|
* Generate nginx server block config for SPA frontend deployments.
|
|
3
12
|
*
|
|
@@ -22,13 +31,61 @@ export function generateNginxConfig(projectName, deployPath, buildOutput = 'dist
|
|
|
22
31
|
`;
|
|
23
32
|
}
|
|
24
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Debian/Ubuntu: sites-available path for this project.
|
|
36
|
+
* @param {string} projectName
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
export function getNginxSitesAvailablePath(projectName) {
|
|
40
|
+
return `/etc/nginx/sites-available/${sanitizeNginxProjectName(projectName)}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Debian/Ubuntu: sites-enabled symlink path for this project.
|
|
45
|
+
* @param {string} projectName
|
|
46
|
+
* @returns {string}
|
|
47
|
+
*/
|
|
48
|
+
export function getNginxSitesEnabledPath(projectName) {
|
|
49
|
+
return `/etc/nginx/sites-enabled/${sanitizeNginxProjectName(projectName)}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* RHEL/Amazon Linux: conf.d drop-in path for this project.
|
|
54
|
+
* @param {string} projectName
|
|
55
|
+
* @returns {string}
|
|
56
|
+
*/
|
|
57
|
+
export function getNginxConfDPath(projectName) {
|
|
58
|
+
return `/etc/nginx/conf.d/${sanitizeNginxProjectName(projectName)}.conf`;
|
|
59
|
+
}
|
|
60
|
+
|
|
25
61
|
/**
|
|
26
62
|
* @param {string} projectName
|
|
27
63
|
* @returns {string}
|
|
28
64
|
*/
|
|
29
65
|
export function getNginxSitePath(projectName) {
|
|
30
|
-
|
|
31
|
-
|
|
66
|
+
return getNginxSitesAvailablePath(projectName);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {string} sshUser
|
|
71
|
+
* @returns {string}
|
|
72
|
+
*/
|
|
73
|
+
export function formatPasswordlessSudoGuidance(sshUser) {
|
|
74
|
+
return (
|
|
75
|
+
`Passwordless sudo required to activate Nginx config during deploy.\n` +
|
|
76
|
+
` On your server, run sudo visudo and add a line like:\n` +
|
|
77
|
+
` ${sshUser} ALL=(ALL) NOPASSWD: /usr/sbin/nginx, /bin/cp, /usr/bin/cp, /bin/systemctl, /usr/bin/systemctl\n` +
|
|
78
|
+
` (replace ${sshUser} with your SSH_USER)\n` +
|
|
79
|
+
` Security: this grants broad cp/systemctl access — see README one-time server setup for a production-hardening note.`
|
|
80
|
+
);
|
|
32
81
|
}
|
|
33
82
|
|
|
34
|
-
export default {
|
|
83
|
+
export default {
|
|
84
|
+
generateNginxConfig,
|
|
85
|
+
sanitizeNginxProjectName,
|
|
86
|
+
getNginxSitesAvailablePath,
|
|
87
|
+
getNginxSitesEnabledPath,
|
|
88
|
+
getNginxConfDPath,
|
|
89
|
+
getNginxSitePath,
|
|
90
|
+
formatPasswordlessSudoGuidance,
|
|
91
|
+
};
|