From 3c1ae4d761723ee8e5aa8dbc69b0b5dc555df29d Mon Sep 17 00:00:00 2001 From: Austin Burdine Date: Mon, 27 Feb 2017 22:26:49 -0600 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Nginx=20Service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refs #64 --- lib/commands/service.js | 6 ++ lib/services/base.js | 4 ++ lib/services/index.js | 5 +- lib/services/nginx/index.js | 126 ++++++++++++++++++++++++++++++++++++ package.json | 1 + yarn.lock | 4 ++ 6 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 lib/services/nginx/index.js diff --git a/lib/commands/service.js b/lib/commands/service.js index b499fd81f..1ebc7eadc 100644 --- a/lib/commands/service.js +++ b/lib/commands/service.js @@ -1,5 +1,11 @@ 'use strict'; +const Config = require('../utils/config'); +const checkValidInstall = require('../utils/check-valid-install'); module.exports.execute = function execute(command, args) { + checkValidInstall('service'); + + this.service.setConfig(Config.load(this.environment)); + return this.service.callCommand(command, args); }; diff --git a/lib/services/base.js b/lib/services/base.js index 905828ed4..e7281e811 100644 --- a/lib/services/base.js +++ b/lib/services/base.js @@ -18,6 +18,10 @@ class BaseService { } command(name, fn) { + if (typeof fn !== 'function') { + fn = this[fn]; + } + this.serviceManager.registerCommand(name, fn, this.name); } diff --git a/lib/services/index.js b/lib/services/index.js index e5681e959..aadbad1fe 100644 --- a/lib/services/index.js +++ b/lib/services/index.js @@ -82,7 +82,7 @@ class ServiceManager { let command = this.commands[name]; let service = this.services[command[0]]; - return Promise.resolve(service[command[1]].apply(service, args)); + return Promise.resolve(command[1].apply(service, args)); } setConfig(config, force) { @@ -169,7 +169,8 @@ ServiceManager.knownServices = [ // TODO: we only know about the nginx & built in process manager services // for now, in the future make this load globally installed services require('./process/systemd'), - localService + localService, + require('./nginx') ]; module.exports = ServiceManager; diff --git a/lib/services/nginx/index.js b/lib/services/nginx/index.js new file mode 100644 index 000000000..ae565c50b --- /dev/null +++ b/lib/services/nginx/index.js @@ -0,0 +1,126 @@ +'use strict'; +const fs = require('fs-extra'); +const url = require('url'); +const path = require('path'); +const execa = require('execa'); +const validator = require('validator'); +const Promise = require('bluebird'); +const NginxConfFile = require('nginx-conf').NginxConfFile; + +const BaseService = require('../base'); + +class NginxService extends BaseService { + init() { + this.on('setup', 'setup'); + this.command('nginx-conf', 'setupConf'); + this.command('nginx-ssl', 'setupSSL'); + // TODO implement + // this.command('ssl-renew', 'renewSSL'); + } + + get parsedUrl() { + return url.parse(this.config.get('url')); + } + + setup(context) { + // This is the result from the `ghost doctor setup` command - it will be false + // if nginx does not exist on the system + if (!context.nginx) { + return; + } + + if (this.parsedUrl.port) { + this.ui.log('Your url contains a port. Skipping automatic nginx setup.', 'yellow'); + return; + } + + if (this.parsedUrl.pathname !== '/') { + this.ui.log('The Nginx service does not support subdirectory configurations yet. Skipping automatic nginx setup.', 'yellow'); + return; + } + + let prompts = [{ + type: 'confirm', + name: 'ssl', + message: 'Do you want to set up your blog with SSL (using letsencrypt)?', + default: true + }, { + type: 'input', + name: 'email', + message: 'Enter your email (used for ssl registration)', + when: ans => ans.ssl, + validate: email => validator.isEmail(email) || 'Invalid email' + }]; + + if (this.config.environment === 'development') { + prompts.splice(1, 0, { + type: 'confirm', + name: 'staging', + message: 'You are running in development mode. Would you like to use letsencrypt\'s' + + ' staging servers instead of the production servers?', + default: true, + when: ans => ans.ssl + }); + } + + let answers; + let ghostExec = process.argv.slice(0,2).join(' '); + + return this.ui.prompt(prompts).then((_answers) => { + answers = _answers; + + return this.ui.noSpin(execa.shell(`sudo ${ghostExec} service nginx-conf${!answers.ssl ? ' no-ssl' : ''}`, {stdio: 'inherit'})); + }).then(() => { + if (answers.ssl) { + return this.ui.noSpin(execa.shell(`sudo ${ghostExec} service nginx-ssl ${answers.email}${answers.staging ? ' staging' : ''}`, { + stdio: 'inherit' + })); + } + }); + } + + setupConf(ssl) { + let isSSL = (!ssl || ssl !== 'no-ssl'); + let confFile = `${this.parsedUrl.hostname}.conf`; + let confFilePath = `/etc/nginx/sites-available/${confFile}`; + + fs.ensureFileSync(confFilePath); + fs.ensureSymlinkSync(confFilePath, `/etc/nginx/sites-enabled/${confFile}`); + + return Promise.fromNode((cb) => NginxConfFile.create(confFilePath, cb)).then((conf) => { + conf.nginx._add('server'); + conf.nginx.server._add('listen', '80'); + conf.nginx.server._add('listen', '[::]:80'); + conf.nginx.server._add('server_name', this.parsedUrl.hostname); + + let rootPath = path.resolve(process.cwd(), 'root'); + fs.ensureDirSync(rootPath); + conf.nginx.server._add('root', rootPath); + + conf.nginx.server._add('location', '/'); + this._addProxyBlock(conf.nginx.server.location); + + if (isSSL) { + conf.nginx.server._add('location', '~ /.well-known'); + conf.nginx.server.location[1]._add('allow', 'all'); + } + }).then(() => { + return execa.shell('service nginx restart', {stdio: 'inherit'}); + }); + } + + setupSSL(/* email, staging */) { + + } + + _addProxyBlock(location) { + location._add('proxy_set_header', 'X-Real-IP $remote_addr'); + location._add('proxy_set_header', 'Host $http_host'); + location._add('proxy_pass', `http://127.0.0.1:${this.config.get('server.port')}`); + } +}; + +module.exports = { + name: 'nginx', + class: NginxService +}; diff --git a/package.json b/package.json index 436d259c5..6a16ac901 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "knex-migrator": "2.0.7", "listr": "0.11.0", "lodash": "4.17.4", + "nginx-conf": "1.3.0", "ora": "1.1.0", "portfinder": "1.0.13", "rxjs": "5.2.0", diff --git a/yarn.lock b/yarn.lock index ffad66488..38da7c3ad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2139,6 +2139,10 @@ ncp@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" +nginx-conf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/nginx-conf/-/nginx-conf-1.3.0.tgz#c41058a87cf7bd7eebcb91b35832a5ad53595904" + node-emoji@^1.0.4: version "1.5.1" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.5.1.tgz#fd918e412769bf8c448051238233840b2aff16a1"