WGPL (WireGuard Peer Lite)
WGPL is a disconnected Python CLI for hub-and-spoke WireGuard (IPv4).
You declare routing intent in SQLite (the SSOT); WGPL derives AllowedIPs,
allocates addresses, and tracks peer lifecycle and audit.
Hot-reload the hub with zero downtime (wg syncconf) — bring your own OS
interface (BYOI). Mutations stay in the database until you apply or remote
syncconf.
Compatibility: Follows Semantic Versioning.
Patch and minor releases will not break existing CLI commands, flags, or public
--json field names. Breaking changes require a new major version (2.0.0).
This documentation describes 1.2.0+ (planned hubs, Ready, --platform
export, optional RouterOS peer comments from device names). Confirm with
wgpl --version.
See it in action
Add a peer. Get a QR. Done.
More: split tunnel · branch offices · RouterOS labels · all scenarios · demo/README.md
Start here: Quick Start · Why WGPL · Documentation map
What WGPL is / is not
| WGPL is | WGPL is not |
|---|---|
| Declarative hub-and-spoke IPv4 topology manager | A network daemon or control plane |
| Routing intent engine + WireGuard config generator | Full-mesh overlay (use Tailscale / Netmaker) |
| Disconnected CLI; SQLite SSOT | A kernel routing or iptables manager |
| Peer lifecycle, IPAM, append-only audit | IPv6 support (IPv4 pools and peers only) |
BYOI: you create the OS wg0 (or equivalent) |
Direct site-to-site P2P without a hub |
Architecture and module layers (maintainers): DESIGN.md.
Quick Start
1. Install
Recommended: Python / uv (Python 3.12+)
uv tool install wgpl
# or: pip install wgpl
Experimental: standalone binaries (Linux / macOS)
Unsigned release artifacts for air-gapped routers or testing. Prefer uv/pip
when possible. Verify the checksum from the GitHub Release
(<binary_name>.sha256) before running.
Linux (amd64)
curl -sL https://github.com/aleaz/wgpl/releases/latest/download/wgpl-linux-amd64 \
-o /usr/local/bin/wgpl
# Verify SHA-256 against wgpl-linux-amd64.sha256 from the same release, then:
chmod +x /usr/local/bin/wgpl
macOS (Apple Silicon / arm64)
Download the .tar.gz, verify the checksum, extract, and clear Gatekeeper quarantine:
curl -sL https://github.com/aleaz/wgpl/releases/latest/download/wgpl-macos-arm64.tar.gz \
-o wgpl-macos-arm64.tar.gz
# Verify SHA-256 against wgpl-macos-arm64.tar.gz.sha256 from the same release, then:
tar -xzf wgpl-macos-arm64.tar.gz
xattr -r -d com.apple.quarantine wgpl-macos-arm64 # Required to bypass macOS Gatekeeper
# You can now run the executable inside the folder:
./wgpl-macos-arm64/wgpl-macos-arm64 --version
# Optional: Link it to your PATH for global access
sudo ln -s $(pwd)/wgpl-macos-arm64/wgpl-macos-arm64 /usr/local/bin/wgpl
[!TIP] The standalone binaries are provided for environments without Python. However, the recommended and fastest way to install WGPL on macOS is via PyPI using
uv tool install wgplorpipx install wgpl.
2. Bring your own hub interface (BYOI)
WGPL does not create the OS WireGuard netdev. Create wg0 (or equivalent)
first, then copy its public key for interface add.
# Minimal hub: [Interface] with PrivateKey, Address (e.g. 10.0.0.1/24), ListenPort
sudo systemctl enable --now wg-quick@wg0
sudo wg show wg0 public-key # <WG0_PUBKEY> below
If wgpl apply later fails because the OS interface does not exist, finish this
step first — see
docs/runbook.md — Troubleshooting.
3. Pin the database and register a hub
Pin a database path so sudo apply and non-root mutations share the same SSOT:
export WGPL_DB_PATH="$HOME/.wgpl.db"
# or pass --db "$HOME/.wgpl.db" on every command
A WGPL hub is the database record for one VPN domain (you may name it wg0
to match your OS interface).
# Register the hub: name, server endpoint host, hub public key, address pool
# Add --port N if the hub does not listen on the default 51820
wgpl interface add wg0 vpn.example.com <WG0_PUBKEY> 10.0.0.0/24
# Attach a Node (default policy: vpn_only). Positional name find-or-creates it.
wgpl peer add "Alice_Laptop" -i wg0
wgpl peer list
Planned hub (no hardware yet): intentional incomplete interface — pool
required; PublicKey/endpoint deferred. Create with
wgpl interface add NAME POOL --planned, attach peers, then commit wire
fields with interface update (--public-key / --endpoint). Clear wire
fields later with --clear-public-key / --clear-endpoint (endpoint+port
pair). Concepts:
runbook — Hub wire fields.
Operator workflows (partial commit either order, clear flags, pool change,
inspection, fail-closed):
runbook — Planned hub workflow.
Defer Step 4 emit (validate/apply/config/qr) until Ready is yes.
wgpl interface add office-c 10.99.0.0/24 --planned
wgpl peer add "Planner_Laptop" -i office-c
# Later: wgpl interface update office-c --public-key <PK> --endpoint vpn.office-c.example.com
| Term | Meaning |
|---|---|
| Address pool | Tunnel CIDR for peer IPs (IPAM) — not the public server IP |
| Endpoint | Host/IP where clients connect to the hub — not the pool |
| PublicKey | Hub WireGuard public key (BYOI) |
| Pool ≠ Endpoint ≠ PublicKey | Three different fields |
| Commit | interface update of wire-facing fields (not a separate verb) |
| Planned interface | Intentional incomplete hub; peers OK; export, apply, peer config, and peer qr blocked until Ready yes |
| Ready | Derived completeness: yes / planned / invalid (never stored; Ready yes = emit allowed) |
peer.role = endpoint |
An end-user device (laptop/phone), not the server endpoint hostname |
Client AllowedIPs default to vpn_only. Policies and overrides:
docs/routing.md.
4. Validate, apply, inspect, and distribute
Canonical flow (hub already Ready yes):
validate → (explain) → apply/export → distribute clients.
Planned hubs: commit wire fields first; validate may pass with warnings while
emit still fails closed.
wgpl validate wg0
sudo --preserve-env=WGPL_DB_PATH wgpl apply wg0
# equivalent: sudo wgpl --db "$HOME/.wgpl.db" apply wg0
wgpl peer list # copy the peer ID (or unique hex prefix) from the ID column
wgpl peer explain <PEER_ID>
wgpl peer qr <PEER_ID>
wgpl peer config <PEER_ID> > alice.conf
chmod 600 alice.conf
<PEER_ID> is a peer UUID or hex prefix from peer list (not the node name).
Multi-hub DBs need -i on secret-bearing commands — docs/cli.md.
Install on the end-user device: docs/runbook.md — Client provisioning.
5. Export for your platform
Same database; --platform selects the target export format;
--output / -o writes platform filenames (CLI does not invent names; no ZIP).
Platforms: wireguard, routeros, opnsense, networkd, networkmanager.
wgpl interface export wg0 --platform routeros -o ./routeros/
wgpl peer config <PEER_ID> --platform networkd -o ./networkd/
wgpl peer config <PEER_ID> -o alice.conf # default wireguard; -o = file path
Full rules and examples: docs/cli.md. RouterOS import: runbook — MikroTik.
Demos
Offline VHS recordings (admin laptop — no local apply). Regenerate with
cd demo && make all. Details: demo/README.md.
| Demo | What it shows |
|---|---|
| 01 Quickstart | Peer + client config + QR |
| 02 Split tunnel | Cloud VPC via split_tunnel + peer explain |
| 03 Branch offices | NY ↔ London subnet_router AllowedIPs matrix |
| 04 Mixed topology | Laptops + three branches + validate --strict |
| 05 Remote sync | interface export | ssh … wg syncconf |
| 06 RouterOS labels | --platform routeros peer comment= from device names |
Why WGPL
Replaces hand-edited AllowedIPs and hub restarts with declared routing intent
and zero-downtime wg syncconf.
vs wg-quick (manual config files)
| Feature | wg-quick (manual) |
wgpl |
|---|---|---|
| Peer storage / IPAM | Text files; manual IPs | SQLite + automatic CIDR IPAM |
| Routing / AllowedIPs | Manual per peer in .conf |
Declared routing intent; derived at emit |
| Applying changes | Restarts interface (drops connections) | Zero-downtime hot-reload (wg syncconf) |
| Audit / TTL / verify | None / manual | Append-only audit, --expires, validate + routing matrix |
Fit / not a fit
You keep keys, backups, and hub relay; you run apply and OS forwarding
yourself — not a mesh control plane.
- Full-mesh or managed overlay — Tailscale, Netmaker, or similar (WGPL targets one hub per VPN domain, not P2P mesh).
- Direct site-to-site without a hub — Out of scope. Configure WireGuard
manually, or use
peer config --allowed-ipsfor a one-off export override. - Site-to-site via a central hub — In scope. Two
subnet_routerpeers; LAN↔LAN through the concentrator. See docs/routing.md — Site-to-site.
Concepts
WGPL models hub-and-spoke routing intent in SQLite — not WireGuard text
files. WireGuard ([Interface], [Peer], AllowedIPs) is an emit format
produced at apply / export time.
Node Peer Interface
(who) → (attachment) → (hub / VPN domain)
| Concept | Meaning |
|---|---|
| Node | Who — global device identity (wgpl node); rename with node update (peer update has no --name) |
| Peer | How — attachment to one hub (keys, IP, routing intent, lifecycle) |
| Interface | Where — WGPL hub for one VPN domain (pool, server endpoint, optional hub routes) |
Full domain table and identity rules: DESIGN.md — Domain model.
Mutations never touch the kernel until you validate and emit:
SQLite intent → validate → emit
├─ apply / interface export (hub)
└─ peer config / QR (client)
- Mutate writes the database only; it does not touch WireGuard.
- Emit (
apply, export, config, QR) derives routes, validates, freezes a Server/Client snapshot, then renders for the selected--platform(defaultwireguard→ WireGuard text). Maintainer detail: DESIGN.md — Projection Engine. wgpl applyfails closed if the database fails consistency checks.
Maintainer architecture: DESIGN.md.
Routing
Declare role, routed_networks, and allowed_ips_policy on hubs and peers.
WGPL derives AllowedIPs at emit time; hub packet relay (ip_forward, firewall)
stays with the operator —
docs/runbook.md — Hub routing relay.
| Policy | Client AllowedIPs (summary) |
|---|---|
vpn_only (default) |
VPN address pool only |
split_tunnel |
Pool + interface.routed_networks |
all_remote_networks |
Split set + other sites' LANs |
full_tunnel |
0.0.0.0/0 |
custom |
peer.custom_allowed_ips |
Eight hub-and-spoke patterns, glossary, LAN↔LAN, and invariants:
docs/routing.md. Executable topology spec:
docs/routing_matrix.md. Inspect with
wgpl peer explain <PEER_ID> (UUID or hex prefix from peer list).
Day-2 ops (validate/apply, TTL, prune, backup, deploy, client OS): docs/runbook.md.
Highlights
- Unique hub names: Interface
nameis globally unique per database (RFC-0003). Use the short interface ID fromwgpl interface listwhen referring to a hub by id rather than name. - Global IPAM within each hub CIDR; idempotent
wgpl apply(deltas only). - Node identity via
wgpl node; the same Node can attach to several hubs. - TTL and cleanup:
--expires, soft delete, and prune — runbook. - Fail-closed emit/apply/restore;
chmod 600on the DB, QR PNG (peer qr -o), anddb dumpoutputs;--outputplatform artifacts use default OS permissions — treat them like private keys; append-only audit (no secrets in metadata) — SECURITY.md. --jsonfor automation — stdout contract: docs/cli.md.- Wire-safe MTU (minimum 1280 or unset); server endpoints RFC 1123 (IPv4/hostname; IPv6 endpoints rejected).
- BYOI deploy: systemd, remote
syncconf, Docker, MikroTik — runbook — Deployment.
Integrations
Copy-paste starting points in examples/:
- Ansible Playbook: Multi-server zero-downtime updates from a control node.
- Terraform & Cloud Firewalls:
Whitelist peer IPs in AWS Security Groups via Terraform
externaldata. - GitHub Actions (GitOps): Deploy VPN state from CI/CD.
- FastAPI Self-Service Portal:
Illustrative API wrapper for QR-based onboarding (requires
WGPL_PORTAL_API_KEY).
Configuration
Pin the DB when using sudo apply so root and your user share the same SSOT.
| Variable | Description | Default |
|---|---|---|
WGPL_DB_PATH |
Path to the SQLite database (also settable with global --db) |
~/.wgpl.db |
WGPL_WG_BIN |
Path to wg for apply / syncconf (ignored when UID 0; defaults to /usr/bin/wg) |
wg (PATH) |
WGPL_EXEC_CMD |
Optional audit metadata: the CLI command that triggered the mutation (sanitized, bounded) | (unset) |
wireguard-tools (wg) is required only for wgpl apply on the same machine.
Full CLI: wgpl --help or docs/cli.md. Docker image
ghcr.io/aleaz/wgpl and DB permissions:
runbook — Docker,
Environment and permissions.
Documentation map
Operators
| Document | Contents |
|---|---|
| docs/cli.md | Full CLI reference (--platform / --output artifact export) |
| docs/runbook.md | Production procedures (validate, apply, hub relay, deploy including RouterOS --platform, client OS, backup, compliance) |
| docs/routing.md | Routing model, patterns, scope, invariants |
| docs/routing_matrix.md | Executable topology spec (valid / invalid) |
| SECURITY.md | Threat model and security policies |
| CHANGELOG.md | Release history |
| docs/platform-projection-acceptance.md | Real-target acceptance validation checklist per platform |
Contributors / maintainers
| Document | Contents |
|---|---|
| DESIGN.md | Domain model, layered architecture, emit path, security boundaries |
| docs/README.md | Documentation hub (ops + architecture index) |
| docs/rfcs/ | Normative RFCs and IDDs |
| docs/architecture/ | Closed process history (non-normative) |
| CONTRIBUTING.md | Development workflow and commit conventions |
| MAINTAINERS.md | Maintainer ownership |
| CODE_OF_CONDUCT.md | Community conduct |
Contributing
git clone https://github.com/aleaz/wgpl.git
cd wgpl
uv sync --dev
uv tool run pre-commit install
uv run pytest
Please read CONTRIBUTING.md before opening a pull request.
Author
- Alejandro Azario — GitHub
License
MIT
Metadata
Release files for wgpl 1.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| wgpl-1.2.0.tar.gz | 3.7 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| wgpl-1.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 3.8 MB
Release files / wgpl-1.2.0.tar.gz
| Download URL | wgpl-1.2.0.tar.gz |
|---|---|
| Size | 3.7 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
14a16d36d2a13519d8458e989c3ab582e0e608824f4a7d6bac867e7065ec1698
|
|
BLAKE2b-256 checksum How to use checksums |
cf5db74b8c8e140c6cf981b764c225ab97a74e0f3e575ccc0eb77268f4a01f53
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 5, 2026.
Transparency logRelease files / wgpl-1.2.0-py3-none-any.whl
| Download URL | wgpl-1.2.0-py3-none-any.whl |
|---|---|
| Size | 88.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bd483844e421b454cf072404767952ee1ddd630473b278a5501c6e9a21dc515b
|
|
BLAKE2b-256 checksum How to use checksums |
899d00e65f75744935cf1da904daf339383e4ea89ad58dfc1631d354280ce98c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 5, 2026.
Transparency log