Skip to content

Commit

Permalink
✨ Nginx Service
Browse files Browse the repository at this point in the history
refs TryGhost#64
- add nginx service
- add ssl provisioning via greenlock-cli
  • Loading branch information
acburdine committed Feb 20, 2017
1 parent cb43654 commit 8d4a23c
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 1 deletion.
3 changes: 2 additions & 1 deletion lib/services/index.js
Expand Up @@ -131,7 +131,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;
45 changes: 45 additions & 0 deletions lib/services/nginx/files/site.conf.template
@@ -0,0 +1,45 @@
server {
listen 80;
listen [::]:80;

server_name <%= url %>;

<% if (ssl) { %>
location / {
return 301 https://$server_name$request_uri;
}

location ~ /.well-known {
allow all;
}
<% } else { %>
root <%= root %>;

location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://127.0.0.1:<%= port %>;
}
<% } %>
}

<% if (ssl) { %>
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;

# SSL Configuration snippets
include snippets/ssl-<%= url %>.conf;
include snippets/ssl-params.conf;

root <%= root %>;

location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://127.0.0.1:<%= port %>;
}
}
<% } %>
2 changes: 2 additions & 0 deletions lib/services/nginx/files/ssl-cert.conf.template
@@ -0,0 +1,2 @@
ssl_certificate /etc/letsencrypt/live/<%= url %>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<%= url %>/privkey.pem;
21 changes: 21 additions & 0 deletions lib/services/nginx/files/ssl-params.conf
@@ -0,0 +1,21 @@
# from https://cipherli.st/
# and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Disable preloading HSTS for now. You can use the commented out header line that includes
# the "preload" directive if you understand the implications.
#add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

ssl_dhparam /etc/ssl/certs/dhparam.pem;
125 changes: 125 additions & 0 deletions lib/services/nginx/index.js
@@ -0,0 +1,125 @@
'use strict';
const fs = require('fs-extra');
const url = require('url');
const path = require('path');
const execa = require('execa');
const template = require('lodash/template');
const validator = require('validator');

const BaseService = require('../base');

class NginxService extends BaseService {
init() {
this.on('setup', 'setup');
}

get parsedUrl() {
return url.parse(this.config.get('url'));
}

setup(context) {
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;

return this.ui.prompt(prompts).then((_answers) => {
answers = _answers;

fs.ensureDirSync(path.join(process.cwd(), 'root'));

let conf = template(fs.readFileSync(path.join(__dirname, 'files', 'site.conf.template'), 'utf8'));
let confFile = `${this.parsedUrl.hostname}.conf`;

fs.writeFileSync(path.join(process.cwd(), confFile), conf({
ssl: answers.ssl,
root: path.join(process.cwd(), 'root'),
url: this.parsedUrl.hostname,
port: this.config.get('server.port')
}));

return this.ui.noSpin(execa.shell(`sudo mv ${confFile} /etc/nginx/sites-available && ` +
`sudo ln -s /etc/nginx/sites-available/${confFile} /etc/nginx/sites-enabled && ` +
'sudo service nginx restart', {stdio: 'inherit'}));
}).then(() => {
if (!answers.ssl) {
return;
}

return this._ssl(answers);
});
}

_ssl(options) {
let letsencrypt = path.resolve(__dirname, '../../../node_modules/.bin/letsencrypt')
let command = `sudo ${letsencrypt} certonly --agree-tos --email ${options.email} --webroot ` +
`--webroot-path ${path.join(process.cwd(), 'root')} --config-dir /etc/letsencrypt ` +
`--domains ${this.parsedUrl.hostname}`;

if (options.staging) {
// Use LetsEncrypt's staging server
command += ' --server https://acme-staging.api.letsencrypt.org/directory';
}

return this.ui.noSpin(execa.shell(command, {stdio: 'inherit'})).then(() => {
if (fs.existsSync('/etc/nginx/snippets/ssl-params.conf')) {
return;
}

return this.ui.noSpin(execa.shell(
`sudo mv ${path.join(__dirname, 'files', 'ssl-params.conf')} /etc/nginx/snippets`,
{stdio: 'inherit'}
));
}).then(() => {
let sslConf = template(fs.readFileSync(path.join(__dirname, 'files', 'ssl-cert.conf.template'), 'utf8'));
let sslConfFile = `ssl-${this.parsedUrl.hostname}.conf`;

fs.writeFileSync(path.join(process.cwd(), sslConfFile), sslConf({
url: this.parsedUrl.hostname
}));

return this.ui.noSpin(execa.shell(`sudo mv ${sslConfFile} /etc/nginx/snippets && ` +
'sudo service nginx restart'));
});
}
}

module.exports = {
name: 'nginx',
class: NginxService
};

0 comments on commit 8d4a23c

Please sign in to comment.