From 2463b7e787c7ce9265b68bb15d35a1871ba03c92 Mon Sep 17 00:00:00 2001 From: alexion Date: Mon, 7 Sep 2026 12:18:52 -0400 Subject: [PATCH] feat(nixos): add neogaia WireGuard client --- configs/nixos/hosts/neogaia/default.nix | 17 +++ .../nixos/modules/desktop/session/waybar.nix | 27 +++++ configs/nixos/modules/network/vpn-client.nix | 104 ++++++++++++++++++ configs/nixos/secrets/neogaia.yaml | 7 +- 4 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 configs/nixos/modules/network/vpn-client.nix diff --git a/configs/nixos/hosts/neogaia/default.nix b/configs/nixos/hosts/neogaia/default.nix index 0bdf539..970d03c 100644 --- a/configs/nixos/hosts/neogaia/default.nix +++ b/configs/nixos/hosts/neogaia/default.nix @@ -33,6 +33,23 @@ registry.address.neogaia.self = "10.23.50.146"; + modules.network.vpn-client = { + enable = true; + sopsFile = ../../secrets/neogaia.yaml; + address = "10.23.100.3/32"; + + peer = { + publicKey = "sGEkSD+gEdIMXi4PBN3AhwHGc4tWMtcjOIey3s7yb3I="; + endpoint = "100.0.90.192:51820"; + allowedIPs = [ "10.23.0.0/16" ]; + }; + }; + + registry.shell-shortcut.neogaia = { + vpn-on = "sudo systemctl start wireguard-wg-home.target"; + vpn-off = ''sudo systemctl stop 'wireguard-wg-home*.service' wireguard-wg-home.target''; + }; + # So setup can be driven over the network. # The matching host public keys sit beside this file in plaintext, since # publishing them is their purpose. diff --git a/configs/nixos/modules/desktop/session/waybar.nix b/configs/nixos/modules/desktop/session/waybar.nix index c6310da..330903f 100644 --- a/configs/nixos/modules/desktop/session/waybar.nix +++ b/configs/nixos/modules/desktop/session/waybar.nix @@ -7,6 +7,7 @@ # Waybar: a top status bar reading system state at a glance. let cfg = config.modules.desktop.session.waybar; + vpnClient = config.modules.network.vpn-client; user = config.user.name; # A Nerd Font Private-Use-Area codepoint as a literal character, decoded @@ -48,6 +49,23 @@ let fi ''; + vpnStatus = pkgs.writeShellScript "waybar-vpn-status" '' + interface=${lib.escapeShellArg vpnClient.interface} + + if ! ${pkgs.iproute2}/bin/ip link show dev "$interface" >/dev/null 2>&1; then + printf '{"text":"","class":"idle"}\n' + exit 0 + fi + + ipaddr=$(${pkgs.iproute2}/bin/ip -4 -o addr show dev "$interface" | ${pkgs.gawk}/bin/awk '{ print $4; exit }') + + ${pkgs.jq}/bin/jq -cn \ + --arg text "󰲝" \ + --arg tooltip "VPN $interface: ''${ipaddr:-no IPv4}" \ + --arg class "active" \ + '{ text: $text, tooltip: $tooltip, class: $class }' + ''; + networkStatus = pkgs.writeShellScript "waybar-network-status" '' active=$( ${pkgs.networkmanager}/bin/nmcli -t -f DEVICE,TYPE,STATE,CONNECTION device status \ @@ -118,6 +136,9 @@ in "custom/idle-lock" "mpris" "wireplumber" + ] + ++ lib.optional vpnClient.enable "custom/vpn" + ++ [ "custom/network" "battery" "custom/dnd" @@ -173,6 +194,12 @@ in on-click = "${pkgs.wireplumber}/bin/wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; }; + "custom/vpn" = { + return-type = "json"; + exec = "${vpnStatus}"; + interval = 5; + }; + "custom/network" = { return-type = "json"; exec = "${networkStatus}"; diff --git a/configs/nixos/modules/network/vpn-client.nix b/configs/nixos/modules/network/vpn-client.nix new file mode 100644 index 0000000..0c7e562 --- /dev/null +++ b/configs/nixos/modules/network/vpn-client.nix @@ -0,0 +1,104 @@ +{ + config, + lib, + ... +}: +let + cfg = config.modules.network.vpn-client; +in +{ + options.modules.network.vpn-client = { + enable = lib.mkEnableOption "a WireGuard VPN client"; + + interface = lib.mkOption { + type = lib.types.str; + default = "wg-home"; + description = "WireGuard interface name."; + }; + + address = lib.mkOption { + type = lib.types.str; + example = "10.23.100.3/32"; + description = "Client tunnel address in CIDR notation."; + }; + + privateKeySecret = lib.mkOption { + type = lib.types.str; + default = "wireguard-home-private-key"; + description = "SOPS secret containing the WireGuard client private key."; + }; + + autoStart = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to start the VPN tunnel during boot."; + }; + + mtu = lib.mkOption { + type = lib.types.nullOr lib.types.int; + default = 1280; + description = "Tunnel MTU, or null to use the WireGuard default."; + }; + + sopsFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = "Encrypted secrets file that contains privateKeySecret."; + }; + + peer = { + publicKey = lib.mkOption { + type = lib.types.str; + description = "WireGuard public key for the remote peer."; + }; + + endpoint = lib.mkOption { + type = lib.types.str; + example = "203.0.113.10:51820"; + description = "WireGuard endpoint for the remote peer."; + }; + + allowedIPs = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ "10.23.0.0/16" ]; + description = "Routes to send through the VPN tunnel."; + }; + + persistentKeepalive = lib.mkOption { + type = lib.types.nullOr lib.types.int; + default = 25; + description = "Persistent keepalive interval in seconds, or null to disable it."; + }; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.sopsFile != null; + message = "modules.network.vpn-client.sopsFile must name the encrypted secrets file containing the WireGuard private key."; + } + ]; + + sops.secrets.${cfg.privateKeySecret}.sopsFile = cfg.sopsFile; + + systemd.targets."wireguard-${cfg.interface}" = lib.mkIf (!cfg.autoStart) { + wantedBy = lib.mkForce [ ]; + }; + + networking.wireguard.interfaces.${cfg.interface} = { + ips = [ cfg.address ]; + mtu = cfg.mtu; + privateKeyFile = config.sops.secrets.${cfg.privateKeySecret}.path; + + peers = [ + ({ + inherit (cfg.peer) publicKey endpoint allowedIPs; + } + // lib.optionalAttrs (cfg.peer.persistentKeepalive != null) { + inherit (cfg.peer) persistentKeepalive; + }) + ]; + }; + }; +} diff --git a/configs/nixos/secrets/neogaia.yaml b/configs/nixos/secrets/neogaia.yaml index e978087..331634d 100644 --- a/configs/nixos/secrets/neogaia.yaml +++ b/configs/nixos/secrets/neogaia.yaml @@ -1,6 +1,7 @@ ssh-host-ed25519-key: ENC[AES256_GCM,data:K6F9JrmmnK0EKqe9wsjGKOM01ygHvKRUj/a65IdWQsnR8MHNKR90xxviwZa8BdJOjhfOnZYua66h/folPYFT8jiR4HRXRTcZ25LNrGK+oOoxnvN2ES/9X0NDRhTbfxRUfBYb6gf38cyJUoCxoVps+NQ19eCO+kbj/4vfrYyiiZRdL6Bf7xMrftzkMZ4Fxr2xK0NmARvh39Q8tYWJX+57H0bNsRdTQkZkRsLl8l8kQyD8U1g5OsMkNCYZchHZyp6YIzaSoZyAZRBGNxKJmSwzC7ewP/JA7S5nqErtrsZIlS4s27yn1ERT7VfJwhx4xGZgnCF5HQDz/eW6/EhdF8MOTA2HkZqz8u0QpamtgGi0X/K/Ip+ntYIyAa4VtmKcgghPrftZ4xc0TeeOVPU1KgbW7HBnqeDiNQd5k/878FDmsoKWt3CrdL1RszeUCJH1DY+kN44cMlbKwo8P3vp7oNTV1Sn5NUuWv6JxCYX4HTYlW92Wt//mqjtX2gWrMB0R63aPlJNmcIVjDN2mNGXn/Uyn,iv:q6chCkDdccg6pyH7fr8yMvNDcGjl5ZhO4CN25M7ydkk=,tag:JSuTMuG+0fSEjvG8uLG56A==,type:str] ssh-host-rsa-key: ENC[AES256_GCM,data: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,iv:xbUR7YIZKQd6qlzsEZT4aur5xZuRBgQvmSSTdpc05ag=,tag:UIuoHsgMYWmhdJR/GDB0iA==,type:str] ssh-user-ed25519-key: ENC[AES256_GCM,data:p8HGPQrlLLj57Zlkbe/g4HzMRnEucrrl/Aeh/iG/3nSM8I73cCEC8lZXcQgU9BtBP3rrY5F0BvT8MKVlkgCVqJ63EVPj/xjZqmmyKQvf9VH7tx8IoqAA3K/eSt9F7HhUPR3f2NQtce4xeOf7qYHFvP1LNEtv0UgmgROhUfYPaHb7JZ3p4kZ8V8LhVttmIJi9k45P182TtZJkgt36Ys6gyBp7A6UOT7Ha/HdhB6fkqt3H5HWqSq786NXUhqXgzPSDFvH4Iy+aBczAehjYRVNPJ8LTnDovmr8fMzuTJ1t8WCEnljkDWen645ToCObfJTmCIphGfD/VkST0ZphvmV8MY4/z+Ch+CUxI2O2XgJYQVsD7qBtKKxlAS4P4U28JgLt01Y4tPK6Qs+VTXEsBqKxYS99AUVjNAfxE2d7c0IpQSNHqm8mw0d41OKiphIAxgJ14RtUQDxsytAlEl8giQKeJ3yFf8xVYKJ0NTvKyf2X0+d7k3ZiEP0FGbjBwA484kCUzfYuTX+bRoKLd7EcaJkdHSuqQprzUfYpAGgS/,iv:f8YPwuXpP94FMhCIgkLpR1PVufl3PPQVnNaEn56WXPE=,tag:8YnJjtOCEleEZdKQu0bBfA==,type:str] +wireguard-home-private-key: ENC[AES256_GCM,data:HRguD8Ctl0XlJP2YB4YxctWqSyy+jrsUBVw4sAturLIbWxHfO2jlRCj22dg=,iv:b87JL7vhxxo8JkPaj6dtd3NJJTx3ImvqVeyWIjffUQI=,tag:LYjuJQSK+fgFniYPmMG8yQ==,type:str] sops: age: - enc: | @@ -21,7 +22,7 @@ sops: cbQ5nwrYjI7FapucJQQFebB7FrLKCDgZRyw+/nhCmEJSWFz7iZKLQA== -----END AGE ENCRYPTED FILE----- recipient: age14a04vphzjq74epfrz9a09wjw8lzchtru84awzuq2n45d8f42ychqjs89qe - lastmodified: "2026-07-20T16:29:45Z" - mac: ENC[AES256_GCM,data:XLrro73Z4hA4ntA2uFlBj+eNJJXlYw3YUFFLI/it9KvvEbxlvAVka++HQpby2rRZm+VjSXa7BDtzSnLA4Jpue2csUQUMVpKCGvWBNxTBnZpkKpi7fJd2AOFcMA+mXk19OS9YdnfZy9VLpr4+XiM8e+zxNEijZmHXn0gsGUotd8I=,iv:6FWSG86Ix764vD/BMlnEWDdQX74yIg0wkmz5Zz343VQ=,tag:tIeODybScPRVuLBpj7gzVw==,type:str] + lastmodified: "2026-09-07T15:53:37Z" + mac: ENC[AES256_GCM,data:OdJmjZwjuVIr6h31HhvjicgFtH8lGUt1KgTw6TUu9ofGAm5YRN3+4G6kXS8q3dShupCSdbH92QUoJOhu+txh0eYVVzH6VdLX2XwDgak1VZVbKlNd37qO8llAVYH7ePZzgCotE1am+4mYSwIMs1F2mzR2b2xEjFpg5qvQrVEaDFg=,iv:DuqhmNCiovWHm1xkqb9+EQFvRraXxrRRaJVoRBnj7vM=,tag:pTbVDgBRK7ctIwspd/dZvA==,type:str] unencrypted_suffix: _unencrypted - version: 3.13.2 + version: 3.13.3