# 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 via `nft` commands.
- **`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

```mermaid
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)

```bash
# 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

```bash
# 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

```bash
# 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

```bash
# 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

```json
{
  "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

```json
{
  "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 `customHeaders` JSON.
- **Global webhooks**: Set `isGlobal: true` to fire across all servers.
- **Action filtering**: Subscribe to specific events via `subscribedActions` array.

---

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

1. Add `#Access` comments under each `[Peer]` block in `wg0.conf`.
2. The watcher daemon (`wg-sync-watch.sh`) detects file changes via `inotifywait`.
3. `wg-sync-policy.sh` parses the config and writes `policy.json` atomically.
4. `wg-policy-engine.sh` reads the JSON and applies iptables/ipset rules.

### Example `wg0.conf`

```ini
[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`

```bash
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.
