boring_services 0.8.0 → 0.8.1

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: d26c6e6770c20727d68f773f48f1505b9eaeedef20b72d73cd9af76668773bc4
4
+ data.tar.gz: b6d7d7ca8c099f958c51f542ff0f7eb64d6b23ae1241ca62e27041c044867f1b
5
5
  SHA512:
6
- metadata.gz: 3c321ae267b8eb3594955db1d965da1633a9f879db5a3bd8986939cfa3770dd54b18fad07299c0bf7a375e9e3ef05224e3303b9e8d1a5af631119698847a6016
7
- data.tar.gz: 1462cef1c4ff1f49c993da325c5db5d847fab9636fbfe66f03c92f96def0c899ba01309f18ce192195029da6be159e0f36fb4c76c0f04acd11674415482554fc
6
+ metadata.gz: eab7cdbc819793e3f49ebb6a2212f37cd5d4c2019d8d96b15e70ca9431732a823ec51a689c450a9f8fd9b2fa8e3e7db849d716428044c988e697701bb36b45f6
7
+ data.tar.gz: 23d2c7f46e5e6a14a1f6adf143771a1869e5dbb66401f7ac175ba70797b0c70ba0a4b114ac5cede972f2dd68c6ace88a46ecd2258033a1226533c0579b461519
data/CHANGELOG.md ADDED
@@ -0,0 +1,56 @@
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.8.1] - 2026-08-20
11
+
12
+ ### Changed
13
+
14
+ - Require Ruby 4.0 or newer.
15
+ - Align runtime and development dependencies with the maintained toolchain.
16
+ - Add blocking test, lint, dependency-audit, and package-build CI checks.
17
+ - Add automated Bundler and GitHub Actions dependency updates.
18
+ - Clarify that private IP metadata does not configure bind addresses or
19
+ firewalls.
20
+
21
+ ## [0.1.0] - 2025-01-21
22
+
23
+ ### Added
24
+ - Initial release of BoringServices
25
+ - Deploy infrastructure services (Memcached, Redis, HAProxy, Nginx) via SSH
26
+ - Rails integration with generators and rake tasks
27
+ - Standalone Ruby support (no Rails dependency required)
28
+ - Configuration override support for HAProxy via `custom_params`:
29
+ - `timeout_connect`, `timeout_client`, `timeout_server`
30
+ - `balance` algorithm (roundrobin, leastconn, etc.)
31
+ - `health_check_path` customization
32
+ - `ssl_ciphers` and `ssl_options` for SSL/TLS configuration
33
+ - Configuration override support for Memcached via `custom_params`:
34
+ - `listen_address`, `max_connections`
35
+ - `max_item_size`, `verbosity`
36
+ - Custom config template support:
37
+ - HAProxy: `custom_config_template` option to provide your own config file
38
+ - Memcached: `custom_config_template` option to provide your own config file
39
+ - VPN/private network support with `private_ip` field for tracking internal IPs
40
+ - Multi-host deployment support for services
41
+ - Environment-based configuration (development, staging, production)
42
+ - Secret management via environment variables or command execution
43
+ - SSL/TLS support for HAProxy with automatic certificate management
44
+ - Health checking and status monitoring
45
+ - SSH key-based authentication with configurable options
46
+
47
+ ### Changed
48
+ - Made railties dependency optional for standalone Ruby usage
49
+ - Improved documentation with comprehensive examples
50
+ - Updated Ruby requirement to >= 3.0.0 (from >= 3.4.0)
51
+
52
+ ### Technical Details
53
+ - Uses SSHKit for SSH operations
54
+ - Supports Ubuntu 20.04+ servers (Debian-based distributions)
55
+ - YAML-based configuration with ERB support
56
+ - 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.
@@ -52,7 +52,8 @@ module BoringServices
52
52
  exit 1
53
53
  end
54
54
 
55
- desc 'reconfigure [SERVICE]', 'Reconfigure service(s) - skips package installation, only updates config and restarts'
55
+ desc 'reconfigure [SERVICE]',
56
+ 'Reconfigure service(s) - skips package installation, only updates config and restarts'
56
57
  def reconfigure(service_name = nil)
57
58
  config = Configuration.load(options[:config], options[:environment])
58
59
  installer = Installer.new(config)
@@ -108,7 +109,7 @@ module BoringServices
108
109
  end
109
110
 
110
111
  def print_memcached_stats(stats)
111
- bytes = format_bytes(stats['bytes']&.to_i || 0)
112
+ bytes = format_bytes(stats['bytes'].to_i)
112
113
  conns = stats['curr_connections'] || '0'
113
114
  items = stats['curr_items'] || '0'
114
115
  hit_rate = calculate_hit_rate(stats['get_hits'], stats['get_misses'])
@@ -116,8 +117,8 @@ module BoringServices
116
117
  end
117
118
 
118
119
  def calculate_hit_rate(hits, misses)
119
- hits = hits&.to_i || 0
120
- misses = misses&.to_i || 0
120
+ hits = hits.to_i
121
+ misses = misses.to_i
121
122
  total = hits + misses
122
123
  total.positive? ? ((hits.to_f / total) * 100).round(1) : 0
123
124
  end
@@ -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
@@ -257,7 +257,7 @@ module BoringServices
257
257
  ports_to_check << stats_port
258
258
 
259
259
  ports_to_check.each do |check_port|
260
- result = execute :sudo, :ss, '-tlnp', '|', :grep, "-E", "':#{check_port} '", raise_on_error: false
260
+ result = execute :sudo, :ss, '-tlnp', '|', :grep, '-E', "':#{check_port} '", raise_on_error: false
261
261
  if result
262
262
  puts " ✓ Port #{check_port} is listening"
263
263
  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'
@@ -152,10 +152,10 @@ module BoringServices
152
152
  end
153
153
  end
154
154
 
155
- def capture_on_host(host, *args)
155
+ def capture_on_host(host, *)
156
156
  output = nil
157
157
  execute_on_host(host) do
158
- output = capture(*args, raise_on_non_zero_exit: false)
158
+ output = capture(*, raise_on_non_zero_exit: false)
159
159
  end
160
160
  output
161
161
  end
@@ -1,3 +1,3 @@
1
1
  module BoringServices
2
- VERSION = '0.8.0'.freeze
2
+ VERSION = '0.8.1'.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.8.1
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
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: []