messhy 0.8.2 → 0.8.3

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: 6cc5f92c8b8a35dfb82b3639ae13d18e962e6f67ff042b845b4e545ed53e9be3
4
- data.tar.gz: 8165c8602fdcbb714fe9382dbadd217b399ee94d7c0ab987379c78964af7a166
3
+ metadata.gz: c1c3b2d1ed2cc9058e323f73a2df9e9a9543709583cead2e9d9a23632c91a18f
4
+ data.tar.gz: c10668cc8cfab172b6dd6b267265fbc310baa4d31323661efb6b2fc32062d152
5
5
  SHA512:
6
- metadata.gz: 0d86591c5ccd0e3276785836a0250f1e23d991d10c8c4d30920d852245506cfd1d405904c9adf657d16289c65571c802f87bf7111db16bccf0c7b71024675e79
7
- data.tar.gz: 7ce9ada0585d20b2cff7a108b4c5e68843b3489ceeab3aa21a572b7ab56910bd0a6fbe28e95a30f2f0b2dc3f317684ed123a8d576008e46e706a2c3f107e5d0b
6
+ metadata.gz: 5f1f2154ec3f236ae9aeb741ffa338af96a9ac4a4ba29db327da04bc2e21c719bde98bd1b1e572cb1d415a1fadf64c5d3f54e2807fd27f2909fddf3dcb18af12
7
+ data.tar.gz: 4ee6e07ed4089a669ab5d11e7c1c074a845fff98782a42c282088c62289d96707df99ab287c4001065d0c097601e75cd1388019afaf60ee195f6be4fc4662ec9
data/CHANGELOG.md ADDED
@@ -0,0 +1,51 @@
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.3] - 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
+ - Document key custody, SSH trust, and full-mesh security boundaries.
19
+
20
+ ## [0.1.0] - 2025-01-21
21
+
22
+ ### Added
23
+ - Initial release of Messhy
24
+ - Full WireGuard VPN mesh network setup across any VMs
25
+ - Every node connects directly to every other node (full mesh topology)
26
+ - Automated WireGuard installation and configuration
27
+ - Dynamic key generation and distribution
28
+ - Automatic peer configuration across all nodes
29
+ - Rails integration with generators and rake tasks
30
+ - Standalone Ruby support (no Rails dependency required)
31
+ - SSH-based deployment (no Python, Ansible, or Kubernetes)
32
+ - Health checking and status monitoring
33
+ - WireGuard status parsing and connection verification
34
+ - Host trust management for SSH connections
35
+ - Support for both public and private IP addresses
36
+ - YAML-based configuration with ERB support
37
+ - Secret management via environment variables or command execution
38
+
39
+ ### Technical Details
40
+ - Uses SSHKit for SSH operations
41
+ - Thor-based CLI interface with `messhy` command
42
+ - Automatic IP allocation for VPN network (10.8.0.0/24 by default)
43
+ - UDP port configuration (default: 51820)
44
+ - Compatible with Ubuntu 20.04+ servers (Debian-based distributions)
45
+ - Ruby >= 3.4.0 required
46
+
47
+ ### Commands
48
+ - `messhy setup` - Deploy WireGuard mesh network
49
+ - `messhy status` - Check mesh network health and connectivity
50
+ - `messhy add_node` - Add new node to existing mesh
51
+ - `messhy remove_node` - Remove node from mesh
data/README.md CHANGED
@@ -6,21 +6,10 @@ A Ruby gem that sets up a full WireGuard VPN mesh across any VMs. Every node con
6
6
 
7
7
  ## Why messhy?
8
8
 
9
- ### Problems It Solves
10
-
11
- **Without messhy:**
12
- - Database replication over public IPs (insecure)
13
- - Complex VPN configurations
14
- - NAT traversal issues
15
- - Manual key management
16
- - Cloud-specific networking (VPC, subnet, security groups)
17
-
18
- ✅ **With messhy:**
19
- - Secure encrypted connections (WireGuard)
20
- - Automatic key generation and distribution
21
- - Works across any cloud/datacenter
22
- - Zero application changes (just use 10.8.0.x IPs)
23
- - Simple configuration
9
+ Messhy generates WireGuard keys and peer configuration, installs it over SSH,
10
+ and gives each node a stable private address. It is intended for small fleets
11
+ whose operators control every host and can explicitly allow WireGuard traffic
12
+ between peers.
24
13
 
25
14
  ## Installation
26
15
 
@@ -285,17 +274,25 @@ iptables -A INPUT -p udp --dport 51820 -j ACCEPT
285
274
  - Optimal routing (direct connections)
286
275
  - Scales to ~50 nodes
287
276
 
288
- ## Performance
277
+ ## Capacity and performance
289
278
 
290
- ### Benchmarks (compared to no VPN)
279
+ A full mesh creates one peer relationship between every pair of nodes, so the
280
+ configuration grows quadratically. Messhy is designed for small fleets (about
281
+ 50 nodes or fewer), not large or frequently changing membership. Throughput,
282
+ latency, and CPU overhead depend on the hosts, network path, MTU, and workload;
283
+ benchmark the actual topology before relying on a capacity estimate.
291
284
 
292
- | Metric | No VPN | WireGuard | Overhead |
293
- |--------|--------|-----------|----------|
294
- | Throughput | 1000 Mbps | 950 Mbps | 5% |
295
- | Latency | 10ms | 10.5ms | +0.5ms |
296
- | CPU usage | 2% | 3% | +1% |
285
+ ## Security boundaries
297
286
 
298
- **WireGuard is FAST!** Negligible overhead for most workloads.
287
+ - `trust-hosts` collects host keys with `ssh-keyscan`; verify fingerprints over
288
+ a separate trusted channel before relying on them.
289
+ - Anyone with a generated node private key can impersonate that node. Store
290
+ `.secrets/wireguard` in an encrypted vault and rotate keys after suspected
291
+ disclosure or membership changes.
292
+ - Messhy configures WireGuard, but it does not replace host firewalls, patching,
293
+ SSH hardening, or application-level authentication.
294
+ - Full-mesh membership grants direct network reachability. Only enroll hosts
295
+ that should be able to reach one another.
299
296
 
300
297
  ## Troubleshooting
301
298
 
@@ -330,7 +327,7 @@ messhy setup
330
327
 
331
328
  ## Requirements
332
329
 
333
- - Ruby 3.0+
330
+ - Ruby 4.0+
334
331
  - WireGuard tools (`wg` command)
335
332
  - Target servers with Linux kernel 5.6+ (WireGuard built-in)
336
333
  - SSH key-based authentication
@@ -345,4 +342,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
345
342
 
346
343
  ---
347
344
 
348
- Built with ❤️ for the Rails community by [Gaurav](https://github.com/yourusername)
345
+ Maintained by [BoringCache](https://github.com/boringcache).
data/SECURITY.md ADDED
@@ -0,0 +1,21 @@
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/messhy/security/advisories/new).
8
+
9
+ Include the affected version, expected impact, and enough reproduction detail
10
+ for us to investigate. Do not include live credentials, private keys, customer
11
+ 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 WireGuard key handling, SSH host verification, generated network
21
+ configuration, command execution, or release integrity are welcome.
@@ -79,7 +79,7 @@ module Messhy
79
79
  execute :sudo, 'resolvectl', 'flush-caches', raise_on_error: false
80
80
  elsif test('[ -d /etc/resolvconf/resolv.conf.d ]', raise_on_error: false)
81
81
  head_path = '/etc/resolvconf/resolv.conf.d/head'
82
- content = server_ips.map { |ip| "nameserver #{ip}" }.join("\n") + "\n"
82
+ content = "#{server_ips.map { |ip| "nameserver #{ip}" }.join("\n")}\n"
83
83
  upload! StringIO.new(content), '/tmp/messhy-resolv.conf'
84
84
  execute :sudo, 'mv', '/tmp/messhy-resolv.conf', head_path
85
85
  execute :sudo, 'chmod', '644', head_path
@@ -112,7 +112,7 @@ module Messhy
112
112
  end
113
113
  end
114
114
 
115
- records.transform_values { |ips| ips.uniq }
115
+ records.transform_values(&:uniq)
116
116
  end
117
117
 
118
118
  def resolve_target(target)
@@ -159,7 +159,7 @@ module Messhy
159
159
  end
160
160
  end
161
161
 
162
- lines.join("\n") + "\n"
162
+ "#{lines.join("\n")}\n"
163
163
  end
164
164
  end
165
165
  end
@@ -94,7 +94,6 @@ module Messhy
94
94
  end
95
95
  end
96
96
 
97
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
98
97
  def test_all
99
98
  puts '==> Testing mesh connectivity...'
100
99
  puts
@@ -148,7 +147,6 @@ module Messhy
148
147
  puts 'If a pair still reports a failure, there has been no recent handshake—check UDP 51820 and ' \
149
148
  'keepalive/route settings.'
150
149
  end
151
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
152
150
 
153
151
  def show_stats(node: nil)
154
152
  if node
@@ -179,10 +177,10 @@ module Messhy
179
177
  service = capture(:systemctl, 'is-active', 'dnsmasq', raise_on_non_zero_exit: false).strip
180
178
  messhy_records = capture(:bash, '-c',
181
179
  "sudo awk 'BEGIN{c=0} /^address=\\//{c++} END{print c}' " \
182
- "/etc/dnsmasq.d/messhy.conf 2>/dev/null || true").strip
180
+ '/etc/dnsmasq.d/messhy.conf 2>/dev/null || true').strip
183
181
  ap_records = capture(:bash, '-c',
184
182
  "sudo awk 'BEGIN{c=0} /^address=\\//{c++} END{print c}' " \
185
- "/etc/dnsmasq.d/active_postgres.conf 2>/dev/null || true").strip
183
+ '/etc/dnsmasq.d/active_postgres.conf 2>/dev/null || true').strip
186
184
 
187
185
  status_icon = service == 'active' ? '✓' : '✗'
188
186
  puts "#{status_icon} #{node_name} (#{node_config['private_ip']})#{label_display} - dnsmasq #{service}"
@@ -224,7 +222,7 @@ module Messhy
224
222
  end
225
223
 
226
224
  max_name_len = node_names.map(&:length).max
227
- header = ' ' * (max_name_len + 2) + node_names.map { |n| n[0..7].rjust(8) }.join(' ')
225
+ header = (' ' * (max_name_len + 2)) + node_names.map { |n| n[0..7].rjust(8) }.join(' ')
228
226
  puts header
229
227
  puts '-' * header.length
230
228
 
@@ -38,9 +38,9 @@ module Messhy
38
38
  if config.dns_enabled?
39
39
  dns_domain = config.dns_domain
40
40
  dns_interface = config.dns_interface
41
- dns_server_ips = config.dns_server_nodes.filter_map { |name|
41
+ dns_server_ips = config.dns_server_nodes.filter_map do |name|
42
42
  config.node_config(name)&.dig('private_ip')
43
- }
43
+ end
44
44
  end
45
45
 
46
46
  is_dns_server = config.dns_enabled? && config.dns_server_nodes.include?(node_name)
@@ -176,9 +176,7 @@ module Messhy
176
176
  latency = nil
177
177
  execute_on_node(source_node) do
178
178
  output = capture(:ping, '-c', '3', '-W', '2', '-I', 'wg0', target_ip, raise_on_non_zero_exit: false)
179
- if output =~ %r{rtt min/avg/max/mdev = [\d.]+/([\d.]+)/}
180
- latency = ::Regexp.last_match(1).to_f
181
- end
179
+ latency = ::Regexp.last_match(1).to_f if output =~ %r{rtt min/avg/max/mdev = [\d.]+/([\d.]+)/}
182
180
  end
183
181
  latency
184
182
  rescue StandardError
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Messhy
4
- VERSION = '0.8.2'
4
+ VERSION = '0.8.3'
5
5
  end
@@ -20,35 +20,50 @@ module Messhy
20
20
  def parse_handshake_seconds(desc)
21
21
  return nil if desc.strip.casecmp('(none)').zero?
22
22
 
23
- matches = desc.scan(/(\d+)\s+(second|minute|hour|day)s?/i)
24
- return nil if matches.empty?
23
+ total = 0
24
+ words = desc.downcase.delete(',').split
25
25
 
26
- matches.sum do |value, unit|
27
- TIME_UNITS_IN_SECONDS[unit.downcase] * value.to_i
26
+ words.each_cons(2) do |value, unit|
27
+ unit = unit.delete_suffix('s')
28
+ next unless integer_token?(value) && TIME_UNITS_IN_SECONDS.key?(unit)
29
+
30
+ total += TIME_UNITS_IN_SECONDS.fetch(unit) * value.to_i
28
31
  end
32
+
33
+ total.positive? ? total : nil
29
34
  end
30
35
 
31
36
  def extract_handshake_time(peer_block)
32
37
  return nil unless peer_block
33
38
 
34
- desc = peer_block[/latest handshake:\s*(.+)/, 1]
39
+ desc = line_value(peer_block, 'latest handshake: ')
35
40
  return nil unless desc&.include?('ago')
36
41
 
37
42
  parse_handshake_seconds(desc)
38
43
  end
39
44
 
40
45
  def extract_transfer_stats(peer_block)
41
- rx = peer_block.match(/transfer: (.+?) received/)&.[](1) || '0 B'
42
- tx = peer_block.match(/received, (.+?) sent/)&.[](1) || '0 B'
43
- { received: rx, sent: tx }
46
+ transfer = line_value(peer_block, 'transfer: ')
47
+ rx, tx = transfer&.split(' received, ', 2)
48
+ tx = tx&.delete_suffix(' sent')
49
+
50
+ { received: rx || '0 B', sent: tx || '0 B' }
44
51
  end
45
52
 
46
53
  def extract_endpoint(peer_block)
47
- peer_block[/endpoint: (.+?)$/, 1]
54
+ line_value(peer_block, 'endpoint: ')
48
55
  end
49
56
 
50
57
  def extract_allowed_ips(peer_block)
51
- peer_block[/allowed ips: (.+?)$/, 1]
58
+ line_value(peer_block, 'allowed ips: ')
59
+ end
60
+
61
+ def line_value(block, prefix)
62
+ block.each_line(chomp: true).find { |line| line.start_with?(prefix) }&.delete_prefix(prefix)
63
+ end
64
+
65
+ def integer_token?(value)
66
+ !value.empty? && value.each_char.all? { |char| char.between?('0', '9') }
52
67
  end
53
68
  end
54
69
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: messhy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 0.8.3
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: Sets up a full WireGuard VPN mesh across any VMs. Every node connects
111
69
  directly to every other node for secure private networking.
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/messhy
122
82
  - lib/messhy.rb
123
83
  - lib/messhy/cli.rb
@@ -138,10 +98,10 @@ homepage: https://github.com/boringcache/messhy
138
98
  licenses:
139
99
  - MIT
140
100
  metadata:
141
- homepage_uri: https://github.com/boringcache/messhy
142
101
  source_code_uri: https://github.com/boringcache/messhy
143
102
  documentation_uri: https://github.com/boringcache/messhy/blob/main/README.md
144
103
  changelog_uri: https://github.com/boringcache/messhy/blob/main/CHANGELOG.md
104
+ bug_tracker_uri: https://github.com/boringcache/messhy/issues
145
105
  rubygems_mfa_required: 'true'
146
106
  rdoc_options: []
147
107
  require_paths:
@@ -150,14 +110,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
150
110
  requirements:
151
111
  - - ">="
152
112
  - !ruby/object:Gem::Version
153
- version: 3.4.0
113
+ version: 4.0.0
154
114
  required_rubygems_version: !ruby/object:Gem::Requirement
155
115
  requirements:
156
116
  - - ">="
157
117
  - !ruby/object:Gem::Version
158
118
  version: '0'
159
119
  requirements: []
160
- rubygems_version: 3.6.9
120
+ rubygems_version: 4.0.16
161
121
  specification_version: 4
162
122
  summary: WireGuard VPN mesh for Ruby & Rails apps
163
123
  test_files: []