.
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/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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
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
|
||||
'';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue