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 +4 -4
- data/lib/potluck/nginx/ssl.rb +37 -12
- data/lib/potluck/nginx/util.rb +32 -4
- data/lib/potluck/nginx.rb +244 -88
- 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: b5dc3bb7d62e4244f719abe5110cdb80a972b10f56f13d35844918a3eaa875df
|
|
4
|
+
data.tar.gz: b971afa507788f9bf2261df078e7df83df963928dba62dcca2e1cbf80a10cd6a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d6b3f68bf12bce2e2035689d9c07bd76af04d0f5c8ad596c1dfafb2c5faca4551e9a9d8c64e51fd6fb614f2b7e5dc4746a8f36137b5f416ecd78dffab961c6db
|
|
7
|
+
data.tar.gz: 3885228d15374b68b7af5d86050aea5bab688e4de24fa4cb18a54599bdece7baa0e6495487b707547be70646eefe4099184b69c60128370151b069153f5b2136
|
data/lib/potluck/nginx/ssl.rb
CHANGED
|
@@ -3,16 +3,22 @@
|
|
|
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 = {
|
|
10
|
-
'ssl_ciphers' => '
|
|
11
|
-
|
|
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:
|
|
14
|
-
'ssl_session_tickets' => '
|
|
15
|
-
'ssl_session_timeout' => '
|
|
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
|
-
|
|
28
|
-
|
|
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,
|
|
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
|
-
}
|
|
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 ]
|
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) && !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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
96
|
-
|
|
172
|
+
'gzip' => 'on',
|
|
173
|
+
'gzip_types' => 'application/javascript application/json application/xml text/css '\
|
|
174
|
+
'text/javascript text/plain',
|
|
97
175
|
|
|
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' => {
|
|
176
|
+
'add_header' => {
|
|
158
177
|
repeat: true,
|
|
159
|
-
'
|
|
160
|
-
'X-
|
|
161
|
-
'X-
|
|
162
|
-
'X-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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-
|
|
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.
|
|
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.
|
|
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.
|
|
95
|
+
rubygems_version: 3.2.32
|
|
96
96
|
signing_key:
|
|
97
97
|
specification_version: 4
|
|
98
98
|
summary: A Ruby manager for Nginx.
|