boring_services 0.8.0 → 0.9.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d1211d9bead3bd016414ec0b41c4750d59bb3afabb1e892ea9e05488bdb12dd2
4
- data.tar.gz: a813b195971c06f10bae0989255fb0a63d99158d2afbcc4ea6aecb756e291a93
3
+ metadata.gz: e67745f3e72ccf6dce8646fc1606dda6f080f7634f58e5e614e21d732a24baef
4
+ data.tar.gz: 6d16d20e74f47f17c855d217ceb59799a4c67e390f695050dd73ba09f949befd
5
5
  SHA512:
6
- metadata.gz: 3c321ae267b8eb3594955db1d965da1633a9f879db5a3bd8986939cfa3770dd54b18fad07299c0bf7a375e9e3ef05224e3303b9e8d1a5af631119698847a6016
7
- data.tar.gz: 1462cef1c4ff1f49c993da325c5db5d847fab9636fbfe66f03c92f96def0c899ba01309f18ce192195029da6be159e0f36fb4c76c0f04acd11674415482554fc
6
+ metadata.gz: 1201bdcc2598275c86c3ceceae12193c398c4135e6e8cbe675b6f5dce5fa2896b1b27fd776ecfc5fd34c2af8456a092ca968734e52d6ceac58c46c7f055c454d
7
+ data.tar.gz: 9bc9c48947ffe1805a73accab2fa699c8c007d744722d785fe51b5cd555595bd14eb4ee33c0deb0ad6ce982c6c7d6b5e8d18c9f61127a7f45d5c2dbb4cb927bf
data/CHANGELOG.md ADDED
@@ -0,0 +1,68 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.9.0] - 2026-10-02
11
+
12
+ ### Changed
13
+
14
+ - Update development dependencies.
15
+ - Link gem source metadata to the matching release tag.
16
+
17
+ ### Added
18
+
19
+ - Per-host jump routing, strict known-hosts configuration, and a targeted
20
+ `install --host LABEL` path for safely adding services to a new origin.
21
+
22
+ ## [0.8.1] - 2026-08-20
23
+
24
+ ### Changed
25
+
26
+ - Require Ruby 4.0 or newer.
27
+ - Align runtime and development dependencies with the maintained toolchain.
28
+ - Add blocking test, lint, dependency-audit, and package-build CI checks.
29
+ - Add automated Bundler and GitHub Actions dependency updates.
30
+ - Clarify that private IP metadata does not configure bind addresses or
31
+ firewalls.
32
+
33
+ ## [0.1.0] - 2025-01-21
34
+
35
+ ### Added
36
+ - Initial release of BoringServices
37
+ - Deploy infrastructure services (Memcached, Redis, HAProxy, Nginx) via SSH
38
+ - Rails integration with generators and rake tasks
39
+ - Standalone Ruby support (no Rails dependency required)
40
+ - Configuration override support for HAProxy via `custom_params`:
41
+ - `timeout_connect`, `timeout_client`, `timeout_server`
42
+ - `balance` algorithm (roundrobin, leastconn, etc.)
43
+ - `health_check_path` customization
44
+ - `ssl_ciphers` and `ssl_options` for SSL/TLS configuration
45
+ - Configuration override support for Memcached via `custom_params`:
46
+ - `listen_address`, `max_connections`
47
+ - `max_item_size`, `verbosity`
48
+ - Custom config template support:
49
+ - HAProxy: `custom_config_template` option to provide your own config file
50
+ - Memcached: `custom_config_template` option to provide your own config file
51
+ - VPN/private network support with `private_ip` field for tracking internal IPs
52
+ - Multi-host deployment support for services
53
+ - Environment-based configuration (development, staging, production)
54
+ - Secret management via environment variables or command execution
55
+ - SSL/TLS support for HAProxy with automatic certificate management
56
+ - Health checking and status monitoring
57
+ - SSH key-based authentication with configurable options
58
+
59
+ ### Changed
60
+ - Made railties dependency optional for standalone Ruby usage
61
+ - Improved documentation with comprehensive examples
62
+ - Updated Ruby requirement to >= 3.0.0 (from >= 3.4.0)
63
+
64
+ ### Technical Details
65
+ - Uses SSHKit for SSH operations
66
+ - Supports Ubuntu 20.04+ servers (Debian-based distributions)
67
+ - YAML-based configuration with ERB support
68
+ - Thor-based CLI interface
data/README.md CHANGED
@@ -371,9 +371,15 @@ BoringServices supports deploying services over VPN or private networks (e.g., W
371
371
 
372
372
  - **Public IPs** (`host`) for SSH deployment access
373
373
  - **Private IPs** (`private_ip`) for service communication
374
- - Services listen on all interfaces (0.0.0.0) by default
374
+ - Some generated service configurations listen on all interfaces (`0.0.0.0`)
375
+ by default
375
376
  - Accessible via private network IPs
376
- - No public exposure of services
377
+ - `private_ip` documents the address clients should use; it does not change the
378
+ service bind address or configure a firewall
379
+
380
+ Before deploying, restrict each service port to the intended private CIDRs with
381
+ the provider firewall and the host firewall, or set a supported service-specific
382
+ listen address. A public SSH address does not make a service endpoint private.
377
383
 
378
384
  **Example with Private IPs:**
379
385
 
@@ -428,7 +434,7 @@ haproxy: healthy
428
434
 
429
435
  ## Requirements
430
436
 
431
- - Ruby 3.0+
437
+ - Ruby 4.0+
432
438
  - Ubuntu 20.04+ servers (Debian-based distributions)
433
439
  - SSH key-based authentication
434
440
  - Optional: VPN solution (WireGuard, Tailscale, etc.) for private networking
data/SECURITY.md ADDED
@@ -0,0 +1,22 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ Please do not open a public issue for a potential vulnerability. Email
6
+ [security@boringcache.com](mailto:security@boringcache.com) or use
7
+ [GitHub private vulnerability reporting](https://github.com/boringcache/services/security/advisories/new).
8
+
9
+ Include the affected version, service, expected impact, and enough reproduction
10
+ detail for us to investigate. Do not include live credentials, private keys,
11
+ certificates, customer data, or production configuration.
12
+
13
+ ## Supported Versions
14
+
15
+ Only the latest published release is supported. Security fixes are normally
16
+ shipped in a new release rather than backported.
17
+
18
+ ## Scope
19
+
20
+ Reports about SSH host verification, network exposure, secret handling,
21
+ generated service configuration, command execution, or release integrity are
22
+ welcome.
@@ -25,8 +25,10 @@ module BoringServices
25
25
  end
26
26
 
27
27
  desc 'install [SERVICE]', 'Install service(s) - all services or specific service'
28
+ option :host, type: :string, desc: 'Install only the host matching this label or address'
28
29
  def install(service_name = nil)
29
30
  config = Configuration.load(options[:config], options[:environment])
31
+ config.only_host!(options[:host]) if options[:host]
30
32
  installer = Installer.new(config)
31
33
  service_name ? installer.install_service(service_name) : installer.install_all
32
34
  rescue Error => e
@@ -52,7 +54,8 @@ module BoringServices
52
54
  exit 1
53
55
  end
54
56
 
55
- desc 'reconfigure [SERVICE]', 'Reconfigure service(s) - skips package installation, only updates config and restarts'
57
+ desc 'reconfigure [SERVICE]',
58
+ 'Reconfigure service(s) - skips package installation, only updates config and restarts'
56
59
  def reconfigure(service_name = nil)
57
60
  config = Configuration.load(options[:config], options[:environment])
58
61
  installer = Installer.new(config)
@@ -108,7 +111,7 @@ module BoringServices
108
111
  end
109
112
 
110
113
  def print_memcached_stats(stats)
111
- bytes = format_bytes(stats['bytes']&.to_i || 0)
114
+ bytes = format_bytes(stats['bytes'].to_i)
112
115
  conns = stats['curr_connections'] || '0'
113
116
  items = stats['curr_items'] || '0'
114
117
  hit_rate = calculate_hit_rate(stats['get_hits'], stats['get_misses'])
@@ -116,8 +119,8 @@ module BoringServices
116
119
  end
117
120
 
118
121
  def calculate_hit_rate(hits, misses)
119
- hits = hits&.to_i || 0
120
- misses = misses&.to_i || 0
122
+ hits = hits.to_i
123
+ misses = misses.to_i
121
124
  total = hits + misses
122
125
  total.positive? ? ((hits.to_f / total) * 100).round(1) : 0
123
126
  end
@@ -14,6 +14,7 @@ module BoringServices
14
14
  @config_path = config_path
15
15
  @environment = environment.to_s
16
16
  @config = load_config
17
+ @all_hosts = host_entries
17
18
  end
18
19
 
19
20
  def service_config(service_name)
@@ -42,6 +43,54 @@ module BoringServices
42
43
  @config['ssh_key'] || '~/.ssh/id_rsa'
43
44
  end
44
45
 
46
+ def ssh_known_hosts_file
47
+ path = @config['ssh_known_hosts_file']
48
+ File.expand_path(path) if path
49
+ end
50
+
51
+ def verify_host_key_mode
52
+ case @config.fetch('verify_host_key', true)
53
+ when true, 'always', :always then :always
54
+ when 'accept_new', :accept_new then :accept_new
55
+ when false, 'never', :never then :never
56
+ else :always
57
+ end
58
+ end
59
+
60
+ def host_config(reference)
61
+ reference = reference.to_s
62
+ @all_hosts.find do |host|
63
+ next unless host.is_a?(Hash)
64
+
65
+ host['host'].to_s == reference ||
66
+ host['label'].to_s.casecmp?(reference) ||
67
+ host['name'].to_s.casecmp?(reference)
68
+ end
69
+ end
70
+
71
+ def jump_host_config(host)
72
+ return unless host.is_a?(Hash)
73
+
74
+ reference = host['jump_host'] || host[:jump_host]
75
+ return unless reference
76
+
77
+ host_config(reference) || raise(Error, "Jump host not found: #{reference}")
78
+ end
79
+
80
+ def only_host!(reference)
81
+ selected = host_config(reference)
82
+ raise Error, "Host not found: #{reference}" unless selected
83
+
84
+ @config['services'] = services.filter_map do |service|
85
+ hosts = Array(service['hosts'] || service['host']).compact
86
+ matches = hosts.select { |host| same_host?(host, selected) }
87
+ next if matches.empty?
88
+
89
+ service.merge('hosts' => matches).tap { |entry| entry.delete('host') }
90
+ end
91
+ self
92
+ end
93
+
45
94
  def forward_agent
46
95
  return @config['forward_agent'] unless @config['forward_agent'].nil?
47
96
 
@@ -64,6 +113,16 @@ module BoringServices
64
113
 
65
114
  private
66
115
 
116
+ def host_entries
117
+ services.flat_map { |service| Array(service['hosts'] || service['host']).compact }
118
+ end
119
+
120
+ def same_host?(candidate, selected)
121
+ return candidate.to_s == selected['host'].to_s unless candidate.is_a?(Hash)
122
+
123
+ candidate['host'].to_s == selected['host'].to_s
124
+ end
125
+
67
126
  def load_config
68
127
  raise Error, "Config file not found: #{@config_path}" unless File.exist?(@config_path)
69
128
 
@@ -115,9 +115,9 @@ module BoringServices
115
115
  end
116
116
 
117
117
  def print_credentials_summary(hints)
118
- puts "\n" + "=" * 50
119
- puts "📋 Add to Rails credentials:"
120
- puts "=" * 50
118
+ puts "\n#{'=' * 50}"
119
+ puts '📋 Add to Rails credentials:'
120
+ puts '=' * 50
121
121
 
122
122
  # Group by service type
123
123
  grouped = hints.group_by { |h| h[:type] }
@@ -129,14 +129,14 @@ module BoringServices
129
129
  by_region = entries.group_by { |e| e[:region] }
130
130
  by_region.each do |region, region_entries|
131
131
  puts " #{region}:"
132
- puts " servers:"
132
+ puts ' servers:'
133
133
  region_entries.each do |entry|
134
134
  puts " - #{entry[:server]} # #{entry[:label]}"
135
135
  end
136
136
  end
137
137
  end
138
138
 
139
- puts "\n" + "=" * 50
139
+ puts "\n#{'=' * 50}"
140
140
  end
141
141
  end
142
142
  end
@@ -1,4 +1,7 @@
1
1
  require 'English'
2
+ require 'open3'
3
+ require 'shellwords'
4
+
2
5
  module BoringServices
3
6
  class Secrets
4
7
  def self.resolve(value)
@@ -8,10 +11,10 @@ module BoringServices
8
11
 
9
12
  if value_str.start_with?('credentials:')
10
13
  resolve_rails_credentials(value_str)
11
- elsif value_str.start_with?('$')
12
- resolve_env_var(value_str)
13
14
  elsif value_str.start_with?('$(') && value_str.end_with?(')')
14
15
  resolve_command(value_str[2..-2])
16
+ elsif value_str.start_with?('$')
17
+ resolve_env_var(value_str)
15
18
  else
16
19
  value_str
17
20
  end
@@ -24,16 +27,19 @@ module BoringServices
24
27
 
25
28
  # Use rails credentials:show to fetch the value
26
29
  # This requires being run from the Rails root directory
27
- command = "RAILS_ENV=production bundle exec rails runner \"puts Rails.application.credentials.dig(#{keys.map { |k| ":#{k}" }.join(', ')})\""
28
- result = `#{command}`.strip
30
+ runner = "puts Rails.application.credentials.dig(#{keys.map { |key| key.to_sym.inspect }.join(', ')})"
31
+ result, stderr, status = Open3.capture3(
32
+ { 'RAILS_ENV' => ENV.fetch('RAILS_ENV', 'production') },
33
+ 'bundle', 'exec', 'rails', 'runner', runner
34
+ )
29
35
 
30
- unless $CHILD_STATUS.success?
31
- raise Error, "Failed to resolve Rails credentials: #{key_path}. Make sure you're running from the Rails root directory."
36
+ unless status.success?
37
+ detail = stderr.strip.empty? ? 'Make sure you are running from the Rails root directory.' : stderr.strip
38
+ raise Error, "Failed to resolve Rails credentials: #{key_path}. #{detail}"
32
39
  end
33
40
 
34
- if result.empty?
35
- raise Error, "Rails credentials key not found: #{key_path}"
36
- end
41
+ result = result.strip
42
+ raise Error, "Rails credentials key not found: #{key_path}" if result.empty?
37
43
 
38
44
  result
39
45
  end
@@ -46,10 +52,13 @@ module BoringServices
46
52
  end
47
53
 
48
54
  def self.resolve_command(command)
49
- result = `#{command}`.strip
50
- raise Error, "Command failed: #{command}" unless $CHILD_STATUS.success?
55
+ argv = Shellwords.split(command)
56
+ raise Error, 'Command secret is empty' if argv.empty?
51
57
 
52
- result
58
+ result, stderr, status = Open3.capture3(*argv)
59
+ raise Error, "Command failed: #{argv.first}: #{stderr.strip}" unless status.success?
60
+
61
+ result.strip
53
62
  end
54
63
  end
55
64
  end
@@ -66,7 +66,7 @@ module BoringServices
66
66
 
67
67
  return nil if hosts.empty?
68
68
 
69
- port = port_for('memcached') || 11211
69
+ port = port_for('memcached') || 11_211
70
70
  hosts.map { |h| "#{h}:#{port}" }.join(',')
71
71
  end
72
72
 
@@ -9,7 +9,7 @@ module BoringServices
9
9
  configure_haproxy
10
10
  validate_config
11
11
  ssh_executor.systemd_enable('haproxy')
12
- ssh_executor.systemd_restart('haproxy') # Use restart instead of start to reload config
12
+ ssh_executor.systemd_restart('haproxy') # Use restart instead of start to reload config
13
13
  verify_listening_ports
14
14
  end
15
15
  end
@@ -124,7 +124,7 @@ module BoringServices
124
124
  end
125
125
  end
126
126
 
127
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
127
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
128
128
  def configure_haproxy
129
129
  # Check if custom config template is provided
130
130
  if service_config['custom_config_template'] && File.exist?(service_config['custom_config_template'])
@@ -239,7 +239,6 @@ module BoringServices
239
239
  execute :sudo, :chown, 'root:root', '/etc/haproxy/haproxy.cfg'
240
240
  execute :sudo, :chmod, '644', '/etc/haproxy/haproxy.cfg'
241
241
  end
242
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
243
242
 
244
243
  def verify_listening_ports
245
244
  puts ' Verifying HAProxy is listening on ports...'
@@ -257,7 +256,7 @@ module BoringServices
257
256
  ports_to_check << stats_port
258
257
 
259
258
  ports_to_check.each do |check_port|
260
- result = execute :sudo, :ss, '-tlnp', '|', :grep, "-E", "':#{check_port} '", raise_on_error: false
259
+ result = execute :sudo, :ss, '-tlnp', '|', :grep, '-E', "':#{check_port} '", raise_on_error: false
261
260
  if result
262
261
  puts " ✓ Port #{check_port} is listening"
263
262
  else
@@ -42,7 +42,7 @@ module BoringServices
42
42
  listen_port = port || 6379
43
43
  password = resolve_secret('redis_password') if config.secrets['redis_password']
44
44
  has_private_ip = !private_ip.to_s.strip.empty?
45
- bind_address = has_private_ip ? "127.0.0.1 #{private_ip}" : "0.0.0.0"
45
+ bind_address = has_private_ip ? "127.0.0.1 #{private_ip}" : '0.0.0.0'
46
46
 
47
47
  config_content = <<~REDIS
48
48
  bind #{bind_address}
@@ -62,9 +62,7 @@ module BoringServices
62
62
  execute :sudo, :chmod, '640', '/etc/redis/redis.conf'
63
63
 
64
64
  # If using private IP (WireGuard), ensure Redis starts after WireGuard
65
- if has_private_ip
66
- configure_wireguard_dependency
67
- end
65
+ configure_wireguard_dependency if has_private_ip
68
66
 
69
67
  # Set vm.overcommit_memory for Redis background saves
70
68
  execute :sudo, :sysctl, '-w', 'vm.overcommit_memory=1'
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'sshkit'
4
4
  require 'sshkit/dsl'
5
+ require 'net/ssh/proxy/command'
6
+ require 'shellwords'
5
7
 
6
8
  module BoringServices
7
9
  class SSHExecutor
@@ -152,10 +154,10 @@ module BoringServices
152
154
  end
153
155
  end
154
156
 
155
- def capture_on_host(host, *args)
157
+ def capture_on_host(host, *)
156
158
  output = nil
157
159
  execute_on_host(host) do
158
- output = capture(*args, raise_on_non_zero_exit: false)
160
+ output = capture(*, raise_on_non_zero_exit: false)
159
161
  end
160
162
  output
161
163
  end
@@ -174,8 +176,10 @@ module BoringServices
174
176
  forward_agent: config.forward_agent,
175
177
  auth_methods: config.ssh_auth_methods,
176
178
  keys_only: !config.use_ssh_agent,
177
- use_agent: config.use_ssh_agent
179
+ use_agent: config.use_ssh_agent,
180
+ verify_host_key: config.verify_host_key_mode
178
181
  }
182
+ ssh.ssh_options[:user_known_hosts_file] = [config.ssh_known_hosts_file] if config.ssh_known_hosts_file
179
183
  end
180
184
  end
181
185
 
@@ -185,19 +189,48 @@ module BoringServices
185
189
  target_host = host['host'] || host[:host]
186
190
  raise Error, 'Host entry missing host field' unless target_host
187
191
 
188
- build_sshkit_host(target_host, host['user'] || host[:user])
192
+ build_sshkit_host(target_host, host['user'] || host[:user], host)
189
193
  else
190
194
  host_string = host.to_s
191
195
  return SSHKit::Host.new(host_string) if host_string.include?('@')
192
196
 
193
- build_sshkit_host(host_string, nil)
197
+ build_sshkit_host(host_string, nil, nil)
194
198
  end
195
199
  end
196
200
 
197
- def build_sshkit_host(hostname, user)
201
+ def build_sshkit_host(hostname, user, host_config)
198
202
  sshkit_host = SSHKit::Host.new(hostname)
199
203
  sshkit_host.user = user || config.user
204
+ options = { verify_host_key: config.verify_host_key_mode }
205
+ options[:user_known_hosts_file] = [config.ssh_known_hosts_file] if config.ssh_known_hosts_file
206
+ if (jump_host = config.jump_host_config(host_config))
207
+ options[:proxy] = jump_proxy(jump_host)
208
+ end
209
+ sshkit_host.ssh_options = options
200
210
  sshkit_host
201
211
  end
212
+
213
+ def jump_proxy(jump_host)
214
+ user = jump_host['user'] || config.user
215
+ command = [
216
+ 'ssh', '-F', '/dev/null',
217
+ '-o', 'BatchMode=yes',
218
+ '-o', 'ForwardAgent=no',
219
+ '-o', "StrictHostKeyChecking=#{open_ssh_host_key_mode}"
220
+ ]
221
+ command.push('-o', "UserKnownHostsFile=#{config.ssh_known_hosts_file}") if config.ssh_known_hosts_file
222
+ command.push('-i', File.expand_path(config.ssh_key), '-o', 'IdentitiesOnly=yes')
223
+ command.push('-W', '%h:%p', "#{user}@#{jump_host.fetch('host')}")
224
+ command_line = Shellwords.join(command).gsub('\\%h', '%h').gsub('\\%p', '%p')
225
+ Net::SSH::Proxy::Command.new(command_line)
226
+ end
227
+
228
+ def open_ssh_host_key_mode
229
+ case config.verify_host_key_mode
230
+ when :accept_new then 'accept-new'
231
+ when :never then 'no'
232
+ else 'yes'
233
+ end
234
+ end
202
235
  end
203
236
  end
@@ -1,3 +1,3 @@
1
1
  module BoringServices
2
- VERSION = '0.8.0'.freeze
2
+ VERSION = '0.9.0'.freeze
3
3
  end
@@ -71,7 +71,7 @@ module BoringServices
71
71
 
72
72
  # Get Memcached port
73
73
  def memcached_port
74
- locator.port_for('memcached') || 11211
74
+ locator.port_for('memcached') || 11_211
75
75
  end
76
76
 
77
77
  # Get Memcached servers string (host:port,host:port)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: boring_services
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - BoringCache
@@ -29,84 +29,42 @@ dependencies:
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '1.3'
32
+ version: '1.4'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '1.3'
39
+ version: '1.4'
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: sshkit
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '1.21'
46
+ version: '1.25'
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '1.21'
53
+ version: '1.25'
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: thor
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '1.3'
60
+ version: '1.5'
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: '1.3'
68
- - !ruby/object:Gem::Dependency
69
- name: minitest
70
- requirement: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: '5.0'
75
- type: :development
76
- prerelease: false
77
- version_requirements: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
80
- - !ruby/object:Gem::Version
81
- version: '5.0'
82
- - !ruby/object:Gem::Dependency
83
- name: rubocop
84
- requirement: !ruby/object:Gem::Requirement
85
- requirements:
86
- - - "~>"
87
- - !ruby/object:Gem::Version
88
- version: '1.50'
89
- type: :development
90
- prerelease: false
91
- version_requirements: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - "~>"
94
- - !ruby/object:Gem::Version
95
- version: '1.50'
96
- - !ruby/object:Gem::Dependency
97
- name: simplecov
98
- requirement: !ruby/object:Gem::Requirement
99
- requirements:
100
- - - "~>"
101
- - !ruby/object:Gem::Version
102
- version: '0.22'
103
- type: :development
104
- prerelease: false
105
- version_requirements: !ruby/object:Gem::Requirement
106
- requirements:
107
- - - "~>"
108
- - !ruby/object:Gem::Version
109
- version: '0.22'
67
+ version: '1.5'
110
68
  description: Simple deployment and management of infrastructure services like Memcached,
111
69
  Redis, HAProxy, and Nginx. Works standalone or with Rails.
112
70
  email:
@@ -116,8 +74,10 @@ executables:
116
74
  extensions: []
117
75
  extra_rdoc_files: []
118
76
  files:
77
+ - CHANGELOG.md
119
78
  - LICENSE
120
79
  - README.md
80
+ - SECURITY.md
121
81
  - exe/boringservices
122
82
  - lib/boring_services.rb
123
83
  - lib/boring_services/cli.rb
@@ -136,14 +96,14 @@ files:
136
96
  - lib/boring_services/ssh_executor.rb
137
97
  - lib/boring_services/version.rb
138
98
  - lib/tasks/services.rake
139
- homepage: https://github.com/boringcache/boring_services
99
+ homepage: https://github.com/boringcache/services
140
100
  licenses:
141
101
  - MIT
142
102
  metadata:
143
- homepage_uri: https://github.com/boringcache/boring_services
144
- source_code_uri: https://github.com/boringcache/boring_services
145
- documentation_uri: https://github.com/boringcache/boring_services/blob/main/README.md
146
- changelog_uri: https://github.com/boringcache/boring_services/blob/main/CHANGELOG.md
103
+ source_code_uri: https://github.com/boringcache/services/tree/v0.9.0
104
+ documentation_uri: https://github.com/boringcache/services/blob/main/README.md
105
+ changelog_uri: https://github.com/boringcache/services/blob/main/CHANGELOG.md
106
+ bug_tracker_uri: https://github.com/boringcache/services/issues
147
107
  rubygems_mfa_required: 'true'
148
108
  rdoc_options: []
149
109
  require_paths:
@@ -152,14 +112,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
152
112
  requirements:
153
113
  - - ">="
154
114
  - !ruby/object:Gem::Version
155
- version: '3.4'
115
+ version: 4.0.0
156
116
  required_rubygems_version: !ruby/object:Gem::Requirement
157
117
  requirements:
158
118
  - - ">="
159
119
  - !ruby/object:Gem::Version
160
120
  version: '0'
161
121
  requirements: []
162
- rubygems_version: 3.6.9
122
+ rubygems_version: 4.0.16
163
123
  specification_version: 4
164
124
  summary: Deploy infrastructure services for Ruby & Rails apps
165
125
  test_files: []