potluck-nginx 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.