boring_services 0.8.1 → 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 +4 -4
- data/CHANGELOG.md +12 -0
- data/lib/boring_services/cli.rb +2 -0
- data/lib/boring_services/configuration.rb +59 -0
- data/lib/boring_services/services/haproxy.rb +1 -2
- data/lib/boring_services/ssh_executor.rb +37 -4
- data/lib/boring_services/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e67745f3e72ccf6dce8646fc1606dda6f080f7634f58e5e614e21d732a24baef
|
|
4
|
+
data.tar.gz: 6d16d20e74f47f17c855d217ceb59799a4c67e390f695050dd73ba09f949befd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1201bdcc2598275c86c3ceceae12193c398c4135e6e8cbe675b6f5dce5fa2896b1b27fd776ecfc5fd34c2af8456a092ca968734e52d6ceac58c46c7f055c454d
|
|
7
|
+
data.tar.gz: 9bc9c48947ffe1805a73accab2fa699c8c007d744722d785fe51b5cd555595bd14eb4ee33c0deb0ad6ce982c6c7d6b5e8d18c9f61127a7f45d5c2dbb4cb927bf
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
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
|
+
|
|
10
22
|
## [0.8.1] - 2026-08-20
|
|
11
23
|
|
|
12
24
|
### Changed
|
data/lib/boring_services/cli.rb
CHANGED
|
@@ -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
|
|
@@ -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
|
|
|
@@ -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...'
|
|
@@ -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
|
|
@@ -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
|
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.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- BoringCache
|
|
@@ -100,7 +100,7 @@ homepage: https://github.com/boringcache/services
|
|
|
100
100
|
licenses:
|
|
101
101
|
- MIT
|
|
102
102
|
metadata:
|
|
103
|
-
source_code_uri: https://github.com/boringcache/services
|
|
103
|
+
source_code_uri: https://github.com/boringcache/services/tree/v0.9.0
|
|
104
104
|
documentation_uri: https://github.com/boringcache/services/blob/main/README.md
|
|
105
105
|
changelog_uri: https://github.com/boringcache/services/blob/main/CHANGELOG.md
|
|
106
106
|
bug_tracker_uri: https://github.com/boringcache/services/issues
|