- Add Dockerfile and docker-compose.yml for containerized deployment - Rewrite install.sh for Docker-based setup (multi-distro support) - Add Go module files (go.mod, go.sum) for WGRplane backend - Add wgrplane.service systemd unit for Go backend - Add CI verification scripts in scripts/ - Update README.md with Docker deployment docs - Update .gitignore for binaries and .sisyphus/
WGRplane
WireGuard Control Plane with Dynamic Policy Firewall.
WGRplane is a Go-native WireGuard management dashboard paired with a shell-based dynamic iptables/ipset policy engine. It gives you full peer lifecycle management, per-peer firewall policies, webhook integrations, and a glassmorphism-styled Vue 3 frontend.
Overview
WGRplane operates in two complementary layers:
| Layer | What it does |
|---|---|
| WGRplane (Go + Vue 3) | Web dashboard and REST API for managing WireGuard servers, peers, webhooks, SMTP, and scheduling. |
| Policy Firewall (Shell) | Dynamic iptables/ipset engine that reads #Access comments from wg0.conf to enforce per-peer egress rules. |
Hybrid Mode
The Go backend supports two server modes:
forward-- The WGRplane instance directly applies nftables rules on the local machine. Peer access policies are enforced immediately vianftcommands.standalone-- The instance acts as a control plane that triggers webhooks to remote WireGuard servers. Policy enforcement happens on the remote side.
2-Column Policy
Each peer has two independent policy columns:
| Column | Purpose |
|---|---|
| AllowAccess | List of CIDRs the peer can reach (internal targets). |
| AllowInternet | Boolean flag. When true, the peer gets unrestricted internet egress. |
Peers with neither rule are isolated from each other and from the internet by default.
Architecture
graph TB
subgraph "WGRplane Dashboard"
A[Vue 3 Frontend] -->|HTTP/WebSocket| B[Go REST API :10087]
end
subgraph "Go Backend"
B --> C[(SQLite DB)]
B --> D[Webhook Engine]
B --> E[Scheduler Cron]
B --> F[nftables Engine]
B --> G[SMTP / Email]
B --> H[Plugin Manager]
end
subgraph "Policy Firewall"
I[wg0.conf #Access] -->|inotifywait| J[wg-sync-watch.sh]
J --> K[wg-sync-policy.sh]
K -->|atomic write| L[policy.json]
L --> M[wg-policy-engine.sh]
M --> N[iptables / ipset]
end
D -.->|peer_created/updated/deleted| O[External Webhooks]
F -.->|forward mode| N
E -.->|daily/monthly| P[Peer Lifecycle]
Data Flow
wg0.conf (#Access comments)
↓ inotifywait detects change
wg-sync-watch.sh (debounced trigger)
↓
wg-sync-policy.sh (parses wg0.conf → policy.json, atomic write)
↓
wg-policy-engine.sh (reads policy.json → iptables/ipset rules)
↓
Active firewall rules (WG_POLICY chain)
Tech Stack
| Component | Technology |
|---|---|
| Backend | Go 1.25, Gorilla Mux, GORM (SQLite) |
| Frontend | Vue 3, TypeScript, Vite, TailwindCSS 4, vue-i18n 9 |
| Auth | JWT (golang-jwt), TOTP (pquerna/otp), API Key |
| Webhooks | Go net/http with retry + exponential backoff |
| Scheduling | robfig/cron v3 |
| Firewall | Bash, iptables, ipset, nftables, inotify-tools, jq |
| Container | Docker (multi-stage build), docker-compose |
Quick Start
Option 1: Docker Compose (Recommended)
# Clone the repository
git clone https://git.datadunia.com/hainzero/WGRplane.git
cd 03.wireguard-policy
# Start both WGRplane and WireGuard
docker compose up -d
# Access the dashboard
# http://localhost:10087
The compose stack runs:
- WGRplane on port
10087(Go API + Vue frontend) - WireGuard container with host networking for kernel module access
Option 2: Install Script
# Run the automated installer (Ubuntu/Debian/CentOS)
sudo ./install.sh install
# Uninstall
sudo ./install.sh uninstall
The install script handles Docker installation, repository cloning, .env creation, and service startup.
Option 3: Manual Build
# Build the Go binary
cd app
go build -o ../wgrplane .
cd ..
# Build the frontend
cd app/frontend
npm install && npm run build
cd ../..
# Run
./wgrplane
# Server starts on :10087
Option 4: Systemd Service
# Install the service file
sudo cp wgrplane.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable wgrplane.service
sudo systemctl start wgrplane.service
# View logs
journalctl -u wgrplane.service -f
API Documentation
All API endpoints are served on port 10087. Authentication is via API key header (wg-rplane-datadunia) or JWT Bearer token with optional TOTP.
Authentication
| Method | Header | Notes |
|---|---|---|
| API Key | wg-rplane-datadunia: <KEY> |
Set via WG_API_KEY env var. Default: test-api-key. |
| JWT | Authorization: Bearer <TOKEN> |
15-minute expiry. Set secret via JWT_SECRET env var. |
| TOTP | X-TOTP: <CODE> |
Required if user has TOTP enabled. |
Servers
| Endpoint | Method | Description |
|---|---|---|
/api/servers |
GET |
List all registered WireGuard servers. |
/api/servers |
POST |
Create a new server entry. Body: {name, mode, publicKey, endpoint}. |
/api/servers/{id} |
GET |
Get a single server by ID. |
/api/servers/{id} |
PUT |
Update server fields (name, mode, publicKey, endpoint). |
/api/servers/{id} |
DELETE |
Delete a server and cascade-remove its peers and webhooks. |
Peers
| Endpoint | Method | Description |
|---|---|---|
/api/servers/{id}/peers |
GET |
List all peers for a server. |
/api/servers/{id}/peers |
POST |
Create a new peer. Body: {publicKey, ip, allowAccess, allowInternet}. Applies nftables rules in forward mode or triggers webhooks in standalone mode. |
/api/peers/{id} |
PUT |
Update a peer. Computes diffs and applies incremental nftables changes. |
/api/peers/{id} |
DELETE |
Delete a peer. Cleans up nftables rules and triggers webhooks. |
/api/peers/{id}/config |
GET |
Download the peer's WireGuard .conf file. |
/api/peers/{id}/qrcode |
GET |
Get a QR code PNG image of the peer config (for mobile import). |
Webhooks
| Endpoint | Method | Description |
|---|---|---|
/api/servers/{id}/webhooks |
GET |
List webhooks for a server. |
/api/servers/{id}/webhooks |
POST |
Create a webhook. Body: {name, url, template, customBody, customHeaders, subscribedActions, isEnabled, verifySSL}. |
/api/webhooks/{id} |
DELETE |
Delete a webhook. |
SMTP Settings
| Endpoint | Method | Description |
|---|---|---|
/api/settings/smtp |
GET |
Get current SMTP configuration. |
/api/settings/smtp |
POST |
Save SMTP settings for email notifications. |
Statistics
| Endpoint | Method | Description |
|---|---|---|
/api/stats |
GET |
Global stats: total servers, peers, webhooks. |
/api/servers/{id}/stats |
GET |
Per-server stats: peer count, webhook count, server details. |
WebSocket
| Endpoint | Protocol | Description |
|---|---|---|
/ws/stats |
WebSocket | Real-time stats broadcast (5-second interval). Connects to the Hub for live peer/traffic updates. |
Webhook Payload Spec
Webhooks are triggered on peer lifecycle events (peer_created, peer_updated, peer_deleted, policy_changed). The engine supports three template modes: default (raw JSON), mikrotik (RouterOS-formatted), and custom (Go template).
Default Payload
{
"event": "peer_created",
"timestamp": "2026-05-03T10:30:00Z",
"server": {
"id": 1,
"name": "wg-server-01",
"mode": "forward",
"publicKey": "abc123...",
"endpoint": "vpn.example.com:51820"
},
"peer": {
"id": 2,
"publicKey": "xyz789...",
"ip": "10.0.0.2",
"allowAccess": ["192.168.1.0/24", "10.10.0.0/16"],
"allowInternet": true,
"enabled": true,
"dataLimitGB": 0,
"expiresAt": "0001-01-01T00:00:00Z"
},
"policy": {
"action": "created",
"changes": ["peer_created"]
}
}
Mikrotik Template
{
"action": "created",
"peer": {
"public_key": "xyz789...",
"ip": "10.0.0.2",
"allow_access": "[\"192.168.1.0/24\"]",
"allow_internet": true
},
"server": {
"name": "wg-server-01",
"mode": "forward"
}
}
Webhook Features
- Retry with backoff: Failed deliveries retry up to 3 times with exponential backoff (2s, 4s, 8s).
- SSL verification: Toggleable per-webhook via
verifySSL. - Custom headers: Per-webhook header injection via
customHeadersJSON. - Global webhooks: Set
isGlobal: trueto fire across all servers. - Action filtering: Subscribe to specific events via
subscribedActionsarray.
UI Features
The frontend is a Vue 3 + TypeScript SPA with a glassmorphism design language.
Design System
- Glassmorphism: Frosted glass cards, buttons, and inputs with backdrop blur effects.
- TailwindCSS 4: Utility-first styling with full dark mode support via
dark:variants. - Responsive: Mobile-first layout that adapts to all screen sizes.
Features
| Feature | Description |
|---|---|
| Multi-language (i18n) | English, Indonesian, and Chinese via vue-i18n 9. Locale auto-detected from browser. |
| Theme Switching | Dark / Light / Auto (follows system preference) via @vueuse/core. |
| Real-time Stats | WebSocket connection broadcasts live peer counts and traffic data every 5 seconds. |
| Charts | Traffic visualization via Chart.js + vue-chartjs. |
| Toast Notifications | Non-intrusive alerts via vue-sonner. |
| QR Code Import | Generate scannable QR codes for quick mobile WireGuard client setup. |
Frontend Structure
app/frontend/src/
├── App.vue # Root component with theme/i18n providers
├── main.ts # App bootstrap (Vue, Router, i18n)
├── router/ # Vue Router definitions
├── i18n/ # Locale files (en.json, id.json, zh.json)
├── components/ # Glass UI components (Card, Button, Input, Toggle)
├── composables/ # Vue composables (useTheme, etc.)
├── views/ # Page components (Dashboard, Servers, Peers, Settings)
└── types/ # TypeScript type definitions
Policy Firewall (#Access)
The shell-based policy engine enforces per-peer firewall rules directly from wg0.conf.
How It Works
- Add
#Accesscomments under each[Peer]block inwg0.conf. - The watcher daemon (
wg-sync-watch.sh) detects file changes viainotifywait. wg-sync-policy.shparses the config and writespolicy.jsonatomically.wg-policy-engine.shreads the JSON and applies iptables/ipset rules.
Example wg0.conf
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <SERVER_PRIVATE_KEY>
PostUp = /usr/local/bin/wg-sync-policy.sh; /usr/local/bin/wg-policy-engine.sh
PostDown = /usr/local/bin/wg-policy-cleanup.sh
[Peer]
PublicKey = <CLIENT_1_PUBKEY>
AllowedIPs = 10.0.0.2/32
#Access 192.168.1.10/32, 192.168.12.0/24
[Peer]
PublicKey = <CLIENT_2_PUBKEY>
AllowedIPs = 10.0.0.3/32
#Access 10.0.0.1/32
[Peer]
PublicKey = <CLIENT_3_PUBKEY>
AllowedIPs = 10.0.0.4/32
#Access
# Empty Access = internet-only, client isolation applies
Why #Access Instead of AllowedIPs?
WireGuard uses AllowedIPs for Cryptokey Routing. Putting destination IPs in the server's AllowedIPs would cause WireGuard to route traffic for those IPs into the client tunnel. The #Access comment cleanly separates firewall policy from routing configuration.
CLI: wg-policy-ctl
wg-policy-ctl status # Health check, lock status, rule counts
wg-policy-ctl policy # View raw policy.json
wg-policy-ctl rules # Inspect active iptables rules
wg-policy-ctl ipset # View ipset mappings
wg-policy-ctl reload # Force re-sync and re-apply
wg-policy-ctl log # View dropped packet logs
wg-policy-ctl stats # Connection statistics
wg-policy-ctl validate # Validate policy.json schema
Prerequisites
| Package | Required | Install |
|---|---|---|
jq |
Yes | apt install jq |
inotify-tools |
Yes (watcher daemon) | apt install inotify-tools |
ipset |
Optional | apt install ipset |
Without ipset, the engine falls back to per-rule iptables entries. This works for small deployments. For large peer counts, ipset provides O(1) lookup performance.
Scheduler
The Go backend runs three background cron jobs:
| Schedule | Job | Action |
|---|---|---|
| Daily 2:00 AM | deleteExpiredPeers |
Removes peers past their ExpiresAt date. |
| Daily 3:00 AM | restrictOverLimitPeers |
Disables peers that exceeded DataLimitGB. |
| 1st of month | resetMonthlyUsage |
Resets CurrentDataUsageBytes to zero for all peers. |
Plugins
The plugin system provides a simple notification interface. Built-in plugins include:
- TelegramNotifier -- Sends Telegram messages on events.
- SlackNotifier -- Sends Slack messages on events.
- TrafficLogger -- Logs traffic events for debugging.
Plugins are loaded at startup via PluginManager.LoadPlugins() and receive events through Trigger(event, payload).
Configuration
Environment Variables
| Variable | Default | Description |
|---|---|---|
WG_API_KEY |
test-api-key |
API key for header-based authentication. |
JWT_SECRET |
secret |
Secret for JWT token signing. |
APP_FRONTEND_DIR |
/var/www/frontend |
Path to built frontend assets. |
WG_RPLANE_MODE |
forward |
Default server mode (forward or standalone). |
Database
WGRplane uses SQLite by default (wgrplane.db). Models auto-migrate on startup:
- Server -- WireGuard server entries with mode and endpoint.
- Peer -- Peer entries with IP, access rules, data limits, expiry.
- Webhook -- Webhook configurations with templates and action filters.
- SMTPSettings -- SMTP server configuration for email notifications.
Project Structure
03.wireguard-policy/
├── app/ # Go backend + Vue frontend
│ ├── handlers.go # REST API route handlers
│ ├── main.go # Server bootstrap, routes
│ ├── models.go # GORM models (Server, Peer, Webhook, SMTP)
│ ├── auth.go # JWT, TOTP, API key auth middleware
│ ├── nftables.go # nftables rule management (forward mode)
│ ├── webhook.go # Webhook engine with retry/backoff
│ ├── scheduler.go # Cron jobs (expiry, data limit, reset)
│ ├── stats.go # WebSocket Hub for real-time stats
│ ├── email.go # SMTP email notifications
│ ├── plugins.go # Plugin system (Telegram, Slack, Logger)
│ ├── validation.go # Input validators (IP, CIDR, PublicKey)
│ ├── wg.go # WireGuard key generation, config export
│ ├── i18n.go # Backend i18n (en/id/zh)
│ ├── frontend/ # Vue 3 SPA (TypeScript, TailwindCSS)
│ └── active.{en,id,zh}.json # Translation files
├── wg-sync-policy.sh # Parses wg0.conf → policy.json
├── wg-policy-engine.sh # Applies policy.json → iptables/ipset
├── wg-sync-watch.sh # inotifywait watcher daemon
├── wg-policy-ctl # CLI wrapper for management
├── wg-policy-cleanup.sh # Cleanup script for PostDown
├── wg-policy.service # Systemd unit for watcher daemon
├── wgrplane.service # Systemd unit for Go backend
├── install.sh # Automated installer (Docker + services)
├── Dockerfile # Multi-stage Docker build
├── docker-compose.yml # Docker Compose stack
├── build.sh / build.bat # Installer rebuild scripts
└── README.md # This file
License
This project builds upon concepts from WGDashboard (donaldzou/WGDashboard) with modifications for policy.json API integration and dynamic firewall enforcement.