shared-infrastructure 0.0.2
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 +7 -0
- data/bin/create-rails-app +7 -0
- data/bin/create-reverse-proxy +7 -0
- data/bin/create-server-block +7 -0
- data/lib/shared_infrastructure.rb +16 -0
- data/lib/shared_infrastructure/nginx/builder.rb +199 -0
- data/lib/shared_infrastructure/nginx/lines.rb +19 -0
- data/lib/shared_infrastructure/nginx/listen.rb +66 -0
- data/lib/shared_infrastructure/nginx/location.rb +98 -0
- data/lib/shared_infrastructure/nginx/nginx.rb +100 -0
- data/lib/shared_infrastructure/nginx/server.rb +53 -0
- data/lib/shared_infrastructure/nginx/server_block.rb +83 -0
- data/lib/shared_infrastructure/nginx/site.rb +29 -0
- data/lib/shared_infrastructure/runner/base.rb +110 -0
- data/lib/shared_infrastructure/runner/reverse_proxy.rb +36 -0
- data/lib/shared_infrastructure/runner/static_site.rb +20 -0
- data/lib/shared_infrastructure/systemd/rails.rb +72 -0
- data/lib/shared_infrastructure/systemd/systemd.rb +31 -0
- metadata +65 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 54a7741f3afa9a30ed79a1b175e2b311ac336094
|
4
|
+
data.tar.gz: fe517bf7ea380c63501d80e2c600eda30a225561
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 24eb0cb374861a315bfe55d73aed35d2bbf9780208eb0a917ad6ca41531804884434ea909280b676f2cac8fdb832293603a2f3331f88b2b5f5d48a3b3c06ff95
|
7
|
+
data.tar.gz: a22e5af7d7dd39576f97e80d458d299d8c61a1cb7429d0b974514bf1b5cdbd26d328834a3be155a67afe2613d4450a34ee67094a05348353899ebc7143c4c75d
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "shared_infrastructure/nginx/nginx.rb"
|
4
|
+
require "shared_infrastructure/nginx/server_block.rb"
|
5
|
+
require "shared_infrastructure/nginx/server.rb"
|
6
|
+
require "shared_infrastructure/nginx/lines.rb"
|
7
|
+
require "shared_infrastructure/nginx/listen.rb"
|
8
|
+
require "shared_infrastructure/nginx/location.rb"
|
9
|
+
require "shared_infrastructure/nginx/upstream.rb"
|
10
|
+
require "shared_infrastructure/nginx/site.rb"
|
11
|
+
require "shared_infrastructure/nginx/builder.rb"
|
12
|
+
require "shared_infrastructure/runner/base.rb"
|
13
|
+
require "shared_infrastructure/runner/reverse_proxy.rb"
|
14
|
+
require "shared_infrastructure/runner/static_site.rb"
|
15
|
+
require "shared_infrastructure/systemd/systemd.rb"
|
16
|
+
require "shared_infrastructure/systemd/rails.rb"
|
@@ -0,0 +1,199 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nginx
|
4
|
+
##
|
5
|
+
# Builders.
|
6
|
+
# Builders build different files.
|
7
|
+
module Builder
|
8
|
+
module Https
|
9
|
+
def save
|
10
|
+
`openssl dhparam #{Nginx.dhparam} -out #{Nginx.certificate_directory(certificate_domain)}/dhparam.pem`
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Base
|
16
|
+
def https_reminder_message
|
17
|
+
puts %(You have to obtain a certificate and enable TLS for the site.
|
18
|
+
To do so, reload the Nginx configuration:
|
19
|
+
|
20
|
+
sudo nginx -s reload
|
21
|
+
|
22
|
+
Then run the following command:
|
23
|
+
|
24
|
+
sudo certbot certonly --webroot -w #{Nginx.root_directory(domain_name)} #{Nginx.certbot_domain_names(domain_name)}
|
25
|
+
|
26
|
+
You can test renewal with:
|
27
|
+
|
28
|
+
sudo certbot renew --dry-run
|
29
|
+
|
30
|
+
Finally, re-run this script to configure nginx for TLS.
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(domain_name, *server_blocks)
|
35
|
+
# puts "Base#initialize domain_name: #{domain_name}"
|
36
|
+
# puts "Base#initialize server_blocks.inspect: #{server_blocks.inspect}"
|
37
|
+
@server_blocks = server_blocks
|
38
|
+
@domain_name = domain_name
|
39
|
+
end
|
40
|
+
|
41
|
+
def save
|
42
|
+
File.open(Nginx.server_block_location(domain_name), "w") do |f|
|
43
|
+
f << to_s
|
44
|
+
end
|
45
|
+
`ln -fs ../sites-available/#{domain_name} #{Nginx.enabled_server_block_location(domain_name)}`
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
server_blocks.map(&:to_s).join("\n")
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_reader :domain_name, :server_blocks
|
53
|
+
end
|
54
|
+
|
55
|
+
class ReverseProxyHttp < Base
|
56
|
+
def initialize(domain_name, proxy_url, _certificate_domain = nil)
|
57
|
+
super(domain_name,
|
58
|
+
Nginx::ServerBlock.new(
|
59
|
+
server: Nginx::Server.new(domain_name),
|
60
|
+
listen: Nginx::ListenHttp.new,
|
61
|
+
location: Nginx::ReverseProxyLocation.new(proxy_url)
|
62
|
+
)
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
def save
|
67
|
+
result = super
|
68
|
+
https_reminder_message
|
69
|
+
result
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class ReverseProxyHttps < Base
|
74
|
+
include Https
|
75
|
+
|
76
|
+
def initialize(domain_name, proxy_url, certificate_domain = nil)
|
77
|
+
@certificate_domain = certificate_domain || domain_name
|
78
|
+
|
79
|
+
super(domain_name,
|
80
|
+
Nginx::ServerBlock.new(
|
81
|
+
server: Nginx::Server.new(domain_name),
|
82
|
+
listen: Nginx::ListenHttps.new(domain_name, certificate_domain),
|
83
|
+
location: Nginx::ReverseProxyLocation.new(proxy_url)
|
84
|
+
),
|
85
|
+
Nginx::TlsRedirectServerBlock.new(domain_name)
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
attr_reader :certificate_domain
|
90
|
+
end
|
91
|
+
|
92
|
+
class Site < Base
|
93
|
+
def initialize(domain_name, user, *server_blocks)
|
94
|
+
super(domain_name, *server_blocks)
|
95
|
+
@user = user
|
96
|
+
end
|
97
|
+
|
98
|
+
def save
|
99
|
+
FileUtils.mkdir_p(Nginx.root_directory(domain_name))
|
100
|
+
if Process.uid.zero?
|
101
|
+
FileUtils.chown(user,
|
102
|
+
"www-data",
|
103
|
+
Nginx.root_directory(domain_name))
|
104
|
+
end
|
105
|
+
super
|
106
|
+
end
|
107
|
+
|
108
|
+
attr_reader :user
|
109
|
+
end
|
110
|
+
|
111
|
+
class SiteHttp < Site
|
112
|
+
def initialize(domain_name, user, _certificate_domain = nil)
|
113
|
+
super(domain_name,
|
114
|
+
user,
|
115
|
+
Nginx::StaticServerBlock.new(
|
116
|
+
server: Nginx::Site.new(domain_name, user),
|
117
|
+
listen: Nginx::ListenHttp.new,
|
118
|
+
location: Nginx::Location.new
|
119
|
+
)
|
120
|
+
)
|
121
|
+
end
|
122
|
+
|
123
|
+
def save
|
124
|
+
result = super
|
125
|
+
https_reminder_message
|
126
|
+
result
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class SiteHttps < Site
|
131
|
+
include Https
|
132
|
+
|
133
|
+
def initialize(domain_name, user, certificate_domain = nil)
|
134
|
+
@certificate_domain = certificate_domain || domain_name
|
135
|
+
|
136
|
+
super(domain_name,
|
137
|
+
user,
|
138
|
+
Nginx::StaticServerBlock.new(
|
139
|
+
server: Nginx::Site.new(domain_name, user),
|
140
|
+
listen: Nginx::ListenHttps.new(domain_name, certificate_domain),
|
141
|
+
location: Nginx::Location.new
|
142
|
+
),
|
143
|
+
Nginx::TlsRedirectServerBlock.new(domain_name)
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
attr_reader :certificate_domain
|
148
|
+
end
|
149
|
+
|
150
|
+
class RailsHttp < Site
|
151
|
+
def initialize(domain_name, user, _certificate_domain = nil)
|
152
|
+
super(domain_name,
|
153
|
+
user,
|
154
|
+
Nginx::RailsServerBlock.new(
|
155
|
+
upstream: Nginx::Upstream.new(domain_name),
|
156
|
+
server: Nginx::RailsServer.new(domain_name),
|
157
|
+
listen: Nginx::ListenHttp.new,
|
158
|
+
location: [
|
159
|
+
Nginx::RailsLocation.new(domain_name),
|
160
|
+
Nginx::ActionCableLocation.new(domain_name)
|
161
|
+
]
|
162
|
+
)
|
163
|
+
)
|
164
|
+
end
|
165
|
+
|
166
|
+
def save
|
167
|
+
Systemd::Rails.write_unit_file(domain_name) && super
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
class RailsHttps < Site
|
172
|
+
include Https
|
173
|
+
|
174
|
+
def initialize(domain_name, user, _certificate_domain = nil)
|
175
|
+
@certificate_domain = certificate_domain || domain_name
|
176
|
+
super(domain_name,
|
177
|
+
user,
|
178
|
+
Nginx::RailsServerBlock.new(
|
179
|
+
upstream: Nginx::Upstream.new(domain_name),
|
180
|
+
server: Nginx::RailsServer.new(domain_name),
|
181
|
+
listen: Nginx::ListenHttps.new(domain_name, certificate_domain),
|
182
|
+
location: [
|
183
|
+
Nginx::RailsLocation.new(domain_name),
|
184
|
+
Nginx::ActionCableLocation.new(domain_name)
|
185
|
+
]
|
186
|
+
),
|
187
|
+
Nginx::TlsRedirectServerBlock.new(domain_name)
|
188
|
+
)
|
189
|
+
end
|
190
|
+
|
191
|
+
# FIXME: DRY this up with the HTTP class.
|
192
|
+
def save
|
193
|
+
Systemd::Rails.write_unit_file(domain_name) && super
|
194
|
+
end
|
195
|
+
|
196
|
+
attr_reader :certificate_domain
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Nginx
|
2
|
+
##
|
3
|
+
# A class to format lines nicely in a file.
|
4
|
+
class Lines < Array
|
5
|
+
def initialize(*lines)
|
6
|
+
@lines = Array(lines)
|
7
|
+
end
|
8
|
+
|
9
|
+
def format(level = 0)
|
10
|
+
@lines.map { |x| Lines.indent(x, level) }.join("\n")
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def indent(s, level = 0)
|
15
|
+
s.empty? ? s : (" " * level * 2) + s
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nginx
|
4
|
+
class Listen
|
5
|
+
def initialize(port)
|
6
|
+
@port = port
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s(level = 0)
|
10
|
+
Lines.new("listen #{port};", "listen [::]:#{port};").format(level)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
attr_reader :port
|
16
|
+
end
|
17
|
+
|
18
|
+
class ListenHttp < Listen
|
19
|
+
def initialize
|
20
|
+
super 80
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class ListenHttps < Listen
|
25
|
+
def initialize(domain_name, certificate_domain = nil)
|
26
|
+
@domain_name = domain_name
|
27
|
+
@certificate_domain = certificate_domain || domain_name
|
28
|
+
super 443
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s(level = 0)
|
32
|
+
Lines.new(
|
33
|
+
"# TLS config from: http://nginx.org/en/docs/http/configuring_https_servers.html",
|
34
|
+
"# HTTP2 doesn't require encryption, but at last reading, no browsers support",
|
35
|
+
"# HTTP2 without TLS, so only do http2 when we have TLS.",
|
36
|
+
"listen #{port} ssl http2;",
|
37
|
+
"listen [::]:#{port} ssl http2;",
|
38
|
+
"# Let's Encrypt file names and locations from: https://certbot.eff.org/docs/using.html#where-are-my-certificates",
|
39
|
+
"ssl_certificate_key #{Nginx.certificate_directory(certificate_domain)}/privkey.pem;",
|
40
|
+
"ssl_certificate #{Nginx.certificate_directory(certificate_domain)}/fullchain.pem;",
|
41
|
+
"",
|
42
|
+
"# Test the site using: https://www.ssllabs.com/ssltest/index.html",
|
43
|
+
"# Optimize TLS, from: https://www.bjornjohansen.no/optimizing-https-nginx, steps 1-3",
|
44
|
+
"ssl_session_cache shared:SSL:1m; # Enough for 4,000 sessions.",
|
45
|
+
"ssl_session_timeout 180m;",
|
46
|
+
"ssl_protocols TLSv1 TLSv1.1 TLSv1.2;",
|
47
|
+
"ssl_prefer_server_ciphers on;",
|
48
|
+
"ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;",
|
49
|
+
"# Step 4",
|
50
|
+
"ssl_dhparam #{Nginx.certificate_directory(certificate_domain)}/dhparam.pem;",
|
51
|
+
"# Step 5",
|
52
|
+
"ssl_stapling on;",
|
53
|
+
"ssl_stapling_verify on;",
|
54
|
+
"ssl_trusted_certificate #{Nginx.certificate_directory(certificate_domain)}/chain.pem;",
|
55
|
+
"resolver 8.8.8.8 8.8.4.4;",
|
56
|
+
"# Step 6 pin for a fortnight",
|
57
|
+
"add_header Strict-Transport-Security \"max-age=1209600\" always;",
|
58
|
+
"# Other steps TBD"
|
59
|
+
).format(level)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
attr_reader :certificate_domain, :domain_name
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nginx
|
4
|
+
class Location
|
5
|
+
def initialize(location = "/")
|
6
|
+
@location = location
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s(level = 0)
|
10
|
+
Lines.new("location #{location} {",
|
11
|
+
" try_files $uri $uri/ =404;",
|
12
|
+
"}").format(level)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :location
|
18
|
+
end
|
19
|
+
|
20
|
+
class ActionCableLocation < Location
|
21
|
+
def initialize(domain_name, location = "/cable")
|
22
|
+
super(location)
|
23
|
+
@domain_name = domain_name
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s(level = 0)
|
27
|
+
Lines.new("location #{location} {",
|
28
|
+
" proxy_pass http://#{domain_name};",
|
29
|
+
" proxy_http_version 1.1;",
|
30
|
+
" proxy_set_header Upgrade $http_upgrade;",
|
31
|
+
" proxy_set_header Connection \"upgrade\";",
|
32
|
+
"}").format(level)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_reader :domain_name
|
38
|
+
end
|
39
|
+
|
40
|
+
class RailsLocation
|
41
|
+
def initialize(domain_name)
|
42
|
+
@domain_name = domain_name
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s(level = 0)
|
46
|
+
Lines.new("location @#{domain_name} {",
|
47
|
+
" # A Rails app should force \"SSL\" so that it generates redirects to HTTPS,",
|
48
|
+
" # among other things.",
|
49
|
+
" # However, you want Nginx to handle the workload of TLS.",
|
50
|
+
" # The trick to proxying to a Rails app, therefore, is to proxy pass to HTTP,",
|
51
|
+
" # but set the header to HTTPS",
|
52
|
+
" # Next two lines.",
|
53
|
+
" proxy_pass http://#{domain_name};",
|
54
|
+
" proxy_set_header X-Forwarded-Proto $scheme; # $scheme says http or https",
|
55
|
+
" proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;",
|
56
|
+
" proxy_set_header Host $http_host;",
|
57
|
+
" proxy_redirect off;",
|
58
|
+
"}").format(level)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
attr_reader :domain_name
|
64
|
+
end
|
65
|
+
|
66
|
+
class ReverseProxyLocation < Location
|
67
|
+
def initialize(proxy_url, location = "/")
|
68
|
+
super location
|
69
|
+
@proxy_url = proxy_url
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_s(level = 0)
|
73
|
+
Lines.new("location #{location} {",
|
74
|
+
" proxy_pass #{proxy_url};",
|
75
|
+
" proxy_set_header Host $http_host;",
|
76
|
+
" proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;",
|
77
|
+
" proxy_set_header X-Forwarded-Proto $scheme;",
|
78
|
+
" proxy_set_header X-Real-IP $remote_addr;",
|
79
|
+
" proxy_redirect off;",
|
80
|
+
"}").format(level)
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
attr_reader :proxy_url
|
86
|
+
end
|
87
|
+
|
88
|
+
class RedirectLocation < Location
|
89
|
+
def initialize
|
90
|
+
super
|
91
|
+
@location = nil
|
92
|
+
end
|
93
|
+
|
94
|
+
def to_s(level = 0)
|
95
|
+
Lines.new("return 301 https://$server_name/$request_uri;").format(level)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
module Nginx
|
6
|
+
class Configuration
|
7
|
+
def certbot_domain_names(domain_name)
|
8
|
+
"#{domain_name} www.#{domain_name}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def certificate_directory(domain_name)
|
12
|
+
"#{root}/etc/letsencrypt/live/#{domain_name}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(root = nil)
|
16
|
+
@dhparam = 2048
|
17
|
+
@root = root
|
18
|
+
end
|
19
|
+
|
20
|
+
def root?
|
21
|
+
!(root.nil? || root.empty?)
|
22
|
+
end
|
23
|
+
|
24
|
+
def root_directory(domain_name)
|
25
|
+
"#{root}/var/www/#{domain_name}/html"
|
26
|
+
end
|
27
|
+
|
28
|
+
def server_block_location(domain_name)
|
29
|
+
"#{root}/etc/nginx/sites-available/#{domain_name}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def enabled_server_block_location(domain_name)
|
33
|
+
"#{root}/etc/nginx/sites-enabled/#{domain_name}"
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_accessor :dhparam, :root
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
##
|
41
|
+
# Change root. If block is given, change the root only for the duration
|
42
|
+
# of the block. If no block is given, is the same as configure.
|
43
|
+
def chroot(root = nil)
|
44
|
+
if block_given?
|
45
|
+
begin
|
46
|
+
save_root = configuration.root
|
47
|
+
chroot(root)
|
48
|
+
result = yield
|
49
|
+
ensure
|
50
|
+
chroot(save_root)
|
51
|
+
result
|
52
|
+
end
|
53
|
+
else
|
54
|
+
configuration.root = root
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def configure
|
59
|
+
yield configuration
|
60
|
+
end
|
61
|
+
|
62
|
+
def configuration
|
63
|
+
@configuration ||= Configuration.new
|
64
|
+
end
|
65
|
+
|
66
|
+
def dhparam
|
67
|
+
configuration.dhparam
|
68
|
+
end
|
69
|
+
|
70
|
+
def dhparam=(key_length)
|
71
|
+
configuration.dhparam = key_length
|
72
|
+
end
|
73
|
+
|
74
|
+
def prepare_fake_files(domain_name, certificate_domain = nil)
|
75
|
+
::FileUtils.mkdir_p(File.dirname(server_block_location(domain_name)))
|
76
|
+
::FileUtils.mkdir_p(File.dirname(enabled_server_block_location(domain_name)))
|
77
|
+
::FileUtils.mkdir_p(certificate_directory(certificate_domain || domain_name))
|
78
|
+
end
|
79
|
+
|
80
|
+
def root
|
81
|
+
configuration.root
|
82
|
+
end
|
83
|
+
|
84
|
+
def root?
|
85
|
+
configuration.root?
|
86
|
+
end
|
87
|
+
|
88
|
+
%i[
|
89
|
+
certbot_domain_names
|
90
|
+
certificate_directory
|
91
|
+
enabled_server_block_location
|
92
|
+
root_directory
|
93
|
+
server_block_location
|
94
|
+
].each do |method|
|
95
|
+
define_method method do |domain_name|
|
96
|
+
configuration.send(method, domain_name)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nginx
|
4
|
+
##
|
5
|
+
# The server_name line of a server block.
|
6
|
+
class Server
|
7
|
+
attr_reader :domain_name
|
8
|
+
|
9
|
+
def initialize(domain_name)
|
10
|
+
@domain_name = domain_name
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s(level = 0)
|
14
|
+
Lines.new("server_name #{Nginx.certbot_domain_names(domain_name)};").format(level)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Server name and site location for a static site.
|
20
|
+
# TODO: I don't like the way this gets twisted when subclassing.
|
21
|
+
class StaticServer < Server
|
22
|
+
def to_s(level = 0)
|
23
|
+
[
|
24
|
+
super(level),
|
25
|
+
Lines.new(
|
26
|
+
"root #{root_directory};",
|
27
|
+
"index index.html index.htm;"
|
28
|
+
).format(level)
|
29
|
+
].join("\n\n")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class RailsServer < Server
|
34
|
+
def root_directory
|
35
|
+
File.join(Nginx.root_directory(domain_name), "public")
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s(level = 0)
|
39
|
+
[
|
40
|
+
super(level),
|
41
|
+
Lines.new(
|
42
|
+
"# http://stackoverflow.com/a/11313241/3109926 said the following",
|
43
|
+
"# is what serves from public directly without hitting Puma",
|
44
|
+
"root #{root_directory};",
|
45
|
+
"try_files $uri/index.html $uri @example.com;",
|
46
|
+
"error_page 500 502 503 504 /500.html;",
|
47
|
+
"client_max_body_size 4G;",
|
48
|
+
"keepalive_timeout 10;"
|
49
|
+
).format(level)
|
50
|
+
].join("\n\n")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# Write nginx configuration files.
|
5
|
+
module Nginx
|
6
|
+
class ServerBlock
|
7
|
+
def initialize(upstream: nil, server: nil, listen: nil, location: nil)
|
8
|
+
@listen = listen
|
9
|
+
@location = Array(location)
|
10
|
+
@server = server
|
11
|
+
@upstream = upstream
|
12
|
+
end
|
13
|
+
|
14
|
+
def save
|
15
|
+
File.open(Nginx.server_block_location(server.domain_name), "w") do |f|
|
16
|
+
f << to_s
|
17
|
+
end
|
18
|
+
`ln -fs ../sites-available/#{server.domain_name} #{Nginx.enabled_server_block_location(server.domain_name)}`
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
[
|
23
|
+
upstream_string,
|
24
|
+
server_block_string
|
25
|
+
].compact.join("\n\n")
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def server_block_string
|
31
|
+
<<~SERVER_BLOCK
|
32
|
+
server {
|
33
|
+
#{[
|
34
|
+
@server&.to_s(1),
|
35
|
+
@listen&.to_s(1),
|
36
|
+
@location&.map { |l| l.to_s(1) }
|
37
|
+
].compact.join("\n\n")}
|
38
|
+
}
|
39
|
+
SERVER_BLOCK
|
40
|
+
end
|
41
|
+
|
42
|
+
def upstream_string
|
43
|
+
upstream&.to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader :listen, :location, :server, :upstream
|
47
|
+
end
|
48
|
+
|
49
|
+
class SiteServerBlock < ServerBlock
|
50
|
+
def make_root_directory(root_directory)
|
51
|
+
FileUtils.mkdir_p(server.root_directory)
|
52
|
+
if Process.uid.zero?
|
53
|
+
FileUtils.chown(server.user,
|
54
|
+
"www-data",
|
55
|
+
server.root_directory)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def save
|
60
|
+
make_root_directory(root_directory)
|
61
|
+
super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class RailsServerBlock < SiteServerBlock
|
66
|
+
def root_directory
|
67
|
+
File.join(server.root_directory, "/public")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class StaticServerBlock < SiteServerBlock
|
72
|
+
end
|
73
|
+
|
74
|
+
class TlsRedirectServerBlock < ServerBlock
|
75
|
+
def initialize(domain_name)
|
76
|
+
super(
|
77
|
+
server: Server.new(domain_name),
|
78
|
+
listen: ListenHttp.new,
|
79
|
+
location: RedirectLocation.new
|
80
|
+
)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nginx
|
4
|
+
##
|
5
|
+
# Server name and site location for a static site.
|
6
|
+
# TODO: I don't like the way this gets twisted when subclassing.
|
7
|
+
class Site < Server
|
8
|
+
attr_reader :user
|
9
|
+
|
10
|
+
def initialize(domain_name, user = "ubuntu")
|
11
|
+
super domain_name
|
12
|
+
@user = user
|
13
|
+
end
|
14
|
+
|
15
|
+
def root_directory
|
16
|
+
Nginx.root_directory(domain_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s(level = 0)
|
20
|
+
[
|
21
|
+
super(level),
|
22
|
+
Lines.new(
|
23
|
+
"root #{Nginx.root_directory(domain_name)};",
|
24
|
+
"index index.html index.htm;"
|
25
|
+
).format(level)
|
26
|
+
].join("\n\n")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
|
5
|
+
module Runner
|
6
|
+
##
|
7
|
+
# Basic runner for nginx config file generation.
|
8
|
+
class Base
|
9
|
+
def main
|
10
|
+
options = process_options
|
11
|
+
options.merge!(process_args)
|
12
|
+
|
13
|
+
puts "options: #{options.inspect}" if options[:debug]
|
14
|
+
|
15
|
+
Nginx.prepare_fake_files(options[:domain_name], options[:certificate_domain]) if Nginx.root?
|
16
|
+
|
17
|
+
@builder_class = protocol_factory(options)
|
18
|
+
puts "builder_class: #{builder_class.inspect}" if options[:debug]
|
19
|
+
builder_class
|
20
|
+
end
|
21
|
+
|
22
|
+
def options_for_config(options)
|
23
|
+
options.select { |k, _v| k == :user }
|
24
|
+
end
|
25
|
+
|
26
|
+
def process_args
|
27
|
+
$stderr.puts "domain required" unless ARGV.size == 1
|
28
|
+
{ domain_name: ARGV[0] }
|
29
|
+
end
|
30
|
+
|
31
|
+
def process_options(http_builder_class = Nginx::Builder::SiteHttp,
|
32
|
+
https_builder_class = Nginx::Builder::SiteHttps)
|
33
|
+
options = {}
|
34
|
+
OptionParser.new do |opts|
|
35
|
+
opts.banner = "Usage: [options]"
|
36
|
+
|
37
|
+
opts.on("-c DOMAIN",
|
38
|
+
"--certificate-domain DOMAIN",
|
39
|
+
"Use the certificate for DOMAIN.") do |certificate_domain|
|
40
|
+
options[:certificate_domain] = certificate_domain
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on("-h", "--help", "Prints this help") do
|
44
|
+
puts opts
|
45
|
+
exit
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on("-d", "--debug", "Print debugging information.") do
|
49
|
+
options[:debug] = true
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.on("-p PROTOCOL",
|
53
|
+
"--protocol PROTOCOL",
|
54
|
+
"HTTP|HTTPS. Default: HTTPS if key files exist, else HTTP.") do |protocol|
|
55
|
+
options[:protocol] = case protocol
|
56
|
+
when "HTTP"
|
57
|
+
http_builder_class
|
58
|
+
when "HTTPS"
|
59
|
+
https_builder_class
|
60
|
+
else
|
61
|
+
puts opts
|
62
|
+
exit
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
opts.on("-r DIRECTORY",
|
67
|
+
"--root DIRECTORY",
|
68
|
+
"DIRECTORY. Set a root for files. This options is for debugging.") do |directory|
|
69
|
+
Nginx.chroot(directory)
|
70
|
+
end
|
71
|
+
|
72
|
+
opts.on("-u USER",
|
73
|
+
"--user USER",
|
74
|
+
"User to be the owner of certain files. Default: ubuntu.") do |user|
|
75
|
+
options[:user] = user
|
76
|
+
end
|
77
|
+
|
78
|
+
opts.on("--dhparam KEYSIZE",
|
79
|
+
"KEYSIZE. Default: 2048 should be used. This option is for testing.") do |keysize|
|
80
|
+
Nginx.dhparam = keysize
|
81
|
+
end
|
82
|
+
|
83
|
+
yield opts if block_given?
|
84
|
+
end.parse!
|
85
|
+
options
|
86
|
+
end
|
87
|
+
|
88
|
+
attr_reader :builder_class
|
89
|
+
|
90
|
+
def protocol_factory(options,
|
91
|
+
http_builder_class = Nginx::Builder::SiteHttp,
|
92
|
+
https_builder_class = Nginx::Builder::SiteHttps)
|
93
|
+
if options[:protocol]
|
94
|
+
options[:protocol]
|
95
|
+
else
|
96
|
+
certificate_directory = Nginx.certificate_directory(
|
97
|
+
options[:certificate_domain] || options[:domain_name]
|
98
|
+
)
|
99
|
+
if File.exist?(File.join(certificate_directory, "privkey.pem")) &&
|
100
|
+
File.exist?(File.join(certificate_directory, "fullchain.pem")) &&
|
101
|
+
File.exist?(File.join(certificate_directory, "chain.pem")) &&
|
102
|
+
File.exist?(File.join(certificate_directory, "cert.pem"))
|
103
|
+
https_builder_class
|
104
|
+
else
|
105
|
+
http_builder_class
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Runner
|
4
|
+
##
|
5
|
+
# Generate reverse proxy config files for Nginx.
|
6
|
+
class ReverseProxy < Base
|
7
|
+
def options_for_config(options)
|
8
|
+
super(options).merge(proxy_url: ARGV[1])
|
9
|
+
end
|
10
|
+
|
11
|
+
def process_args
|
12
|
+
$stderr.puts "domain and target url required" unless ARGV.size == 2
|
13
|
+
{
|
14
|
+
domain_name: ARGV[0],
|
15
|
+
proxy_url: ARGV[1]
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def process_options
|
20
|
+
super(Nginx::Builder::ReverseProxyHttp, Nginx::Builder::ReverseProxyHttps)
|
21
|
+
end
|
22
|
+
|
23
|
+
def protocol_factory(options)
|
24
|
+
protocol_class = super(
|
25
|
+
options,
|
26
|
+
Nginx::Builder::ReverseProxyHttp,
|
27
|
+
Nginx::Builder::ReverseProxyHttps
|
28
|
+
)
|
29
|
+
|
30
|
+
domain_name = options.delete(:domain_name)
|
31
|
+
proxy_url = options.delete(:proxy_url)
|
32
|
+
certificate_domain = options.delete(:certificate_domain)
|
33
|
+
protocol_class.new(domain_name, proxy_url, certificate_domain)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Runner
|
4
|
+
##
|
5
|
+
# Generate static site config files for Nginx.
|
6
|
+
class StaticSite < Base
|
7
|
+
def protocol_factory(options)
|
8
|
+
protocol_class = super(
|
9
|
+
options,
|
10
|
+
Nginx::Builder::SiteHttp,
|
11
|
+
Nginx::Builder::SiteHttps
|
12
|
+
)
|
13
|
+
|
14
|
+
domain_name = options.delete(:domain_name)
|
15
|
+
user = options.delete(:user) || "ubuntu"
|
16
|
+
certificate_domain = options.delete(:certificate_domain)
|
17
|
+
protocol_class.new(domain_name, user, certificate_domain)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Systemd
|
4
|
+
module Rails
|
5
|
+
class << self
|
6
|
+
def puma_uri(domain_name)
|
7
|
+
"unix:///tmp/#{domain_name}.sock"
|
8
|
+
end
|
9
|
+
|
10
|
+
def redis_location(domain_name)
|
11
|
+
"redis." + domain_name
|
12
|
+
end
|
13
|
+
|
14
|
+
def write_unit_file(domain_name)
|
15
|
+
if ENV["SECRET_KEY_BASE"].nil? ||
|
16
|
+
ENV["DATABASE_USERNAME"].nil? ||
|
17
|
+
ENV["DATABASE_PASSWORD"].nil? ||
|
18
|
+
ENV["EMAIL_PASSWORD"].nil?
|
19
|
+
raise "Missing environment variable"
|
20
|
+
end
|
21
|
+
|
22
|
+
result = File.open(Systemd.unit_file(domain_name), "w") do |f|
|
23
|
+
f << <<~UNIT_FILE
|
24
|
+
[Unit]
|
25
|
+
Description=Puma HTTP Server for #{domain_name}
|
26
|
+
After=network.target
|
27
|
+
|
28
|
+
# Uncomment for socket activation (see below)
|
29
|
+
# Requires=#{domain_name}.socket
|
30
|
+
|
31
|
+
[Service]
|
32
|
+
# Foreground process (do not use --daemon in ExecStart or config.rb)
|
33
|
+
Type=simple
|
34
|
+
|
35
|
+
User=nobody
|
36
|
+
Group=www-data
|
37
|
+
|
38
|
+
# Specify the path to the Rails application root
|
39
|
+
WorkingDirectory=#{Nginx.root_directory(domain_name)}
|
40
|
+
|
41
|
+
# Helpful for debugging socket activation, etc.
|
42
|
+
# Environment=PUMA_DEBUG=1
|
43
|
+
Environment=RACK_ENV=production
|
44
|
+
Environment=RAILS_ENV=production
|
45
|
+
Environment=SECRET_KEY_BASE=#{ENV['SECRET_KEY_BASE']}
|
46
|
+
Environment=DATABASE_USERNAME=#{ENV['DATABASE_USERNAME']}
|
47
|
+
Environment=DATABASE_PASSWORD=#{ENV['DATABASE_PASSWORD']}
|
48
|
+
Environment=EMAIL_PASSWORD=#{ENV['EMAIL_PASSWORD']}
|
49
|
+
Environment=REDIS_URL=unix:///tmp/#{redis_location(domain_name)}.sock
|
50
|
+
|
51
|
+
# The command to start Puma
|
52
|
+
# NOTE: TLS would be handled by Nginx
|
53
|
+
ExecStart=#{Nginx.root_directory(domain_name)}/bin/puma -b #{puma_uri(domain_name)} \
|
54
|
+
--redirect-stdout=#{Nginx.root_directory(domain_name)}/log/puma-production.stdout.log \
|
55
|
+
--redirect-stderr=#{Nginx.root_directory(domain_name)}/log/puma-production.stderr.log
|
56
|
+
# ExecStart=/usr/local/bin/puma -b tcp://#{puma_uri(domain_name)}
|
57
|
+
|
58
|
+
Restart=always
|
59
|
+
|
60
|
+
[Install]
|
61
|
+
WantedBy=multi-user.target
|
62
|
+
UNIT_FILE
|
63
|
+
end
|
64
|
+
|
65
|
+
FileUtils.chmod(0o600, Systemd.unit_file(domain_name))
|
66
|
+
`systemctl enable $domain_name.service` if Process.uid.zero?
|
67
|
+
|
68
|
+
result
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Systemd
|
2
|
+
class Configuration
|
3
|
+
def initialize
|
4
|
+
@unit_file_path = File.join(Nginx.root, "/lib/systemd/system")
|
5
|
+
end
|
6
|
+
|
7
|
+
def unit_file(domain_name)
|
8
|
+
File.join(unit_file_path, domain_name + ".service")
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :unit_file_path
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def configure
|
16
|
+
yield configuration
|
17
|
+
end
|
18
|
+
|
19
|
+
def configuration
|
20
|
+
@configuration ||= Configuration.new
|
21
|
+
end
|
22
|
+
|
23
|
+
%i[
|
24
|
+
unit_file
|
25
|
+
].each do |method|
|
26
|
+
define_method method do |domain_name|
|
27
|
+
configuration.send(method, domain_name)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shared-infrastructure
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Larry Reid
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-10-26 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: 'For static sites, Rails apps, and reverse proxies.
|
14
|
+
|
15
|
+
'
|
16
|
+
email: lcreid@jadesystems.ca
|
17
|
+
executables:
|
18
|
+
- create-server-block
|
19
|
+
- create-rails-app
|
20
|
+
- create-reverse-proxy
|
21
|
+
extensions: []
|
22
|
+
extra_rdoc_files: []
|
23
|
+
files:
|
24
|
+
- bin/create-rails-app
|
25
|
+
- bin/create-reverse-proxy
|
26
|
+
- bin/create-server-block
|
27
|
+
- lib/shared_infrastructure.rb
|
28
|
+
- lib/shared_infrastructure/nginx/builder.rb
|
29
|
+
- lib/shared_infrastructure/nginx/lines.rb
|
30
|
+
- lib/shared_infrastructure/nginx/listen.rb
|
31
|
+
- lib/shared_infrastructure/nginx/location.rb
|
32
|
+
- lib/shared_infrastructure/nginx/nginx.rb
|
33
|
+
- lib/shared_infrastructure/nginx/server.rb
|
34
|
+
- lib/shared_infrastructure/nginx/server_block.rb
|
35
|
+
- lib/shared_infrastructure/nginx/site.rb
|
36
|
+
- lib/shared_infrastructure/runner/base.rb
|
37
|
+
- lib/shared_infrastructure/runner/reverse_proxy.rb
|
38
|
+
- lib/shared_infrastructure/runner/static_site.rb
|
39
|
+
- lib/shared_infrastructure/systemd/rails.rb
|
40
|
+
- lib/shared_infrastructure/systemd/systemd.rb
|
41
|
+
homepage: https://github.com/weenhanceit/infrastructure
|
42
|
+
licenses:
|
43
|
+
- MIT
|
44
|
+
metadata: {}
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 2.5.1
|
62
|
+
signing_key:
|
63
|
+
specification_version: 4
|
64
|
+
summary: Configure nginx, systemd, and/or Puma
|
65
|
+
test_files: []
|