potluck-nginx 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c15060a79498a8616523a551ef3031549bbb8b09af0e6d7eb7e329c166b9e8d
4
- data.tar.gz: bf716efed8715260cec9439c731e5c799073080fda9f25ebf19b8ea1e09d061b
3
+ metadata.gz: f489f7ab1e64d5447a26b87a96bfb1ceb50174cce9a25d9131e64ee1a06a7f66
4
+ data.tar.gz: 7f7518e1835ef8d454b2925bded018cc189fde565dedae2f8b8274f105531040
5
5
  SHA512:
6
- metadata.gz: 99f5a935c622ec367f01a7852b1992ff1c687cfb575286e7e05b211eb98788b97fa7684f11cd1f1f7afe95ed46cb110a8589602dc8cb288bbfec2d0f9b5e522d
7
- data.tar.gz: 4ccc0baa9b708d498bf46b7f925ba8e725eba121c0ed17e5a41d54f281972839a39fb5baa46db9dc153f7fbe88e06709596d62de9d9fdaf1e6503aa8e846c7e2
6
+ metadata.gz: c8cd92c4fdbd976b2330a433ff6cacac50ee143c2b90dd91776af91350b4540b304fc89f3ad0456acb108ad4f29affe37e58699c8ba122a3652eca86306f495c
7
+ data.tar.gz: 0f366d7c77baef42dae840b4e410c596623297bede06a7daa2c15c52d079b789118e89c8f7319d91012a74766e9baa033b416b8f56603a61ddd7200ff4de6d8e
@@ -3,7 +3,11 @@
3
3
  require('time')
4
4
 
5
5
  module Potluck
6
- class Nginx < Dish
6
+ class Nginx < Service
7
+ ##
8
+ # SSL-specific configuration for Nginx. Provides self-signed certificate generation for use in
9
+ # developemnt.
10
+ #
7
11
  class SSL
8
12
  # Reference: https://ssl-config.mozilla.org/#server=nginx&config=intermediate&guideline=5.6
9
13
  DEFAULT_CONFIG = {
@@ -26,8 +30,18 @@ module Potluck
26
30
 
27
31
  attr_reader(:csr_file, :key_file, :crt_file, :dhparam_file, :config)
28
32
 
29
- def initialize(nginx, dir, host, crt_file: nil, key_file: nil, dhparam_file: nil,
30
- config: {})
33
+ ##
34
+ # Creates a new instance. Providing no SSL files will cue generation of a self-signed certificate.
35
+ #
36
+ # * +nginx+ - Nginx instance.
37
+ # * +dir+ - Directory where SSL files are located or should be written to.
38
+ # * +host+ - Name of the host for determining file names and generating a self-signed certificate.
39
+ # * +crt_file+ - Path to the CRT file (optional).
40
+ # * +key_file+ - Path to the KEY file (optional).
41
+ # * +dhparam_file+ - Path to the DH parameters file (optional).
42
+ # * +config+ - Nginx configuration hash (optional).
43
+ #
44
+ def initialize(nginx, dir, host, crt_file: nil, key_file: nil, dhparam_file: nil, config: {})
31
45
  @nginx = nginx
32
46
  @dir = dir
33
47
  @host = host
@@ -52,6 +66,11 @@ module Potluck
52
66
  }.merge!(DEFAULT_CONFIG).merge!(config)
53
67
  end
54
68
 
69
+ ##
70
+ # If SSL files were passed to SSL.new, does nothing. Otherwise checks if auto-generated SSL files
71
+ # exist and generates them if not. If they do exist, the expiration for the certificate is checked and
72
+ # the certificate regenerated if the expiration date is soon or in the past.
73
+ #
55
74
  def ensure_files
56
75
  return if !@auto_generated || (
57
76
  File.exists?(@csr_file) &&
@@ -88,6 +107,9 @@ module Potluck
88
107
 
89
108
  private
90
109
 
110
+ ##
111
+ # OpenSSL configuration content used when auto-generating an SSL certificate.
112
+ #
91
113
  def openssl_config
92
114
  <<~EOS
93
115
  [ req ]
@@ -2,7 +2,35 @@
2
2
 
3
3
  module Potluck
4
4
  class Nginx
5
+ ##
6
+ # Utility methods for Nginx class.
7
+ #
5
8
  class Util
9
+ ##
10
+ # Merges one or more other hashes into a hash by merging nested hashes rather than overwriting them as
11
+ # is the case with <tt>Hash#merge!</tt>.
12
+ #
13
+ # * +hashes+ - Hashes to deep merge. The first one will be modified with the result of the merge.
14
+ # * +arrays+ - True if arrays should be merged rather than overwritten (optional, default: false).
15
+ #
16
+ # Example:
17
+ #
18
+ # h1 = {hello: {item1: 'world'}}
19
+ # h2 = {hello: {item2: 'friend'}}
20
+ #
21
+ # Util.deep_merge!(h1, h2)
22
+ # # => {hello: {item1: 'world', item2: 'friend'}}
23
+ #
24
+ # By default, only hashes are merged and arrays are still overwritten as they are with
25
+ # <tt>Hash#merge!</tt>. But passing <tt>arrays: true</tt> will result in arrays being merged similarly
26
+ # to hashes. Example:
27
+ #
28
+ # h1 = {hello: {item1: ['world']}}
29
+ # h2 = {hello: {item1: ['friend']}}
30
+ #
31
+ # Util.deep_merge!(h1, h2, arrays: true)
32
+ # # => {hello: {item1: ['world', 'friend']}}
33
+ #
6
34
  def self.deep_merge!(*hashes, arrays: false)
7
35
  hash = hashes[0]
8
36
 
data/lib/potluck/nginx.rb CHANGED
@@ -6,7 +6,14 @@ require_relative('nginx/ssl')
6
6
  require_relative('nginx/util')
7
7
 
8
8
  module Potluck
9
- class Nginx < Dish
9
+ ##
10
+ # A Ruby interface for configuring and controlling Nginx. Each instance of this class manages a separate
11
+ # Nginx configuration file, which is loaded and unloaded from the base Nginx configuration when #start and
12
+ # #stop are called, respectively. Any number of Ruby processes can thus each manage their own Nginx
13
+ # configuration and control whether or not it is active without interfering with any other instances or
14
+ # non-Ruby processes leveraging Nginx.
15
+ #
16
+ class Nginx < Service
10
17
  CONFIG_NAME_ACTIVE = 'nginx.conf'
11
18
  CONFIG_NAME_INACTIVE = 'nginx-stopped.conf'
12
19
  ACTIVE_CONFIG_PATTERN = File.join(DIR, '*', CONFIG_NAME_ACTIVE).freeze
@@ -20,6 +27,32 @@ module Potluck
20
27
  stop: 'nginx -s stop',
21
28
  }.freeze
22
29
 
30
+ ##
31
+ # Creates a new instance.
32
+ #
33
+ # * +hosts+ - One or more hosts.
34
+ # * +port+ - Port that the upstream (Ruby web server) is running on.
35
+ # * +subdomains+ - One or more subdomains (optional).
36
+ # * +ssl+ - SSL configuration arguments to pass to SSL.new (optional).
37
+ # * +one_host+ - True if URLs should be normalized to the first host in +hosts+ (optional, default:
38
+ # false).
39
+ # * +www+ - +true+ if URLs should be normalized to include 'www.' prefix, +false+ to exclude 'www.', and
40
+ # +nil+ if either is acceptable (optional, default: +nil+).
41
+ # * +multiple_slashes+ - +false+ if any occurrence of multiple slashes in the path portion of the URL
42
+ # should be normalized to a single slash (optional, default: +nil+).
43
+ # * +multiple_question_marks+ - +false+ if multiple question marks in the URL signifying the start of
44
+ # the query string should be normalized to a single question mark (optional, default: +nil+).
45
+ # * +trailing_slash+ - +true+ if URLs should be normalized to include a trailing slash at the end of the
46
+ # path portion, +false+ to strip any trailing slash, and +nil+ if either is acceptable (optional,
47
+ # default: +nil+).
48
+ # * +trailing_question_mark+ - +true+ if URLs should be normalized to include a trailing question mark
49
+ # when the query string is empty, +false+ to strip any trailing question mark, and +nil+ if either is
50
+ # acceptable (optional, default: +nil+).
51
+ # * +config+ - Nginx configuration hash; see #config (optional).
52
+ # * +ensure_host_entries+ - True if +hosts+ should be added to system /etc/hosts file as mappings to
53
+ # localhost (optional, default: false).
54
+ # * +args+ - Arguments to pass to Potluck::Service.new (optional).
55
+ #
23
56
  def initialize(hosts, port, subdomains: nil, ssl: nil, one_host: false, www: nil, multiple_slashes: nil,
24
57
  multiple_question_marks: nil, trailing_slash: nil, trailing_question_mark: nil, config: {},
25
58
  ensure_host_entries: false, **args)
@@ -49,13 +82,16 @@ module Potluck
49
82
  @trailing_question_mark = trailing_question_mark
50
83
  @additional_config = config
51
84
 
52
- FileUtils.mkdir_p(DIR)
53
85
  FileUtils.mkdir_p(@dir)
54
86
 
55
87
  @config_file_active = File.join(@dir, CONFIG_NAME_ACTIVE).freeze
56
88
  @config_file_inactive = File.join(@dir, CONFIG_NAME_INACTIVE).freeze
57
89
  end
58
90
 
91
+ ##
92
+ # Ensures this instance's configuration file is active and starts Nginx if it's managed. If Nginx is
93
+ # already running, a reload signal is sent to the process after activating the configuration file.
94
+ #
59
95
  def start
60
96
  return unless manage?
61
97
 
@@ -71,6 +107,13 @@ module Potluck
71
107
  status == :active ? reload : super
72
108
  end
73
109
 
110
+ ##
111
+ # Ensures this instance's configuration file is inactive and optionally stops the Nginx process if it's
112
+ # managed.
113
+ #
114
+ # * +hard+ - True if the Nginx process should be stopped, false to just inactivate this instance's
115
+ # configuration file and leave Nginx running (optional, default: false).
116
+ #
74
117
  def stop(hard = false)
75
118
  return unless manage?
76
119
 
@@ -79,14 +122,29 @@ module Potluck
79
122
  hard || status != :active ? super() : reload
80
123
  end
81
124
 
125
+ ##
126
+ # Reloads Nginx if it's managed.
127
+ #
82
128
  def reload
83
129
  return unless manage?
84
130
 
85
131
  run('nginx -s reload')
86
132
  end
87
133
 
134
+ ##
135
+ # Returns the content for the Nginx configuration file as a string.
136
+ #
137
+ def config_file_content
138
+ self.class.to_nginx_config(config)
139
+ end
140
+
88
141
  private
89
142
 
143
+ ##
144
+ # Returns a hash representation of the Nginx configuration file content. Any configuration passed to
145
+ # Nginx.new is deep-merged into a base configuration hash, meaning nested hashes are merged rather than
146
+ # overwritten (see Util.deep_merge!).
147
+ #
90
148
  def config
91
149
  host_subdomains_regex = ([@host] + @subdomains).join('|')
92
150
  hosts_subdomains_regex = (@hosts + @subdomains).join('|')
@@ -111,14 +169,15 @@ module Potluck
111
169
  'server_name' => (@hosts + @subdomains).join(' '),
112
170
 
113
171
  'gzip' => 'on',
114
- 'gzip_types' => 'application/javascript application/json text/css text/plain',
172
+ 'gzip_types' => 'application/javascript application/json application/xml text/css '\
173
+ 'text/javascript text/plain',
115
174
 
116
175
  'add_header' => {
117
176
  repeat: true,
118
- 'Referrer-Policy' => 'same-origin',
119
- 'X-Frame-Options' => 'DENY',
120
- 'X-XSS-Protection' => '\'1; mode=block\'',
121
- 'X-Content-Type-Options' => 'nosniff',
177
+ 'Referrer-Policy' => '\'same-origin\' always',
178
+ 'X-Frame-Options' => '\'DENY\' always',
179
+ 'X-XSS-Protection' => '\'1; mode=block\' always',
180
+ 'X-Content-Type-Options' => '\'nosniff\' always',
122
181
  },
123
182
  }, @ssl ? @ssl.config : {}).merge!(
124
183
  'location /' => {
@@ -128,6 +187,7 @@ module Potluck
128
187
  set $r 0;
129
188
  set $s $scheme;
130
189
  set $h $host;
190
+ set $port #{@ssl ? '443' : '80'};
131
191
  set $p '';
132
192
  set $u '';
133
193
  set $q '';
@@ -147,7 +207,7 @@ module Potluck
147
207
  end}
148
208
 
149
209
  if ($scheme = #{@other_scheme}) { set $s #{@scheme}; set $r 1; }
150
- if ($http_host ~ :[0-9]+$) { set $p :#{@ssl ? '4433' : '8080'}; }
210
+ if ($http_host ~ :([0-9]+)$) { set $p :$1; set $port $1; }
151
211
  if ($request_uri ~ ^([^\\?]+)(\\?+.*)?$) { set $u $1; set $q $2; }
152
212
 
153
213
  #{'if ($u ~ //) { set $u $uri; set $r 1; }' if @multiple_slashes == false}
@@ -174,11 +234,11 @@ module Potluck
174
234
  'proxy_redirect' => 'off',
175
235
  'proxy_set_header' => {
176
236
  repeat: true,
177
- 'Host' => @host,
237
+ 'Host' => '$http_host',
178
238
  'X-Real-IP' => '$remote_addr',
179
239
  'X-Forwarded-For' => '$proxy_add_x_forwarded_for',
180
240
  'X-Forwarded-Proto' => @ssl ? 'https' : 'http',
181
- 'X-Forwarded-Port' => @ssl ? '443' : '80',
241
+ 'X-Forwarded-Port' => '$port',
182
242
  },
183
243
  },
184
244
  ),
@@ -189,20 +249,33 @@ module Potluck
189
249
  config
190
250
  end
191
251
 
252
+ ##
253
+ # Writes the Nginx configuration to the (inactive) configuration file.
254
+ #
192
255
  def write_config
193
256
  File.open(@config_file_inactive, 'w') do |file|
194
- file.write(self.class.to_nginx_config(config))
257
+ file.write(config_file_content)
195
258
  end
196
259
  end
197
260
 
261
+ ##
262
+ # Renames the inactive Nginx configuration file to its active name.
263
+ #
198
264
  def activate_config
199
265
  FileUtils.mv(@config_file_inactive, @config_file_active)
200
266
  end
201
267
 
268
+ ##
269
+ # Renames the active Nginx configuration file to its inactive name.
270
+ #
202
271
  def deactivate_config
203
272
  FileUtils.mv(@config_file_active, @config_file_inactive) if File.exists?(@config_file_active)
204
273
  end
205
274
 
275
+ ##
276
+ # Ensures hosts are mapped to localhost in the system /etc/hosts file. Useful in development. Uses sudo
277
+ # to perform the write, which will prompt for the system user's password.
278
+ #
206
279
  def ensure_host_entries
207
280
  content = File.read('/etc/hosts')
208
281
  missing_entries = (@hosts + @subdomains).each_with_object([]) do |h, a|
@@ -222,6 +295,11 @@ module Potluck
222
295
  )
223
296
  end
224
297
 
298
+ ##
299
+ # Ensures Nginx's base configuration file contains an include statement for Potluck's Nginx
300
+ # configuration files. Sudo is not used, so Nginx's base configuration file must be writable by the
301
+ # system user running this Ruby process.
302
+ #
225
303
  def ensure_include
226
304
  config_file = `nginx -t 2>&1`[TEST_CONFIG_REGEX, :config]
227
305
  config_content = File.read(config_file)
@@ -232,6 +310,57 @@ module Potluck
232
310
  end
233
311
  end
234
312
 
313
+ ##
314
+ # Converts a hash to an Nginx configuration file content string. Keys should be strings and values
315
+ # either strings or hashes. A +nil+ value in a hash will result in that key-value pair being omitted.
316
+ #
317
+ # * +hash+ - Hash to convert to the string content of an Nginx configuration file.
318
+ # * +indent+ - Number of spaces to indent; used when the method is called recursively and should not be
319
+ # set explicitly (optional, default: 0).
320
+ # * +repeat+ - Value to prepend to each entry of the hash; used when the method is called recursively
321
+ # and should not be set explicitly (optional).
322
+ #
323
+ # Symbol keys in hashes are used as special directives. Including <tt>repeat: true</tt> will cause the
324
+ # parent hash's key for the child hash to be prefixed to each line of the output. Example:
325
+ #
326
+ # {
327
+ # # ...
328
+ #
329
+ # 'add_header' => {
330
+ # repeat: true,
331
+ # 'X-Frame-Options' => 'DENY',
332
+ # 'X-Content-Type-Options' => 'nosniff',
333
+ # }
334
+ # }
335
+ #
336
+ # Result:
337
+ #
338
+ # # ...
339
+ #
340
+ # add_header X-Frame-Options DENY;
341
+ # add_header X-Content-Type-Options nosniff;
342
+ #
343
+ # A hash containing <tt>raw: '...'</tt> can be used to include a raw chunk of text rather than key-value
344
+ # pairs. Example:
345
+ #
346
+ # {
347
+ # # ...
348
+ #
349
+ # 'location /' => {
350
+ # raw: """
351
+ # if ($scheme = https) { ... }
352
+ # if ($host ~ ^www.) { ... }
353
+ # """,
354
+ # }
355
+ # }
356
+ #
357
+ # Result:
358
+ #
359
+ # location / {
360
+ # if ($scheme = https) { ... }
361
+ # if ($host ~ ^www.) { ... }
362
+ # }
363
+ #
235
364
  def self.to_nginx_config(hash, indent: 0, repeat: nil)
236
365
  hash.each_with_object(+'') do |(k, v), config|
237
366
  next if v.nil?
@@ -253,6 +382,9 @@ module Potluck
253
382
  end
254
383
  end
255
384
 
385
+ ##
386
+ # Content of the launchctl plist file.
387
+ #
256
388
  def self.plist
257
389
  super(
258
390
  <<~EOS
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: potluck-nginx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nate Pickens
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-16 00:00:00.000000000 Z
11
+ date: 2021-12-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: potluck
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.0.3
19
+ version: 0.0.4
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.0.3
26
+ version: 0.0.4
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -92,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
92
92
  - !ruby/object:Gem::Version
93
93
  version: '0'
94
94
  requirements: []
95
- rubygems_version: 3.2.3
95
+ rubygems_version: 3.2.32
96
96
  signing_key:
97
97
  specification_version: 4
98
98
  summary: A Ruby manager for Nginx.