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 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,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), "..", "lib")
5
+ require "shared_infrastructure"
6
+
7
+ Runner::Rails.new.main.save
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), "..", "lib")
5
+ require "shared_infrastructure"
6
+
7
+ Runner::ReverseProxy.new.main.save
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), "..", "lib")
5
+ require "shared_infrastructure"
6
+
7
+ Runner::StaticSite.new.main.save
@@ -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: []