| .opencode/plans | ||
| modules | ||
| targets | ||
| terraform | ||
| .envrc | ||
| .gitignore | ||
| .sops.yaml | ||
| AGENTS.md | ||
| flake.lock | ||
| flake.nix | ||
| README.md | ||
VPN server configuration
Note: This configuration is old and rushed. I plan to merge it into my configurations project when I have a chance.
This is an experimental configuration for my Hetzner VPS and Cloudflare to run a VPN using OpenTofu and Nix, based on NixOS/nixos-wiki-infra on Github.
Configure ssh
Add keys to targets/admins/terraform.tf.
Configure secrets
API tokens are stored in Bitwarden and fetched with rbw. Create these
entries (value in the password field):
hcloud_token— Hetzner Cloud API token (dashboard → Security → API Tokens)cloudflare_api_token— Cloudflare token with write access to DNS zonespg_tofu_state— Postgres password for the OpenTofu state backend
Run rbw sync after adding them. The targets/Justfile exports
HCLOUD_TOKEN, CLOUDFLARE_API_TOKEN, PGCONNSTR, and PGPASSWORD
automatically. The OpenTofu state backend additionally needs the one-time
password setup described under "OpenTofu state backend" below.
Configure sops
On the Terraform client, run:
nix develop
cd targets/vpn/secrets
just generate-key
cp secrets.yaml.example secrets.yaml
Populate secrets.yaml with the desired values. Run age-keygen to get another key specifically for the server and put it in secrets.yaml (note that this will only work during installation). Then run just encrypt to encrypt and just decrypt to decrypt. Put the public key for both the VPN server and the OpenTofu client in .sops.yaml in the project root directory.
If you have already installed the server without a key, then run on the server:
sudo mkdir -p /var/lib/secrets
sudo chmod 700 /var/lib/secrets
sudo chown root:root /var/lib/secrets
umask 0177
age-keygen | sudo tee /var/lib/secrets/age >/dev/null
sudo chmod 600 /var/lib/secrets/age
sudo chown root:root /var/lib/secrets/age
umask 0022
cat /var/lib/secrets/age
Or run on the client: nix-shell -p ssh-to-age --run 'ssh-keyscan example.com | ssh-to-age' to get a public age key from the server's public ssh key.
Then follow the above instructions to add the public key.
Apply configuration
For cross-compiling on different architectures, you will need to add a builder by visiting the following resources:
- https://nix.dev/tutorials/nixos/distributed-builds-setup.html
- https://nix.dev/manual/nix/2.25/advanced-topics/distributed-builds
Run nix develop at the root of the project directory to access a shell where OpenTofu is accessible.
In the targets directory, run just to update the configurations.
VPN
To set up the VPN, on the VPS run:
headscale users create default
headscale preauthkeys create --user default --reusable
On the client run:
tailscale up --login-server <HEADSCALE_URL> --auth-key <KEY>
OpenTofu state backend
The state is stored in PostgreSQL on the VPS itself, reachable only over the
tailnet (Postgres listens on all interfaces, but the firewall has no allow rule
for port 5432 — only tailnet peers can reach it, via tailscaled's own rules).
The database is tofu_state, owned by the tofu_state user.
After the first deploy that enables Postgres, set the database password once, on the VPS:
sudo -u postgres psql -c "ALTER USER tofu_state PASSWORD '<pw>';"
Store <pw> in Bitwarden as pg_tofu_state. The backend reads its connection
string from the PGCONNSTR environment variable and the password from
PGPASSWORD — both are exported by the targets/Justfile from rbw (see
"Configure secrets" above). Then run tofu init inside targets/; on first
switch it will ask to migrate the existing local state.
Troubleshooting
Hetzner VPN
Some stuff may need to be configured by hand. For instance, the network settings may change with a new installation and need to be updated in targets/vpn/configuration.nix, or it may cause the network to be disabled requiring mounting with a NixOS recovery image and using nixos-enter to redo the networking and rebuild the system.
It may also complain about a hostname change. Changing the name of the VPN in terraform/nixos-vpn from nixos-vpn to vpn might help but I haven't tested it out yet. Otherwise you may need to clone the repo inside of the VM and do nixos-rebuild there.