Skip to content

Commit 65d3da5

Browse files
committed
feat(nginx): refactor to use async/await
1 parent 6ec06fb commit 65d3da5

File tree

5 files changed

+180
-207
lines changed

5 files changed

+180
-207
lines changed

extensions/nginx/acme.js

+41-48
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ const path = require('path');
66
const download = require('download');
77

88
const {errors: {CliError, ProcessError, SystemError}} = require('../../lib');
9+
const {errorWrapper} = require('./utils');
910

1011
const nginxProgramName = process.env.NGINX_PROGRAM_NAME || 'nginx';
1112

1213
function isInstalled() {
1314
return fs.existsSync('/etc/letsencrypt/acme.sh');
1415
}
1516

16-
function install(ui, task) {
17+
async function install(ui, task) {
1718
if (isInstalled()) {
1819
return task.skip('acme.sh is already installed');
1920
}
@@ -25,78 +26,70 @@ function install(ui, task) {
2526

2627
// acme.sh creates the directory without global read permissions, so we need to make
2728
// sure it has global read permissions first
28-
return ui.sudo('mkdir -p /etc/letsencrypt').then(() => {
29-
ui.logVerbose('ssl: downloading acme.sh to temporary directory', 'green');
30-
return fs.emptyDir(acmeTmpDir);
31-
}).then(() => got(acmeApiUrl)).then((response) => {
32-
try {
33-
response = JSON.parse(response.body).tarball_url;
34-
} catch (e) {
35-
return Promise.reject(new CliError({
36-
message: 'Unable to parse Github api response for acme',
37-
err: e
38-
}));
39-
}
29+
await ui.sudo('mkdir -p /etc/letsencrypt');
30+
ui.logVerbose('ssl: downloading acme.sh to temporary directory', 'green');
31+
await fs.emptyDir(acmeTmpDir);
32+
33+
let downloadURL;
34+
35+
try {
36+
downloadURL = JSON.parse((await got(acmeApiUrl)).body).tarball_url;
37+
} catch (error) {
38+
throw new CliError({
39+
message: 'Unable to fetch download URL from GitHub',
40+
err: error
41+
});
42+
}
4043

41-
return download(response, acmeTmpDir, {extract: true});
42-
}).then(() => {
43-
// The archive contains a single folder with the structure
44-
// `{user}-{repo}-{commit}`, but we don't know what commit is
45-
// from the API call. Since the dir is empty (we cleared it),
46-
// the only thing in acmeTmpDir will be the extracted zip.
47-
const acmeCodeDir = path.resolve(acmeTmpDir, fs.readdirSync(acmeTmpDir)[0]);
48-
49-
ui.logVerbose('ssl: installing acme.sh components', 'green');
50-
51-
// Installs acme.sh into /etc/letsencrypt
52-
return ui.sudo('./acme.sh --install --home /etc/letsencrypt', {cwd: acmeCodeDir});
53-
}).catch((error) => {
54-
// CASE: error is already a cli error, just pass it along
55-
if (error instanceof CliError) {
56-
return Promise.reject(error);
57-
}
44+
await download(downloadURL, acmeTmpDir, {extract: true});
45+
// The archive contains a single folder with the structure
46+
// `{user}-{repo}-{commit}`, but we don't know what commit is
47+
// from the API call. Since the dir is empty (we cleared it),
48+
// the only thing in acmeTmpDir will be the extracted zip.
49+
const acmeCodeDir = path.resolve(acmeTmpDir, fs.readdirSync(acmeTmpDir)[0]);
5850

59-
// catch any request errors first, which isn't a ProcessError
60-
if (!error.stderr) {
61-
return Promise.reject(new CliError({
62-
message: 'Unable to query GitHub for ACME download URL',
63-
err: error
64-
}));
65-
}
66-
return Promise.reject(new ProcessError(error));
67-
});
51+
ui.logVerbose('ssl: installing acme.sh components', 'green');
52+
53+
// Installs acme.sh into /etc/letsencrypt
54+
await ui.sudo('./acme.sh --install --home /etc/letsencrypt', {cwd: acmeCodeDir});
6855
}
6956

70-
function generateCert(ui, domain, webroot, email, staging) {
57+
async function generateCert(ui, domain, webroot, email, staging) {
7158
const cmd = `/etc/letsencrypt/acme.sh --issue --home /etc/letsencrypt --domain ${domain} --webroot ${webroot} ` +
7259
`--reloadcmd "${nginxProgramName} -s reload" --accountemail ${email}${staging ? ' --staging' : ''}`;
7360

74-
return ui.sudo(cmd).catch((error) => {
61+
try {
62+
await ui.sudo(cmd);
63+
} catch (error) {
7564
if (error.code === 2) {
7665
// error code 2 is given if a cert doesn't need to be renewed
77-
return Promise.resolve();
66+
return;
7867
}
7968

8069
if (error.stderr.match(/Verify error:(Fetching|Invalid Response)/)) {
8170
// Domain verification failed
82-
return Promise.reject(new SystemError('Your domain name is not pointing to the correct IP address of your server, check your DNS has propagated and run `ghost setup ssl` again'));
71+
throw new SystemError('Your domain name is not pointing to the correct IP address of your server, check your DNS has propagated and run `ghost setup ssl` again');
8372
}
8473

8574
// It's not an error we expect might happen, throw a ProcessError instead.
86-
return Promise.reject(new ProcessError(error));
87-
});
75+
throw new ProcessError(error);
76+
}
8877
}
8978

90-
function remove(domain, ui, acmeHome) {
79+
async function remove(domain, ui, acmeHome) {
9180
acmeHome = acmeHome || '/etc/letsencrypt';
9281

9382
const cmd = `${acmeHome}/acme.sh --remove --home ${acmeHome} --domain ${domain}`;
9483

95-
return ui.sudo(cmd).catch(error => Promise.reject(new ProcessError(error)));
84+
try {
85+
await ui.sudo(cmd);
86+
} catch (error) {
87+
throw new ProcessError(error);
88+
}
9689
}
9790

9891
module.exports = {
99-
install: install,
92+
install: errorWrapper(install),
10093
isInstalled: isInstalled,
10194
generate: generateCert,
10295
remove: remove

extensions/nginx/index.js

+64-77
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
'use strict';
2-
31
const fs = require('fs-extra');
42
const os = require('os');
53
const dns = require('dns');
64
const url = require('url');
75
const isIP = require('validator/lib/isIP');
86
const path = require('path');
9-
const execa = require('execa');
107
const Promise = require('bluebird');
118
const template = require('lodash/template');
9+
const sysinfo = require('systeminformation');
1210

1311
const {Extension, errors} = require('../../lib');
14-
const {CliError, ProcessError} = errors;
12+
const {CliError} = errors;
13+
14+
const {errorWrapper} = require('./utils');
1515

1616
const nginxConfigPath = process.env.NGINX_CONFIG_PATH || '/etc/nginx';
1717
const nginxProgramName = process.env.NGINX_PROGRAM_NAME || 'nginx';
@@ -35,10 +35,11 @@ class NginxExtension extends Extension {
3535
return [{
3636
id: 'nginx',
3737
name: 'Nginx',
38-
task: (...args) => this.setupNginx(...args),
38+
task: errorWrapper((...args) => this.setupNginx(...args)),
3939
enabled,
40-
skip: ({instance}) => {
41-
if (!this.isSupported()) {
40+
skip: async ({instance}) => {
41+
const isSupported = await this.isSupported();
42+
if (!isSupported) {
4243
return 'Nginx is not installed. Skipping Nginx setup.';
4344
}
4445

@@ -94,7 +95,7 @@ class NginxExtension extends Extension {
9495
}];
9596
}
9697

97-
setupNginx({instance}) {
98+
async setupNginx({instance}) {
9899
const {hostname, pathname} = url.parse(instance.config.get('url'));
99100
const conf = template(fs.readFileSync(path.join(__dirname, 'templates', 'nginx.conf'), 'utf8'));
100101

@@ -108,18 +109,9 @@ class NginxExtension extends Extension {
108109
port: instance.config.get('server.port')
109110
});
110111

111-
return this.template(instance, generatedConfig, 'nginx config', confFile, `${nginxConfigPath}/sites-available`).then(
112-
() => this.ui.sudo(`ln -sf ${nginxConfigPath}/sites-available/${confFile} ${nginxConfigPath}/sites-enabled/${confFile}`)
113-
).then(
114-
() => this.restartNginx()
115-
).catch((error) => {
116-
// CASE: error is already a cli error, just pass it along
117-
if (error instanceof CliError) {
118-
return Promise.reject(error);
119-
}
120-
121-
return Promise.reject(new ProcessError(error));
122-
});
112+
await this.template(instance, generatedConfig, 'nginx config', confFile, `${nginxConfigPath}/sites-available`);
113+
await this.ui.sudo(`ln -sf ${nginxConfigPath}/sites-available/${confFile} ${nginxConfigPath}/sites-enabled/${confFile}`);
114+
await this.restartNginx();
123115
}
124116

125117
setupSSL({instance, argv}) {
@@ -158,23 +150,18 @@ class NginxExtension extends Extension {
158150
})
159151
}, {
160152
title: 'Getting additional configuration',
161-
task: () => {
162-
let promise;
163-
153+
task: async () => {
164154
if (argv.sslemail) {
165-
promise = Promise.resolve(argv.sslemail);
166-
} else {
167-
promise = this.ui.prompt({
168-
name: 'email',
169-
type: 'input',
170-
message: 'Enter your email (For SSL Certificate)',
171-
validate: value => Boolean(value) || 'You must supply an email'
172-
}).then(({email}) => {
173-
argv.sslemail = email;
174-
});
155+
return;
175156
}
176157

177-
return promise;
158+
const {email} = await this.ui.prompt({
159+
name: 'email',
160+
type: 'input',
161+
message: 'Enter your email (For SSL Certificate)',
162+
validate: value => Boolean(value) || 'You must supply an email'
163+
});
164+
argv.sslemail = email;
178165
}
179166
}, {
180167
title: 'Installing acme.sh',
@@ -185,22 +172,18 @@ class NginxExtension extends Extension {
185172
}, {
186173
title: 'Generating Encryption Key (may take a few minutes)',
187174
skip: () => fs.existsSync(dhparamFile),
188-
task: () => this.ui.sudo(`openssl dhparam -out ${dhparamFile} 2048`)
189-
.catch(error => Promise.reject(new ProcessError(error)))
175+
task: errorWrapper(() => this.ui.sudo(`openssl dhparam -out ${dhparamFile} 2048`))
190176
}, {
191177
title: 'Generating SSL security headers',
192178
skip: () => fs.existsSync(sslParamsFile),
193-
task: () => {
179+
task: errorWrapper(async () => {
194180
const tmpfile = path.join(os.tmpdir(), 'ssl-params.conf');
195-
196-
return fs.writeFile(tmpfile, sslParamsConf({dhparam: dhparamFile}), {encoding: 'utf8'})
197-
.then(() => this.ui.sudo(`mv ${tmpfile} ${sslParamsFile}`).catch(
198-
error => Promise.reject(new ProcessError(error))
199-
));
200-
}
181+
await fs.writeFile(tmpfile, sslParamsConf({dhparam: dhparamFile}), {encoding: 'utf8'});
182+
await this.ui.sudo(`mv ${tmpfile} ${sslParamsFile}`);
183+
})
201184
}, {
202185
title: 'Generating SSL configuration',
203-
task: () => {
186+
task: errorWrapper(async () => {
204187
const acmeFolder = path.join('/etc/letsencrypt', parsedUrl.hostname);
205188
const sslConf = template(fs.readFileSync(path.join(__dirname, 'templates', 'nginx-ssl.conf'), 'utf8'));
206189
const generatedSslConfig = sslConf({
@@ -213,77 +196,81 @@ class NginxExtension extends Extension {
213196
port: instance.config.get('server.port')
214197
});
215198

216-
return this.template(instance, generatedSslConfig, 'ssl config', confFile, `${nginxConfigPath}/sites-available`).then(
217-
() => this.ui.sudo(`ln -sf ${nginxConfigPath}/sites-available/${confFile} ${nginxConfigPath}/sites-enabled/${confFile}`)
218-
).catch(error => Promise.reject(new ProcessError(error)));
219-
}
199+
await this.template(instance, generatedSslConfig, 'ssl config', confFile, `${nginxConfigPath}/sites-available`);
200+
await this.ui.sudo(`ln -sf ${nginxConfigPath}/sites-available/${confFile} ${nginxConfigPath}/sites-enabled/${confFile}`);
201+
})
220202
}, {
221203
title: 'Restarting Nginx',
222204
task: () => this.restartNginx()
223205
}], false);
224206
}
225207

226-
uninstall({config}) {
208+
async uninstall({config}) {
227209
const instanceUrl = config.get('url');
228210

229211
if (!instanceUrl) {
230-
return Promise.resolve();
212+
return;
231213
}
232214

233215
const parsedUrl = url.parse(instanceUrl);
234216
const confFile = `${parsedUrl.hostname}.conf`;
235217
const sslConfFile = `${parsedUrl.hostname}-ssl.conf`;
236218

237-
const promises = [];
219+
let restart = false;
238220

239221
if (fs.existsSync(`${nginxConfigPath}/sites-available/${confFile}`)) {
222+
restart = true;
223+
240224
// Nginx config exists, remove it
241-
promises.push(
242-
Promise.all([
243-
this.ui.sudo(`rm -f ${nginxConfigPath}/sites-available/${confFile}`),
244-
this.ui.sudo(`rm -f ${nginxConfigPath}/sites-enabled/${confFile}`)
245-
]).catch(error => Promise.reject(new CliError({
225+
try {
226+
await this.ui.sudo(`rm -f ${nginxConfigPath}/sites-available/${confFile}`);
227+
await this.ui.sudo(`rm -f ${nginxConfigPath}/sites-enabled/${confFile}`);
228+
} catch (error) {
229+
throw new CliError({
246230
message: `Nginx config file link could not be removed, you will need to do this manually for ${nginxConfigPath}/sites-available/${confFile}.`,
247231
help: `Try running 'rm -f ${nginxConfigPath}/sites-available/${confFile} && rm -f ${nginxConfigPath}/sites-enabled/${confFile}'`,
248232
err: error
249-
})))
250-
);
233+
});
234+
}
251235
}
252236

253237
if (fs.existsSync(`${nginxConfigPath}/sites-available/${sslConfFile}`)) {
238+
restart = true;
239+
254240
// SSL config exists, remove it
255-
promises.push(
256-
Promise.all([
257-
this.ui.sudo(`rm -f ${nginxConfigPath}/sites-available/${sslConfFile}`),
258-
this.ui.sudo(`rm -f ${nginxConfigPath}/sites-enabled/${sslConfFile}`)
259-
]).catch(error => Promise.reject(new CliError({
241+
try {
242+
await this.ui.sudo(`rm -f ${nginxConfigPath}/sites-available/${sslConfFile}`);
243+
await this.ui.sudo(`rm -f ${nginxConfigPath}/sites-enabled/${sslConfFile}`);
244+
} catch (error) {
245+
throw new CliError({
260246
message: `SSL config file link could not be removed, you will need to do this manually for ${nginxConfigPath}/sites-available/${sslConfFile}.`,
261247
help: `Try running 'rm -f ${nginxConfigPath}/sites-available/${sslConfFile} && rm -f ${nginxConfigPath}/sites-enabled/${sslConfFile}'`,
262248
err: error
263-
})))
264-
);
249+
});
250+
}
265251
}
266252

267-
if (!promises.length) {
268-
return Promise.resolve();
253+
if (restart) {
254+
await this.restartNginx();
269255
}
270-
271-
return Promise.all(promises).then(() => this.restartNginx());
272256
}
273257

274-
restartNginx() {
275-
return this.ui.sudo(`${nginxProgramName} -s reload`)
276-
.catch(error => Promise.reject(new CliError({
258+
async restartNginx() {
259+
try {
260+
await this.ui.sudo(`${nginxProgramName} -s reload`);
261+
} catch (error) {
262+
throw new CliError({
277263
message: 'Failed to restart Nginx.',
278264
err: error
279-
})));
265+
});
266+
}
280267
}
281268

282-
isSupported() {
269+
async isSupported() {
283270
try {
284-
execa.shellSync(`dpkg -l | grep ${nginxProgramName}`, {stdio: 'ignore'});
285-
return true;
286-
} catch (e) {
271+
const services = await sysinfo.services('*');
272+
return services.some(s => s.name === nginxProgramName);
273+
} catch (error) {
287274
return false;
288275
}
289276
}

0 commit comments

Comments
 (0)