potluck-nginx 0.0.1 → 0.0.5

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: 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.