1
1
'use strict' ;
2
2
3
3
const fs = require ( 'fs-extra' ) ;
4
+ const os = require ( 'os' ) ;
4
5
const dns = require ( 'dns' ) ;
5
6
const url = require ( 'url' ) ;
6
7
const path = require ( 'path' ) ;
7
8
const execa = require ( 'execa' ) ;
8
- const template = require ( 'lodash/template' ) ;
9
-
10
9
const Promise = require ( 'bluebird' ) ;
11
- const NginxConfFile = require ( 'nginx-conf' ) . NginxConfFile ;
10
+ const template = require ( 'lodash/template' ) ;
12
11
13
12
const cli = require ( '../../lib' ) ;
14
13
@@ -21,7 +20,6 @@ class NginxExtension extends cli.Extension {
21
20
22
21
cmd . addStage ( 'nginx' , this . setupNginx . bind ( this ) , null , 'Nginx' ) ;
23
22
cmd . addStage ( 'ssl' , this . setupSSL . bind ( this ) , 'nginx' , 'SSL' ) ;
24
- cmd . addStage ( 'ssl-renew' , this . setupRenew . bind ( this ) , 'ssl' , 'automatic SSL renewal' ) ;
25
23
}
26
24
27
25
setupNginx ( argv , ctx , task ) {
@@ -37,57 +35,39 @@ class NginxExtension extends cli.Extension {
37
35
return task . skip ( ) ;
38
36
}
39
37
40
- if ( parsedUrl . pathname !== '/' ) {
41
- this . ui . log ( 'The Nginx service does not support subdirectory configurations yet. Skipping Nginx setup.' , 'yellow' ) ;
42
- return task . skip ( ) ;
43
- }
38
+ let confFile = `${ parsedUrl . hostname } .conf` ;
44
39
45
- if ( fs . existsSync ( `/etc/nginx/sites-available/${ parsedUrl . hostname } .conf ` ) ) {
40
+ if ( fs . existsSync ( `/etc/nginx/sites-available/${ confFile } ` ) ) {
46
41
this . ui . log ( 'Nginx configuration already found for this url. Skipping Nginx setup.' , 'yellow' ) ;
47
42
return task . skip ( ) ;
48
43
}
49
44
50
- return Promise . fromNode ( ( cb ) => NginxConfFile . createFromSource ( '' , cb ) ) . then ( ( conf ) => {
51
- conf . nginx . _add ( 'server' ) ;
52
-
53
- let http = conf . nginx . server ;
45
+ let conf = template ( fs . readFileSync ( path . join ( __dirname , 'templates' , 'nginx.conf' ) , 'utf8' ) ) ;
54
46
55
- http . _add ( 'listen' , '80' ) ;
56
- http . _add ( 'listen' , '[::]:80' ) ;
57
- http . _add ( 'server_name' , parsedUrl . hostname ) ;
58
-
59
- let rootPath = path . resolve ( ctx . instance . dir , 'system' , 'nginx-root' ) ;
60
- fs . ensureDirSync ( rootPath ) ;
61
- http . _add ( 'root' , rootPath ) ;
62
-
63
- http . _add ( 'location' , '/' ) ;
64
- this . _addProxyBlock ( http . location , ctx . instance . config . get ( 'server.port' ) ) ;
65
-
66
- http . _add ( 'client_max_body_size' , '50m' ) ;
67
-
68
- let confFile = `${ parsedUrl . hostname } .conf` ;
69
-
70
- return ctx . instance . template (
71
- conf . toString ( ) ,
72
- 'nginx config' ,
73
- confFile ,
74
- '/etc/nginx/sites-available'
75
- ) . then ( ( generated ) => {
76
- if ( ! generated ) {
77
- this . ui . log ( 'Nginx config not generated' , 'yellow' ) ;
78
- return ;
79
- }
80
-
81
- ctx . instance . cliConfig . set ( 'extension.nginx' , true ) . save ( ) ;
47
+ let rootPath = path . resolve ( ctx . instance . dir , 'system' , 'nginx-root' ) ;
82
48
83
- return this . ui . sudo ( `ln -sf /etc/nginx/sites-available/${ confFile } /etc/nginx/sites-enabled/${ confFile } ` )
84
- . then ( ( ) => this . restartNginx ( ) ) ;
85
- } ) ;
49
+ let generatedConfig = conf ( {
50
+ url : parsedUrl . hostname ,
51
+ webroot : rootPath ,
52
+ location : parsedUrl . pathname !== '/' ? `^~ ${ parsedUrl . pathname } ` : '/' ,
53
+ port : ctx . instance . config . get ( 'server.port' )
86
54
} ) ;
55
+
56
+ return ctx . instance . template (
57
+ generatedConfig ,
58
+ 'nginx config' ,
59
+ confFile ,
60
+ '/etc/nginx/sites-available'
61
+ ) . then ( ( ) => {
62
+ return this . ui . sudo ( `ln -sf /etc/nginx/sites-available/${ confFile } /etc/nginx/sites-enabled/${ confFile } ` ) ;
63
+ } ) . then ( ( ) => this . restartNginx ( ) ) ;
87
64
}
88
65
89
66
setupSSL ( argv , ctx , task ) {
90
- if ( ctx . instance . cliConfig . get ( 'extension.ssl' , false ) ) {
67
+ let parsedUrl = url . parse ( ctx . instance . config . get ( 'url' ) ) ;
68
+ let confFile = `${ parsedUrl . hostname } -ssl.conf` ;
69
+
70
+ if ( fs . existsSync ( `/etc/nginx/sites-available/${ confFile } ` ) ) {
91
71
this . ui . log ( 'SSL has already been set up, skipping' , 'yellow' ) ;
92
72
return task . skip ( ) ;
93
73
}
@@ -97,12 +77,7 @@ class NginxExtension extends cli.Extension {
97
77
return task . skip ( ) ;
98
78
}
99
79
100
- let parsedUrl = url . parse ( ctx . instance . config . get ( 'url' ) ) ;
101
-
102
- let confFile = `${ parsedUrl . hostname } .conf` ;
103
- let nginxConfPath = path . join ( ctx . instance . dir , 'system' , 'files' , confFile ) ;
104
-
105
- if ( ! fs . existsSync ( nginxConfPath ) ) {
80
+ if ( ! fs . existsSync ( `/etc/nginx/sites-available/${ parsedUrl . hostname } .conf` ) ) {
106
81
if ( ctx . single ) {
107
82
this . ui . log ( 'Nginx config file does not exist, skipping SSL setup' , 'yellow' ) ;
108
83
}
@@ -111,7 +86,7 @@ class NginxExtension extends cli.Extension {
111
86
}
112
87
113
88
let rootPath = path . resolve ( ctx . instance . dir , 'system' , 'nginx-root' ) ;
114
- const letsencrypt = require ( './letsencrypt ') ;
89
+ let dhparamFile = path . join ( ctx . instance . dir , 'system' , 'files' , 'dhparam.pem ') ;
115
90
116
91
return this . ui . listr ( [ {
117
92
title : 'Checking DNS resolution' ,
@@ -135,9 +110,9 @@ class NginxExtension extends cli.Extension {
135
110
} ) ;
136
111
}
137
112
} , {
138
- title : 'Preparing Nginx for Let\'s Encrypt SSL certificate creation ' ,
113
+ title : 'Getting additional configuration ' ,
139
114
skip : ( ctx ) => ctx . dnsfail ,
140
- task : ( ctx ) => {
115
+ task : ( ) => {
141
116
let promise ;
142
117
143
118
if ( argv . sslemail ) {
@@ -151,91 +126,68 @@ class NginxExtension extends cli.Extension {
151
126
} ) . then ( answer => { argv . sslemail = answer . email ; } ) ;
152
127
}
153
128
154
- return promise . then ( ( ) => {
155
- return Promise . fromCallback ( ( cb ) => NginxConfFile . create ( nginxConfPath , cb ) ) . then ( ( conf ) => {
156
- ctx . ssl = { } ;
157
-
158
- ctx . ssl . conf = conf ;
159
- ctx . ssl . http = conf . nginx . server ;
160
-
161
- let location = ctx . ssl . http . location ;
162
-
163
- // Don't add well-known block if it already exists
164
- if ( ! Array . isArray ( location ) || location . length === 1 ) {
165
- ctx . ssl . http . _add ( 'location' , '~ /.well-known' ) ;
166
- ctx . ssl . http . location [ 1 ] . _add ( 'allow' , 'all' ) ;
167
- }
168
- } ) ;
169
- } ) ;
129
+ return promise ;
170
130
}
171
131
} , {
172
- title : 'Restarting Nginx' ,
173
- skip : ( ctx ) => ctx . dnsfail ,
174
- task : ( ) => this . restartNginx ( )
175
- } , {
176
- title : 'Getting SSL Certificate' ,
132
+ title : 'Getting SSL Certificate from Let\'s Encrypt' ,
177
133
skip : ( ctx ) => ctx . dnsfail ,
178
134
task : ( ) => {
179
- return letsencrypt ( ctx . instance , argv . sslemail , argv . sslstaging ) . catch ( ( error ) => {
180
- if ( ! ( error instanceof cli . errors . ProcessError ) ) {
181
- return Promise . reject ( error ) ;
182
- }
135
+ return execa . shell ( 'curl https://get.acme.sh | sh' ) . then ( ( ) => {
136
+ let acmeScriptPath = path . join ( os . homedir ( ) , '.acme.sh' , 'acme.sh' ) ;
183
137
184
- // Ensure ~/.well-known location gets cleaned up
185
- ctx . ssl . http . _remove ( 'location' , 1 ) ;
186
- return Promise . reject ( error ) ;
138
+ let cmd = `${ acmeScriptPath } --issue --domain ${ parsedUrl . hostname } --webroot ${ rootPath } ` +
139
+ `--accountemail ${ argv . sslemail } ${ argv . sslstaging ? ' --staging' : '' } ` ;
140
+
141
+ return execa . shell ( cmd ) ;
142
+ } ) . catch ( ( error ) => {
143
+ // Certs have been generated before, skip
144
+ if ( ! error . stdout . match ( / S k i p / ) ) {
145
+ return Promise . reject ( new cli . errors . ProcessError ( error ) ) ;
146
+ }
187
147
} ) ;
188
148
}
189
149
} , {
190
150
title : 'Generating Encryption Key (may take a few minutes)' ,
191
151
skip : ( ctx ) => ctx . dnsfail ,
192
- task : ( ctx ) => {
193
- ctx . ssl . dhparamOutFile = path . join ( ctx . instance . dir , 'system' , 'files' , 'dhparam.pem' ) ;
194
- return execa . shell ( `openssl dhparam -out ${ ctx . ssl . dhparamOutFile } 2048` )
152
+ task : ( ) => {
153
+ return execa . shell ( `openssl dhparam -out ${ dhparamFile } 2048` )
195
154
. catch ( ( error ) => Promise . reject ( new cli . errors . ProcessError ( error ) ) ) ;
196
155
}
197
156
} , {
198
- title : 'Writing SSL parameters ' ,
157
+ title : 'Generating SSL security headers ' ,
199
158
skip : ( ctx ) => ctx . dnsfail ,
200
159
task : ( ctx ) => {
201
- let sslParamsTemplate = template ( fs . readFileSync ( path . join ( __dirname , 'ssl-params.conf.template' ) , 'utf8' ) ) ;
202
- return ctx . instance . template ( sslParamsTemplate ( {
203
- dhparam : ctx . ssl . dhparamOutFile
204
- } ) , 'ssl parameters' , 'ssl-params.conf' ) ;
160
+ let sslParamsConf = template ( fs . readFileSync ( path . join ( __dirname , 'templates' , 'ssl-params.conf' ) , 'utf8' ) ) ;
161
+ return ctx . instance . template (
162
+ sslParamsConf ( { dhparam : dhparamFile } ) ,
163
+ 'ssl security parameters' ,
164
+ 'ssl-params.conf'
165
+ ) ;
205
166
}
206
167
} , {
207
- title : 'Updating Nginx with SSL config ' ,
168
+ title : 'Generating SSL configuration ' ,
208
169
skip : ( ctx ) => ctx . dnsfail ,
209
170
task : ( ctx ) => {
210
- // add ssl server block
211
- ctx . ssl . conf . nginx . _add ( 'server' ) ;
212
- let https = ctx . ssl . conf . nginx . server [ 1 ] ;
213
-
214
- // add listen directives
215
- https . _add ( 'listen' , '443 ssl http2' ) ;
216
- https . _add ( 'listen' , '[::]:443 ssl http2' ) ;
217
- https . _add ( 'server_name' , parsedUrl . hostname ) ;
218
-
219
- let letsencryptPath = path . join ( ctx . instance . dir , 'system' , 'letsencrypt' ) ;
220
-
221
- // add ssl cert directives
222
- https . _add ( 'ssl_certificate' , path . join ( letsencryptPath , 'fullchain.pem' ) ) ;
223
- https . _add ( 'ssl_certificate_key' , path . join ( letsencryptPath , 'privkey.pem' ) ) ;
224
- // add ssl-params snippet
225
- https . _add ( 'include' , path . join ( ctx . instance . dir , 'system' , 'files' , 'ssl-params.conf' ) ) ;
226
- // add root directive
227
- https . _add ( 'root' , rootPath ) ;
228
-
229
- https . _add ( 'location' , '/' ) ;
230
- this . _addProxyBlock ( https . location , ctx . instance . config . get ( 'server.port' ) ) ;
231
-
232
- https . _add ( 'client_max_body_size' , '50m' ) ;
233
-
234
- https . _add ( 'location' , '~ /.well-known' ) ;
235
- https . location [ 1 ] . _add ( 'allow' , 'all' ) ;
171
+ let acmeFolder = path . join ( os . homedir ( ) , '.acme.sh' , parsedUrl . hostname ) ;
172
+ let sslConf = template ( fs . readFileSync ( path . join ( __dirname , 'templates' , 'nginx-ssl.conf' ) , 'utf8' ) ) ;
173
+ let generatedSslConfig = sslConf ( {
174
+ url : parsedUrl . hostname ,
175
+ webroot : rootPath ,
176
+ fullchain : path . join ( acmeFolder , 'fullchain.cer' ) ,
177
+ privkey : path . join ( acmeFolder , `${ parsedUrl . hostname } .key` ) ,
178
+ sslparams : path . join ( ctx . instance . dir , 'system' , 'files' , 'ssl-params.conf' ) ,
179
+ location : parsedUrl . pathname !== '/' ? `^~ ${ parsedUrl . pathname } ` : '/' ,
180
+ port : ctx . instance . config . get ( 'server.port' )
181
+ } ) ;
236
182
237
- ctx . instance . cliConfig . set ( 'extension.ssl' , true )
238
- . set ( 'extension.sslemail' , argv . sslemail ) . save ( ) ;
183
+ return ctx . instance . template (
184
+ generatedSslConfig ,
185
+ 'ssl config' ,
186
+ confFile ,
187
+ '/etc/nginx/sites-available'
188
+ ) . then (
189
+ ( ) => this . ui . sudo ( `ln -sf /etc/nginx/sites-available/${ confFile } /etc/nginx/sites-enabled/${ confFile } ` )
190
+ ) ;
239
191
}
240
192
} , {
241
193
title : 'Restarting Nginx' ,
@@ -244,55 +196,38 @@ class NginxExtension extends cli.Extension {
244
196
} ] , false ) ;
245
197
}
246
198
247
- setupRenew ( argv , ctx ) {
248
- return this . _cron ( ( cron ) => {
249
- // Ensure any crontab with the same instance name is removed
250
- cron . remove ( { comment : ctx . instance . name } ) ;
251
- let cmd = `cd ${ ctx . instance . dir } && ${ process . argv . slice ( 0 , 2 ) . join ( ' ' ) } ssl-renew` ;
252
- cron . create ( cmd , '@monthly' , ctx . instance . name ) ;
253
- } ) ;
254
- }
255
-
256
- _addProxyBlock ( location , port ) {
257
- location . _add ( 'proxy_set_header' , 'X-Forwarded-For $proxy_add_x_forwarded_for' ) ;
258
- location . _add ( 'proxy_set_header' , 'X-Forwarded-Proto $scheme' ) ;
259
- location . _add ( 'proxy_set_header' , 'X-Real-IP $remote_addr' ) ;
260
- location . _add ( 'proxy_set_header' , 'Host $http_host' ) ;
261
- location . _add ( 'proxy_pass' , `http://127.0.0.1:${ port } ` ) ;
262
- }
263
-
264
199
uninstall ( instance ) {
265
- if ( ! instance . cliConfig . get ( 'extension.nginx' , false ) ) {
266
- return ;
267
- }
268
-
269
200
let parsedUrl = url . parse ( instance . config . get ( 'url' ) ) ;
270
201
let confFile = `${ parsedUrl . hostname } .conf` ;
202
+ let sslConfFile = `${ parsedUrl . hostname } -ssl.conf` ;
271
203
272
- let promises = [
273
- this . _cron ( cron => cron . remove ( { comment : instance . name } ) )
274
- ] ;
204
+ let promises = [ ] ;
275
205
276
206
if ( fs . existsSync ( `/etc/nginx/sites-available/${ confFile } ` ) ) {
207
+ // Nginx config exists, remove it
277
208
promises . push (
278
- this . ui . sudo ( `rm /etc/nginx/sites-available/${ confFile } ` ) . then ( ( ) => {
279
- return this . ui . sudo ( `rm /etc/nginx/sites-enabled/${ confFile } ` ) ;
280
- } ) . catch (
209
+ Promise . all ( [
210
+ this . ui . sudo ( `rm -f /etc/nginx/sites-available/${ confFile } ` ) ,
211
+ this . ui . sudo ( `rm -f /etc/nginx/sites-enabled/${ confFile } ` )
212
+ ] ) . catch (
281
213
( ) => Promise . reject ( new cli . errors . SystemError ( 'Nginx config file link could not be removed, you will need to do this manually.' ) )
282
214
)
283
215
) ;
284
216
}
285
217
286
- return Promise . all ( promises ) . then ( ( ) => this . restartNginx ( ) ) ;
287
- }
288
-
289
- _cron ( fn ) {
290
- const crontab = require ( 'crontab' ) ;
218
+ if ( fs . existsSync ( `/etc/nginx/sites-available/${ sslConfFile } ` ) ) {
219
+ // SSL config exists, remove it
220
+ promises . push (
221
+ Promise . all ( [
222
+ this . ui . sudo ( `rm -f /etc/nginx/sites-available/${ sslConfFile } ` ) ,
223
+ this . ui . sudo ( `rm -f /etc/nginx/sites-enabled/${ sslConfFile } ` )
224
+ ] ) . catch (
225
+ ( ) => Promise . reject ( new cli . errors . SystemError ( 'SSL config file link could not be removed, you will need to do this manually.' ) )
226
+ )
227
+ ) ;
228
+ }
291
229
292
- return Promise . fromCallback ( cb => crontab . load ( cb ) ) . then ( ( cron ) => {
293
- fn ( cron ) ;
294
- return Promise . fromCallback ( cb => cron . save ( cb ) ) ;
295
- } ) ;
230
+ return Promise . all ( promises ) . then ( ( ) => this . restartNginx ( ) ) ;
296
231
}
297
232
298
233
restartNginx ( ) {
0 commit comments