Adds the missing reconciler piece: render each host's /etc/wireguard/wg1.conf
from data/mesh-hosts.json (WG config was previously hand-built).
- mesh.segments maps <segment> -> {hub, endpoint, dns_host, dns_listen}; hosts
carry `segment` + `wg_pubkey` (public key only). iceland(yuzu) and nyc3(citron)
are independent stars. Legacy single-hub (mesh.hub) still works as fallback.
- bin/wg-render: --keygen/--pubkey bootstrap, --dry-run/--whoami inspect,
--apply installs + `wg syncconf` (idempotent, rollback). Hub gets a [Peer] per
spoke + ip_forward/MASQUERADE; spoke gets one [Peer] = its hub. WG_RENDER_SELF
override for tests/ops.
- bin/wg-dns-sync: segment-aware listen — a segment's dns_host binds its own
dns_listen (citron serves nyc3 on 10.9.0.7; apricot unchanged on 10.9.0.2).
- Registers citron (com.uvlava.quinn.infra, nyc3 hub) + nyc3 keys for lime;
carries the com.uvlava.ct.* DO-name aliases. Tests cover hub/spoke/dns.
(data/mesh-hosts.json also carries pre-existing working-tree normalization:
literal em-dash -> — escapes and expanded alias arrays.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
56 lines
No EOL
2.6 KiB
Bash
Executable file
56 lines
No EOL
2.6 KiB
Bash
Executable file
#!/bin/sh
|
|
# test — run the net-tools test suite (no root required).
|
|
set -eu
|
|
|
|
self=$0
|
|
while [ -L "$self" ]; do
|
|
link=$(readlink "$self")
|
|
case $link in /*) self=$link ;; *) self=$(dirname "$self")/$link ;; esac
|
|
done
|
|
root=$(cd "$(dirname "$self")" && pwd)
|
|
while [ "$root" != "/" ] && [ ! -f "$root/data/mesh-hosts.json" ]; do root=$(dirname "$root"); done
|
|
|
|
echo "==> python unit tests"
|
|
python3 "$root/smart-lan-router/test_smart_lan_router.py"
|
|
|
|
echo "==> shell syntax"
|
|
sh -n "$root/bin/mesh-hosts-render"
|
|
sh -n "$root/bin/host-apply"
|
|
sh -n "$root/bin/fleet-status"
|
|
sh -n "$root/bin/wg-dns-sync"
|
|
sh -n "$root/bin/wg-render"
|
|
|
|
echo "==> wg-render multi-segment"
|
|
hub_who=$(WG_RENDER_SELF=citron "$root/bin/wg-render" --whoami 2>/dev/null)
|
|
case "$hub_who" in *"role=hub"*) : ;; *) echo "FAIL: citron not resolved as hub ($hub_who)" >&2; exit 1 ;; esac
|
|
# Hub config must include a [Peer] block for its spoke(s).
|
|
WG_RENDER_SELF=citron "$root/bin/wg-render" --dry-run 2>/dev/null | grep -q '^\[Peer\]' \
|
|
|| { echo "FAIL: citron hub render has no [Peer]" >&2; exit 1; }
|
|
# Spoke config must point at the segment hub endpoint with a keepalive.
|
|
spoke=$(WG_RENDER_SELF=lime "$root/bin/wg-render" --dry-run 2>/dev/null)
|
|
case "$spoke" in *"Endpoint = 143.244.223.5:51820"*"PersistentKeepalive"*) : ;;
|
|
*) echo "FAIL: lime spoke render missing hub endpoint/keepalive" >&2; exit 1 ;; esac
|
|
echo "ok: citron=hub peers spokes, lime=spoke -> citron"
|
|
|
|
echo "==> wg-dns-sync segment listen"
|
|
cit_listen=$(WG_DNS_SELF=citron "$root/bin/wg-dns-sync" --dry-run 2>/dev/null | sed -n 's/^listen-address=//p')
|
|
apr_listen=$(WG_DNS_SELF=apricot "$root/bin/wg-dns-sync" --dry-run 2>/dev/null | sed -n 's/^listen-address=//p')
|
|
case "$cit_listen" in *10.9.0.7*) : ;; *) echo "FAIL: citron dns listen not nyc3 ($cit_listen)" >&2; exit 1 ;; esac
|
|
case "$apr_listen" in *10.9.0.2*) : ;; *) echo "FAIL: apricot dns listen changed ($apr_listen)" >&2; exit 1 ;; esac
|
|
echo "ok: citron serves nyc3 ($cit_listen), apricot unchanged ($apr_listen)"
|
|
|
|
echo "==> wg-dns-sync overlay"
|
|
lan_ip=$("$root/bin/wg-dns-sync" --dry-run | sed -n 's|address=/apricot\.lan/\([^[:space:]#]*\).*|\1|p' | head -1)
|
|
state_ip=$(jq -r '.apricot // empty' "$root/data/lan-state.json" 2>/dev/null || true)
|
|
seed_ip=$(jq -r '.hosts[] | select(.name=="apricot") | .lan' "$root/data/mesh-hosts.json")
|
|
if [ -n "$state_ip" ] && [ "$lan_ip" != "$state_ip" ]; then
|
|
echo "FAIL: apricot.lan is $lan_ip, expected overlay $state_ip" >&2
|
|
exit 1
|
|
fi
|
|
if [ -z "$state_ip" ] && [ "$lan_ip" != "$seed_ip" ]; then
|
|
echo "FAIL: apricot.lan is $lan_ip, expected seed $seed_ip" >&2
|
|
exit 1
|
|
fi
|
|
echo "ok: apricot.lan -> ${lan_ip:-?}"
|
|
|
|
echo "==> all passed" |