From 9a99024688d59a657c21aff658480886db7f2389 Mon Sep 17 00:00:00 2001 From: soraefir Date: Sun, 1 Nov 2026 10:09:50 +0100 Subject: [PATCH] vpn mode to eww --- .../wayland/apps/eww/bar/modules/net.yuck | 4 +- .../home/wayland/apps/eww/bar/scripts/net/net | 17 +++--- .../apps/eww/bar/scripts/net/wg-vpn-toggle | 13 +++++ .../wayland/apps/eww/bar/windows/net.yuck | 7 +++ modules/home/wayland/apps/eww/default.nix | 7 +++ .../system/network/wireguard/default.nix | 58 ++++++++++++++++--- 6 files changed, 90 insertions(+), 16 deletions(-) create mode 100755 modules/home/wayland/apps/eww/bar/scripts/net/wg-vpn-toggle diff --git a/modules/home/wayland/apps/eww/bar/modules/net.yuck b/modules/home/wayland/apps/eww/bar/modules/net.yuck index 57581b2..a28f5c7 100644 --- a/modules/home/wayland/apps/eww/bar/modules/net.yuck +++ b/modules/home/wayland/apps/eww/bar/modules/net.yuck @@ -1,5 +1,5 @@ (deflisten net - :initial '{"wifi":{"connected":false,"enabled":false,"icon":"󰤮","ssid":"","ip":"","freq":0,"band":"","gen":"","signal":0},"ethernet":{"connected":false,"interface":"","ip":"","speed":""},"usb":{"connected":false,"interface":"","ip":""},"wireguard":[],"vpn":{"up":false,"name":""}}' + :initial '{"wifi":{"connected":false,"enabled":false,"icon":"󰤮","ssid":"","ip":"","freq":0,"band":"","gen":"","signal":0},"ethernet":{"connected":false,"interface":"","ip":"","speed":""},"usb":{"connected":false,"interface":"","ip":""},"wireguard":[],"vpn":{"up":false,"full":false,"name":""}}' "scripts/net/net") (deflisten bt @@ -23,7 +23,7 @@ (label :visible {net.vpn.up} :class "net-icon net-active" - :tooltip "VPN: ${net.vpn.name}" + :tooltip {net.vpn.full ? "VPN: all traffic through ${net.vpn.name}" : "WireGuard: ${net.vpn.name}"} :text "󰖂") (label :class "net-icon ${net.wifi.connected ? 'net-active' : 'net-dim'}" diff --git a/modules/home/wayland/apps/eww/bar/scripts/net/net b/modules/home/wayland/apps/eww/bar/scripts/net/net index a64596e..3321eec 100755 --- a/modules/home/wayland/apps/eww/bar/scripts/net/net +++ b/modules/home/wayland/apps/eww/bar/scripts/net/net @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Network state for the bar icons and the net panel: wifi, ethernet and USB -# tethering. Refreshes on link/address changes and every 30 s (signal level). +# tethering. Refreshes on link/address/rule changes and every 30 s (signal level). wifi_iface() { awk 'NR > 2 { sub(":", "", $1); print $1; exit }' /proc/net/wireless; } eth_iface() { ip -o link 2>/dev/null | awk '$2 ~ /^en[op]/ { sub(":", "", $2); print $2; exit }'; } @@ -84,7 +84,7 @@ state() { wg_ifaces() { { systemctl list-units --all --plain --no-legend 'wireguard-*.service' 2>/dev/null \ - | awk '$1 !~ /-peer-/ { sub(/^wireguard-/, "", $1); sub(/\.service$/, "", $1); print $1 }' + | awk '$1 !~ /-peer-|-vpn\.service$/ { sub(/^wireguard-/, "", $1); sub(/\.service$/, "", $1); print $1 }' ip -d -o link 2>/dev/null | awk '/ wireguard / { sub(":", "", $2); print $2 }' } | sort -u } @@ -97,10 +97,12 @@ human_bytes() { } wg_json() { # prints the "wireguard" array and the "vpn" summary - local name up ip rx tx peers endpoint peer_unit peer_script sep="" out="[" vpn_up=false vpn_name="" + local name up ip rx tx peers endpoint peer_unit peer_script vpn_able vpn sep="" out="[" vpn_up=false vpn_name="" vpn_full=false while read -r name; do [[ -n $name ]] || continue - up=false ip="" rx=0 tx=0 endpoint="" + up=false ip="" rx=0 tx=0 endpoint="" vpn_able=false vpn=false + [[ $(systemctl show -p LoadState --value "wireguard-$name-vpn.service" 2>/dev/null) == loaded ]] && vpn_able=true + systemctl is-active --quiet "wireguard-$name-vpn.service" 2>/dev/null && vpn=true if [[ -d /sys/class/net/$name ]]; then ip -o link show "$name" 2>/dev/null | grep -q '<[^>]*UP' && up=true ip=$(ipv4 "$name") @@ -120,14 +122,15 @@ wg_json() { # prints the "wireguard" array and the "vpn" summary fi if [[ $up == true && $vpn_up == false ]]; then vpn_up=true vpn_name=$name; fi [[ -z $vpn_name ]] && vpn_name=$name - out+="$sep{\"name\":\"$(jstr "$name")\",\"up\":$up,\"ip\":\"$ip\",\"rx\":\"$(human_bytes "$rx")\",\"tx\":\"$(human_bytes "$tx")\",\"peers\":\"$(jstr "$peers")\",\"endpoint\":\"$(jstr "$endpoint")\"}" + [[ $vpn == true ]] && vpn_full=true vpn_name=$name + out+="$sep{\"name\":\"$(jstr "$name")\",\"up\":$up,\"ip\":\"$ip\",\"rx\":\"$(human_bytes "$rx")\",\"tx\":\"$(human_bytes "$tx")\",\"peers\":\"$(jstr "$peers")\",\"endpoint\":\"$(jstr "$endpoint")\",\"vpn_able\":$vpn_able,\"vpn\":$vpn}" sep=, done < <(wg_ifaces) - printf '"wireguard":%s],"vpn":{"up":%s,"name":"%s"}' "$out" "$vpn_up" "$(jstr "$vpn_name")" + printf '"wireguard":%s],"vpn":{"up":%s,"full":%s,"name":"%s"}' "$out" "$vpn_up" "$vpn_full" "$(jstr "$vpn_name")" } refresh() { emit "$(state)"; } -_link_events() { exec ip monitor link address; } +_link_events() { exec ip monitor link address rule; } refresh mux_init diff --git a/modules/home/wayland/apps/eww/bar/scripts/net/wg-vpn-toggle b/modules/home/wayland/apps/eww/bar/scripts/net/wg-vpn-toggle new file mode 100755 index 0000000..d1f2c0b --- /dev/null +++ b/modules/home/wayland/apps/eww/bar/scripts/net/wg-vpn-toggle @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Toggle VPN mode (all traffic through the tunnel) of WireGuard interface $1, +# independently of the tunnel itself. Starting it brings the tunnel up. +# Needs the polkit rule from modules/nixos/system/network/wireguard. +name=$1 +[[ -n $name ]] || exit 64 +detach_self "$@" +unit=wireguard-$name-vpn.service +if systemctl is-active --quiet "$unit"; then + systemctl stop "$unit" +else + systemctl start "$unit" +fi diff --git a/modules/home/wayland/apps/eww/bar/windows/net.yuck b/modules/home/wayland/apps/eww/bar/windows/net.yuck index 27adaf7..b51c349 100644 --- a/modules/home/wayland/apps/eww/bar/windows/net.yuck +++ b/modules/home/wayland/apps/eww/bar/windows/net.yuck @@ -96,12 +96,19 @@ (box :orientation "v" :space-evenly false :class "wg-row" (box :orientation "h" :space-evenly false :valign "center" (label :class "wg-name ${wg.up ? 'wg-up' : 'wg-down'}" :halign "start" :hexpand true :text {wg.name}) + (button + :class "net-toggle-btn net-add-btn ${wg.vpn ? 'net-toggle-on' : 'net-toggle-off'}" + :visible {wg.vpn_able} + :onclick "scripts/net/wg-vpn-toggle ${wg.name}" + :tooltip {wg.vpn ? "VPN: all traffic through ${wg.name}" : "VPN off: only the private subnet"} + (label :text "󰖟")) (button :class "net-toggle-btn ${wg.up ? 'net-toggle-on' : 'net-toggle-off'}" :onclick "scripts/net/wg-toggle ${wg.name}" :tooltip {wg.up ? "Disconnect" : "Connect"} (label :text {wg.up ? "󰖂" : "󰦞"}))) (box :orientation "v" :space-evenly false :visible {wg.up} + (netinfo-row :label "Routing" :value {wg.vpn ? "All traffic" : "Private subnet"}) (netinfo-row :label "IP" :value {wg.ip}) (netinfo-row :label "Peer" :value {wg.endpoint != "" ? "${wg.peers} · ${wg.endpoint}" : wg.peers}) (netinfo-row :label "Traffic" :value "󰇚 ${wg.rx} 󰕒 ${wg.tx}")) diff --git a/modules/home/wayland/apps/eww/default.nix b/modules/home/wayland/apps/eww/default.nix index 9c33b92..c1766f9 100755 --- a/modules/home/wayland/apps/eww/default.nix +++ b/modules/home/wayland/apps/eww/default.nix @@ -21,6 +21,7 @@ let "scripts/net/bt" = mkScript "bt" ./bar/scripts/net/bt [ jq glib ]; "scripts/net/bt-toggle" = mkScript "bt-toggle" ./bar/scripts/net/bt-toggle [ bluez ]; "scripts/net/wg-toggle" = mkScript "wg-toggle" ./bar/scripts/net/wg-toggle [ ]; + "scripts/net/wg-vpn-toggle" = mkScript "wg-vpn-toggle" ./bar/scripts/net/wg-vpn-toggle [ ]; "scripts/net/wifi-connect" = mkScript "wifi-connect" ./bar/scripts/net/wifi-connect [ eww wpa_supplicant wofi libnotify ]; "scripts/sys/gpu" = mkScript "gpu" ./bar/scripts/sys/gpu [ custom.amdgpu_top jq ]; "scripts/sys/memory" = mkScript "memory" ./bar/scripts/sys/memory [ ]; @@ -52,6 +53,12 @@ in { # graphical session, opens the bar once the daemon answers, and is # restarted on crash. `eww-bar` (packages/eww-bar) does the heavy # lifting; kanshi calls `eww-bar place` to move the bar between outputs. + home.activation.ewwReload = lib.hm.dag.entryAfter [ "linkGeneration" ] '' + if ${pkgs.systemd}/bin/systemctl --user is-active --quiet eww.service; then + run ${ewwBar} reload || true + fi + ''; + systemd.user.services.eww = { Unit = { Description = "eww daemon and bar"; diff --git a/modules/nixos/system/network/wireguard/default.nix b/modules/nixos/system/network/wireguard/default.nix index 0ff2e1e..7148507 100644 --- a/modules/nixos/system/network/wireguard/default.nix +++ b/modules/nixos/system/network/wireguard/default.nix @@ -5,6 +5,37 @@ (p.syscfg.net.wg.enable == true) && (p.syscfg.net.wg.pubkey != config.syscfg.net.wg.pubkey); activePeers = builtins.filter isValidPeer config.syscfg.peers; + + isServer = config.syscfg.server ? wireguard && config.syscfg.server.wireguard; + vpnPeer = { + name = "vpn-helcel"; + publicKey = "NFBJvYXZC+bd62jhrKnM7/pugidWhgR6+C5qIiUiq3Q="; + allowedIPs = [ "10.10.1.0/24" "fd10:10:10::0/64" ]; + }; + + vpnTable = "51820"; + vpnDown = pkgs.writeShellScript "wg0-vpn-down" '' + PATH=${lib.makeBinPath [ pkgs.iproute2 pkgs.wireguard-tools ]} + for f in -4 -6; do + ip $f rule del pref 31000 2>/dev/null + ip $f rule del pref 31001 2>/dev/null + ip $f route flush table ${vpnTable} 2>/dev/null + done + wg set wg0 fwmark off peer ${vpnPeer.publicKey} allowed-ips ${lib.concatStringsSep "," vpnPeer.allowedIPs} 2>/dev/null + true + ''; + vpnUp = pkgs.writeShellScript "wg0-vpn-up" '' + set -e + PATH=${lib.makeBinPath [ pkgs.iproute2 pkgs.wireguard-tools pkgs.procps ]} + ${vpnDown} + sysctl -q net.ipv4.conf.all.src_valid_mark=1 + wg set wg0 fwmark ${vpnTable} peer ${vpnPeer.publicKey} allowed-ips 0.0.0.0/0,::/0 + for f in -4 -6; do + ip $f route replace default dev wg0 table ${vpnTable} + ip $f rule add table main suppress_prefixlength 0 pref 31000 + ip $f rule add not fwmark ${vpnTable} table ${vpnTable} pref 31001 + done + ''; in { config = lib.mkIf (config.syscfg.net.wg.enable) { @@ -18,20 +49,17 @@ in listenPort = 1515; mtu = 1340; peers = - if (config.syscfg.server ? wireguard && config.syscfg.server.wireguard) then + if isServer then map (p: { name = p.syscfg.hostname; publicKey = p.syscfg.net.wg.pubkey; allowedIPs = [ p.syscfg.net.wg.ip4 p.syscfg.net.wg.ip6 ]; }) activePeers else - [{ - allowedIPs = [ "10.10.1.0/24" "fd10:10:10::0/64" ]; - name = "vpn-helcel"; + [(vpnPeer // { endpoint = "vpn.helcel.net:1515"; - publicKey = "NFBJvYXZC+bd62jhrKnM7/pugidWhgR6+C5qIiUiq3Q="; persistentKeepalive = 30; - }]; + })]; }; }; }; @@ -59,7 +87,23 @@ in Restart = "on-failure"; RestartSec = "10s"; # Wait 2 seconds before retrying the domain query }; - startLimitIntervalSec = 0; + startLimitIntervalSec = 0; }; + + systemd.services."wireguard-wg0-vpn" = lib.mkIf (!isServer) { + description = "WireGuard wg0 - route all traffic"; + bindsTo = [ "wireguard-wg0.service" ]; + wants = [ "wireguard-wg0-peer-vpn-helcel.service" ]; + partOf = [ "wireguard-wg0-peer-vpn-helcel.service" ]; + after = [ "wireguard-wg0.service" "wireguard-wg0-peer-vpn-helcel.service" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = vpnUp; + ExecStop = vpnDown; + }; + }; + + networking.firewall.checkReversePath = lib.mkIf (!isServer) "loose"; }; }