diff --git a/flake.nix b/flake.nix index 76b1cc9..1ecc950 100644 --- a/flake.nix +++ b/flake.nix @@ -27,7 +27,7 @@ }; }; - outputs = { nixpkgs, nixpkgs-unstable, nur, ... }@inputs: + outputs = { self, nixpkgs, nixpkgs-unstable, nur, ... }@inputs: let mkPkgs = system: import nixpkgs { inherit system; diff --git a/system/machines/server/modules/frigate/default.nix b/system/machines/server/modules/frigate/default.nix index e067de0..402cfcc 100644 --- a/system/machines/server/modules/frigate/default.nix +++ b/system/machines/server/modules/frigate/default.nix @@ -7,6 +7,7 @@ let domain = "ramos.codes"; user = config.sops.placeholder."RTSP_USER"; pass = config.sops.placeholder."RTSP_PASS"; + privateAccessRules = concatMapStringsSep "\n" (cidr: "allow ${cidr};") nginx.privateAllowCidrs + "\ndeny all;"; in { @@ -239,9 +240,13 @@ in services.nginx.virtualHosts."frigate.${domain}" = mkIf nginx.enable { useACMEHost = domain; forceSSL = true; + locations."/" = { + extraConfig = privateAccessRules; + }; locations."/go2rtc/" = { proxyPass = "http://127.0.0.1:1984/"; proxyWebsockets = true; + extraConfig = privateAccessRules; }; }; diff --git a/system/machines/server/modules/immich/default.nix b/system/machines/server/modules/immich/default.nix index 031336d..f38e079 100644 --- a/system/machines/server/modules/immich/default.nix +++ b/system/machines/server/modules/immich/default.nix @@ -6,6 +6,7 @@ let nginx = config.modules.system.nginx; domain = "ramos.codes"; port = 2283; + privateAccessRules = concatMapStringsSep "\n" (cidr: "allow ${cidr};") nginx.privateAllowCidrs + "\ndeny all;"; in { @@ -51,6 +52,7 @@ in locations."/" = { proxyPass = "http://127.0.0.1:${toString port}"; proxyWebsockets = true; + extraConfig = privateAccessRules; }; }; }; diff --git a/system/machines/server/modules/nginx/default.nix b/system/machines/server/modules/nginx/default.nix index 44ad00f..ca4fbf0 100644 --- a/system/machines/server/modules/nginx/default.nix +++ b/system/machines/server/modules/nginx/default.nix @@ -4,11 +4,23 @@ with lib; let cfg = config.modules.system.nginx; domain = "ramos.codes"; + privateAccessRules = concatMapStringsSep "\n" (cidr: "allow ${cidr};") cfg.privateAllowCidrs + "\ndeny all;"; in { options.modules.system.nginx = { enable = mkEnableOption "Nginx Reverse Proxy"; + + privateAllowCidrs = mkOption { + type = types.listOf types.str; + default = [ + "192.168.0.0/24" + "10.8.0.0/24" + ]; + description = '' + CIDR ranges allowed to access private vhosts (LAN + WireGuard). + ''; + }; }; config = mkIf cfg.enable { @@ -28,25 +40,12 @@ in }; }; - services.sslh = { - enable = true; - listenAddresses = [ "0.0.0.0" ]; - port = 443; - settings = { - protocols = [ - { name = "ssh"; host = "127.0.0.1"; port = "22"; } - { name = "tls"; host = "127.0.0.1"; port = "4443"; } - ]; - }; - }; - services.nginx = { enable = true; recommendedTlsSettings = true; recommendedOptimisation = true; recommendedGzipSettings = true; eventsConfig = "worker_connections 4096;"; - defaultSSLListenPort = 4443; # Catch-all default - friendly error for unknown subdomains virtualHosts."_" = { @@ -78,6 +77,7 @@ in locations."/" = { proxyPass = "http://192.168.0.23:3080"; proxyWebsockets = true; + extraConfig = privateAccessRules; }; }; @@ -87,6 +87,7 @@ in locations."/" = { proxyPass = "http://192.168.0.23:8000"; proxyWebsockets = true; + extraConfig = privateAccessRules; }; }; @@ -96,6 +97,7 @@ in locations."/" = { proxyPass = "http://192.168.0.23:8188"; proxyWebsockets = true; + extraConfig = privateAccessRules; }; }; }; diff --git a/system/machines/server/modules/sandpack/default.nix b/system/machines/server/modules/sandpack/default.nix index e4e5a9c..d8b46a1 100644 --- a/system/machines/server/modules/sandpack/default.nix +++ b/system/machines/server/modules/sandpack/default.nix @@ -4,6 +4,7 @@ with lib; let cfg = config.modules.system.sandpack; domain = "ramos.codes"; + privateAccessRules = concatMapStringsSep "\n" (cidr: "allow ${cidr};") config.modules.system.nginx.privateAllowCidrs + "\ndeny all;"; staticBrowserServer = pkgs.stdenvNoCC.mkDerivation (finalAttrs: let pnpm = pkgs.pnpm_10; @@ -97,6 +98,8 @@ in locations."/" = { proxyPass = "http://127.0.0.1:4333"; extraConfig = '' + ${privateAccessRules} + add_header Access-Control-Allow-Origin "*" always; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Content-Type, Authorization" always; @@ -116,6 +119,8 @@ in locations."/" = { proxyPass = "http://127.0.0.1:4324"; extraConfig = '' + ${privateAccessRules} + add_header Access-Control-Allow-Origin "*" always; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Content-Type, Authorization" always; diff --git a/system/machines/server/modules/webdav/default.nix b/system/machines/server/modules/webdav/default.nix index 1b90573..1eb5684 100644 --- a/system/machines/server/modules/webdav/default.nix +++ b/system/machines/server/modules/webdav/default.nix @@ -4,6 +4,7 @@ with lib; let cfg = config.modules.system.webdav; domain = "ramos.codes"; + privateAccessRules = concatMapStringsSep "\n" (cidr: "allow ${cidr};") config.modules.system.nginx.privateAllowCidrs + "\ndeny all;"; in { @@ -50,6 +51,8 @@ in locations."/" = { proxyPass = "http://127.0.0.1:8090"; extraConfig = '' + ${privateAccessRules} + proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 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..1d4065c 100644 --- a/system/machines/server/system.nix +++ b/system/machines/server/system.nix @@ -19,6 +19,15 @@ frigate.enable = true; immich.enable = true; webdav.enable = false; + wireguard = { + enable = true; + peers = [ + { + publicKey = "HRFsVXn3jeqKQLQIl0cB6KC/qia7M1gQf2lqG5HDxF8="; + allowedIPs = [ "10.8.0.2/32" ]; + } + ]; + }; # bitcoin = { # enable = true; # electrum.enable = true; @@ -172,7 +181,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"; @@ -185,6 +194,11 @@ }; }; + systemd.services.dnsmasq = { + after = [ "wireguard-wg0.service" ]; + wants = [ "wireguard-wg0.service" ]; + }; + services.fail2ban = { enable = true; maxretry = 5; diff --git a/user/modules/utils/dev/default.nix b/user/modules/utils/dev/default.nix index 1c10315..89c4809 100644 --- a/user/modules/utils/dev/default.nix +++ b/user/modules/utils/dev/default.nix @@ -3,13 +3,14 @@ with lib; let cfg = config.modules.user.utils.dev; - in { options.modules.user.utils.dev = { enable = mkEnableOption "user.utils.dev"; }; config = mkIf cfg.enable { home.packages = with pkgs; [ unstable.claude-code unstable.codex + unstable.opencode + bubblewrap nix-init