messhy 0.9.0 → 0.9.2

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: 14432b60b44c17938ab995bb9e9ca0a360f1381bbfe4f746e3bcc6a07be2e658
4
- data.tar.gz: d98203d9b327d971f61e81022611e0f54d9e08673d381ae57fa27b58a55dabeb
3
+ metadata.gz: fbcab708f43ed1ee21e30b2162ce6ec7aa4e762dfebeea5874d4f0f7d55a46d1
4
+ data.tar.gz: 5085a9ad4d3f8429e22544d32ca60275eda1079934f4dd63c5a80dd54c0944ec
5
5
  SHA512:
6
- metadata.gz: c8db3c1b1b7f71680e565c0373b463f4c749d4be645a90f587970a8955ef95d1684dffe2bf46d4cbbaf4de763e4d21482ed7c9e8aa72d4a64c56190c1076038b
7
- data.tar.gz: 2e59b8e950931e8f27a4f004a5e9e381176ced1cb670fd16f03045ea4ee0856a2f81e577a5fc07fc5b535d4bd45218989db72b896ec0ab18cfe003af864517aa
6
+ metadata.gz: 490c2acab762f4f14b39731a1c33408b0e9131316dff4f52ec2b1083d0b03273e475709d47a435cf7566f524013bff90ae7fb4cdb70cfbf5d2b4fc15fe62def8
7
+ data.tar.gz: 2f29f271a42b84f6f9b205233c175f203fa0a69efe5fc65b119e570a2029cafc2a8e6b4f4cc725aa7da6ac07eabf8216e7c745cf53d66ada9e53e88a89ae649a
data/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.9.2] - 2026-10-02
11
+
12
+ ### Changed
13
+
14
+ - Update development dependencies.
15
+ - Link gem source metadata to the matching release tag.
16
+
17
+ ## [0.9.1] - 2026-09-02
18
+
19
+ ### Fixed
20
+
21
+ - Preserve live peers outside the source-owned mesh during reconciliation.
22
+ Exact-source pruning is available only through the explicit `--prune` option.
23
+
10
24
  ## [0.9.0] - 2026-09-02
11
25
 
12
26
  ### Added
data/README.md CHANGED
@@ -178,8 +178,11 @@ messhy reconcile --environment=production
178
178
  `import-keys` writes the same mode-`0600` local secret files as initial setup
179
179
  and never prints key material. `reconcile` validates each candidate config,
180
180
  applies active peer changes with `wg syncconf`, and restores the previous config
181
- if a live sync fails. Set `ssh_known_hosts_file` to a reviewed file when the
182
- mesh should not depend on the operator's global SSH configuration.
181
+ if a live sync fails. Existing peers outside the source configuration are
182
+ preserved by default so another system can own adjacent mesh members. Pass
183
+ `--prune` only when the source configuration should remove those peers. Set
184
+ `ssh_known_hosts_file` to a reviewed file when the mesh should not depend on
185
+ the operator's global SSH configuration.
183
186
 
184
187
  ### Status & Monitoring
185
188
 
data/lib/messhy/cli.rb CHANGED
@@ -28,9 +28,10 @@ module Messhy
28
28
 
29
29
  desc 'reconcile', 'Apply mesh changes without restarting active WireGuard interfaces'
30
30
  option :dry_run, type: :boolean, default: false
31
+ option :prune, type: :boolean, default: false, desc: 'Remove live peers absent from the source configuration'
31
32
  def reconcile
32
33
  config = load_config
33
- Installer.new(config, dry_run: options[:dry_run]).reconcile
34
+ Installer.new(config, dry_run: options[:dry_run], prune: options[:prune]).reconcile
34
35
  rescue SSHKit::Runner::ExecuteError => e
35
36
  handle_ssh_error(e, config)
36
37
  end
@@ -8,7 +8,7 @@ module Messhy
8
8
  DEFAULT_TIMEOUT = 5
9
9
  DEFAULT_KEY_TYPES = %w[ed25519 ecdsa rsa].freeze
10
10
 
11
- # rubocop:disable Metrics/ParameterLists
11
+ # rubocop:disable-next Metrics/ParameterLists
12
12
  def initialize(config,
13
13
  known_hosts_path: File.expand_path('~/.ssh/known_hosts'),
14
14
  timeout: DEFAULT_TIMEOUT,
@@ -22,7 +22,6 @@ module Messhy
22
22
  @hash_hosts = hash_hosts
23
23
  @replace_existing = replace_existing
24
24
  end
25
- # rubocop:enable Metrics/ParameterLists
26
25
 
27
26
  def trust_all_hosts
28
27
  ensure_ssh_keyscan!
@@ -7,13 +7,14 @@ require 'digest'
7
7
  require 'base64'
8
8
 
9
9
  module Messhy
10
- # rubocop:disable Metrics/ClassLength
10
+ # rubocop:disable-next Metrics/ClassLength
11
11
  class Installer
12
- attr_reader :config, :dry_run, :ssh_executor
12
+ attr_reader :config, :dry_run, :prune, :ssh_executor
13
13
 
14
- def initialize(config, dry_run: false, ssh_executor: SSHExecutor.new(config))
14
+ def initialize(config, dry_run: false, prune: false, ssh_executor: SSHExecutor.new(config))
15
15
  @config = config
16
16
  @dry_run = dry_run
17
+ @prune = prune
17
18
  @ssh_executor = ssh_executor
18
19
  @node_keys = load_existing_keys
19
20
  @psk_map = load_existing_psks
@@ -52,6 +53,7 @@ module Messhy
52
53
  if dry_run
53
54
  puts " [DRY RUN] Would reconcile #{node_name}"
54
55
  else
56
+ config_content = preserve_unmanaged_peers(node_name, config_content) unless prune
55
57
  ssh_executor.reconcile_config(node_name, config_content)
56
58
  puts " ✓ Reconciled #{node_name}"
57
59
  end
@@ -179,6 +181,12 @@ module Messhy
179
181
 
180
182
  private
181
183
 
184
+ def preserve_unmanaged_peers(node_name, config_content)
185
+ current = WireguardConfig.parse(ssh_executor.read_wireguard_config(node_name))
186
+ managed_peer_names = config.node_names - [node_name]
187
+ current.preserve_unmanaged_peers_in(config_content, managed_peer_names: managed_peer_names)
188
+ end
189
+
182
190
  def validate_imported_address!(node_name, node_config, snapshot)
183
191
  address = snapshot.interface['Address'].to_s.split('/').first
184
192
  return if address == node_config['mesh_ip']
@@ -414,5 +422,4 @@ module Messhy
414
422
  base[0, 44]
415
423
  end
416
424
  end
417
- # rubocop:enable Metrics/ClassLength
418
425
  end
@@ -12,7 +12,7 @@ module Messhy
12
12
  @psk_map = psk_map
13
13
  end
14
14
 
15
- # rubocop:disable Metrics/AbcSize
15
+ # rubocop:disable-next Metrics/AbcSize
16
16
  def build_config_for_node(node_name)
17
17
  node_config = config.node_config(node_name)
18
18
  raise Error, "Node not found: #{node_name}" unless node_config
@@ -73,7 +73,6 @@ module Messhy
73
73
  binding_context = binding
74
74
  template.result(binding_context)
75
75
  end
76
- # rubocop:enable Metrics/AbcSize
77
76
 
78
77
  def build_all_configs
79
78
  configs = {}
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Messhy
4
- VERSION = '0.9.0'
4
+ VERSION = '0.9.2'
5
5
  end
@@ -36,5 +36,33 @@ module Messhy
36
36
 
37
37
  self
38
38
  end
39
+
40
+ def preserve_unmanaged_peers_in(candidate_content, managed_peer_names:)
41
+ candidate = self.class.parse(candidate_content)
42
+ candidate_public_keys = candidate.peers.filter_map { |peer| peer['PublicKey'] }
43
+ managed_peer_names = managed_peer_names.to_set
44
+
45
+ unmanaged_peers = peers.reject do |peer|
46
+ managed_peer_names.include?(peer['Name']) || candidate_public_keys.include?(peer['PublicKey'])
47
+ end
48
+
49
+ return candidate_content if unmanaged_peers.empty?
50
+
51
+ [candidate_content.rstrip, *unmanaged_peers.map { |peer| render_peer(peer) }, ''].join("\n\n")
52
+ end
53
+
54
+ private
55
+
56
+ def render_peer(peer)
57
+ lines = []
58
+ lines << "# Peer: #{peer['Name']}" if peer['Name']
59
+ lines << '[Peer]'
60
+ peer.each do |key, value|
61
+ next if key == 'Name'
62
+
63
+ lines << "#{key} = #{value}"
64
+ end
65
+ lines.join("\n")
66
+ end
39
67
  end
40
68
  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.9.0
4
+ version: 0.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - BoringCache
@@ -99,7 +99,7 @@ homepage: https://github.com/boringcache/messhy
99
99
  licenses:
100
100
  - MIT
101
101
  metadata:
102
- source_code_uri: https://github.com/boringcache/messhy
102
+ source_code_uri: https://github.com/boringcache/messhy/tree/v0.9.2
103
103
  documentation_uri: https://github.com/boringcache/messhy/blob/main/README.md
104
104
  changelog_uri: https://github.com/boringcache/messhy/blob/main/CHANGELOG.md
105
105
  bug_tracker_uri: https://github.com/boringcache/messhy/issues