WireGuard upstreams
Use type: wireguard, source.kind: config, and a file below /etc/vpn-hub. The parser reads one [Interface] and exactly one [Peer].
Required provider fields are Interface.PrivateKey, Interface.Address, Peer.PublicKey, and Peer.Endpoint. Supported optional fields are Interface.DNS, Interface.MTU, Peer.PresharedKey, Peer.AllowedIPs, and Peer.PersistentKeepalive. Interface.DNS is parsed as provider metadata but does not change VPN Hub’s public DNS plan, which currently uses 1.1.1.1 and 9.9.9.9 through the selected egress. Unknown provider keys are ignored because provider files commonly carry tool-specific additions.
[Interface]PrivateKey = SYNTHETIC-PRIVATE-KEY-REPLACE-BEFORE-USEAddress = 192.0.2.2/32DNS = 192.0.2.53MTU = 1380
[Peer]PublicKey = W/kKaUP1n48AgIzxs8po0HKV+UEk1vMcTuBW648atSE=Endpoint = edge.example.com:51820AllowedIPs = 0.0.0.0/0PersistentKeepalive = 25The private and preshared keys are secrets. On a fresh hub, open a root SSH session, create the age identity used by the systemd units, and record its public recipient:
ssh root@"$HUB_HOST"At the remote root prompt, run:
apt-get updateapt-get install -y ageinstall -d -m 0700 /etc/vpn-hub/age /etc/vpn-hub/secretstest ! -e /etc/vpn-hub/age/keys.txtage-keygen -o /etc/vpn-hub/age/keys.txtchmod 0600 /etc/vpn-hub/age/keys.txtage-keygen -y /etc/vpn-hub/age/keys.txt > /etc/vpn-hub/age/recipient.txtchmod 0644 /etc/vpn-hub/age/recipient.txtStill in that root session, create plaintext only on tmpfs, encrypt it explicitly as binary data, install the envelope at mode 0600, and prove the exact runtime decrypt path:
umask 077plain=$(mktemp /run/edge-wg.XXXXXX)encrypted=$(mktemp /run/edge-wg-sops.XXXXXX)trap 'rm -f -- "$plain" "$encrypted"' EXIT${EDITOR:-vi} "$plain"recipient=$(cat /etc/vpn-hub/age/recipient.txt)sops encrypt --age "$recipient" --input-type binary --output-type binary "$plain" \ > "$encrypted"install -o root -g root -m 0600 "$encrypted" \ /etc/vpn-hub/secrets/edge-wg.conf.sops.binaryrm -f -- "$plain" "$encrypted"trap - EXITexport SOPS_AGE_KEY_FILE=/etc/vpn-hub/age/keys.txtsops --decrypt /etc/vpn-hub/secrets/edge-wg.conf.sops.binary >/dev/nullPoint source.value at secrets/edge-wg.conf.sops.binary. Then validate hub fields and exercise the same decrypt and provider parser used by reconcile without changing the live host:
hubctl --config /etc/vpn-hub/hub.yaml validateverify_state=$(mktemp -d /run/vpn-hub-verify.XXXXXX)trap 'rm -rf -- "$verify_state"' EXIThubctl --config /etc/vpn-hub/hub.yaml deploy --state-dir "$verify_state"vpn-hub-agent reconcile --dry-run --state-dir "$verify_state" \ --config-dir /etc/vpn-hub --server-key /etc/vpn-hub/server.keyrm -rf -- "$verify_state"trap - EXITExpected result: decryption exits zero and the dry run prints planned operations rather than a SOPS or WireGuard parser error. Back up the age identity separately from encrypted provider files; losing it makes those files unreadable. Rotate a disclosed identity by encrypting every provider file to a new recipient before removing the old key.
The documentation-only endpoint and private key above cannot establish a tunnel.
Next: Tunnel fields and Health probes.