Infrastructure-as-code for a personal server stack and home network. Podman Quadlet configs, OpenWrt router configs, service configs and secrets — all templated, versioned and deployed over SSH or distributed via Cloudflare Workers KV.
| Service | What |
|---|---|
system |
Base OS hardening: sshd (TCP forwarding allowed), sysctl, systemd-networkd, maintenance timers (btrfs scrub, paccache, sysctl reapply) |
firewall |
Firewalld zones: public, wireguard, filter-closed, trusted — ports opened per-instance from secrets |
backup |
Kopia snapshots to S3 with btrfs atomic snapshots, systemd timer |
certs |
Centralized wildcard TLS certificates (Google ACME + Cloudflare DNS challenge via lego) |
traefik |
Reverse proxy, TLS termination |
synapse |
Matrix homeserver + PostgreSQL |
element |
Element Web + Synapse Admin |
element-call |
Element Call via LiveKit SFU + lk-jwt-service |
nextcloud |
Nextcloud + MariaDB + Valkey + Nginx + cron timer |
metrics |
Prometheus + Node Exporter + Grafana |
i2p |
I2P anonymous overlay network (i2pd daemon, native, no container) |
mirotalk |
MiroTalk SFU — WebRTC video conferencing (Podman container) |
wireguard |
WireGuard mesh + client tunnels (native, no container) |
sing-box |
Proxy server + client/router config generator with Cloudflare KV distribution |
router |
OpenWrt router configs: nftables tproxy, network, wireless, firewall, dhcp — distributed via KV |
infra/
...
├── certs/
│ ├── deploy.py
│ ├── secrets/
│ └── .certstore/ ← gitignored, lego state + certs
...
├── sing-box/
│ ├── deploy.py ← server deploy (render/diff/deploy)
│ ├── generate.py ← client/router config generator + KV
│ ├── templates/
│ ├── secrets/
│ └── .output/ ← gitignored
...
└── router/
├── generate.py ← OpenWrt config generator + KV
├── templates/
├── secrets/
└── .output/ ← gitignored
Each service has:
templates/— Jinja2 templates for Quadlet units, configs or scriptssecrets/— SOPS-encrypted YAML with passwords, domains, keysdeploy.py— thin config that plugs intolib/deploy.py
- Decrypts secrets with SOPS
- Resolves SSH target from
secrets/hosts.enc.yaml - Renders Jinja2 templates
- Syncs files to remote via rsync (checksum-based, idempotent)
- Applies file ownership/permissions if specified (
owner,modein file config) - Restarts systemd units only if something changed (restart command can be a static string or a callable for dynamic commands)
sing-box/generate.py and router/generate.py use a different delivery model — configs are rendered locally, uploaded to Cloudflare Workers KV, and pulled by devices over HTTPS:
sops decrypt → jinja render → KV upload → device wget/curl
This avoids SSH to constrained devices (OpenWrt routers, phones) while keeping configs versioned and secrets encrypted at rest.
All secrets are SOPS-encrypted.
SSH connection info (shared by all services) is stored at:
sops secrets/hosts.enc.yamlAnd service secrets are stored at:
.../secrets/secrets.enc.yaml- Python 3.10+
pip install jinja2 pyyaml requests- SOPS configured with your age key
- SSH access to target hosts
- rsync
- lego (for
certs/) - dnsproxy (for
certs/)
Fork-Parity-1.0.1