Skip to content

πŸ¦” MoleHopper β€” Automated Mullvad WireGuard rotation for UniFi Dream Router. Hop between 100+ servers on schedule, route specific VLANs through VPN, survive reboots, and maintain CLI control.

License

Notifications You must be signed in to change notification settings

sambegui/MoleHopper

Repository files navigation

MoleHopper

Automated Mullvad VPN rotation for UniFi Dream Router

Hop between 100+ Mullvad WireGuard servers on a schedule, with smart selection and manual override capabilities. Named after Mullvad's mole mascot β€” always burrowing through a new tunnel.

Features

  • Automated Rotation β€” Switches servers twice daily (configurable) via cron
  • Weighted-Random Selection β€” Avoids recently-used servers for better distribution
  • Manual Control β€” CLI tool for instant server switching when needed
  • Lock Protection β€” Prevent auto-rotation when you've manually selected a server
  • Boot Persistence β€” Survives router reboots with proper systemd integration
  • Automatic Rollback β€” Falls back to previous config if rotation fails
  • Split-Tunnel Routing β€” Routes only specific VLANs through VPN (e.g., IoT network)
  • Remote Access β€” UniFi WireGuard Server clients also route through Mullvad

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    UniFi Dream Router (UDR)                         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚   WireGuard      β”‚     β”‚         Rotation System              β”‚ β”‚
β”‚  β”‚   (wgclt1)       β”‚     β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚ β”‚
β”‚  β”‚                  β”‚     β”‚  β”‚  mullvad-rotate.sh (cron)      β”‚  β”‚ β”‚
β”‚  β”‚  β€’ Table = off   │◄────│  β”‚  β€’ Weighted-random selection   β”‚  β”‚ β”‚
β”‚  β”‚  β€’ 107 configs   β”‚     β”‚  β”‚  β€’ Avoids last 10 servers      β”‚  β”‚ β”‚
β”‚  β”‚                  β”‚     β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚ β”‚
β”‚           β”‚               β”‚  β”‚  mullvad-switch.sh (CLI)       β”‚  β”‚ β”‚
β”‚           β”‚               β”‚  β”‚  β€’ Manual server selection     β”‚  β”‚ β”‚
β”‚           β”‚               β”‚  β”‚  β€’ Lock/unlock rotation        β”‚  β”‚ β”‚
β”‚           β”‚               β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚ β”‚
β”‚           β”‚               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚           β”‚                                                        β”‚
β”‚           β–Ό                                                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                              β”‚
β”‚  β”‚   split-vpn      β”‚     Routes VPN VLAN (br50) traffic          β”‚
β”‚  β”‚   (table 178)    β”‚     through wgclt1 interface                β”‚
β”‚  β”‚                  β”‚                                              β”‚
β”‚  β”‚  β€’ Policy routes β”‚     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚  β”‚  β€’ MASQUERADE    β”‚     β”‚   br50      β”‚    β”‚  Other      β”‚      β”‚
β”‚  β”‚  β€’ Mark: 0x169   │◄────│ VPN VLAN    β”‚    β”‚  VLANs      │──►WANβ”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚192.168.50.x β”‚    β”‚             β”‚      β”‚
β”‚                           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Prerequisites

  • UniFi Dream Router (UDR) with SSH access enabled
  • Mullvad VPN subscription with WireGuard configs
  • Dedicated VLAN for VPN traffic (e.g., br50 / 192.168.50.0/24)
  • Basic familiarity with command line

Quick Start

1. Download Mullvad Configs

Download all WireGuard configuration files from Mullvad's website for your desired servers.

2. Sanitize Configs (Remove IPv6)

# On your Mac/Linux machine
for conf in *.conf; do
    # Remove IPv6 addresses and DNS line
    sed -i.bak \
        -e 's/,.*:.*\/[0-9]*//g' \
        -e '/^DNS/d' \
        "$conf"
    # Add Table = off after [Interface]
    sed -i '' '/\[Interface\]/a\
Table = off
' "$conf"
done

3. Upload to Router

# Create directories
ssh root@<UDR-IP> "mkdir -p /data/mullvad-rotation/configs"

# Upload configs
scp *.conf root@<UDR-IP>:/data/mullvad-rotation/configs/

# Upload rotation scripts
scp mullvad-switch.sh mullvad-rotate.sh install-rotation.sh \
    root@<UDR-IP>:/data/mullvad-rotation/

4. Install

ssh root@<UDR-IP>
cd /data/mullvad-rotation
chmod +x install-rotation.sh
./install-rotation.sh

See DEPLOYMENT_GUIDE.md for detailed step-by-step instructions.

Usage

Check Status

mullvad-switch status

Output:

Mullvad VPN Status:
==========================================
Current Server:  us-nyc-wg-501
Last Rotation:   2025-12-16T03:00:00-05:00
Rotation Status: Active
WireGuard:       Connected to 146.70.165.2:51820
==========================================

List Available Servers

mullvad-switch list

Manual Server Switch

# Switch to specific server (rotation continues normally)
mullvad-switch switch us-nyc-wg-501

# Switch and lock for 24 hours (prevents auto-rotation)
mullvad-switch switch us-lax-wg-303 --lock

# Unlock to resume auto-rotation
mullvad-switch unlock

Force Rotation

# Rotate now (respects locks)
mullvad-rotate

# Rotate now (bypasses locks)
mullvad-rotate --force

Configuration

Setting Location Default Description
Rotation schedule crontab -e 0 3,15 * * * 3am & 3pm daily
History avoidance mullvad-rotate.sh:18 10 Skip last N servers
Lock duration mullvad-switch.sh:282 24 hours Auto-unlock time
Route table split-vpn/vpn/vpn.conf 178 Policy routing table
VPN VLAN split-vpn/vpn/vpn.conf br50 Interface to route

See CONFIGURATION.md for all options.

File Structure

/data/mullvad-rotation/
β”œβ”€β”€ configs/                    # Mullvad WireGuard configs (107 servers)
β”œβ”€β”€ logs/
β”‚   β”œβ”€β”€ rotation.log           # Auto-rotation history
β”‚   β”œβ”€β”€ switch.log             # Manual switch history
β”‚   └── cron.log               # Cron execution logs
β”œβ”€β”€ state.json                 # Current state & rotation history
β”œβ”€β”€ mullvad-switch.sh          # Manual control CLI
β”œβ”€β”€ mullvad-rotate.sh          # Automated rotation script
β”œβ”€β”€ install-rotation.sh        # Installation script
└── restore-after-update.sh    # Post-firmware-update recovery

/data/split-vpn/
β”œβ”€β”€ vpn/
β”‚   β”œβ”€β”€ vpn.conf               # Split-VPN routing configuration
β”‚   └── updown.sh              # Routing script
└── cleanup-unifi-conflicts.sh # Boot-time NAT/route fixes

/etc/wireguard/
└── wgclt1.conf                # Active WireGuard config

/etc/systemd/system/
└── split-vpn.service          # systemd service unit

Documentation

Security Notes

Never commit to Git:

  • configs/*.conf β€” Contains WireGuard private keys
  • state.json β€” Contains rotation history
  • logs/ β€” May contain server IPs
  • /etc/wireguard/wgclt1.conf β€” Active private key

The included .gitignore protects these files automatically.

Firmware Update Resilience

Dream Router 7 is a newer device with uncertain /etc/ persistence across firmware updates. MoleHopper includes a restoration script:

# After a firmware update, if services aren't working:
/data/mullvad-rotation/restore-after-update.sh

All scripts and configs live in /data/ which persists across updates.

How It Works

  1. Cron triggers mullvad-rotate.sh at scheduled times
  2. Weighted selection picks a server, favoring those not recently used
  3. WireGuard is restarted with the new config
  4. split-vpn is restarted to apply routing rules
  5. State is saved to track history and enable smart selection

The system handles edge cases:

  • If rotation fails, it rolls back to the previous working config
  • If locked, rotation is skipped (unless forced)
  • Boot-time conflicts with UniFi's routing are automatically cleaned up

Contributing

Issues and pull requests welcome. Please ensure no private keys or sensitive data are included in submissions.

License

MIT License β€” See LICENSE for details.

Acknowledgments

  • split-vpn by peacey β€” Policy-based routing for UniFi
  • Mullvad VPN β€” Privacy-focused VPN provider

About

πŸ¦” MoleHopper β€” Automated Mullvad WireGuard rotation for UniFi Dream Router. Hop between 100+ servers on schedule, route specific VLANs through VPN, survive reboots, and maintain CLI control.

Topics

Resources

License

Stars

Watchers

Forks

Languages