From 56bb3d7f7c90e2f7012d96ebbcd178ac8180df53 Mon Sep 17 00:00:00 2001 From: mikl Date: Mon, 3 Aug 2026 08:41:57 +0300 Subject: [PATCH] feat(mesh): add selected Telegram and OpenRouter egress policy --- hosts/heather/default.nix | 5 ++- hosts/heather/mesh-egress.nix | 76 +++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 hosts/heather/mesh-egress.nix diff --git a/hosts/heather/default.nix b/hosts/heather/default.nix index f550076..e546a2d 100644 --- a/hosts/heather/default.nix +++ b/hosts/heather/default.nix @@ -22,6 +22,7 @@ ./tuwunel-admin.nix # matrix admin web UI (systemd, статический бинарник) ./pi-web.nix # pi-web (web UI для pi coding agent, systemd + npm global) ./mesh-clients.nix # Sol-style client pool (wg-clients1) + ./mesh-egress.nix # Sol-style selected-destination egress policy ]; # First server-to-server WireGuard backbone link: heather ↔ hyacinth. @@ -57,7 +58,9 @@ peers = [ { publicKey = "BxTH8+MiA8Hv48UfpWJDVXIhbO5HgO2KTBEXIqqr23k="; - allowedIPs = [ "10.99.0.2/32" "10.0.0.2/32" ]; + # Match all destinations for future policy routes; allowedIPsAsRoutes=false + # keeps this from installing a default route automatically. + allowedIPs = [ "0.0.0.0/0" ]; endpoint = "195.242.119.59:51820"; persistentKeepalive = 25; } diff --git a/hosts/heather/mesh-egress.nix b/hosts/heather/mesh-egress.nix new file mode 100644 index 0000000..99d941e --- /dev/null +++ b/hosts/heather/mesh-egress.nix @@ -0,0 +1,76 @@ +# Static, Sol-style destination policy for selected traffic via the EU peer. +# This is policy routing, not application learning: prefixes are declared here, +# and OpenRouter addresses are refreshed from DNS on a timer. +{ config, pkgs, ... }: +let + ip = "${pkgs.iproute2}/bin/ip"; + getent = "${pkgs.glibc}/bin/getent"; + routeScript = pkgs.writeShellScript "mesh-egress-routes" '' + set -eu + ${ip} link show wg-mesh >/dev/null + + # Rebuild the destination table, while leaving the normal main table alone. + ${ip} route flush table 1101 2>/dev/null || true + ${ip} rule del pref 1101 2>/dev/null || true + ${ip} rule add pref 1101 lookup 1101 + + add_route() { + prefix="$1" + ${ip} route replace "$prefix" dev wg-mesh table 1101 + } + + # Telegram DC ranges (static destination policy, as in Sol's prefix lists). + for prefix in \ + 91.105.192.0/23 \ + 91.108.0.0/22 \ + 91.108.4.0/22 \ + 91.108.8.0/22 \ + 91.108.12.0/22 \ + 91.108.16.0/22 \ + 91.108.20.0/22 \ + 91.108.56.0/22 \ + 95.161.64.0/20 \ + 149.154.160.0/20; do + add_route "$prefix" + done + + # OpenRouter is behind a CDN; refresh its current IPv4 addresses. We do + # not route all Cloudflare, only the names used by the API/web endpoint. + for name in openrouter.ai www.openrouter.ai api.openrouter.ai; do + ${getent} ahostsv4 "$name" 2>/dev/null | ${pkgs.gawk}/bin/awk '{print $1}' | sort -u | while read -r addr; do + [ -n "$addr" ] || continue + add_route "$addr/32" + done + done + ''; +in { + # Clients whose selected destinations match table 1101 are forwarded to + # hyacinth. Other traffic keeps Heather's normal route. + networking.nat = { + enable = true; + internalInterfaces = [ "wg-clients1" ]; + externalInterface = "enp7s0"; + }; + + systemd.services.mesh-egress-routes = { + description = "Install Sol-style selected destination routes via hyacinth"; + wantedBy = [ "network-online.target" ]; + after = [ "network-online.target" "wireguard-wg-mesh.service" ]; + wants = [ "wireguard-wg-mesh.service" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = routeScript; + }; + }; + + systemd.timers.mesh-egress-routes = { + description = "Refresh selected mesh egress destinations"; + wantedBy = [ "timers.target" ]; + timerConfig = { + OnBootSec = "30s"; + OnUnitActiveSec = "5min"; + Unit = "mesh-egress-routes.service"; + }; + }; +}