.
This commit is contained in:
parent
f4760f39da
commit
57dafa4d25
6 changed files with 225 additions and 116 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
108
hosts/nixos/system/game-bypass.nix
Normal file
108
hosts/nixos/system/game-bypass.nix
Normal 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.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
95
hosts/nixos/system/vpn.nix
Normal file
95
hosts/nixos/system/vpn.nix
Normal 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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
'';
|
'';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue