This commit is contained in:
ashisgreat22 2026-01-28 20:10:48 +01:00
parent f4760f39da
commit 57dafa4d25
6 changed files with 225 additions and 116 deletions

View file

@ -52,7 +52,8 @@
./system/secrets.nix # SOPS secrets ./system/secrets.nix # SOPS secrets
./system/compatibility.nix # Compatibility layers (nix-ld) ./system/compatibility.nix # Compatibility layers (nix-ld)
./system/game-drive.nix ./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 #./system/authelia.nix # SSO/2FA
../../modules/nixos/media.nix # Arr Stack ../../modules/nixos/media.nix # Arr Stack
../../modules/nixos/steam-gamemode.nix # Steam GameMode Session ../../modules/nixos/steam-gamemode.nix # Steam GameMode Session

View file

@ -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";
};
} }

View file

@ -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.
}

View file

@ -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;
}
];
}

View file

@ -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.<name>.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 <<EOF
[Interface]
Address = $ADDRESS
PrivateKey = $PRIVATE_KEY
DNS = 1.1.1.1
[Peer]
PublicKey = $PEER_KEY
PresharedKey = $PRESHARED_KEY
Endpoint = $ENDPOINT_IP:$ENDPOINT_PORT
AllowedIPs = 0.0.0.0/0
EOF
chmod 600 /run/wg0.conf
${pkgs.wireguard-tools}/bin/wg-quick up /run/wg0.conf
'';
ExecStop = "${pkgs.wireguard-tools}/bin/wg-quick down /run/wg0.conf";
};
};
}

View file

@ -36,7 +36,7 @@
fi fi
echo "Launching gamescope..." echo "Launching gamescope..."
exec ${pkgs.gamescope}/bin/gamescope -f -e -- steam -gamepadui exec ${pkgs.gamescope}/bin/gamescope -f -e -- game-bypass steam -gamepadui
''} ''}
Type=Application Type=Application
''; '';