diff --git a/README.md b/README.md index 79a5ce4..2aa7146 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ home-manager switch --flake github:Logan-Lin/nix-config#yanlin@vps │ ├── btop.nix # Modern system monitor (includes package) │ ├── ghostty.nix # GPU-accelerated terminal emulator │ ├── syncthing.nix # File synchronization service (includes package) -│ ├── tailscale.nix # Secure networking and VPN service +│ ├── wireguard.nix # Hub-and-spoke VPN networking │ ├── borg.nix # Borg backup system with automated scheduling │ └── homebrew.nix # Homebrew and nix-homebrew configuration ├── config/ # Configuration files @@ -890,69 +890,65 @@ echo "BORG_PASSPHRASE=your-secure-passphrase" | sudo tee /etc/borg-passphrase sudo chmod 600 /etc/borg-passphrase ``` -## 🔒 Secure Networking: Tailscale +## 🔒 Secure Networking: WireGuard VPN -**Configuration**: `modules/tailscale.nix` -**Purpose**: Secure mesh VPN for private networking across devices +**Configuration**: `modules/wireguard.nix` +**Purpose**: Hub-and-spoke VPN for secure connectivity between VPS and home server + +### Network Architecture: +- **VPS (Hub)**: 10.2.2.1/24 - Central WireGuard server with public endpoint +- **HS (Spoke)**: 10.2.2.20/24 - Home server connecting through VPS +- **LAN Access**: HS remains accessible at 10.1.1.152 on local network +- **DNS Setup**: hs.yanlincs.com resolves to 10.1.1.152 (LAN) with 10.2.2.20 (WireGuard) fallback ### Key Features: -- **Automatic Startup**: Runs as a system service at boot -- **MagicDNS**: Access devices by name instead of IP addresses -- **Secure Connectivity**: Zero-configuration encrypted connections -- **Exit Nodes**: Route traffic through specific devices +- **Hub-and-Spoke Topology**: VPS acts as central gateway for all connections +- **Dual Access**: Home server accessible via both LAN (10.1.1.152) and WireGuard (10.2.2.20) +- **Automatic Key Management**: Private keys generated and managed per host +- **Firewall Integration**: Automatic firewall rules and IP forwarding +- **Systemd Integration**: Uses wg-quick for reliable service management ### Command Line Usage: -#### Basic Operations: +#### Service Management: ```bash -# Check connection status and see all devices -tailscale status +# Check WireGuard status +sudo systemctl status wg-quick-wg0 -# Connect to your Tailscale network (first-time setup) -tailscale up +# Start/stop WireGuard +sudo systemctl start wg-quick-wg0 +sudo systemctl stop wg-quick-wg0 -# Disconnect temporarily -tailscale down +# View WireGuard interface status +sudo wg show -# View current Tailscale IP address -tailscale ip -4 +# Check connectivity +ping 10.2.2.1 # Ping VPS from HS +ping 10.2.2.20 # Ping HS from VPS ``` -#### Exit Node Management: +#### Key Management: ```bash -# List available exit nodes -tailscale exit-node list +# View public key (add to peer configurations) +sudo wg pubkey < /etc/wireguard/private.key -# Use a specific exit node -tailscale set --exit-node= -# or -tailscale up --exit-node= - -# Stop using exit node -tailscale set --exit-node= -# or -tailscale up --exit-node= - -# Allow LAN access while using exit node -tailscale set --exit-node= --exit-node-allow-lan-access -``` - -#### Advanced Usage: -```bash -# Get suggested exit node -tailscale exit-node suggest - -# Check detailed network diagnostics -tailscale netcheck - -# Show network configuration -tailscale debug netmap +# Generate new keys if needed +wg genkey | sudo tee /etc/wireguard/private.key +sudo wg pubkey < /etc/wireguard/private.key ``` ### Configuration Details: -- **Auto-start**: Enabled via nix-darwin service management -- **DNS Override**: Uses Tailscale's MagicDNS (100.100.100.100) for name resolution -- **System Integration**: Runs as a daemon accessible to all users +- **Server Mode**: Configured on VPS with NAT forwarding and firewall rules +- **Client Mode**: Configured on HS with persistent keepalive to VPS +- **Automatic Startup**: Enabled via systemd wg-quick service +- **Key Storage**: Private keys stored in `/etc/wireguard/private.key` with 600 permissions +- **Port**: Default UDP 51820 (configurable) + +### Setup Process: +1. Deploy configurations to both VPS and HS +2. Retrieve public keys from each host after first boot +3. Update peer configurations with actual public keys and VPS endpoint IP +4. Restart WireGuard services to establish connection ## 🏠 Home Server (`hs` Host) @@ -1054,7 +1050,7 @@ Comprehensive suite of self-hosted services managed via Podman with automatic st ### 📍 Service Access -All services accessible via Tailscale VPN with SSL certificates: +All services accessible via DNS with dual-IP resolution (LAN: 10.1.1.152, WireGuard: 10.2.2.20) with SSL certificates: | Service | URL | Purpose | |---------|-----|---------| diff --git a/config/firefox/bookmarks.nix b/config/firefox/bookmarks.nix index f5e8f1e..2934c97 100644 --- a/config/firefox/bookmarks.nix +++ b/config/firefox/bookmarks.nix @@ -78,10 +78,6 @@ name = "Gotify"; url = "https://notify.yanlincs.com/"; } - { - name = "Tailscale"; - url = "https://login.tailscale.com/admin/machines"; - } ]; } { diff --git a/hosts/nixos/hs/system.nix b/hosts/nixos/hs/system.nix index 9b29e92..e591755 100644 --- a/hosts/nixos/hs/system.nix +++ b/hosts/nixos/hs/system.nix @@ -5,7 +5,7 @@ ./containers.nix # Host-specific container definitions ./proxy.nix # Host-specific Traefik dynamic configuration ./disk-health.nix # Host-specific disk health monitoring - ../../../modules/tailscale.nix + ../../../modules/wireguard.nix ../../../modules/podman.nix ../../../modules/traefik.nix ../../../modules/samba.nix @@ -290,6 +290,20 @@ ''; }; + # WireGuard VPN configuration (HS as client/spoke) + services.wireguard-custom = { + enable = true; + mode = "client"; + clientConfig = { + address = "10.2.2.20/24"; + # Public key will be generated when VPS is configured + # Replace with actual public key from VPS after initial setup + serverPublicKey = "REPLACE_WITH_VPS_PUBLIC_KEY"; + serverEndpoint = "YOUR_VPS_IP:51820"; # Replace with actual VPS public IP + allowedIPs = [ "10.2.2.0/24" ]; + }; + }; + # This value determines the NixOS release from which the default # settings for stateful data, like file locations and database versions # on your system were taken. It's perfectly fine and recommended to leave diff --git a/hosts/nixos/vps/proxy.nix b/hosts/nixos/vps/proxy.nix index b0edeae..bf75f0f 100644 --- a/hosts/nixos/vps/proxy.nix +++ b/hosts/nixos/vps/proxy.nix @@ -56,20 +56,20 @@ }; }; - # Photo service backend + # Photo service backend (via WireGuard) photo = { loadBalancer = { servers = [{ - url = "http://hs.yanlincs.com:5000"; + url = "http://10.2.2.20:5000"; }]; }; }; - # Cloud service backend + # Cloud service backend (via WireGuard) cloud = { loadBalancer = { servers = [{ - url = "http://hs.yanlincs.com:5001"; + url = "http://10.2.2.20:5001"; }]; }; }; diff --git a/hosts/nixos/vps/system.nix b/hosts/nixos/vps/system.nix index 2214032..fd4ebc0 100644 --- a/hosts/nixos/vps/system.nix +++ b/hosts/nixos/vps/system.nix @@ -4,7 +4,7 @@ ./disk-config.nix ./containers.nix # Host-specific container definitions ./proxy.nix # Host-specific Traefik dynamic configuration - ../../../modules/tailscale.nix + ../../../modules/wireguard.nix ../../../modules/podman.nix ../../../modules/traefik.nix ../../../modules/borg.nix @@ -135,6 +135,24 @@ ''; }; + # WireGuard VPN configuration (VPS as hub/server) + services.wireguard-custom = { + enable = true; + mode = "server"; + serverConfig = { + address = "10.2.2.1/24"; + peers = [ + { + name = "hs"; + # Public key will be generated when HS is configured + # Replace with actual public key from HS after initial setup + publicKey = "REPLACE_WITH_HS_PUBLIC_KEY"; + allowedIPs = [ "10.2.2.20/32" ]; + } + ]; + }; + }; + # This value determines the NixOS release from which the default # settings for stateful data, like file locations and database versions # on your system were taken. It's perfectly fine and recommended to leave diff --git a/modules/homebrew.nix b/modules/homebrew.nix index cd7980b..2fb9736 100644 --- a/modules/homebrew.nix +++ b/modules/homebrew.nix @@ -15,7 +15,6 @@ ]; casks = [ # GUI applications - manually installed apps now managed by Homebrew - "tailscale-app" "inkscape" "firefox" "obsidian" diff --git a/modules/tailscale.nix b/modules/tailscale.nix deleted file mode 100644 index a396c0e..0000000 --- a/modules/tailscale.nix +++ /dev/null @@ -1,18 +0,0 @@ -{ config, pkgs, lib, ... }: - -{ - # Enable Tailscale service for NixOS - services.tailscale = { - enable = true; - # Enable MagicDNS for better name resolution on NixOS server - useRoutingFeatures = "server"; - }; - - # Allow Tailscale through the firewall if enabled - networking.firewall = { - # Allow Tailscale UDP port - allowedUDPPorts = [ 41641 ]; - # Allow traffic from Tailscale subnet - trustedInterfaces = [ "tailscale0" ]; - }; -} diff --git a/modules/wireguard.nix b/modules/wireguard.nix new file mode 100644 index 0000000..8650d1d --- /dev/null +++ b/modules/wireguard.nix @@ -0,0 +1,196 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.wireguard-custom; +in + +{ + options.services.wireguard-custom = { + enable = mkEnableOption "WireGuard VPN"; + + mode = mkOption { + type = types.enum [ "server" "client" ]; + description = "Whether to run as server (hub) or client (spoke)"; + }; + + interface = mkOption { + type = types.str; + default = "wg0"; + description = "WireGuard interface name"; + }; + + listenPort = mkOption { + type = types.port; + default = 51820; + description = "UDP port to listen on (server mode only)"; + }; + + privateKeyFile = mkOption { + type = types.str; + default = "/etc/wireguard/private.key"; + description = "Path to private key file"; + }; + + serverConfig = mkOption { + type = types.submodule { + options = { + address = mkOption { + type = types.str; + example = "10.2.2.1/24"; + description = "Server IP address with CIDR"; + }; + + peers = mkOption { + type = types.listOf (types.submodule { + options = { + name = mkOption { + type = types.str; + description = "Peer name for identification"; + }; + + publicKey = mkOption { + type = types.str; + description = "Peer's public key"; + }; + + allowedIPs = mkOption { + type = types.listOf types.str; + description = "IP addresses this peer is allowed to use"; + }; + }; + }); + default = []; + description = "List of client peers"; + }; + }; + }; + description = "Server-specific configuration"; + }; + + clientConfig = mkOption { + type = types.submodule { + options = { + address = mkOption { + type = types.str; + example = "10.2.2.20/24"; + description = "Client IP address with CIDR"; + }; + + serverPublicKey = mkOption { + type = types.str; + description = "Server's public key"; + }; + + serverEndpoint = mkOption { + type = types.str; + example = "vpn.example.com:51820"; + description = "Server endpoint (host:port)"; + }; + + allowedIPs = mkOption { + type = types.listOf types.str; + default = [ "10.2.2.0/24" ]; + description = "IP ranges to route through the tunnel"; + }; + }; + }; + description = "Client-specific configuration"; + }; + }; + + config = mkIf cfg.enable { + # Install WireGuard tools + environment.systemPackages = with pkgs; [ wireguard-tools ]; + + # Create private key file if it doesn't exist + systemd.tmpfiles.rules = [ + "d /etc/wireguard 0700 root root - -" + "f ${cfg.privateKeyFile} 0600 root root - -" + ]; + + # Generate private key on first run + systemd.services.wireguard-keygen = { + description = "Generate WireGuard private key"; + before = [ "wg-quick-${cfg.interface}.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + if [ ! -s ${cfg.privateKeyFile} ]; then + echo "Generating WireGuard private key..." + ${pkgs.wireguard-tools}/bin/wg genkey > ${cfg.privateKeyFile} + chmod 600 ${cfg.privateKeyFile} + echo "Private key generated. Public key:" + ${pkgs.wireguard-tools}/bin/wg pubkey < ${cfg.privateKeyFile} + echo "Please add this public key to your peer configurations." + fi + ''; + }; + + # Server configuration + networking.wg-quick.interfaces = mkIf (cfg.mode == "server") { + ${cfg.interface} = { + address = [ cfg.serverConfig.address ]; + listenPort = cfg.listenPort; + privateKeyFile = cfg.privateKeyFile; + + # Enable IP forwarding and NAT for server + preUp = '' + ${pkgs.iptables}/bin/iptables -A FORWARD -i ${cfg.interface} -j ACCEPT + ${pkgs.iptables}/bin/iptables -A FORWARD -o ${cfg.interface} -j ACCEPT + ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s 10.2.2.0/24 -o eth0 -j MASQUERADE + ''; + + postDown = '' + ${pkgs.iptables}/bin/iptables -D FORWARD -i ${cfg.interface} -j ACCEPT + ${pkgs.iptables}/bin/iptables -D FORWARD -o ${cfg.interface} -j ACCEPT + ${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s 10.2.2.0/24 -o eth0 -j MASQUERADE + ''; + + peers = map (peer: { + publicKey = peer.publicKey; + allowedIPs = peer.allowedIPs; + }) cfg.serverConfig.peers; + }; + }; + + # Client configuration + networking.wg-quick.interfaces = mkIf (cfg.mode == "client") { + ${cfg.interface} = { + address = [ cfg.clientConfig.address ]; + privateKeyFile = cfg.privateKeyFile; + + peers = [{ + publicKey = cfg.clientConfig.serverPublicKey; + allowedIPs = cfg.clientConfig.allowedIPs; + endpoint = cfg.clientConfig.serverEndpoint; + persistentKeepalive = 25; + }]; + }; + }; + + # Firewall configuration + networking.firewall = mkMerge [ + # Server firewall rules + (mkIf (cfg.mode == "server") { + allowedUDPPorts = [ cfg.listenPort ]; + trustedInterfaces = [ cfg.interface ]; + }) + + # Client firewall rules + (mkIf (cfg.mode == "client") { + trustedInterfaces = [ cfg.interface ]; + }) + ]; + + # Enable IP forwarding for server + boot.kernel.sysctl = mkIf (cfg.mode == "server") { + "net.ipv4.ip_forward" = 1; + "net.ipv6.conf.all.forwarding" = 1; + }; + }; +} \ No newline at end of file