1
0
Fork 0
Configuration using OpenTofu and Nix
Find a file
2026-08-24 23:01:26 -06:00
.opencode/plans Fetch tokens from Bitwarden via rbw; drop .env; expand rewrite plan 2026-08-24 22:57:47 -06:00
modules Add reverse proxy to existing docker host 2025-02-22 23:53:45 -06:00
targets Fetch tokens from Bitwarden via rbw; drop .env; expand rewrite plan 2026-08-24 22:57:47 -06:00
terraform Update server 2025-07-23 02:08:14 -05:00
.envrc Update 2025-08-03 23:14:54 -05:00
.gitignore Add reverse proxy to existing docker host 2025-02-22 23:53:45 -06:00
.sops.yaml Add reverse proxy to existing docker host 2025-02-22 23:53:45 -06:00
AGENTS.md Update AGENTS.md for current setup; replace completed plan with rewrite plan 2026-08-24 14:55:04 -06:00
flake.lock Rotate access keys, replace Caddy DNS plugin with lego ACME, add git SSH forward 2026-08-24 14:50:52 -06:00
flake.nix Fetch tokens from Bitwarden via rbw; drop .env; expand rewrite plan 2026-08-24 22:57:47 -06:00
README.md Fetch tokens from Bitwarden via rbw; drop .env; expand rewrite plan 2026-08-24 22:57:47 -06:00

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 zones
  • pg_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:

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.