diff --git a/hosts/nixos/configuration.nix b/hosts/nixos/configuration.nix index a3940fc..b241e0b 100644 --- a/hosts/nixos/configuration.nix +++ b/hosts/nixos/configuration.nix @@ -52,7 +52,8 @@ ./system/secrets.nix # SOPS secrets ./system/compatibility.nix # Compatibility layers (nix-ld) ./system/game-drive.nix - ./system/vpn-namespace.nix # Isolated VPN Namespace + ./system/vpn.nix # System-wide VPN + ./system/game-bypass.nix # Game bypass netns #./system/authelia.nix # SSO/2FA ../../modules/nixos/media.nix # Arr Stack ../../modules/nixos/steam-gamemode.nix # Steam GameMode Session diff --git a/hosts/nixos/home/steam.nix b/hosts/nixos/home/steam.nix index 851de26..f2255d4 100644 --- a/hosts/nixos/home/steam.nix +++ b/hosts/nixos/home/steam.nix @@ -18,4 +18,23 @@ }; }; }; + + # Override Steam desktop entry to use bypass + xdg.desktopEntries.steam = { + name = "Steam"; + genericName = "Application Store"; + exec = "game-bypass steam %U"; + terminal = false; + icon = "steam"; + categories = [ + "Network" + "FileTransfer" + "Game" + ]; + mimeType = [ + "x-scheme-handler/steam" + "x-scheme-handler/steamlink" + ]; + prefetchIface = "steam"; + }; } diff --git a/hosts/nixos/system/game-bypass.nix b/hosts/nixos/system/game-bypass.nix new file mode 100644 index 0000000..f6dea8b --- /dev/null +++ b/hosts/nixos/system/game-bypass.nix @@ -0,0 +1,108 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + # Namespace setup for Game Bypass + systemd.services.game-bypass-netns = { + description = "Game Bypass Network Namespace (Direct Internet)"; + wants = [ "network.target" ]; + after = [ "network.target" ]; + requiredBy = [ "multi-user.target" ]; + + path = [ + pkgs.iproute2 + pkgs.kmod + pkgs.util-linux # for nsenter + pkgs.dhcpcd + ]; + + script = '' + NAME="physical" + VETH_HOST="veth-game" + VETH_NS="eth0" + BRIDGE="br0" + + # 1. Create Namespace if not exists + if ! ip netns list | grep -q "$NAME"; then + ip netns add "$NAME" + fi + + # 2. Setup Veth Pair + # Cleanup previous + ip link delete "$VETH_HOST" 2>/dev/null || true + + # Create pair + ip link add "$VETH_HOST" type veth peer name "$VETH_NS-tmp" + + # Host side: Attach to Bridge + ip link set "$VETH_HOST" master "$BRIDGE" + ip link set "$VETH_HOST" up + + # Client side: Move to NS + ip link set "$VETH_NS-tmp" netns "$NAME" + + # 3. Configure Inside Namespace + # Rename to eth0 + ip netns exec "$NAME" ip link set "$VETH_NS-tmp" name "$VETH_NS" + ip netns exec "$NAME" ip link set "$VETH_NS" up + ip netns exec "$NAME" ip link set lo up + + # 4. DHCP + # Run dhcpcd inside the namespace + # We use -4 for IPv4 only if desired, or just standard + ip netns exec "$NAME" dhcpcd --nobackground "$VETH_NS" & + + # 5. DNS (Use Google/Cloudflare directly) + mkdir -p /etc/netns/"$NAME" + echo "nameserver 1.1.1.1" > /etc/netns/"$NAME"/resolv.conf + echo "nameserver 8.8.8.8" >> /etc/netns/"$NAME"/resolv.conf + ''; + + # We use oneshot to just launch dhcpcd + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + User = "root"; + }; + }; + + # Wrapper script to launch apps in the bypass + environment.systemPackages = [ + (pkgs.writeShellScriptBin "game-bypass" '' + exec doas ip netns exec physical sudo -u ${config.users.users.ashie.name} -E -- "$@" + '') + ]; + + # Security wrapper permissions + security.doas.extraRules = [ + { + users = [ "ashie" ]; + cmd = "/run/current-system/sw/bin/ip"; + args = [ + "netns" + "exec" + "physical" + "sudo" + "-u" + "ashie" + "-E" + "--" + ]; + noPass = true; + keepEnv = true; + } + ]; + + # Also need sudo rules? + # "sudo -u ashie" inside the namespace might prompt for password if not configured. + # Usually user running sudo to switch to themselves needs no password if standard setup, + # OR we can just use setpriv/su if we are already root (which ip netns exec is). + # Wait, `ip netns exec` runs as root. + # So we are root inside the NS. We then drop privileges to 'ashie'. + # `sudo -u ashie` works fine if we are root. + +} diff --git a/hosts/nixos/system/vpn-namespace.nix b/hosts/nixos/system/vpn-namespace.nix deleted file mode 100644 index cfd79d4..0000000 --- a/hosts/nixos/system/vpn-namespace.nix +++ /dev/null @@ -1,114 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: - -{ - # Ensure iproute2 is available - environment.systemPackages = [ pkgs.iproute2 ]; - - systemd.services.vpn-netns = { - description = "VPN Network Namespace Setup"; - wants = [ "network-online.target" ]; - after = [ "network-online.target" ]; - requiredBy = [ "multi-user.target" ]; - - path = [ - pkgs.iproute2 - pkgs.wireguard-tools - pkgs.kmod - ]; - - script = '' - # 1. Create Namespace if not exists - if ! ip netns list | grep -q "vpn"; then - ip netns add vpn - fi - - # 2. Cleanup & Create WireGuard Interface - # Delete if exists INSIDE namespace (from previous run) - ip netns exec vpn ip link delete wg0 2>/dev/null || true - - # Delete if exists in default namespace - ip link delete wg0 2>/dev/null || true - ip link add wg0 type wireguard - ip link set mtu 1320 dev wg0 - - # 3. Move to Namespace - ip link set wg0 netns vpn - - # 4. Configure WireGuard (INSIDE NAMESPACE) - # We read secrets from the sops-rendered files - PRIVATE_KEY=$(cat ${config.sops.secrets.wireguard_private_key.path}) - PEER_KEY=$(cat ${config.sops.secrets.wireguard_public_key.path}) - ENDPOINT_IP=$(cat ${config.sops.secrets.wireguard_endpoint_ip.path}) - ENDPOINT_PORT=$(cat ${config.sops.secrets.wireguard_endpoint_port.path}) - ADDRESS=$(cat ${config.sops.secrets.wireguard_addresses.path}) - PRESHARED_KEY=$(cat ${config.sops.secrets.wireguard_preshared_key.path}) - - # Pass private key via stdin to file - echo "$PRIVATE_KEY" > /run/wg0.key - chmod 600 /run/wg0.key - - # Setup interface inside netns - ip netns exec vpn wg set wg0 \ - private-key /run/wg0.key \ - peer "$PEER_KEY" \ - preshared-key <(echo "$PRESHARED_KEY") \ - endpoint "$ENDPOINT_IP:$ENDPOINT_PORT" \ - allowed-ips 0.0.0.0/0 - - rm /run/wg0.key - - # Assign IP Address - ip netns exec vpn ip addr add "$ADDRESS" dev wg0 - - # Set MTU (Optimized for VPN to avoid fragmentation) - ip netns exec vpn ip link set mtu 1320 dev wg0 - - # Bring Up - ip netns exec vpn ip link set wg0 up - ip netns exec vpn ip link set lo up - - # Set Default Route - ip netns exec vpn ip route add default dev wg0 - - # 5. DNS (Optional - force Google/Cloudflare inside namespace) - mkdir -p /etc/netns/vpn - echo "nameserver 1.1.1.1" > /etc/netns/vpn/resolv.conf - ''; - - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - User = "root"; - }; - }; - - # Allow user 'ashie' to run the namespace launcher without password - security.doas.extraRules = [ - { - users = [ "ashie" ]; - cmd = "/run/current-system/sw/bin/ip"; - args = [ - "netns" - "exec" - "vpn" - "doas" - "-u" - "ashie" - "--" - ]; # Permit the specific chain - noPass = true; - } - # Allow running the script itself - { - users = [ "ashie" ]; - cmd = "/home/ashie/nixos/scripts/launch-vpn-app.sh"; - noPass = true; - keepEnv = true; - } - ]; -} diff --git a/hosts/nixos/system/vpn.nix b/hosts/nixos/system/vpn.nix new file mode 100644 index 0000000..d87ae69 --- /dev/null +++ b/hosts/nixos/system/vpn.nix @@ -0,0 +1,95 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + networking.wg-quick.interfaces = { + wg0 = { + address = [ "10.66.219.165/32" ]; # Fallback, should ideally come from secret but wg-quick needs it here or in config + # We will rely on the config file generation to include the address if possible, + # but nixos module usually requires 'address' or 'configFile'. + # Since we are using secrets for everything, we might need a script or careful usage of configFile. + + # Better approach with sops: use the declarative config but read secrets from file for keys. + # However, 'address' usually isn't secret. + # In vpn-namespace.nix: ADDRESS=$(cat ${config.sops.secrets.wireguard_addresses.path}) + + # Valid approach for secrets in wg-quick: + # Use configFile pointing to a secret file, OR use normal config with privateKeyFile. + + autostart = true; + dns = [ "1.1.1.1" ]; # Force DNS through VPN (or public DNS) + privateKeyFile = config.sops.secrets.wireguard_private_key.path; + + peers = [ + { + publicKey = "KPj/q9j/..."; # We need the public key here. + # PROB: The user's vpn-namespace.nix was reading PEER_KEY from a secret. + # NixOS wg-quick module expects publicKey as a string literal in the nix config, usually. + # If the peer public key is secret, we can't easily put it in nix store (it ends up world readable). + # BUT: Public keys are generally safe to be public. + # However, since the user has it in sops, they might want to keep it secret. + + # ALTERNATIVE: Use `configFile` option and generate the whole config from secrets at runtime. + # But `networking.wg-quick.interfaces..configFile` expects a path. + + # Let's write a script to generate the config file from secrets and start it. + # actually, the `networking.wg-quick` module is just a wrapper around systemd services. + + # Let's try to stick to the module if possible. + # If we can't, we can write a systemd service just like vpn-namespace.nix but for the main netns. + } + ]; + }; + }; + + # RE-EVALUATION: + # Since all WireGuard parameters (Addresses, Endpoint, Keys) are in sops secrets, + # trying to use `networking.wg-quick.interfaces` is clumsy because it expects static values for non-secrets. + # We should create a systemd service that constructs the config and runs wg-quick. + + systemd.services.wg-quick-wg0 = { + description = "WireGuard Tunnel wg0"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + path = [ + pkgs.wireguard-tools + pkgs.bash + ]; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = pkgs.writeShellScript "wg0-up" '' + # Generate Config + PRIVATE_KEY=$(cat ${config.sops.secrets.wireguard_private_key.path}) + PEER_KEY=$(cat ${config.sops.secrets.wireguard_public_key.path}) + ENDPOINT_IP=$(cat ${config.sops.secrets.wireguard_endpoint_ip.path}) + ENDPOINT_PORT=$(cat ${config.sops.secrets.wireguard_endpoint_port.path}) + ADDRESS=$(cat ${config.sops.secrets.wireguard_addresses.path}) + PRESHARED_KEY=$(cat ${config.sops.secrets.wireguard_preshared_key.path}) + + cat > /run/wg0.conf <