Replace tailscale with wireguard

This commit is contained in:
Yan Lin 2025-09-10 18:57:24 +02:00
parent 81f59a8148
commit d0084adcc9
8 changed files with 278 additions and 77 deletions

View file

@ -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=<hostname>
# or
tailscale up --exit-node=<hostname>
# Stop using exit node
tailscale set --exit-node=
# or
tailscale up --exit-node=
# Allow LAN access while using exit node
tailscale set --exit-node=<hostname> --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 |
|---------|-----|---------|

View file

@ -78,10 +78,6 @@
name = "Gotify";
url = "https://notify.yanlincs.com/";
}
{
name = "Tailscale";
url = "https://login.tailscale.com/admin/machines";
}
];
}
{

View file

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

View file

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

View file

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

View file

@ -15,7 +15,6 @@
];
casks = [
# GUI applications - manually installed apps now managed by Homebrew
"tailscale-app"
"inkscape"
"firefox"
"obsidian"

View file

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

196
modules/wireguard.nix Normal file
View file

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