potluck-nginx 0.0.2 → 0.0.6
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 +4 -4
- data/LICENSE +1 -1
- data/lib/potluck/nginx/ssl.rb +32 -10
- data/lib/potluck/nginx/util.rb +32 -4
- data/lib/potluck/nginx.rb +282 -126
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27f10a23324844c7c0e3733f2ce1d7d48c0fd4fd61d0444e45f7db62a3a4d58a
|
4
|
+
data.tar.gz: '094a492be3e1efa5c5919f14111d03b86de1ab809e8482635b313671d658bb12'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9216e7234a2ccfcb0ae3589b517b76b2bc4ba7800cd73cd9fc5330b516511ab0ad41ae80b0daf3266dada155dd1e8005866cccbab1a28afabdd6a2473d41a62f
|
7
|
+
data.tar.gz: 5bce05141ee5156f2cf1b9d83d062bb9c4fbea5226a8b666ecce8dbb54e5d0bd273e027c014e847a9f24cf11c22ad77a4bc68c733b8e9f9f56908e279382a0fc
|
data/LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright 2021 Nate Pickens
|
1
|
+
Copyright 2021-2022 Nate Pickens
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
4
4
|
documentation files (the "Software"), to deal in the Software without restriction, including without
|
data/lib/potluck/nginx/ssl.rb
CHANGED
@@ -3,7 +3,11 @@
|
|
3
3
|
require('time')
|
4
4
|
|
5
5
|
module Potluck
|
6
|
-
class Nginx <
|
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
|
-
|
30
|
-
|
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
|
@@ -35,7 +49,7 @@ module Potluck
|
|
35
49
|
@auto_generated = !crt_file && !key_file && !dhparam_file
|
36
50
|
|
37
51
|
if !@auto_generated && (!crt_file || !key_file || !dhparam_file)
|
38
|
-
raise('Must supply values for all three or none: crt_file, key_file, dhparam_file')
|
52
|
+
raise(ArgumentError, 'Must supply values for all three or none: crt_file, key_file, dhparam_file')
|
39
53
|
end
|
40
54
|
|
41
55
|
@csr_file = File.join(@dir, "#{@host}.csr").freeze
|
@@ -43,15 +57,20 @@ module Potluck
|
|
43
57
|
@key_file = key_file || File.join(@dir, "#{@host}.key").freeze
|
44
58
|
@dhparam_file = dhparam_file || File.join(@dir, 'dhparam.pem').freeze
|
45
59
|
|
46
|
-
@config = {
|
60
|
+
@config = Util.deep_merge({
|
47
61
|
'ssl_certificate' => @crt_file,
|
48
62
|
'ssl_certificate_key' => @key_file,
|
49
63
|
'ssl_dhparam' => @dhparam_file,
|
50
64
|
'ssl_stapling' => ('on' unless @auto_generated),
|
51
65
|
'ssl_stapling_verify' => ('on' unless @auto_generated),
|
52
|
-
}
|
66
|
+
}, DEFAULT_CONFIG, 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) &&
|
@@ -65,13 +84,13 @@ module Potluck
|
|
65
84
|
|
66
85
|
@nginx.log('Generating SSL files...')
|
67
86
|
|
68
|
-
@nginx.run("openssl genrsa -out #{@key_file} 4096",
|
87
|
+
@nginx.run("openssl genrsa -out #{@key_file} 4096", capture_stderr: false)
|
69
88
|
@nginx.run("openssl req -out #{@csr_file} -key #{@key_file} -new -sha256 -config /dev/stdin <<< "\
|
70
|
-
"'#{openssl_config}'",
|
89
|
+
"'#{openssl_config}'", capture_stderr: false)
|
71
90
|
@nginx.run("openssl x509 -in #{@csr_file} -out #{@crt_file} -signkey #{@key_file} -days "\
|
72
91
|
"#{CERT_DAYS} -req -sha256 -extensions req_ext -extfile /dev/stdin <<< '#{openssl_config}'",
|
73
|
-
|
74
|
-
@nginx.run("openssl dhparam -out #{@dhparam_file} 2048",
|
92
|
+
capture_stderr: false)
|
93
|
+
@nginx.run("openssl dhparam -out #{@dhparam_file} 2048", capture_stderr: false)
|
75
94
|
|
76
95
|
if IS_MACOS
|
77
96
|
@nginx.log('Adding cert to keychain...')
|
@@ -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 ]
|
data/lib/potluck/nginx/util.rb
CHANGED
@@ -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
|
-
|
7
|
-
|
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
|
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
|
-
|
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: {},
|
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) && !self.class.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,136 @@ 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
|
+
|
141
|
+
##
|
142
|
+
# Content of the launchctl plist file.
|
143
|
+
#
|
144
|
+
def self.plist
|
145
|
+
super(
|
146
|
+
<<~EOS
|
147
|
+
<key>ProgramArguments</key>
|
148
|
+
<array>
|
149
|
+
<string>/usr/local/opt/nginx/bin/nginx</string>
|
150
|
+
<string>-g</string>
|
151
|
+
<string>daemon off;</string>
|
152
|
+
</array>
|
153
|
+
<key>StandardOutPath</key>
|
154
|
+
<string>/usr/local/var/log/nginx/access.log</string>
|
155
|
+
<key>StandardErrorPath</key>
|
156
|
+
<string>/usr/local/var/log/nginx/error.log</string>
|
157
|
+
EOS
|
158
|
+
)
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# Converts a hash to an Nginx configuration file content string. Keys should be strings and values
|
163
|
+
# either strings or hashes. A +nil+ value in a hash will result in that key-value pair being omitted.
|
164
|
+
#
|
165
|
+
# * +hash+ - Hash to convert to the string content of an Nginx configuration file.
|
166
|
+
# * +indent+ - Number of spaces to indent; used when the method is called recursively and should not be
|
167
|
+
# set explicitly (optional, default: 0).
|
168
|
+
# * +repeat+ - Value to prepend to each entry of the hash; used when the method is called recursively
|
169
|
+
# and should not be set explicitly (optional).
|
170
|
+
#
|
171
|
+
# Symbol keys in hashes are used as special directives. Including <tt>repeat: true</tt> will cause the
|
172
|
+
# parent hash's key for the child hash to be prefixed to each line of the output. Example:
|
173
|
+
#
|
174
|
+
# {
|
175
|
+
# # ...
|
176
|
+
#
|
177
|
+
# 'add_header' => {
|
178
|
+
# repeat: true,
|
179
|
+
# 'X-Frame-Options' => 'DENY',
|
180
|
+
# 'X-Content-Type-Options' => 'nosniff',
|
181
|
+
# }
|
182
|
+
# }
|
183
|
+
#
|
184
|
+
# Result:
|
185
|
+
#
|
186
|
+
# # ...
|
187
|
+
#
|
188
|
+
# add_header X-Frame-Options DENY;
|
189
|
+
# add_header X-Content-Type-Options nosniff;
|
190
|
+
#
|
191
|
+
# A hash containing <tt>raw: '...'</tt> can be used to include a raw chunk of text rather than key-value
|
192
|
+
# pairs. Example:
|
193
|
+
#
|
194
|
+
# {
|
195
|
+
# # ...
|
196
|
+
#
|
197
|
+
# 'location /' => {
|
198
|
+
# raw: """
|
199
|
+
# if ($scheme = https) { ... }
|
200
|
+
# if ($host ~ ^www.) { ... }
|
201
|
+
# """,
|
202
|
+
# }
|
203
|
+
# }
|
204
|
+
#
|
205
|
+
# Result:
|
206
|
+
#
|
207
|
+
# location / {
|
208
|
+
# if ($scheme = https) { ... }
|
209
|
+
# if ($host ~ ^www.) { ... }
|
210
|
+
# }
|
211
|
+
#
|
212
|
+
def self.to_nginx_config(hash, indent: 0, repeat: nil)
|
213
|
+
hash.each_with_object(+'') do |(k, v), config|
|
214
|
+
next if v.nil?
|
215
|
+
next if k == :repeat
|
216
|
+
|
217
|
+
config << (
|
218
|
+
if v.kind_of?(Hash)
|
219
|
+
if v[:repeat]
|
220
|
+
to_nginx_config(v, indent: indent, repeat: k)
|
221
|
+
else
|
222
|
+
"#{' ' * indent}#{k} {\n#{to_nginx_config(v, indent: indent + 2)}#{' ' * indent}}\n"
|
223
|
+
end
|
224
|
+
elsif k == :raw
|
225
|
+
"#{v.gsub(/^(?=.)/, ' ' * indent)}\n\n"
|
226
|
+
else
|
227
|
+
"#{' ' * indent}#{"#{repeat} " if repeat}#{k}#{" #{v}" unless v == true};\n"
|
228
|
+
end
|
229
|
+
)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
70
233
|
private
|
71
234
|
|
235
|
+
##
|
236
|
+
# Returns a hash representation of the Nginx configuration file content. Any configuration passed to
|
237
|
+
# Nginx.new is deep-merged into a base configuration hash, meaning nested hashes are merged rather than
|
238
|
+
# overwritten (see Util.deep_merge).
|
239
|
+
#
|
72
240
|
def config
|
73
241
|
host_subdomains_regex = ([@host] + @subdomains).join('|')
|
74
242
|
hosts_subdomains_regex = (@hosts + @subdomains).join('|')
|
@@ -78,113 +246,134 @@ module Potluck
|
|
78
246
|
'server' => "127.0.0.1:#{@port}",
|
79
247
|
},
|
80
248
|
|
81
|
-
'server' => Util.deep_merge
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
249
|
+
'server' => Util.deep_merge(
|
250
|
+
{
|
251
|
+
'charset' => 'UTF-8',
|
252
|
+
'access_log' => File.join(@dir, 'nginx-access.log'),
|
253
|
+
'error_log' => File.join(@dir, 'nginx-error.log'),
|
254
|
+
|
255
|
+
'listen' => {
|
256
|
+
repeat: true,
|
257
|
+
'8080' => true,
|
258
|
+
'[::]:8080' => true,
|
259
|
+
'4433 ssl http2' => @ssl ? true : nil,
|
260
|
+
'[::]:4433 ssl http2' => @ssl ? true : nil,
|
261
|
+
},
|
262
|
+
'server_name' => (@hosts + @subdomains).join(' '),
|
94
263
|
|
95
|
-
|
96
|
-
|
264
|
+
'gzip' => 'on',
|
265
|
+
'gzip_types' => 'application/javascript application/json application/xml text/css '\
|
266
|
+
'text/javascript text/plain',
|
97
267
|
|
98
|
-
|
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' => {
|
268
|
+
'add_header' => {
|
158
269
|
repeat: true,
|
159
|
-
'
|
160
|
-
'X-
|
161
|
-
'X-
|
162
|
-
'X-
|
163
|
-
|
270
|
+
'Referrer-Policy' => '\'same-origin\' always',
|
271
|
+
'X-Frame-Options' => '\'DENY\' always',
|
272
|
+
'X-XSS-Protection' => '\'1; mode=block\' always',
|
273
|
+
'X-Content-Type-Options' => '\'nosniff\' always',
|
274
|
+
},
|
275
|
+
},
|
276
|
+
|
277
|
+
@ssl ? @ssl.config : {},
|
278
|
+
|
279
|
+
{
|
280
|
+
'location /' => {
|
281
|
+
raw: """
|
282
|
+
if ($host !~ ^#{hosts_subdomains_regex}$) { return 404; }
|
283
|
+
|
284
|
+
set $r 0;
|
285
|
+
set $s $scheme;
|
286
|
+
set $h $host;
|
287
|
+
set $port #{@ssl ? '443' : '80'};
|
288
|
+
set $p '';
|
289
|
+
set $u '';
|
290
|
+
set $q '';
|
291
|
+
|
292
|
+
#{if @www.nil? && @one_host == false
|
293
|
+
nil
|
294
|
+
elsif @www.nil? && @one_host == true
|
295
|
+
"if ($host !~ ^(www.)?#{host_subdomains_regex}$) { set $h $1#{@host}; set $r 1; }"
|
296
|
+
elsif @www == false && @one_host == false
|
297
|
+
"if ($host ~ ^www.(.+)$) { set $h $1; set $r 1; }"
|
298
|
+
elsif @www == false && @one_host == true
|
299
|
+
"if ($host !~ ^#{host_subdomains_regex}$) { set $h #{@host}; set $r 1; }"
|
300
|
+
elsif @www == true && @one_host == false
|
301
|
+
"if ($host !~ ^www.(.+)$) { set $h $1; set $r 1; }"
|
302
|
+
elsif @www == true && @one_host == true
|
303
|
+
"if ($host !~ ^www.#{host_subdomains_regex}$) { set $h www.#{@host}; set $r 1; }"
|
304
|
+
end}
|
305
|
+
|
306
|
+
if ($scheme = #{@other_scheme}) { set $s #{@scheme}; set $r 1; }
|
307
|
+
if ($http_host ~ :([0-9]+)$) { set $p :$1; set $port $1; }
|
308
|
+
if ($request_uri ~ ^([^\\?]+)(\\?+.*)?$) { set $u $1; set $q $2; }
|
309
|
+
|
310
|
+
#{'if ($u ~ //) { set $u $uri; set $r 1; }' if @multiple_slashes == false}
|
311
|
+
#{'if ($q ~ ^\?\?+(.*)$) { set $q ?$1; set $r 1; }' if @multiple_question_marks == false}
|
312
|
+
|
313
|
+
#{if @trailing_question_mark == false
|
314
|
+
'if ($q ~ \?+$) { set $q \'\'; set $r 1; }'
|
315
|
+
elsif @trailing_question_mark == true
|
316
|
+
'if ($q !~ .) { set $q ?; set $r 1; }'
|
317
|
+
end}
|
318
|
+
#{if @trailing_slash == false
|
319
|
+
'if ($u ~ (.+?)/+$) { set $u $1; set $r 1; }'
|
320
|
+
elsif @trailing_slash == true
|
321
|
+
'if ($u ~ [^/]$) { set $u $u/; set $r 1; }'
|
322
|
+
end}
|
323
|
+
|
324
|
+
set $mr $request_method$r;
|
325
|
+
|
326
|
+
if ($mr ~ ^(GET|HEAD)1$) { return 301 $s://$h$p$u$q; }
|
327
|
+
if ($mr ~ 1$) { return 308 $s://$h$p$u$q; }
|
328
|
+
""".strip.gsub(/^ +/, '').gsub(/\n{3,}/, "\n\n"),
|
329
|
+
|
330
|
+
'proxy_pass' => "http://#{@host}",
|
331
|
+
'proxy_redirect' => 'off',
|
332
|
+
'proxy_set_header' => {
|
333
|
+
repeat: true,
|
334
|
+
'Host' => '$http_host',
|
335
|
+
'X-Real-IP' => '$remote_addr',
|
336
|
+
'X-Forwarded-For' => '$proxy_add_x_forwarded_for',
|
337
|
+
'X-Forwarded-Proto' => @ssl ? 'https' : 'http',
|
338
|
+
'X-Forwarded-Port' => '$port',
|
339
|
+
},
|
164
340
|
},
|
165
341
|
},
|
166
|
-
),
|
167
|
-
}
|
168
342
|
|
169
|
-
|
343
|
+
@additional_config,
|
344
|
+
)
|
345
|
+
}
|
170
346
|
|
171
347
|
config
|
172
348
|
end
|
173
349
|
|
350
|
+
##
|
351
|
+
# Writes the Nginx configuration to the (inactive) configuration file.
|
352
|
+
#
|
174
353
|
def write_config
|
175
354
|
File.open(@config_file_inactive, 'w') do |file|
|
176
|
-
file.write(
|
355
|
+
file.write(config_file_content)
|
177
356
|
end
|
178
357
|
end
|
179
358
|
|
359
|
+
##
|
360
|
+
# Renames the inactive Nginx configuration file to its active name.
|
361
|
+
#
|
180
362
|
def activate_config
|
181
363
|
FileUtils.mv(@config_file_inactive, @config_file_active)
|
182
364
|
end
|
183
365
|
|
366
|
+
##
|
367
|
+
# Renames the active Nginx configuration file to its inactive name.
|
368
|
+
#
|
184
369
|
def deactivate_config
|
185
370
|
FileUtils.mv(@config_file_active, @config_file_inactive) if File.exists?(@config_file_active)
|
186
371
|
end
|
187
372
|
|
373
|
+
##
|
374
|
+
# Ensures hosts are mapped to localhost in the system /etc/hosts file. Useful in development. Uses sudo
|
375
|
+
# to perform the write, which will prompt for the system user's password.
|
376
|
+
#
|
188
377
|
def ensure_host_entries
|
189
378
|
content = File.read('/etc/hosts')
|
190
379
|
missing_entries = (@hosts + @subdomains).each_with_object([]) do |h, a|
|
@@ -204,6 +393,11 @@ module Potluck
|
|
204
393
|
)
|
205
394
|
end
|
206
395
|
|
396
|
+
##
|
397
|
+
# Ensures Nginx's base configuration file contains an include statement for Potluck's Nginx
|
398
|
+
# configuration files. Sudo is not used, so Nginx's base configuration file must be writable by the
|
399
|
+
# system user running this Ruby process.
|
400
|
+
#
|
207
401
|
def ensure_include
|
208
402
|
config_file = `nginx -t 2>&1`[TEST_CONFIG_REGEX, :config]
|
209
403
|
config_content = File.read(config_file)
|
@@ -213,43 +407,5 @@ module Potluck
|
|
213
407
|
"\\1\\2\\3include #{ACTIVE_CONFIG_PATTERN};\n\n\\3"))
|
214
408
|
end
|
215
409
|
end
|
216
|
-
|
217
|
-
def self.to_nginx_config(hash, indent: 0, repeat: nil)
|
218
|
-
hash.each_with_object(+'') do |(k, v), config|
|
219
|
-
next if v.nil?
|
220
|
-
next if k == :repeat
|
221
|
-
|
222
|
-
config << (
|
223
|
-
if v.kind_of?(Hash)
|
224
|
-
if v[:repeat]
|
225
|
-
to_nginx_config(v, indent: indent, repeat: k)
|
226
|
-
else
|
227
|
-
"#{' ' * indent}#{k} {\n#{to_nginx_config(v, indent: indent + 2)}#{' ' * indent}}\n"
|
228
|
-
end
|
229
|
-
elsif k == :raw
|
230
|
-
"#{v.gsub(/^(?=.)/, ' ' * indent)}\n\n"
|
231
|
-
else
|
232
|
-
"#{' ' * indent}#{"#{repeat} " if repeat}#{k}#{" #{v}" unless v == true};\n"
|
233
|
-
end
|
234
|
-
)
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
def self.plist
|
239
|
-
super(
|
240
|
-
<<~EOS
|
241
|
-
<key>ProgramArguments</key>
|
242
|
-
<array>
|
243
|
-
<string>/usr/local/opt/nginx/bin/nginx</string>
|
244
|
-
<string>-g</string>
|
245
|
-
<string>daemon off;</string>
|
246
|
-
</array>
|
247
|
-
<key>StandardOutPath</key>
|
248
|
-
<string>/usr/local/var/log/nginx/access.log</string>
|
249
|
-
<key>StandardErrorPath</key>
|
250
|
-
<string>/usr/local/var/log/nginx/error.log</string>
|
251
|
-
EOS
|
252
|
-
)
|
253
|
-
end
|
254
410
|
end
|
255
411
|
end
|
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.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nate Pickens
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-21 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.
|
19
|
+
version: 0.0.6
|
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.
|
26
|
+
version: 0.0.6
|
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.
|
95
|
+
rubygems_version: 3.2.32
|
96
96
|
signing_key:
|
97
97
|
specification_version: 4
|
98
98
|
summary: A Ruby manager for Nginx.
|