Class: Messhy::SSHExecutor

Inherits:
Object
  • Object
show all
Includes:
SSHKit::DSL
Defined in:
lib/messhy/ssh_executor.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ SSHExecutor

Returns a new instance of SSHExecutor.



13
14
15
16
# File 'lib/messhy/ssh_executor.rb', line 13

def initialize(config)
  @config = config
  setup_sshkit
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



11
12
13
# File 'lib/messhy/ssh_executor.rb', line 11

def config
  @config
end

Instance Method Details

#enable_and_start_wireguard(node_name) ⇒ Object



141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/messhy/ssh_executor.rb', line 141

def enable_and_start_wireguard(node_name)
  execute_on_node(node_name) do
    # Enable systemd service
    execute :sudo, 'systemctl', 'enable', 'wg-quick@wg0'

    # Restart WireGuard
    if test('systemctl is-active wg-quick@wg0')
      execute :sudo, 'systemctl', 'restart', 'wg-quick@wg0'
    else
      execute :sudo, 'systemctl', 'start', 'wg-quick@wg0'
    end
  end
end

#execute_on_all_nodes(skip: nil) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
# File 'lib/messhy/ssh_executor.rb', line 26

def execute_on_all_nodes(skip: nil, &)
  hosts = config.each_node.with_object([]) do |(node_name, node_config), collection|
    next if skip && node_name == skip

    collection << host_for(node_name, node_config)
  end

  return if hosts.empty?

  on(hosts, in: :parallel, &)
end

#execute_on_node(node_name) ⇒ Object

Raises:



18
19
20
21
22
23
24
# File 'lib/messhy/ssh_executor.rb', line 18

def execute_on_node(node_name, &)
  node_config = config.node_config(node_name)
  raise Error, "Node not found: #{node_name}" unless node_config

  host = host_for(node_name, node_config)
  on(host, &)
end

#generate_keypair_on_node(node_name) ⇒ Object



60
61
62
63
64
65
66
67
# File 'lib/messhy/ssh_executor.rb', line 60

def generate_keypair_on_node(node_name)
  keypair = {}
  execute_on_node(node_name) do
    keypair[:private_key] = capture('wg', 'genkey').strip
    keypair[:public_key] = capture(:echo, keypair[:private_key], '|', 'wg', 'pubkey').strip
  end
  keypair
end

#generate_psk_on_node(node_name) ⇒ Object



69
70
71
72
73
74
75
# File 'lib/messhy/ssh_executor.rb', line 69

def generate_psk_on_node(node_name)
  psk = nil
  execute_on_node(node_name) do
    psk = capture('wg', 'genpsk').strip
  end
  psk
end

#get_wireguard_status(node_name) ⇒ Object



155
156
157
158
159
160
161
# File 'lib/messhy/ssh_executor.rb', line 155

def get_wireguard_status(node_name)
  result = nil
  execute_on_node(node_name) do
    result = capture(:sudo, 'wg', 'show', 'wg0')
  end
  result
end

#install_wireguard(node_name) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/messhy/ssh_executor.rb', line 38

def install_wireguard(node_name)
  execute_on_node(node_name) do
    # Check if WireGuard is already installed
    if test('[ -f /usr/bin/wg ]')
      info 'WireGuard already installed'
    else
      info 'Installing WireGuard...'
      execute :sudo, 'apt-get', 'update', '-qq'
      execute :sudo, 'DEBIAN_FRONTEND=noninteractive', 'apt-get', 'install', '-y', '-qq', 'wireguard',
              'iputils-ping'
    end

    # Install ping if not available
    unless test('which', 'ping', raise_on_error: false)
      info 'Installing ping utility...'
      execute :sudo, 'apt-get', 'update', '-qq', raise_on_error: false
      execute :sudo, 'DEBIAN_FRONTEND=noninteractive', 'apt-get', 'install', '-y', '-qq', 'iputils-ping',
              raise_on_error: false
    end
  end
end

#install_wireguard_on_all_nodes(skip: nil) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/messhy/ssh_executor.rb', line 77

def install_wireguard_on_all_nodes(skip: nil)
  execute_on_all_nodes(skip: skip) do
    # Check if WireGuard is already installed
    if test('[ -f /usr/bin/wg ]')
      info 'WireGuard already installed'
    else
      info 'Installing WireGuard...'
      execute :sudo, 'apt-get', 'update', '-qq'
      execute :sudo, 'DEBIAN_FRONTEND=noninteractive', 'apt-get', 'install', '-y', '-qq', 'wireguard',
              'iputils-ping'
    end

    # Install ping if not available
    unless test('which', 'ping', raise_on_error: false)
      info 'Installing ping utility...'
      execute :sudo, 'apt-get', 'update', '-qq', raise_on_error: false
      execute :sudo, 'DEBIAN_FRONTEND=noninteractive', 'apt-get', 'install', '-y', '-qq', 'iputils-ping',
              raise_on_error: false
    end
  end
end

#measure_latency(source_node, target_ip) ⇒ Object



175
176
177
178
179
180
181
182
183
184
# File 'lib/messhy/ssh_executor.rb', line 175

def measure_latency(source_node, target_ip)
  latency = nil
  execute_on_node(source_node) do
    output = capture(:ping, '-c', '3', '-W', '2', '-I', 'wg0', target_ip, raise_on_non_zero_exit: false)
    latency = ::Regexp.last_match(1).to_f if output =~ %r{rtt min/avg/max/mdev = [\d.]+/([\d.]+)/}
  end
  latency
rescue StandardError
  nil
end

#ping_node_from(source_node, target_ip) ⇒ Object



163
164
165
166
167
168
169
170
171
172
173
# File 'lib/messhy/ssh_executor.rb', line 163

def ping_node_from(source_node, target_ip)
  success = false
  execute_on_node(source_node) do
    if test('which', 'ping', raise_on_error: false)
      success = test('timeout', '3', 'ping', '-c', '1', '-W', '1', '-I', 'wg0', target_ip, raise_on_error: false)
    end
  end
  success
rescue StandardError
  false
end

#purge_wireguard(node_name) ⇒ Object



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/messhy/ssh_executor.rb', line 220

def purge_wireguard(node_name)
  execute_on_node(node_name) do
    # Stop and disable service
    if test('systemctl is-active wg-quick@wg0', raise_on_error: false)
      execute :sudo, 'systemctl', 'stop', 'wg-quick@wg0', raise_on_error: false
    end
    if test('systemctl is-enabled wg-quick@wg0', raise_on_error: false)
      execute :sudo, 'systemctl', 'disable', 'wg-quick@wg0', raise_on_error: false
    end

    # Remove interface
    if test('[ -d /sys/class/net/wg0 ]', raise_on_error: false)
      execute :sudo, 'ip', 'link', 'delete', 'wg0', raise_on_error: false
    end

    # Remove config
    if test('[ -f /etc/wireguard/wg0.conf ]', raise_on_error: false)
      execute :sudo, 'rm', '-f', '/etc/wireguard/wg0.conf', raise_on_error: false
    end
  end
end

#restart_wireguard(node_name) ⇒ Object



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/messhy/ssh_executor.rb', line 197

def restart_wireguard(node_name)
  execute_on_node(node_name) do
    # Stop service first
    if test('systemctl is-active wg-quick@wg0', raise_on_error: false)
      execute :sudo, 'systemctl', 'stop', 'wg-quick@wg0'
    end

    # Remove interface if it exists
    if test('[ -d /sys/class/net/wg0 ]', raise_on_error: false)
      execute :sudo, 'ip', 'link', 'delete', 'wg0', raise_on_error: false
    end

    # Start fresh
    execute :sudo, 'systemctl', 'start', 'wg-quick@wg0'
  end
end

#stop_wireguard(node_name) ⇒ Object



214
215
216
217
218
# File 'lib/messhy/ssh_executor.rb', line 214

def stop_wireguard(node_name)
  execute_on_node(node_name) do
    execute :sudo, 'systemctl', 'stop', 'wg-quick@wg0' if test('systemctl is-active wg-quick@wg0')
  end
end

#test_tcp_connectivity(source_node, target_ip, port = 22) ⇒ Object



186
187
188
189
190
191
192
193
194
195
# File 'lib/messhy/ssh_executor.rb', line 186

def test_tcp_connectivity(source_node, target_ip, port = 22)
  success = false
  execute_on_node(source_node) do
    success = test('timeout', '2', 'bash', '-c',
                   "exec 3<>/dev/tcp/#{target_ip}/#{port} 2>&1 && exec 3<&- && exec 3>&-", raise_on_error: false)
  end
  success
rescue StandardError
  false
end

#upload_and_start_configs(configs) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/messhy/ssh_executor.rb', line 111

def upload_and_start_configs(configs)
  hosts = configs.filter_map do |node_name, config_content|
    node_config = config.node_config(node_name)
    next unless node_config

    host = host_for(node_name, node_config)
    manage_property(host.properties, :config_content, config_content)
    host
  end

  return if hosts.empty?

  executor = self

  on hosts, in: :parallel do |host|
    properties = host.properties
    config_content = executor.send(:manage_property, properties, :config_content)
    temp_file = '/tmp/wg0.conf'
    upload! StringIO.new(config_content), temp_file
    execute :sudo, 'mv', temp_file, '/etc/wireguard/wg0.conf'
    execute :sudo, 'chmod', '600', '/etc/wireguard/wg0.conf'
    execute :sudo, 'systemctl', 'enable', 'wg-quick@wg0'
    if test('systemctl is-active wg-quick@wg0')
      execute :sudo, 'systemctl', 'restart', 'wg-quick@wg0'
    else
      execute :sudo, 'systemctl', 'start', 'wg-quick@wg0'
    end
  end
end

#upload_config(node_name, config_content) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
# File 'lib/messhy/ssh_executor.rb', line 99

def upload_config(node_name, config_content)
  execute_on_node(node_name) do
    # Create temporary file
    temp_file = '/tmp/wg0.conf'
    upload! StringIO.new(config_content), temp_file

    # Move to /etc/wireguard with proper permissions
    execute :sudo, 'mv', temp_file, '/etc/wireguard/wg0.conf'
    execute :sudo, 'chmod', '600', '/etc/wireguard/wg0.conf'
  end
end