From b4393ffc8c18226eb16bc16268f72e6a23f3fbd6 Mon Sep 17 00:00:00 2001
From: Hannah Wolfe <erisds@gmail.com>
Date: Sun, 26 May 2019 10:45:43 +0100
Subject: [PATCH] feat(nginx): customisable paths & program name

no issue

- Respect environment variables that customise where nginx lives and what it is called
- Use env vars, not config, because this will be system-wide
- Allows CLI to work with "flavours" of nginx, like OpenResty
---
 extensions/nginx/acme.js                   |  4 +-
 extensions/nginx/index.js                  | 45 ++++++++++++----------
 lib/commands/doctor/checks/system-stack.js |  8 ++--
 3 files changed, 32 insertions(+), 25 deletions(-)

diff --git a/extensions/nginx/acme.js b/extensions/nginx/acme.js
index 8c3bb0a14..b952dc17b 100644
--- a/extensions/nginx/acme.js
+++ b/extensions/nginx/acme.js
@@ -7,6 +7,8 @@ const download = require('download');
 
 const {errors: {CliError, ProcessError, SystemError}} = require('../../lib');
 
+const nginxProgramName = process.env.NGINX_PROGRAM_NAME || 'nginx';
+
 function isInstalled() {
     return fs.existsSync('/etc/letsencrypt/acme.sh');
 }
@@ -67,7 +69,7 @@ function install(ui, task) {
 
 function generateCert(ui, domain, webroot, email, staging) {
     const cmd = `/etc/letsencrypt/acme.sh --issue --home /etc/letsencrypt --domain ${domain} --webroot ${webroot} ` +
-    `--reloadcmd "nginx -s reload" --accountemail ${email}${staging ? ' --staging' : ''}`;
+    `--reloadcmd "${nginxProgramName} -s reload" --accountemail ${email}${staging ? ' --staging' : ''}`;
 
     return ui.sudo(cmd).catch((error) => {
         if (error.code === 2) {
diff --git a/extensions/nginx/index.js b/extensions/nginx/index.js
index 30ec98c7b..dadc89e18 100644
--- a/extensions/nginx/index.js
+++ b/extensions/nginx/index.js
@@ -13,6 +13,9 @@ const template = require('lodash/template');
 const {Extension, errors} = require('../../lib');
 const {CliError, ProcessError} = errors;
 
+const nginxConfigPath = process.env.NGINX_CONFIG_PATH || '/etc/nginx';
+const nginxProgramName = process.env.NGINX_PROGRAM_NAME || 'nginx';
+
 class NginxExtension extends Extension {
     migrations() {
         const migrations = require('./migrations');
@@ -47,7 +50,7 @@ class NginxExtension extends Extension {
 
                 const confFile = `${hostname}.conf`;
 
-                if (fs.existsSync(`/etc/nginx/sites-available/${confFile}`)) {
+                if (fs.existsSync(`${nginxConfigPath}/sites-available/${confFile}`)) {
                     return 'Nginx configuration already found for this url. Skipping Nginx setup.';
                 }
 
@@ -74,7 +77,7 @@ class NginxExtension extends Extension {
                     return 'SSL certs cannot be generated for IP addresses, skipping';
                 }
 
-                if (fs.existsSync(`/etc/nginx/sites-available/${confFile}`)) {
+                if (fs.existsSync(`${nginxConfigPath}/sites-available/${confFile}`)) {
                     return 'SSL has already been set up, skipping';
                 }
 
@@ -82,7 +85,7 @@ class NginxExtension extends Extension {
                     return 'SSL email must be provided via the --sslemail option, skipping SSL setup';
                 }
 
-                if (!fs.existsSync(`/etc/nginx/sites-available/${hostname}.conf`)) {
+                if (!fs.existsSync(`${nginxConfigPath}/sites-available/${hostname}.conf`)) {
                     return single ? 'Nginx config file does not exist, skipping SSL setup' : true;
                 }
 
@@ -105,8 +108,8 @@ class NginxExtension extends Extension {
             port: instance.config.get('server.port')
         });
 
-        return this.template(instance, generatedConfig, 'nginx config', confFile, '/etc/nginx/sites-available').then(
-            () => this.ui.sudo(`ln -sf /etc/nginx/sites-available/${confFile} /etc/nginx/sites-enabled/${confFile}`)
+        return this.template(instance, generatedConfig, 'nginx config', confFile, `${nginxConfigPath}/sites-available`).then(
+            () => this.ui.sudo(`ln -sf ${nginxConfigPath}/sites-available/${confFile} ${nginxConfigPath}/sites-enabled/${confFile}`)
         ).then(
             () => this.restartNginx()
         ).catch((error) => {
@@ -126,8 +129,8 @@ class NginxExtension extends Extension {
         const acme = require('./acme');
 
         const rootPath = path.resolve(instance.dir, 'system', 'nginx-root');
-        const dhparamFile = '/etc/nginx/snippets/dhparam.pem';
-        const sslParamsFile = '/etc/nginx/snippets/ssl-params.conf';
+        const dhparamFile = `${nginxConfigPath}/snippets/dhparam.pem`;
+        const sslParamsFile = `${nginxConfigPath}/snippets/ssl-params.conf`;
         const sslParamsConf = template(fs.readFileSync(path.join(__dirname, 'templates', 'ssl-params.conf'), 'utf8'));
 
         return this.ui.listr([{
@@ -210,8 +213,8 @@ class NginxExtension extends Extension {
                     port: instance.config.get('server.port')
                 });
 
-                return this.template(instance, generatedSslConfig, 'ssl config', confFile, '/etc/nginx/sites-available').then(
-                    () => this.ui.sudo(`ln -sf /etc/nginx/sites-available/${confFile} /etc/nginx/sites-enabled/${confFile}`)
+                return this.template(instance, generatedSslConfig, 'ssl config', confFile, `${nginxConfigPath}/sites-available`).then(
+                    () => this.ui.sudo(`ln -sf ${nginxConfigPath}/sites-available/${confFile} ${nginxConfigPath}/sites-enabled/${confFile}`)
                 ).catch(error => Promise.reject(new ProcessError(error)));
             }
         }, {
@@ -233,29 +236,29 @@ class NginxExtension extends Extension {
 
         const promises = [];
 
-        if (fs.existsSync(`/etc/nginx/sites-available/${confFile}`)) {
+        if (fs.existsSync(`${nginxConfigPath}/sites-available/${confFile}`)) {
             // Nginx config exists, remove it
             promises.push(
                 Promise.all([
-                    this.ui.sudo(`rm -f /etc/nginx/sites-available/${confFile}`),
-                    this.ui.sudo(`rm -f /etc/nginx/sites-enabled/${confFile}`)
+                    this.ui.sudo(`rm -f ${nginxConfigPath}/sites-available/${confFile}`),
+                    this.ui.sudo(`rm -f ${nginxConfigPath}/sites-enabled/${confFile}`)
                 ]).catch(error => Promise.reject(new CliError({
-                    message: `Nginx config file link could not be removed, you will need to do this manually for /etc/nginx/sites-available/${confFile}.`,
-                    help: `Try running 'rm -f /etc/nginx/sites-available/${confFile} && rm -f /etc/nginx/sites-enabled/${confFile}'`,
+                    message: `Nginx config file link could not be removed, you will need to do this manually for ${nginxConfigPath}/sites-available/${confFile}.`,
+                    help: `Try running 'rm -f ${nginxConfigPath}/sites-available/${confFile} && rm -f ${nginxConfigPath}/sites-enabled/${confFile}'`,
                     err: error
                 })))
             );
         }
 
-        if (fs.existsSync(`/etc/nginx/sites-available/${sslConfFile}`)) {
+        if (fs.existsSync(`${nginxConfigPath}/sites-available/${sslConfFile}`)) {
             // SSL config exists, remove it
             promises.push(
                 Promise.all([
-                    this.ui.sudo(`rm -f /etc/nginx/sites-available/${sslConfFile}`),
-                    this.ui.sudo(`rm -f /etc/nginx/sites-enabled/${sslConfFile}`)
+                    this.ui.sudo(`rm -f ${nginxConfigPath}/sites-available/${sslConfFile}`),
+                    this.ui.sudo(`rm -f ${nginxConfigPath}/sites-enabled/${sslConfFile}`)
                 ]).catch(error => Promise.reject(new CliError({
-                    message: `SSL config file link could not be removed, you will need to do this manually for /etc/nginx/sites-available/${sslConfFile}.`,
-                    help: `Try running 'rm -f /etc/nginx/sites-available/${sslConfFile} && rm -f /etc/nginx/sites-enabled/${sslConfFile}'`,
+                    message: `SSL config file link could not be removed, you will need to do this manually for ${nginxConfigPath}/sites-available/${sslConfFile}.`,
+                    help: `Try running 'rm -f ${nginxConfigPath}/sites-available/${sslConfFile} && rm -f ${nginxConfigPath}/sites-enabled/${sslConfFile}'`,
                     err: error
                 })))
             );
@@ -269,7 +272,7 @@ class NginxExtension extends Extension {
     }
 
     restartNginx() {
-        return this.ui.sudo('nginx -s reload')
+        return this.ui.sudo(`${nginxProgramName} -s reload`)
             .catch(error => Promise.reject(new CliError({
                 message: 'Failed to restart Nginx.',
                 err: error
@@ -278,7 +281,7 @@ class NginxExtension extends Extension {
 
     isSupported() {
         try {
-            execa.shellSync('dpkg -l | grep nginx', {stdio: 'ignore'});
+            execa.shellSync(`dpkg -l | grep ${nginxProgramName}`, {stdio: 'ignore'});
             return true;
         } catch (e) {
             return false;
diff --git a/lib/commands/doctor/checks/system-stack.js b/lib/commands/doctor/checks/system-stack.js
index b06b1f7d6..70b75f9b9 100644
--- a/lib/commands/doctor/checks/system-stack.js
+++ b/lib/commands/doctor/checks/system-stack.js
@@ -6,6 +6,8 @@ const errors = require('../../../errors');
 
 const taskTitle = 'Checking operating system compatibility';
 
+const nginxProgramName = process.env.NGINX_PROGRAM_NAME || 'nginx';
+
 function systemStack(ctx, task) {
     let promise;
 
@@ -25,9 +27,9 @@ function systemStack(ctx, task) {
                 task: () => execa.shell('dpkg -l | grep systemd')
                     .catch(() => Promise.reject({missing: 'systemd'}))
             }, {
-                title: 'Checking nginx is installed',
-                task: () => execa.shell('dpkg -l | grep nginx')
-                    .catch(() => Promise.reject({missing: 'nginx'}))
+                title: `Checking ${nginxProgramName} is installed`,
+                task: () => execa.shell(`dpkg -l | grep ${nginxProgramName}`)
+                    .catch(() => Promise.reject({missing: nginxProgramName}))
             }], ctx, {
                 concurrent: true,
                 exitOnError: false,