potluck-nginx 0.0.1 → 0.0.5

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: 599537fcab6df50b0126d73460295be4b366101667ad4c2317199b361153a5c1
4
- data.tar.gz: 6a38b16b915efa971b1b93361dd1b995937b67a61031376879cf0aae857438be
3
+ metadata.gz: b5dc3bb7d62e4244f719abe5110cdb80a972b10f56f13d35844918a3eaa875df
4
+ data.tar.gz: b971afa507788f9bf2261df078e7df83df963928dba62dcca2e1cbf80a10cd6a
5
5
  SHA512:
6
- metadata.gz: f007969613094559c4b68707b8bf87f570def7da9b5122673de84ca2a4c45409450ba348f7d8ff522191188afacdf1cf1a94c15f1a0b4cc36419e446ca921644
7
- data.tar.gz: 6c3672941908c91d9151e7863ed0cb93c7e7ebf4463bcc2b7c8d997ac3baf529ef124bad4a1733c33ee3221a12cfad0cd41c60f72f85f9f2c1921f4d2646a436
6
+ metadata.gz: d6b3f68bf12bce2e2035689d9c07bd76af04d0f5c8ad596c1dfafb2c5faca4551e9a9d8c64e51fd6fb614f2b7e5dc4746a8f36137b5f416ecd78dffab961c6db
7
+ data.tar.gz: 3885228d15374b68b7af5d86050aea5bab688e4de24fa4cb18a54599bdece7baa0e6495487b707547be70646eefe4099184b69c60128370151b069153f5b2136
@@ -3,16 +3,22 @@
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
- # Based on https://hackernoon.com/how-properly-configure-nginx-server-for-tls-sg1d3udt
12
+ # Reference: https://ssl-config.mozilla.org/#server=nginx&config=intermediate&guideline=5.6
9
13
  DEFAULT_CONFIG = {
10
- 'ssl_ciphers' => 'ECDH+AESGCM:ECDH+AES256-CBC:ECDH+AES128-CBC:DH+3DES:!ADH:!AECDH:!MD5',
11
- 'ssl_prefer_server_ciphers' => 'on',
14
+ 'ssl_ciphers' => 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM'\
15
+ '-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:D'\
16
+ 'HE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384',
17
+ 'ssl_prefer_server_ciphers' => 'off',
12
18
  'ssl_protocols' => 'TLSv1.2 TLSv1.3',
13
- 'ssl_session_cache' => 'shared:SSL:40m',
14
- 'ssl_session_tickets' => 'on',
15
- 'ssl_session_timeout' => '4h',
19
+ 'ssl_session_cache' => 'shared:SSL:10m',
20
+ 'ssl_session_tickets' => 'off',
21
+ 'ssl_session_timeout' => '1d',
16
22
  'add_header' => {
17
23
  repeat: true,
18
24
  'Strict-Transport-Security' => '\'max-age=31536000; includeSubDomains\' always',
@@ -24,8 +30,18 @@ module Potluck
24
30
 
25
31
  attr_reader(:csr_file, :key_file, :crt_file, :dhparam_file, :config)
26
32
 
27
- def initialize(nginx, dir, host, crt_file: nil, key_file: nil, dhparam_file: nil,
28
- 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: {})
29
45
  @nginx = nginx
30
46
  @dir = dir
31
47
  @host = host
@@ -33,7 +49,8 @@ module Potluck
33
49
  @auto_generated = !crt_file && !key_file && !dhparam_file
34
50
 
35
51
  if !@auto_generated && (!crt_file || !key_file || !dhparam_file)
36
- raise('Must supply values for all three or none: crt_file, key_file, dhparam_file')
52
+ raise(ArgumentError.new('Must supply values for all three or none: crt_file, key_file, '\
53
+ 'dhparam_file'))
37
54
  end
38
55
 
39
56
  @csr_file = File.join(@dir, "#{@host}.csr").freeze
@@ -41,15 +58,20 @@ module Potluck
41
58
  @key_file = key_file || File.join(@dir, "#{@host}.key").freeze
42
59
  @dhparam_file = dhparam_file || File.join(@dir, 'dhparam.pem').freeze
43
60
 
44
- @config = {
61
+ @config = Util.deep_merge({
45
62
  'ssl_certificate' => @crt_file,
46
63
  'ssl_certificate_key' => @key_file,
47
64
  'ssl_dhparam' => @dhparam_file,
48
65
  'ssl_stapling' => ('on' unless @auto_generated),
49
66
  'ssl_stapling_verify' => ('on' unless @auto_generated),
50
- }.merge!(DEFAULT_CONFIG).merge!(config)
67
+ }, DEFAULT_CONFIG, config)
51
68
  end
52
69
 
70
+ ##
71
+ # If SSL files were passed to SSL.new, does nothing. Otherwise checks if auto-generated SSL files
72
+ # exist and generates them if not. If they do exist, the expiration for the certificate is checked and
73
+ # the certificate regenerated if the expiration date is soon or in the past.
74
+ #
53
75
  def ensure_files
54
76
  return if !@auto_generated || (
55
77
  File.exists?(@csr_file) &&
@@ -86,6 +108,9 @@ module Potluck
86
108
 
87
109
  private
88
110
 
111
+ ##
112
+ # OpenSSL configuration content used when auto-generating an SSL certificate.
113
+ #
89
114
  def openssl_config
90
115
  <<~EOS
91
116
  [ req ]
@@ -1,17 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Potluck
4
- class Nginx
4
+ class Nginx < Service
5
+ ##
6
+ # Utility methods for Nginx class.
7
+ #
5
8
  class Util
6
- def self.deep_merge!(*hashes, arrays: false)
7
- hash = hashes[0]
9
+ ##
10
+ # Merges N hashes by merging nested hashes rather than overwriting them as is the case with
11
+ # <tt>Hash#merge</tt>.
12
+ #
13
+ # * +hashes+ - Hashes to deep 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>. Passing <tt>arrays: true</tt> will result in arrays being merged similarly to
26
+ # 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
+ #
34
+ def self.deep_merge(*hashes, arrays: false)
35
+ hash = hashes[0].dup
8
36
 
9
37
  hashes[1..-1].each do |other_hash|
10
38
  other_hash.each do |key, other_value|
11
39
  this_value = hash[key]
12
40
 
13
41
  if this_value.kind_of?(Hash) && other_value.kind_of?(Hash)
14
- deep_merge!(this_value, other_value, arrays: arrays)
42
+ hash[key] = deep_merge(this_value, other_value, arrays: arrays)
15
43
  elsif arrays && this_value.kind_of?(Array)
16
44
  hash[key] |= Array(other_value)
17
45
  else
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
@@ -14,8 +21,45 @@ module Potluck
14
21
  TEST_CONFIG_REGEX = /nginx: configuration file (?<config>.+) test (failed|is successful)/.freeze
15
22
  INCLUDE_REGEX = /^ *include +#{Regexp.escape(ACTIVE_CONFIG_PATTERN)} *;/.freeze
16
23
 
24
+ NON_LAUNCHCTL_COMMANDS = {
25
+ status: 'ps aux | grep \'[n]ginx: master process\'',
26
+ start: 'nginx',
27
+ stop: 'nginx -s stop',
28
+ }.freeze
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
+ #
17
56
  def initialize(hosts, port, subdomains: nil, ssl: nil, one_host: false, www: nil, multiple_slashes: nil,
18
- multiple_question_marks: nil, trailing_slash: nil, trailing_question_mark: nil, config: {}, **args)
57
+ multiple_question_marks: nil, trailing_slash: nil, trailing_question_mark: nil, config: {},
58
+ ensure_host_entries: false, **args)
59
+ if args[:manage] && !args[:manage].kind_of?(Hash) && !launchctl?
60
+ args[:manage] = NON_LAUNCHCTL_COMMANDS
61
+ end
62
+
19
63
  super(**args)
20
64
 
21
65
  @hosts = Array(hosts).map { |h| h.sub(/^www\./, '') }.uniq
@@ -23,6 +67,7 @@ module Potluck
23
67
  @host = @hosts.first
24
68
  @port = port
25
69
 
70
+ @ensure_host_entries = ensure_host_entries
26
71
  @dir = File.join(DIR, @host)
27
72
  @ssl = SSL.new(self, @dir, @host, **ssl) if ssl
28
73
 
@@ -37,16 +82,21 @@ module Potluck
37
82
  @trailing_question_mark = trailing_question_mark
38
83
  @additional_config = config
39
84
 
40
- FileUtils.mkdir_p(DIR)
41
85
  FileUtils.mkdir_p(@dir)
42
86
 
43
87
  @config_file_active = File.join(@dir, CONFIG_NAME_ACTIVE).freeze
44
88
  @config_file_inactive = File.join(@dir, CONFIG_NAME_INACTIVE).freeze
45
89
  end
46
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
+ #
47
95
  def start
96
+ return unless manage?
97
+
48
98
  @ssl&.ensure_files
49
- ensure_host_entries
99
+ ensure_host_entries if @ensure_host_entries
50
100
  ensure_include
51
101
 
52
102
  write_config
@@ -57,18 +107,44 @@ module Potluck
57
107
  status == :active ? reload : super
58
108
  end
59
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
+ #
60
117
  def stop(hard = false)
118
+ return unless manage?
119
+
61
120
  deactivate_config
62
121
 
63
122
  hard || status != :active ? super() : reload
64
123
  end
65
124
 
125
+ ##
126
+ # Reloads Nginx if it's managed.
127
+ #
66
128
  def reload
129
+ return unless manage?
130
+
67
131
  run('nginx -s reload')
68
132
  end
69
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
+
70
141
  private
71
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
+ #
72
148
  def config
73
149
  host_subdomains_regex = ([@host] + @subdomains).join('|')
74
150
  hosts_subdomains_regex = (@hosts + @subdomains).join('|')
@@ -78,113 +154,134 @@ module Potluck
78
154
  'server' => "127.0.0.1:#{@port}",
79
155
  },
80
156
 
81
- 'server' => Util.deep_merge!({
82
- 'charset' => 'UTF-8',
83
- 'access_log' => File.join(@dir, 'nginx-access.log'),
84
- 'error_log' => File.join(@dir, 'nginx-error.log'),
85
-
86
- 'listen' => {
87
- repeat: true,
88
- '8080' => true,
89
- '[::]:8080' => true,
90
- '4433 ssl http2' => @ssl ? true : nil,
91
- '[::]:4433 ssl http2' => @ssl ? true : nil,
92
- },
93
- 'server_name' => (@hosts + @subdomains).join(' '),
157
+ 'server' => Util.deep_merge(
158
+ {
159
+ 'charset' => 'UTF-8',
160
+ 'access_log' => File.join(@dir, 'nginx-access.log'),
161
+ 'error_log' => File.join(@dir, 'nginx-error.log'),
162
+
163
+ 'listen' => {
164
+ repeat: true,
165
+ '8080' => true,
166
+ '[::]:8080' => true,
167
+ '4433 ssl http2' => @ssl ? true : nil,
168
+ '[::]:4433 ssl http2' => @ssl ? true : nil,
169
+ },
170
+ 'server_name' => (@hosts + @subdomains).join(' '),
94
171
 
95
- 'gzip' => 'on',
96
- 'gzip_types' => 'application/javascript application/json text/css text/plain',
172
+ 'gzip' => 'on',
173
+ 'gzip_types' => 'application/javascript application/json application/xml text/css '\
174
+ 'text/javascript text/plain',
97
175
 
98
- 'add_header' => {
99
- repeat: true,
100
- 'Referrer-Policy' => 'same-origin',
101
- 'X-Frame-Options' => 'DENY',
102
- 'X-XSS-Protection' => '\'1; mode=block\'',
103
- 'X-Content-Type-Options' => 'nosniff',
104
- },
105
- }, @ssl ? @ssl.config : {}).merge!(
106
- 'location /' => {
107
- raw: """
108
- if ($host !~ ^#{hosts_subdomains_regex}$) { return 404; }
109
-
110
- set $r 0;
111
- set $s $scheme;
112
- set $h $host;
113
- set $p '';
114
- set $u '';
115
- set $q '';
116
-
117
- #{if @www.nil? && @one_host == false
118
- nil
119
- elsif @www.nil? && @one_host == true
120
- "if ($host !~ ^(www.)?#{host_subdomains_regex}$) { set $h $1#{@host}; set $r 1; }"
121
- elsif @www == false && @one_host == false
122
- "if ($host ~ ^www.(.+)$) { set $h $1; set $r 1; }"
123
- elsif @www == false && @one_host == true
124
- "if ($host !~ ^#{host_subdomains_regex}$) { set $h #{@host}; set $r 1; }"
125
- elsif @www == true && @one_host == false
126
- "if ($host !~ ^www.(.+)$) { set $h $1; set $r 1; }"
127
- elsif @www == true && @one_host == true
128
- "if ($host !~ ^www.#{host_subdomains_regex}$) { set $h www.#{@host}; set $r 1; }"
129
- end}
130
-
131
- if ($scheme = #{@other_scheme}) { set $s #{@scheme}; set $r 1; }
132
- if ($http_host ~ :[0-9]+$) { set $p :#{@ssl ? '4433' : '8080'}; }
133
- if ($request_uri ~ ^([^\\?]+)(\\?+.*)?$) { set $u $1; set $q $2; }
134
-
135
- #{'if ($u ~ //) { set $u $uri; set $r 1; }' if @multiple_slashes == false}
136
- #{'if ($q ~ ^\?\?+(.*)$) { set $q ?$1; set $r 1; }' if @multiple_question_marks == false}
137
-
138
- #{if @trailing_question_mark == false
139
- 'if ($q ~ \?+$) { set $q \'\'; set $r 1; }'
140
- elsif @trailing_question_mark == true
141
- 'if ($q !~ .) { set $q ?; set $r 1; }'
142
- end}
143
- #{if @trailing_slash == false
144
- 'if ($u ~ (.+?)/+$) { set $u $1; set $r 1; }'
145
- elsif @trailing_slash == true
146
- 'if ($u ~ [^/]$) { set $u $u/; set $r 1; }'
147
- end}
148
-
149
- set $mr $request_method$r;
150
-
151
- if ($mr ~ ^(GET|HEAD)1$) { return 301 $s://$h$p$u$q; }
152
- if ($mr ~ 1$) { return 308 $s://$h$p$u$q; }
153
- """.strip.gsub(/^ +/, '').gsub(/\n{3,}/, "\n\n"),
154
-
155
- 'proxy_pass' => "http://#{@host}",
156
- 'proxy_redirect' => 'off',
157
- 'proxy_set_header' => {
176
+ 'add_header' => {
158
177
  repeat: true,
159
- 'Host' => @host,
160
- 'X-Real-IP' => '$remote_addr',
161
- 'X-Forwarded-For' => '$proxy_add_x_forwarded_for',
162
- 'X-Forwarded-Proto' => @ssl ? 'https' : 'http',
163
- 'X-Forwarded-Port' => @ssl ? '443' : '80',
178
+ 'Referrer-Policy' => '\'same-origin\' always',
179
+ 'X-Frame-Options' => '\'DENY\' always',
180
+ 'X-XSS-Protection' => '\'1; mode=block\' always',
181
+ 'X-Content-Type-Options' => '\'nosniff\' always',
164
182
  },
165
183
  },
166
- ),
167
- }
168
184
 
169
- Util.deep_merge!(config['server'], @additional_config)
185
+ @ssl ? @ssl.config : {},
186
+
187
+ {
188
+ 'location /' => {
189
+ raw: """
190
+ if ($host !~ ^#{hosts_subdomains_regex}$) { return 404; }
191
+
192
+ set $r 0;
193
+ set $s $scheme;
194
+ set $h $host;
195
+ set $port #{@ssl ? '443' : '80'};
196
+ set $p '';
197
+ set $u '';
198
+ set $q '';
199
+
200
+ #{if @www.nil? && @one_host == false
201
+ nil
202
+ elsif @www.nil? && @one_host == true
203
+ "if ($host !~ ^(www.)?#{host_subdomains_regex}$) { set $h $1#{@host}; set $r 1; }"
204
+ elsif @www == false && @one_host == false
205
+ "if ($host ~ ^www.(.+)$) { set $h $1; set $r 1; }"
206
+ elsif @www == false && @one_host == true
207
+ "if ($host !~ ^#{host_subdomains_regex}$) { set $h #{@host}; set $r 1; }"
208
+ elsif @www == true && @one_host == false
209
+ "if ($host !~ ^www.(.+)$) { set $h $1; set $r 1; }"
210
+ elsif @www == true && @one_host == true
211
+ "if ($host !~ ^www.#{host_subdomains_regex}$) { set $h www.#{@host}; set $r 1; }"
212
+ end}
213
+
214
+ if ($scheme = #{@other_scheme}) { set $s #{@scheme}; set $r 1; }
215
+ if ($http_host ~ :([0-9]+)$) { set $p :$1; set $port $1; }
216
+ if ($request_uri ~ ^([^\\?]+)(\\?+.*)?$) { set $u $1; set $q $2; }
217
+
218
+ #{'if ($u ~ //) { set $u $uri; set $r 1; }' if @multiple_slashes == false}
219
+ #{'if ($q ~ ^\?\?+(.*)$) { set $q ?$1; set $r 1; }' if @multiple_question_marks == false}
220
+
221
+ #{if @trailing_question_mark == false
222
+ 'if ($q ~ \?+$) { set $q \'\'; set $r 1; }'
223
+ elsif @trailing_question_mark == true
224
+ 'if ($q !~ .) { set $q ?; set $r 1; }'
225
+ end}
226
+ #{if @trailing_slash == false
227
+ 'if ($u ~ (.+?)/+$) { set $u $1; set $r 1; }'
228
+ elsif @trailing_slash == true
229
+ 'if ($u ~ [^/]$) { set $u $u/; set $r 1; }'
230
+ end}
231
+
232
+ set $mr $request_method$r;
233
+
234
+ if ($mr ~ ^(GET|HEAD)1$) { return 301 $s://$h$p$u$q; }
235
+ if ($mr ~ 1$) { return 308 $s://$h$p$u$q; }
236
+ """.strip.gsub(/^ +/, '').gsub(/\n{3,}/, "\n\n"),
237
+
238
+ 'proxy_pass' => "http://#{@host}",
239
+ 'proxy_redirect' => 'off',
240
+ 'proxy_set_header' => {
241
+ repeat: true,
242
+ 'Host' => '$http_host',
243
+ 'X-Real-IP' => '$remote_addr',
244
+ 'X-Forwarded-For' => '$proxy_add_x_forwarded_for',
245
+ 'X-Forwarded-Proto' => @ssl ? 'https' : 'http',
246
+ 'X-Forwarded-Port' => '$port',
247
+ },
248
+ },
249
+ },
250
+
251
+ @additional_config,
252
+ )
253
+ }
170
254
 
171
255
  config
172
256
  end
173
257
 
258
+ ##
259
+ # Writes the Nginx configuration to the (inactive) configuration file.
260
+ #
174
261
  def write_config
175
262
  File.open(@config_file_inactive, 'w') do |file|
176
- file.write(self.class.to_nginx_config(config))
263
+ file.write(config_file_content)
177
264
  end
178
265
  end
179
266
 
267
+ ##
268
+ # Renames the inactive Nginx configuration file to its active name.
269
+ #
180
270
  def activate_config
181
271
  FileUtils.mv(@config_file_inactive, @config_file_active)
182
272
  end
183
273
 
274
+ ##
275
+ # Renames the active Nginx configuration file to its inactive name.
276
+ #
184
277
  def deactivate_config
185
278
  FileUtils.mv(@config_file_active, @config_file_inactive) if File.exists?(@config_file_active)
186
279
  end
187
280
 
281
+ ##
282
+ # Ensures hosts are mapped to localhost in the system /etc/hosts file. Useful in development. Uses sudo
283
+ # to perform the write, which will prompt for the system user's password.
284
+ #
188
285
  def ensure_host_entries
189
286
  content = File.read('/etc/hosts')
190
287
  missing_entries = (@hosts + @subdomains).each_with_object([]) do |h, a|
@@ -204,6 +301,11 @@ module Potluck
204
301
  )
205
302
  end
206
303
 
304
+ ##
305
+ # Ensures Nginx's base configuration file contains an include statement for Potluck's Nginx
306
+ # configuration files. Sudo is not used, so Nginx's base configuration file must be writable by the
307
+ # system user running this Ruby process.
308
+ #
207
309
  def ensure_include
208
310
  config_file = `nginx -t 2>&1`[TEST_CONFIG_REGEX, :config]
209
311
  config_content = File.read(config_file)
@@ -214,6 +316,57 @@ module Potluck
214
316
  end
215
317
  end
216
318
 
319
+ ##
320
+ # Converts a hash to an Nginx configuration file content string. Keys should be strings and values
321
+ # either strings or hashes. A +nil+ value in a hash will result in that key-value pair being omitted.
322
+ #
323
+ # * +hash+ - Hash to convert to the string content of an Nginx configuration file.
324
+ # * +indent+ - Number of spaces to indent; used when the method is called recursively and should not be
325
+ # set explicitly (optional, default: 0).
326
+ # * +repeat+ - Value to prepend to each entry of the hash; used when the method is called recursively
327
+ # and should not be set explicitly (optional).
328
+ #
329
+ # Symbol keys in hashes are used as special directives. Including <tt>repeat: true</tt> will cause the
330
+ # parent hash's key for the child hash to be prefixed to each line of the output. Example:
331
+ #
332
+ # {
333
+ # # ...
334
+ #
335
+ # 'add_header' => {
336
+ # repeat: true,
337
+ # 'X-Frame-Options' => 'DENY',
338
+ # 'X-Content-Type-Options' => 'nosniff',
339
+ # }
340
+ # }
341
+ #
342
+ # Result:
343
+ #
344
+ # # ...
345
+ #
346
+ # add_header X-Frame-Options DENY;
347
+ # add_header X-Content-Type-Options nosniff;
348
+ #
349
+ # A hash containing <tt>raw: '...'</tt> can be used to include a raw chunk of text rather than key-value
350
+ # pairs. Example:
351
+ #
352
+ # {
353
+ # # ...
354
+ #
355
+ # 'location /' => {
356
+ # raw: """
357
+ # if ($scheme = https) { ... }
358
+ # if ($host ~ ^www.) { ... }
359
+ # """,
360
+ # }
361
+ # }
362
+ #
363
+ # Result:
364
+ #
365
+ # location / {
366
+ # if ($scheme = https) { ... }
367
+ # if ($host ~ ^www.) { ... }
368
+ # }
369
+ #
217
370
  def self.to_nginx_config(hash, indent: 0, repeat: nil)
218
371
  hash.each_with_object(+'') do |(k, v), config|
219
372
  next if v.nil?
@@ -235,6 +388,9 @@ module Potluck
235
388
  end
236
389
  end
237
390
 
391
+ ##
392
+ # Content of the launchctl plist file.
393
+ #
238
394
  def self.plist
239
395
  super(
240
396
  <<~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.1
4
+ version: 0.0.5
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-03-27 00:00:00.000000000 Z
11
+ date: 2021-12-31 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.1
19
+ version: 0.0.5
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.1
26
+ version: 0.0.5
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.