diff --git a/system/machines/server/modules/wireguard/default.nix b/system/machines/server/modules/wireguard/default.nix new file mode 100644 index 0000000..ff3f516 --- /dev/null +++ b/system/machines/server/modules/wireguard/default.nix @@ -0,0 +1,106 @@ +{ pkgs, lib, config, ... }: + +with lib; +let + cfg = config.modules.system.wireguard; +in +{ + options.modules.system.wireguard = { + enable = mkEnableOption "WireGuard VPN"; + + address = mkOption { + type = types.str; + default = "10.8.0.1/24"; + description = "WireGuard interface address with CIDR"; + }; + + subnet = mkOption { + type = types.str; + default = "10.8.0.0/24"; + description = "WireGuard subnet used for peer allocations"; + }; + + listenPort = mkOption { + type = types.port; + default = 51820; + description = "WireGuard UDP listen port"; + }; + + privateKeyFile = mkOption { + type = types.str; + default = "/var/lib/wireguard/server.key"; + description = "Path to WireGuard server private key"; + }; + + peers = mkOption { + type = types.listOf (types.submodule ({ ... }: { + options = { + publicKey = mkOption { + type = types.str; + description = "Peer public key"; + }; + + allowedIPs = mkOption { + type = types.listOf types.str; + description = "Allowed IPs for peer, usually a single /32"; + }; + + presharedKeyFile = mkOption { + type = types.nullOr types.str; + default = null; + description = "Optional preshared key file"; + }; + + persistentKeepalive = mkOption { + type = types.nullOr types.int; + default = 25; + description = "Persistent keepalive interval seconds"; + }; + }; + })); + default = [ ]; + description = "WireGuard peers"; + }; + }; + + config = mkIf cfg.enable { + networking.firewall.allowedUDPPorts = [ cfg.listenPort ]; + networking.nat.internalInterfaces = mkAfter [ "wg0" ]; + + systemd.tmpfiles.rules = [ + "d /var/lib/wireguard 0700 root root -" + ]; + + systemd.services.wireguard-generate-key = { + description = "Generate WireGuard server key if missing"; + before = [ "wireguard-wg0.service" ]; + wantedBy = [ "wireguard-wg0.service" ]; + serviceConfig = { + Type = "oneshot"; + }; + path = with pkgs; [ wireguard-tools coreutils ]; + script = '' + set -euo pipefail + + if [ ! -s "${cfg.privateKeyFile}" ]; then + umask 077 + wg genkey | tee "${cfg.privateKeyFile}" | wg pubkey > /var/lib/wireguard/server.pub + elif [ ! -s /var/lib/wireguard/server.pub ]; then + umask 077 + wg pubkey < "${cfg.privateKeyFile}" > /var/lib/wireguard/server.pub + fi + ''; + }; + + networking.wireguard.interfaces.wg0 = { + ips = [ cfg.address ]; + listenPort = cfg.listenPort; + privateKeyFile = cfg.privateKeyFile; + peers = map (peer: { + inherit (peer) publicKey allowedIPs; + presharedKeyFile = peer.presharedKeyFile; + persistentKeepalive = peer.persistentKeepalive; + }) cfg.peers; + }; + }; +} diff --git a/system/machines/server/system.nix b/system/machines/server/system.nix index c5c839a..b843f7e 100644 --- a/system/machines/server/system.nix +++ b/system/machines/server/system.nix @@ -19,6 +19,10 @@ frigate.enable = true; immich.enable = true; webdav.enable = false; + wireguard = { + enable = true; + peers = [ ]; + }; # bitcoin = { # enable = true; # electrum.enable = true; @@ -172,7 +176,7 @@ cache-size = 1000; # Camera network DHCP (isolated - no gateway = no internet) - interface = "enp2s0f1"; + interface = [ "enp2s0f1" "wg0" ]; bind-interfaces = true; dhcp-range = "192.168.1.100,192.168.1.200,24h";