feat: add Docker deployment, update install script, and project configs
- 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/
This commit is contained in:
@@ -1 +1,3 @@
|
||||
.test/
|
||||
wgrplane
|
||||
.sisyphus/
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
## Multi-stage Dockerfile for WGRplane
|
||||
## Stage 1: Go backend build
|
||||
FROM --platform=$BUILDPLATFORM golang:1.21-alpine AS builder-go
|
||||
WORKDIR /src
|
||||
COPY go.mod go.sum ./
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
go mod download
|
||||
COPY app ./app
|
||||
ENV CGO_ENABLED=0
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
|
||||
go build -o /bin/wgrplane ./app
|
||||
|
||||
## Stage 2: Frontend build (Vue 3)
|
||||
FROM node:20-alpine AS frontend-builder
|
||||
WORKDIR /src/app/frontend
|
||||
COPY app/frontend/package*.json ./
|
||||
RUN npm ci
|
||||
COPY app/frontend/ .
|
||||
RUN npm run build
|
||||
|
||||
## Stage 3: Production image
|
||||
FROM alpine:latest
|
||||
RUN apk --no-cache add ca-certificates
|
||||
WORKDIR /root
|
||||
COPY --from=builder-go /bin/wgrplane /usr/local/bin/wgrplane
|
||||
COPY --from=frontend-builder /src/app/frontend/dist /var/www/frontend
|
||||
ENV APP_FRONTEND_DIR=/var/www/frontend
|
||||
EXPOSE 10087
|
||||
ENTRYPOINT ["/usr/local/bin/wgrplane"]
|
||||
@@ -1,68 +1,337 @@
|
||||
# WireGuard Dynamic Policy Firewall
|
||||
# WGRplane
|
||||
|
||||
A lightweight, robust, and highly dynamic iptables/ipset policy firewall engine designed to restrict and control WireGuard peer traffic (egress traffic mapping) straight from `wg0.conf`.
|
||||
**WireGuard Control Plane with Dynamic Policy Firewall.**
|
||||
|
||||
Rather than allowing all VPN clients to reach any part of your internal network, this tool isolates clients from each other by default and reads a custom `#Access` comment inside `wg0.conf` to automatically generate strict `iptables` rules and `ipset` whitelists per-client on the fly.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Architecture & Data Flow
|
||||
## Overview
|
||||
|
||||
1. **`wg0.conf`**: The standard WireGuard configuration. Contains standard `[Peer]` configs alongside a custom `#Access` tag.
|
||||
2. **`wg-sync-policy.sh`**: Safely parses `wg0.conf` and generates a structured `/etc/wireguard/policy.json` atomically.
|
||||
3. **`wg-policy-engine.sh`**: Reads `policy.json` to generate robust rules, applying `iptables` and `ipset` directly to the system.
|
||||
4. **Watcher Daemon**: Monitors `wg0.conf` for changes via `inotifywait` and triggers the pipeline seamlessly when updates are made.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## 📁 Installation
|
||||
## Architecture
|
||||
|
||||
The easiest way to install is using the provided `install.sh` script, which automatically installs dependencies, copies all scripts to `/usr/local/bin/`, sets up the systemd daemon, and enables the service.
|
||||
```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
|
||||
# Install everything
|
||||
# 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 everything
|
||||
# Uninstall
|
||||
sudo ./install.sh uninstall
|
||||
```
|
||||
|
||||
### Building the Installer (For Developers)
|
||||
The install script handles Docker installation, repository cloning, `.env` creation, and service startup.
|
||||
|
||||
If you modify any of the source `.sh` or `.service` files, you must rebuild the `install.sh` script using the provided builders.
|
||||
### Option 3: Manual Build
|
||||
|
||||
**On Linux (Bash):**
|
||||
```bash
|
||||
./build.sh
|
||||
# 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
|
||||
```
|
||||
|
||||
**On Windows (CMD/PowerShell):**
|
||||
```cmd
|
||||
build.bat
|
||||
### 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Prerequisites
|
||||
## API Documentation
|
||||
|
||||
| Package | Required | Install |
|
||||
|---------|----------|---------|
|
||||
| `jq` | **Yes** | `apt install jq` |
|
||||
| `inotify-tools` | **Yes** (for watcher daemon) | `apt install inotify-tools` |
|
||||
| `ipset` | Optional | `apt install ipset` |
|
||||
All API endpoints are served on port `10087`. Authentication is via API key header (`wg-rplane-datadunia`) or JWT Bearer token with optional TOTP.
|
||||
|
||||
If `ipset` is not installed, the engine will automatically fall back to per-rule `iptables` whitelist entries. This works fine for small deployments. For large numbers of clients/targets, `ipset` is recommended for O(1) lookup performance.
|
||||
### 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. |
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Integrasi ke `wg0.conf`
|
||||
## Webhook Payload Spec
|
||||
|
||||
To integrate the engine, you need to append hooks into your `wg0.conf` interface block, and declare the `#Access` tags under each peer.
|
||||
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).
|
||||
|
||||
### 1. Interface Block (Hooks)
|
||||
Add the `PostUp` and `PostDown` scripts so the engine initializes correctly during VPN startup and removes traces upon shutdown.
|
||||
### Default Payload
|
||||
|
||||
> **⚠️ WireGuard does NOT support multiline values.** Every command must be on a `PostUp = ...` or `PostDown = ...` line. Bare commands without the `PostUp =` prefix will cause `Configuration parsing error`.
|
||||
```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]
|
||||
@@ -70,20 +339,9 @@ Address = 10.0.0.1/24
|
||||
ListenPort = 51820
|
||||
PrivateKey = <SERVER_PRIVATE_KEY>
|
||||
|
||||
# Policy engine: auto-handles NAT, IP forwarding, and dynamic firewall rules
|
||||
PostUp = /usr/local/bin/wg-sync-policy.sh; /usr/local/bin/wg-policy-engine.sh
|
||||
|
||||
# Policy engine: cleanup all firewall and routing traces
|
||||
PostDown = /usr/local/bin/wg-policy-cleanup.sh
|
||||
```
|
||||
|
||||
### 2. Peer Block (`#Access` Tags)
|
||||
For each client, use the `#Access` comment line. Define the destinations (targets) the peer is allowed to access. You can separate multiple IPs or CIDRs with commas or semicolons.
|
||||
|
||||
> **⚠️ WARNING: Do NOT use `SaveConfig = true`!**
|
||||
> WireGuard's `SaveConfig` feature overwrites `wg0.conf` directly and **strips all comments**, which will permanently delete all `#Access` tags. If you are using a Web UI/Dashboard, make sure it does not strip unknown comments when saving.
|
||||
|
||||
```ini
|
||||
[Peer]
|
||||
PublicKey = <CLIENT_1_PUBKEY>
|
||||
AllowedIPs = 10.0.0.2/32
|
||||
@@ -92,82 +350,126 @@ AllowedIPs = 10.0.0.2/32
|
||||
[Peer]
|
||||
PublicKey = <CLIENT_2_PUBKEY>
|
||||
AllowedIPs = 10.0.0.3/32
|
||||
#Access = 10.0.0.1/32
|
||||
#Access 10.0.0.1/32
|
||||
|
||||
[Peer]
|
||||
PublicKey = <CLIENT_3_PUBKEY>
|
||||
AllowedIPs = 10.0.0.4/32
|
||||
#Access
|
||||
# ^ (Empty Access implies internet-only, client isolation applies)
|
||||
# Empty Access = internet-only, client isolation applies
|
||||
```
|
||||
|
||||
**⚠️ Important constraint:** Why `#Access` instead of just using `AllowedIPs` directly?
|
||||
WireGuard uses `AllowedIPs` for Cryptokey Routing (deciding which tunnel interface to route outbound packets). If you put an internal server IP inside the server's `wg0.conf` AllowedIPs block, WireGuard will aggressively capture and redirect packets bound for that internal server into the VPN client's tunnel. The `#Access` comment separates routing parameters from firewall parameters cleanly.
|
||||
### 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.
|
||||
|
||||
---
|
||||
|
||||
## 🛠 `wg-policy-ctl` CLI Usage
|
||||
## Scheduler
|
||||
|
||||
You don't need to manually interact with `iptables` or `.json` files. Use the `wg-policy-ctl` wrapper.
|
||||
The Go backend runs three background cron jobs:
|
||||
|
||||
```bash
|
||||
# View the health of the firewall engine and active locks
|
||||
wg-policy-ctl status
|
||||
| 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. |
|
||||
|
||||
# View the raw, parsed JSON policy
|
||||
wg-policy-ctl policy
|
||||
---
|
||||
|
||||
# Inspect active iptables rules
|
||||
wg-policy-ctl rules
|
||||
## Plugins
|
||||
|
||||
# Check memory sets mapping IP targets (ipset)
|
||||
wg-policy-ctl ipset
|
||||
The plugin system provides a simple notification interface. Built-in plugins include:
|
||||
|
||||
# Manually re-sync rules immediately
|
||||
wg-policy-ctl reload
|
||||
- **TelegramNotifier** -- Sends Telegram messages on events.
|
||||
- **SlackNotifier** -- Sends Slack messages on events.
|
||||
- **TrafficLogger** -- Logs traffic events for debugging.
|
||||
|
||||
# Inspect dropped packets (rate-limited log output)
|
||||
wg-policy-ctl log
|
||||
Plugins are loaded at startup via `PluginManager.LoadPlugins()` and receive events through `Trigger(event, payload)`.
|
||||
|
||||
# See connection statistics, how many targets loaded
|
||||
wg-policy-ctl stats
|
||||
---
|
||||
|
||||
# Force validation of the policy schema
|
||||
wg-policy-ctl validate
|
||||
## 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Systemd Integration (Watcher Daemon)
|
||||
## License
|
||||
|
||||
If you use the `install.sh` script, the daemon is automatically installed, enabled, and started for you. It monitors `wg0.conf` for changes and triggers the pipeline seamlessly.
|
||||
|
||||
### Manual File Installation (If not using install.sh)
|
||||
Place the three provided systemd unit files into `/etc/systemd/system/`.
|
||||
|
||||
| Systemd File | Location | Description |
|
||||
|--------------|----------|-------------|
|
||||
| `wg-policy.service` | `/etc/systemd/system/wg-policy.service` | The main daemon that runs `wg-sync-watch.sh` |
|
||||
| `wg-policy-health.timer` | `/etc/systemd/system/wg-policy-health.timer` | Triggers the health check every 5 minutes |
|
||||
| `wg-policy-health.service`| `/etc/systemd/system/wg-policy-health.service`| Executes the actual health check logic |
|
||||
|
||||
```bash
|
||||
# Example copy command
|
||||
cp wg-policy.service wg-policy-health.timer wg-policy-health.service /etc/systemd/system/
|
||||
```
|
||||
|
||||
### 2. Enable & Start Services
|
||||
After copying the files, reload systemd to recognize them, then enable and start the services.
|
||||
|
||||
```bash
|
||||
systemctl daemon-reload
|
||||
systemctl enable wg-policy.service
|
||||
systemctl enable wg-policy-health.timer
|
||||
systemctl start wg-policy.service
|
||||
systemctl start wg-policy-health.timer
|
||||
```
|
||||
|
||||
Check the watcher logs:
|
||||
```bash
|
||||
journalctl -u wg-policy.service -f
|
||||
```
|
||||
This project builds upon concepts from WGDashboard (donaldzou/WGDashboard) with modifications for policy.json API integration and dynamic firewall enforcement.
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
wgrplane:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "10087:10087"
|
||||
volumes:
|
||||
- ./wgrplane-data:/var/lib/wgrplane
|
||||
- ./wireguard-config:/etc/wireguard
|
||||
depends_on:
|
||||
- wireguard
|
||||
restart: unless-stopped
|
||||
|
||||
wireguard:
|
||||
image: ghcr.io/linuxserver/wireguard:latest
|
||||
container_name: wireguard
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
network_mode: "host"
|
||||
volumes:
|
||||
- /etc/wireguard:/config
|
||||
- /lib/modules:/lib/modules
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=UTC
|
||||
restart: unless-stopped
|
||||
@@ -0,0 +1,39 @@
|
||||
module github.com/your-org/03.wireguard-policy
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/swaggo/http-swagger v1.3.4
|
||||
github.com/swaggo/swag v1.16.6
|
||||
golang.org/x/text v0.36.0
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
gorm.io/gorm v1.31.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/spec v0.20.6 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/swaggo/files v1.0.1 // indirect
|
||||
golang.org/x/mod v0.34.0 // indirect
|
||||
golang.org/x/net v0.52.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/tools v0.43.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
@@ -0,0 +1,124 @@
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
|
||||
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
|
||||
github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ=
|
||||
github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
||||
github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww=
|
||||
github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ=
|
||||
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
+103
-1286
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "[CI] Verifying Gorilla dependencies and WebSocket endpoint (WS) in Go-enabled environment"
|
||||
|
||||
cd app
|
||||
|
||||
echo ">> Checking Go toolchain..."
|
||||
if ! command -v go >/dev/null 2>&1; then
|
||||
echo "Go is not installed in this environment. Exiting.";
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ">> Fetching dependencies..."
|
||||
go get -u github.com/gorilla/mux
|
||||
go get -u github.com/gorilla/websocket
|
||||
|
||||
echo ">> Tidying modules..."
|
||||
go mod tidy
|
||||
|
||||
echo ">> Building project..."
|
||||
go build ./...
|
||||
|
||||
echo ">> Running server in background..."
|
||||
go run main.go &
|
||||
PID=$!
|
||||
echo "[CI] Server PID: $PID"
|
||||
sleep 2
|
||||
|
||||
echo ">> Testing WebSocket endpoint (ws://localhost:8080/ws/stats) using simple client..."
|
||||
if command -v websocat >/dev/null 2>&1; then
|
||||
websocat -b ws://localhost:8080/ws/stats >/tmp/ws_test.txt &
|
||||
WS_PID=$!
|
||||
sleep 6
|
||||
kill $WS_PID 2>/dev/null || true
|
||||
echo "Captured output:"; head -n 5 /tmp/ws_test.txt || true
|
||||
else
|
||||
echo "websocat not installed. Install to run WS test or use another client."
|
||||
fi
|
||||
|
||||
echo ">> Cleaning up server process..."
|
||||
kill $PID 2>/dev/null || true
|
||||
wait $PID 2>/dev/null || true
|
||||
|
||||
echo "[CI] Verification script completed."
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
###########################################
|
||||
# End-to-end verification for /api/peers/*
|
||||
# Requires: curl, jq, xxd, file (coreutils)
|
||||
###########################################
|
||||
|
||||
API_KEY="${WG_API_KEY:-test-api-key}"
|
||||
BASE_URL="${WG_BASE_URL:-http://127.0.0.1:8080}"
|
||||
|
||||
echo "[VERIF-EE] Starting end-to-end verification against ${BASE_URL}"
|
||||
|
||||
echo "[STEP] Seed server"
|
||||
SERVER_PAYLOAD='{"name":"test-server","mode":"forward","PublicKey":"SERVER_PUBLIC_KEY_BASE64","Endpoint":"server.example:51820"}'
|
||||
SERVER_ID=$(curl -s -H "wg-rplane-datadunia: ${API_KEY}" -H "Content-Type: application/json" -d "$SERVER_PAYLOAD" "$BASE_URL/api/servers" | jq -r '.id')
|
||||
echo "[INFO] SERVER_ID=${SERVER_ID}"
|
||||
|
||||
echo "[STEP] Seed peer"
|
||||
PEER_PAYLOAD='{"PublicKey":"PEER_PUBLIC_KEY_BASE64","IP":"10.0.0.2","AllowAccess":"[]","AllowInternet":false}'
|
||||
PEER_ID=$(curl -s -H "wg-rplane-datadunia: ${API_KEY}" -H "Content-Type: application/json" -d "$PEER_PAYLOAD" "$BASE_URL/api/servers/${SERVER_ID}/peers" | jq -r '.id')
|
||||
echo "[INFO] PEER_ID=${PEER_ID}"
|
||||
|
||||
echo "[STEP] Fetch config"
|
||||
CONFIG_JSON=$(curl -s -w "%{http_code}" -H "wg-rplane-datadunia: ${API_KEY}" "$BASE_URL/api/peers/${PEER_ID}/config")
|
||||
STATUS=$(tail -c 3 <<< "$CONFIG_JSON");
|
||||
CONFIG_BODY=$(echo "$CONFIG_JSON" | sed '$d')
|
||||
if [ "$STATUS" != "200" ]; then
|
||||
echo "[ERROR] Config endpoint returned status code ${STATUS}"; exit 1
|
||||
fi
|
||||
echo "$CONFIG_BODY" > /tmp/peer_config.conf
|
||||
|
||||
echo "[STEP] Validate config content"
|
||||
grep -q "^\\[Interface\\]" /tmp/peer_config.conf || { echo "Config missing [Interface]"; exit 1; }
|
||||
grep -q "^\\[Peer\\]" /tmp/peer_config.conf || { echo "Config missing [Peer]"; exit 1; }
|
||||
|
||||
echo "[STEP] Fetch QR code"
|
||||
curl -s -o /tmp/peer.png -D /tmp/peer_headers.txt -H "wg-rplane-datadunia: ${API_KEY}" "$BASE_URL/api/peers/${PEER_ID}/qrcode" || { echo "Failed to fetch QR code"; exit 1; }
|
||||
echo "[INFO] QR code saved to /tmp/peer.png"
|
||||
|
||||
echo "[STEP] Validate PNG file"
|
||||
if file /tmp/peer.png | grep -qi png; then
|
||||
echo "[OK] PNG detected"
|
||||
else
|
||||
echo "[ERROR] Not a PNG file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[VERIF-EE] End-to-end verification completed successfully."
|
||||
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=WGRPlane Go Gateway Service
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Environment=WG_RPLANE_MODE=forward
|
||||
ExecStart=/usr/local/bin/wgrplane
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user