summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-29 10:56:54 +0300
committerPaul Buetow <paul@buetow.org>2026-06-29 10:56:54 +0300
commit70acbd00e0f235d8f257a1f23a558a66ece55e9c (patch)
treef2ec14d6b1a5d70f1fe84499a4f248118d51cd12
parent66c23888d15f764d910945445dd00036b6d7d80e (diff)
Add reachable_via support so earth is durably reachable via fishfinger gateway
Adds a reachable_via: fishfinger field to the earth host entry in YAML. In the Ruby generator, when building a gateway peer's AllowedIPs on infra hosts, hosts declaring reachable_via: <gateway> now have their wg0 IPv4/32 and IPv6/128 appended to that gateway peer's AllowedIPs. This means every infra host that peers with fishfinger now routes 192.168.2.200/32, fd42:beef:cafe:2::200/128 via wg0, so traffic destined for earth flows fishfinger -> earth without requiring a direct peer block. The fix survives --generate and --install, replacing the prior manual one-off edits that were reverted on each regen. Refactors WireguardConfig#peers into peers + compute_allowed_ips + roaming_no_gateway_ips + extra_ips_via_gateway (each under ~30 lines). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--wireguardmeshgenerator.rb94
-rw-r--r--wireguardmeshgenerator.yaml348
2 files changed, 249 insertions, 193 deletions
diff --git a/wireguardmeshgenerator.rb b/wireguardmeshgenerator.rb
index c4dc11a..ee9492f 100644
--- a/wireguardmeshgenerator.rb
+++ b/wireguardmeshgenerator.rb
@@ -172,10 +172,8 @@ WireguardConfig = Struct.new(:myself, :hosts) do
'DNS = 1.1.1.1, 8.8.8.8'
end
- # Generates a list of peer configurations for the WireGuard mesh network.
- # Excludes peers specified in the `exclude_peers` list and the current host itself.
- # Determines the appropriate endpoint and keepalive settings for each peer.
- # Roaming clients (no 'lan' or 'internet' sections) get PersistentKeepalive to all peers.
+ # Builds peer entries for the WireGuard mesh. Excludes hosts in exclude_peers and self.
+ # Determines endpoint, keepalive, and AllowedIPs per peer based on network topology.
def peers
exclude = hosts[myself].fetch('exclude_peers', []).append(myself)
# Check if the current host is in the local area network (LAN).
@@ -185,8 +183,9 @@ WireguardConfig = Struct.new(:myself, :hosts) do
is_roaming = !hosts[myself].key?('lan') && !hosts[myself].key?('internet')
# Check if this host should use gateways for default route (gateway: false disables this).
use_gateway = hosts[myself].fetch('gateway', true)
- # Track if we've assigned the primary gateway (for mesh subnet routing).
+ # Track if we've assigned the primary gateway (for mesh subnet routing via gateway:false).
primary_gateway_assigned = false
+
hosts.reject { exclude.include?(_1) }.map do |peer, data|
# Check if peer is roaming (no lan or internet section).
# Roaming peers are always behind NAT and cannot be reached directly.
@@ -205,36 +204,69 @@ WireguardConfig = Struct.new(:myself, :hosts) do
# Set keepalive: LAN hosts connecting to internet hosts, OR roaming clients connecting to anyone.
keepalive = is_roaming || (in_lan && !peer_in_lan)
- # For roaming clients with gateway: true, route all traffic through VPN (0.0.0.0/0, ::/0).
- # For roaming clients with gateway: false, route mesh subnet through first gateway,
- # and use specific IPs for other gateways.
- # For regular mesh peers, route their specific IPv4 (and IPv6 if present).
- if is_roaming && use_gateway
- allowed_ips = '0.0.0.0/0, ::/0'
- elsif is_roaming && !use_gateway
- # Roaming client but not using gateways for default route.
- # First internet gateway gets the mesh subnet, others get specific IPs.
- peer_is_gateway = data.key?('internet')
- if peer_is_gateway && !primary_gateway_assigned
- # Primary gateway: route all mesh traffic through it
- allowed_ips = '192.168.2.0/24, fd42:beef:cafe:2::/64'
- primary_gateway_assigned = true
- else
- # Secondary gateway or non-gateway: just its specific IP
- ipv4 = data['wg0']['ip']
- ipv6 = data['wg0']['ipv6']
- allowed_ips = ipv6 ? "#{ipv4}/32, #{ipv6}/128" : "#{ipv4}/32"
- end
- else
- # For mesh peers, allow both IPv4 and IPv6 if present
- ipv4 = data['wg0']['ip']
- ipv6 = data['wg0']['ipv6']
- allowed_ips = ipv6 ? "#{ipv4}/32, #{ipv6}/128" : "#{ipv4}/32"
- end
+ allowed_ips, primary_gateway_assigned = compute_allowed_ips(
+ peer, data, is_roaming, use_gateway, primary_gateway_assigned
+ )
+
PeerSnippet.new(peer, myself, reach['domain'], data['wg0']['domain'],
allowed_ips, endpoint, keepalive)
end
end
+
+ # Returns additional AllowedIPs to append to a gateway peer's entry on infra hosts.
+ # Hosts that declare `reachable_via: <gateway>` are NAT-roaming clients whose
+ # return traffic must flow via that gateway back to them. This ensures the gateway
+ # peer's AllowedIPs covers the roaming client's wg0 IPs, so infra hosts route
+ # traffic destined for those clients through the gateway.
+ def extra_ips_via_gateway(gateway_name)
+ hosts.filter_map do |_name, data|
+ next unless data['reachable_via'] == gateway_name
+
+ ipv4 = data['wg0']['ip']
+ ipv6 = data['wg0']['ipv6']
+ ipv6 ? "#{ipv4}/32, #{ipv6}/128" : "#{ipv4}/32"
+ end
+ end
+
+ # Computes AllowedIPs for a peer entry on the current (myself) host.
+ # Returns [allowed_ips_string, updated_primary_gateway_assigned].
+ # - Roaming clients with gateway:true: all traffic via VPN (0.0.0.0/0, ::/0).
+ # - Roaming clients with gateway:false: mesh subnet for primary gateway, specific IPs for others.
+ # - Regular mesh peers: their specific IPs; gateway peers also get extra IPs for
+ # roaming clients declared via reachable_via, so infra hosts can route to them via the gateway.
+ def compute_allowed_ips(peer, data, is_roaming, use_gateway, primary_gateway_assigned)
+ peer_is_gateway = data.key?('internet')
+ ipv4 = data['wg0']['ip']
+ ipv6 = data['wg0']['ipv6']
+
+ if is_roaming && use_gateway
+ return '0.0.0.0/0, ::/0', primary_gateway_assigned
+ elsif is_roaming && !use_gateway
+ return roaming_no_gateway_ips(peer_is_gateway, ipv4, ipv6, primary_gateway_assigned)
+ end
+
+ # Regular (non-roaming) host: route specific IPs only.
+ # For gateway peers, also append IPs of roaming clients reachable via this gateway,
+ # so infra hosts can reach them (e.g. earth via fishfinger) without a direct peer block.
+ ips = ipv6 ? "#{ipv4}/32, #{ipv6}/128" : "#{ipv4}/32"
+ if peer_is_gateway
+ extra = extra_ips_via_gateway(peer)
+ ips = ([ips] + extra).join(', ') unless extra.empty?
+ end
+ [ips, primary_gateway_assigned]
+ end
+
+ # Computes AllowedIPs for a roaming client that has gateway: false.
+ # Primary internet gateway gets the full mesh subnet for routing all mesh traffic;
+ # subsequent gateways and regular peers get only their specific IPs.
+ def roaming_no_gateway_ips(peer_is_gateway, ipv4, ipv6, primary_gateway_assigned)
+ if peer_is_gateway && !primary_gateway_assigned
+ return '192.168.2.0/24, fd42:beef:cafe:2::/64', true
+ end
+
+ ips = ipv6 ? "#{ipv4}/32, #{ipv6}/128" : "#{ipv4}/32"
+ [ips, primary_gateway_assigned]
+ end
end
# InstallConfig is a utility class for managing the installation,
diff --git a/wireguardmeshgenerator.yaml b/wireguardmeshgenerator.yaml
index 1a2057b..afceb5b 100644
--- a/wireguardmeshgenerator.yaml
+++ b/wireguardmeshgenerator.yaml
@@ -1,4 +1,3 @@
----
hosts:
f0:
os: FreeBSD
@@ -8,16 +7,17 @@ hosts:
sudo_cmd: doas
reload_cmd: service wireguard reload
lan:
- domain: 'lan.buetow.org'
- ip: '192.168.1.130'
+ domain: lan.buetow.org
+ ip: 192.168.1.130
wg0:
- domain: 'wg0.wan.buetow.org'
- ip: '192.168.2.130'
- ipv6: 'fd42:beef:cafe:2::130'
+ domain: wg0.wan.buetow.org
+ ip: 192.168.2.130
+ ipv6: fd42:beef:cafe:2::130
exclude_peers:
- - earth
- - pixel7pro
- - freebsd
+ - earth
+ - pixel7pro
+ - freebsd
+ - uranus
f1:
os: FreeBSD
ssh:
@@ -26,16 +26,17 @@ hosts:
sudo_cmd: doas
reload_cmd: service wireguard reload
lan:
- domain: 'lan.buetow.org'
- ip: '192.168.1.131'
+ domain: lan.buetow.org
+ ip: 192.168.1.131
wg0:
- domain: 'wg0.wan.buetow.org'
- ip: '192.168.2.131'
- ipv6: 'fd42:beef:cafe:2::131'
+ domain: wg0.wan.buetow.org
+ ip: 192.168.2.131
+ ipv6: fd42:beef:cafe:2::131
exclude_peers:
- - earth
- - pixel7pro
- - freebsd
+ - earth
+ - pixel7pro
+ - freebsd
+ - uranus
f2:
os: FreeBSD
ssh:
@@ -44,16 +45,17 @@ hosts:
sudo_cmd: doas
reload_cmd: service wireguard reload
lan:
- domain: 'lan.buetow.org'
- ip: '192.168.1.132'
+ domain: lan.buetow.org
+ ip: 192.168.1.132
wg0:
- domain: 'wg0.wan.buetow.org'
- ip: '192.168.2.132'
- ipv6: 'fd42:beef:cafe:2::132'
+ domain: wg0.wan.buetow.org
+ ip: 192.168.2.132
+ ipv6: fd42:beef:cafe:2::132
exclude_peers:
- - earth
- - pixel7pro
- - freebsd
+ - earth
+ - pixel7pro
+ - freebsd
+ - uranus
f3:
os: FreeBSD
ssh:
@@ -62,70 +64,74 @@ hosts:
sudo_cmd: doas
reload_cmd: service wireguard reload
lan:
- domain: 'lan.buetow.org'
- ip: '192.168.1.133'
+ domain: lan.buetow.org
+ ip: 192.168.1.133
wg0:
- domain: 'wg0.wan.buetow.org'
- ip: '192.168.2.133'
- ipv6: 'fd42:beef:cafe:2::133'
+ domain: wg0.wan.buetow.org
+ ip: 192.168.2.133
+ ipv6: fd42:beef:cafe:2::133
exclude_peers:
- - earth
- - pixel7pro
- - freebsd
+ - earth
+ - pixel7pro
+ - freebsd
+ - uranus
r0:
os: Linux
ssh:
user: root
conf_dir: /etc/wireguard
- sudo_cmd:
+ sudo_cmd: null
reload_cmd: systemctl reload wg-quick@wg0.service
lan:
- domain: 'lan.buetow.org'
- ip: '192.168.1.120'
+ domain: lan.buetow.org
+ ip: 192.168.1.120
wg0:
- domain: 'wg0.wan.buetow.org'
- ip: '192.168.2.120'
- ipv6: 'fd42:beef:cafe:2::120'
+ domain: wg0.wan.buetow.org
+ ip: 192.168.2.120
+ ipv6: fd42:beef:cafe:2::120
exclude_peers:
- - earth
- - pixel7pro
- - freebsd
+ - earth
+ - pixel7pro
+ - freebsd
+ - uranus
r1:
os: Linux
ssh:
user: root
conf_dir: /etc/wireguard
- sudo_cmd:
+ sudo_cmd: null
reload_cmd: systemctl reload wg-quick@wg0.service
lan:
- domain: 'lan.buetow.org'
- ip: '192.168.1.121'
+ domain: lan.buetow.org
+ ip: 192.168.1.121
wg0:
- domain: 'wg0.wan.buetow.org'
- ip: '192.168.2.121'
- ipv6: 'fd42:beef:cafe:2::121'
+ domain: wg0.wan.buetow.org
+ ip: 192.168.2.121
+ ipv6: fd42:beef:cafe:2::121
exclude_peers:
- - earth
- - pixel7pro
- - freebsd
+ - earth
+ - pixel7pro
+ - freebsd
+ - uranus
r2:
os: Linux
ssh:
user: root
conf_dir: /etc/wireguard
- sudo_cmd:
+ sudo_cmd: null
reload_cmd: systemctl reload wg-quick@wg0.service
lan:
- domain: 'lan.buetow.org'
- ip: '192.168.1.122'
+ domain: lan.buetow.org
+ ip: 192.168.1.122
wg0:
- domain: 'wg0.wan.buetow.org'
- ip: '192.168.2.122'
- ipv6: 'fd42:beef:cafe:2::122'
+ domain: wg0.wan.buetow.org
+ ip: 192.168.2.122
+ ipv6: fd42:beef:cafe:2::122
exclude_peers:
- - earth
- - pixel7pro
- - freebsd
+ - earth
+ - pixel7pro
+ - freebsd
+ - uranus
blowfish:
os: OpenBSD
ssh:
@@ -135,12 +141,12 @@ hosts:
sudo_cmd: doas
reload_cmd: sh /etc/netstart wg0
internet:
- domain: 'buetow.org'
- ip: '23.88.35.144'
+ domain: buetow.org
+ ip: 23.88.35.144
wg0:
- domain: 'wg0.wan.buetow.org'
- ip: '192.168.2.110'
- ipv6: 'fd42:beef:cafe:2::110'
+ domain: wg0.wan.buetow.org
+ ip: 192.168.2.110
+ ipv6: fd42:beef:cafe:2::110
fishfinger:
os: OpenBSD
ssh:
@@ -150,67 +156,82 @@ hosts:
sudo_cmd: doas
reload_cmd: sh /etc/netstart wg0
internet:
- domain: 'buetow.org'
- ip: '46.23.94.99'
+ domain: buetow.org
+ ip: 46.23.94.99
wg0:
- domain: 'wg0.wan.buetow.org'
- ip: '192.168.2.111'
- ipv6: 'fd42:beef:cafe:2::111'
+ domain: wg0.wan.buetow.org
+ ip: 192.168.2.111
+ ipv6: fd42:beef:cafe:2::111
earth:
os: Linux
+ # reachable_via declares that earth's wg0 IPs should be appended to the named
+ # gateway peer's AllowedIPs on infra hosts that peer with that gateway.
+ # Earth is behind NAT and cannot be a direct peer on infra hosts; instead,
+ # traffic routes via fishfinger -> earth.
+ reachable_via: fishfinger
wg0:
- domain: 'wg0.wan.buetow.org'
- ip: '192.168.2.200'
- ipv6: 'fd42:beef:cafe:2::200'
+ domain: wg0.wan.buetow.org
+ ip: 192.168.2.200
+ ipv6: fd42:beef:cafe:2::200
exclude_peers:
- - f0
- - f1
- - f2
- - r0
- - r1
- - r2
- - pixel7pro
- - freebsd
- # Note: No 'lan' or 'internet' section = roaming client
- # Note: No 'ssh' section = manual installation
- # Note: Only connects to blowfish and fishfinger (internet gateways)
+ - f0
+ - f1
+ - f2
+ - r0
+ - r1
+ - r2
+ - pixel7pro
+ - freebsd
+ - uranus
+ uranus:
+ os: Linux
+ wg0:
+ domain: wg0.wan.buetow.org
+ ip: 192.168.2.205
+ ipv6: fd42:beef:cafe:2::205
+ exclude_peers:
+ - f0
+ - f1
+ - f2
+ - r0
+ - r1
+ - r2
+ - earth
+ - pixel7pro
+ - freebsd
pixel7pro:
os: Android
wg0:
- domain: 'wg0.wan.buetow.org'
- ip: '192.168.2.201'
- ipv6: 'fd42:beef:cafe:2::201'
+ domain: wg0.wan.buetow.org
+ ip: 192.168.2.201
+ ipv6: fd42:beef:cafe:2::201
exclude_peers:
- - f0
- - f1
- - f2
- - r0
- - r1
- - r2
- - earth
- - freebsd
- # Note: No 'lan' or 'internet' section = roaming client
- # Note: No 'ssh' section = manual installation
- # Note: Only connects to blowfish and fishfinger (internet gateways)
+ - f0
+ - f1
+ - f2
+ - r0
+ - r1
+ - r2
+ - earth
+ - freebsd
+ - uranus
freebsd:
os: FreeBSD
wg0:
- domain: 'wg0.wan.buetow.org'
- ip: '192.168.2.202'
- ipv6: 'fd42:beef:cafe:2::202'
+ domain: wg0.wan.buetow.org
+ ip: 192.168.2.202
+ ipv6: fd42:beef:cafe:2::202
exclude_peers:
- - f0
- - f1
- - f2
- - r0
- - r1
- - r2
- - earth
- - pixel7pro
+ - f0
+ - f1
+ - f2
+ - r0
+ - r1
+ - r2
+ - earth
+ - pixel7pro
+ - uranus
gateway: false
- # Note: No 'lan' or 'internet' section = roaming client
- # Note: No 'ssh' section = manual installation
- # Note: gateway: false = connect to gateways but don't route internet through them
pi0:
os: Linux
ssh:
@@ -219,24 +240,25 @@ hosts:
sudo_cmd: sudo
reload_cmd: systemctl reload wg-quick@wg0.service
lan:
- domain: 'lan.buetow.org'
- ip: '192.168.1.125'
+ domain: lan.buetow.org
+ ip: 192.168.1.125
wg0:
- domain: 'wg0.wan.buetow.org'
- ip: '192.168.2.203'
- ipv6: 'fd42:beef:cafe:2::203'
+ domain: wg0.wan.buetow.org
+ ip: 192.168.2.203
+ ipv6: fd42:beef:cafe:2::203
exclude_peers:
- - f0
- - f1
- - f2
- - f3
- - r0
- - r1
- - r2
- - earth
- - pixel7pro
- - freebsd
- - pi1
+ - f0
+ - f1
+ - f2
+ - f3
+ - r0
+ - r1
+ - r2
+ - earth
+ - pixel7pro
+ - freebsd
+ - pi1
+ - uranus
pi1:
os: Linux
ssh:
@@ -245,48 +267,50 @@ hosts:
sudo_cmd: sudo
reload_cmd: systemctl reload wg-quick@wg0.service
lan:
- domain: 'lan.buetow.org'
- ip: '192.168.1.126'
+ domain: lan.buetow.org
+ ip: 192.168.1.126
wg0:
- domain: 'wg0.wan.buetow.org'
- ip: '192.168.2.204'
- ipv6: 'fd42:beef:cafe:2::204'
+ domain: wg0.wan.buetow.org
+ ip: 192.168.2.204
+ ipv6: fd42:beef:cafe:2::204
exclude_peers:
- - f0
- - f1
- - f2
- - f3
- - r0
- - r1
- - r2
- - earth
- - pixel7pro
- - freebsd
- - pi0
+ - f0
+ - f1
+ - f2
+ - f3
+ - r0
+ - r1
+ - r2
+ - earth
+ - pixel7pro
+ - freebsd
+ - pi0
+ - uranus
rocky:
os: Linux
ssh:
user: root
conf_dir: /etc/wireguard
- sudo_cmd:
+ sudo_cmd: null
reload_cmd: systemctl reload wg-quick@wg0.service
lan:
- domain: 'lan.buetow.org'
- ip: '192.168.1.123'
+ domain: lan.buetow.org
+ ip: 192.168.1.123
wg0:
- domain: 'wg0.wan.buetow.org'
- ip: '192.168.2.123'
- ipv6: 'fd42:beef:cafe:2::123'
+ domain: wg0.wan.buetow.org
+ ip: 192.168.2.123
+ ipv6: fd42:beef:cafe:2::123
exclude_peers:
- - f0
- - f1
- - f2
- - f3
- - r0
- - r1
- - r2
- - earth
- - pixel7pro
- - freebsd
- - pi0
- - pi1
+ - f0
+ - f1
+ - f2
+ - f3
+ - r0
+ - r1
+ - r2
+ - earth
+ - pixel7pro
+ - freebsd
+ - pi0
+ - pi1
+ - uranus