A template repository for deploying and operating your own Subnetra mesh.
Subnetra is a pure-Zig, zero-dependency, fully encrypted
hub-and-spoke layer-3 UDP tunnel whose final artifact is a single static binary under 512 KB. This repo
contains no Subnetra source and no real network data — it only pins the upstream source submodule, a
redacted, --check-able example network, the ops-contract docs, and the ops skills. Your real configs
(including PSKs) are yours to keep and must never enter git.
🔐 Before you touch anything, read § Security policy and
AGENT.md. Once you operate a real mesh with this template, the only secret protecting the network is a set of per-link keys (PSKs), and they must never enter git.
subnetra-deploy/
├── subnetra/ # git submodule → git@github.com:jamiesun/subnetra.git (pinned, read-only)
├── example-network/ # redacted example net (1 hub + 2 spoke, --check-able; PSKs are public, do not deploy)
│ ├── README.md # example-network notes & usage
│ ├── hub.example.json # multi-port hub (10.66.0.1)
│ ├── spoke-a.example.json # spoke (10.66.0.2)
│ └── spoke-b.example.json # spoke (10.66.0.3)
├── .agents/skills/ # ops skills (runbooks, version-controlled)
│ ├── deploy/SKILL.md # deploy / upgrade nodes
│ └── bench/SKILL.md # overlay performance benchmark
├── AGENT.md # operations contract — safety-first rules for humans and agents
├── README.md # this file
└── .gitignore # keeps real configs / keys out of git
# Clone with the submodule (the binary, docs, and deploy units all depend on it).
git clone --recurse-submodules git@github.com:<you>/subnetra-deploy.git
cd subnetra-deploy
# Already cloned without --recurse-submodules?
git submodule update --init --recursive
# Show the currently pinned upstream commit.
git submodule statusThe pinned upstream under subnetra/ provides everything you need:
subnetra/deploy/— ready-to-editsubnetrad.service, launchd plist, hub/spoke configs.subnetra/config.example.json— a minimal config example.subnetra/docs-site/en/src/— full documentation (security model, configuration, operations).
Subnetra encrypts every packet with a per-link pre-shared key (PSK) — a 32-byte key written as 64
hexadecimal characters and stored inline in each node's config.json. These PSKs are the trust boundary
of the whole mesh; treat them as top secrets.
- Real configs (PSKs, node inventory, IP plan) are yours to keep and must never enter git (keep them
outside this repo). The only committable, redacted templates live in
example-network/. ⚠️ The example PSKs inexample-network/are real but public 64-hex strings (so the samples can actually pass--check) — for demonstration only; never use them on any real network. After copying a sample as a starting point, the first step is to regenerate every link's key withopenssl rand -hex 32.- Once a real PSK is committed to history it is permanently leaked (rewriting history cannot undo it). If it happens: rotate that link's PSK immediately, then clean up.
- Before every
git add, rungit status/git diff --cachedto confirm no real config or 64-hex string is staged.
- Generate each key with
openssl rand -hex 32. - Each link has one PSK shared by its two endpoints; no two links may share a PSK (rejected as
DuplicatePsk). There is no globalpsk(rejected asInvalidPsk).
- Give each peer an explicit
allowed_srcCIDR (a/32for a spoke, the whole overlay for the hub). The permissive default0.0.0.0/0disables anti-spoofing — never use it on a real peer.
- A config must pass
subnetrad --check --config <file>before it lands on a host. The parser is fail-closed by design.
- Install
config.jsonto/etc/subnetra/, ownedroot:root, mode0600. - Run it via the upstream systemd unit: only
CAP_NET_ADMIN,/dev/net/tun,NoNewPrivileges,ProtectSystem=strict. Do not relax the sandbox.
- hub: accepts inbound UDP on its
listen_ports(the example network uses18020/18023/18026, multi-port) — open every one of them; keep its endpoint stable. - spoke: outbound only — no inbound port-forwarding. The hub relearns a spoke's roamed address from its next authenticated packet; the built-in keepalive keeps the NAT mapping alive.
- The session epoch is wall-clock based and fail-closed (≥ 2024-01-01). Run NTP/RTC on every node — a node whose clock has gone backwards is locked out by design.
subnetra status [--json]never serializes keys. Keep it that way: never print, log, or export PSKs to monitoring, issues, or chat.
- The pinned
subnetracommit is a reviewed artifact. Upgrade deliberately to a release tag, review the diff, and re-validate configs — never auto-track a moving branch (seeAGENT.md§4).
| Task | Where |
|---|---|
| Learn the example network / derive real nodes from it | example-network/README.md |
| Deploy / upgrade nodes | .agents/skills/deploy/SKILL.md |
| Overlay performance benchmark | .agents/skills/bench/SKILL.md |
| Upgrade the pinned Subnetra version | AGENT.md §4 |
| Rotate a PSK | AGENT.md §5 |
| Full deployment walkthrough | subnetra/docs-site/en/src/operations/deployment.md |
| Security & threat model | subnetra/docs-site/en/src/concepts/security-model.md |
| Config field reference | subnetra/docs-site/en/src/configuration/reference.md |
Source, releases, and issues: https://github.com/jamiesun/subnetra