# AGENTS.md -- WireGuard Policy Firewall + WGRplane **Generated:** 2026-05-08 **Project:** 03.wireguard-policy ## OVERVIEW Hybrid project: (1) WireGuard Policy Firewall -- shell script iptables/ipset engine driven by `wg0.conf` `#Access` comments. (2) WGRplane -- Go binary (port 10087) serving REST API + Vue 3 SPA for WireGuard control plane management. ## STRUCTURE ``` 03.wireguard-policy/ ├── app/ # WGRplane: Go backend + Vue 3 frontend (see app/AGENTS.md) │ ├── *.go # 13 Go files, flat package (no subdirs) │ ├── frontend/ # Vue 3 SPA (see app/frontend/src/AGENTS.md) │ └── docs/ # Swagger auto-generated (DO NOT EDIT) ├── wg-sync-policy.sh # Parses wg0.conf → policy.json (atomic write + flock) ├── wg-policy-engine.sh # Reads policy.json → iptables/ipset WG_POLICY chain ├── wg-sync-watch.sh # inotifywait daemon, debounces wg0.conf changes ├── wg-policy-lib.sh # Shared shell library (source only, never execute directly) ├── wg-policy-ctl # CLI wrapper for operator use ├── wg-policy-cleanup.sh # PostDown cleanup (run by WireGuard) ├── *.service / *.timer # systemd units for daemon + health check ├── install.sh # Unified installer (embeds all scripts, built by build.sh) ├── build.sh / build.bat # Rebuilds install.sh from source scripts ├── Dockerfile # Multi-stage: Go build + frontend build └── README.md # Full user documentation ``` ## WHERE TO LOOK | Task | Location | |------|----------| | Policy firewall logic | `wg-sync-policy.sh`, `wg-policy-engine.sh`, `wg-policy-lib.sh` | | Firewall rule chain | `wg-policy-engine.sh` -- WG_POLICY iptables chain | | Policy JSON schema | `wg-sync-policy.sh` output / `wg-policy-engine.sh` input | | WGRplane API handlers | `app/handlers.go` | | WGRplane DB models | `app/models.go` | | Auth middleware | `app/auth.go` (CAUTION: see known issues) | | nftables rules (forward mode) | `app/nftables.go` | | Webhook delivery | `app/webhook.go` | | Vue frontend | `app/frontend/src/` | | i18n translations | `app/frontend/src/i18n/locales/` (en, id, zh) | | Backend i18n | `app/active.{en,id,zh}.json` | | Systemd service config | `wg-policy.service`, `wgrplane.service` | ## CRITICAL DESIGN RULES ### Policy Firewall -- `#Access` MUST NOT be changed - WireGuard `AllowedIPs` on the server side = Cryptokey Routing, not firewall whitelist - Putting destination IPs in server's `AllowedIPs` breaks routing (WG tunnels those packets INTO client) - `#Access` comment is the ONLY correct way to declare firewall destinations per-peer - **NEVER refactor to parse targets from `AllowedIPs`** -- architecturally incorrect ### Atomic Writes - Always `mv -f tmp target` for `policy.json` -- never write directly - `wg-sync-policy.sh` uses `flock` -- never bypass locking ### Rollback - `wg-policy-engine.sh` creates `WG_POLICY_BAK` chain; traps `ERR` for rollback ### No SaveConfig - WireGuard `SaveConfig = true` strips ALL comments including `#Access` -- NEVER enable ## KNOWN ISSUES / GOTCHAS - **AuthMiddleware NOT applied**: `auth.go` defines `AuthMiddleware` but it is NOT wired to any routes in `main.go`. All API endpoints currently unprotected (auth header still checked inside handlers via manual if-check, but middleware chain is absent). - **TOTP secrets in-memory**: `totpSecrets` map in `auth.go` is not persisted; lost on restart. - **WebSocket stats are MOCK**: `stats.go` broadcasts randomly generated numbers, not real WireGuard traffic. - **Plugin system is stub**: `plugins.go` TelegramNotifier/SlackNotifier just print to stdout. - **Binary + DB in app/**: `wgrplane` binary and `wgrplane.db` live in `app/` (non-standard, intentional). - **Duplicate i18n**: `app/active.*.json` (backend i18n) and `app/frontend/src/i18n/locales/` (frontend i18n) are separate systems. ## ANTI-PATTERNS - Never parse firewall targets from `AllowedIPs` -- use `#Access` only - Never write `policy.json` without atomic mv + flock - Never run `wg-policy-lib.sh` directly (source-only library) - Never enable `SaveConfig = true` in wg0.conf - Do NOT edit `app/docs/docs.go` -- auto-generated by swaggo - Do NOT put business logic in `app/main.go` -- it's bootstrap only ## COMMANDS ### Policy Firewall ```bash wg-policy-ctl status # Health: interface, JSON validity, rule counts wg-policy-ctl validate # Validate policy.json without applying wg-policy-ctl rules # Show active WG_POLICY iptables rules wg-policy-ctl reload # Force re-sync from wg0.conf + re-apply wg-policy-ctl policy # Show raw policy.json wg-policy-ctl log # Show dropped packet logs (rate-limited) systemctl restart wg-policy.service journalctl -u wg-policy.service -f ``` ### WGRplane (Go App) ```bash cd app && go build -o ../wgrplane . # Build Go binary cd app/frontend && npm install && npm run build # Build Vue frontend ./wgrplane # Run (serves :10087) systemctl restart wgrplane.service journalctl -u wgrplane.service -f curl -H "wg-rplane-datadunia: test-api-key" http://localhost:10087/api/servers ``` ### Installer ```bash ./build.sh # Rebuild install.sh from source scripts (Linux) ./build.bat # Rebuild install.sh from source scripts (Windows) sudo ./install.sh install # Full install (deps + scripts + systemd + service) sudo ./install.sh uninstall # Remove all ``` ## DEPENDENCIES - Shell: `jq`, `inotify-tools` (required); `ipset` (optional, O(1) lookup) - Go: 1.25.1, SQLite (glebarez/sqlite), Gorilla Mux, GORM, JWT, TOTP, WebSocket - Frontend: Vue 3, TypeScript, Vite, TailwindCSS 4, vue-i18n 9 - Auth headers: `wg-rplane-datadunia: ` (API key) | `Authorization: Bearer ` | `X-TOTP: ` - Env vars: `WG_API_KEY` (default: test-api-key), `JWT_SECRET`, `WG_RPLANE_MODE` (forward|standalone), `APP_FRONTEND_DIR`