feat: Swapped tofu init scripting for ansible scripting

This commit is contained in:
2026-07-13 21:23:31 +01:00
parent cf5504281c
commit c12a507fc1
34 changed files with 471 additions and 501 deletions
+77
View File
@@ -0,0 +1,77 @@
# Ansible
Bootstraps, syncs, and starts/stops the Docker Compose stacks in `../services/`
on the three servers OpenTofu provisions (see `../infra/`). OpenTofu still owns
the actual cloud resources (servers, network, firewalls, volumes, DNS);
everything downstream of "the server exists" - installing Docker, creating the
`deploy` user, hardening SSH, copying `services/<host>/`, rendering `.env` /
the Gitea runner compose file, and running `spinup.sh`/`spindown.sh` - lives
here instead.
## Contents
- `inventory/hcloud.yml` - dynamic inventory, queries the Hetzner Cloud API
directly (not Terraform state) and groups servers by their `role` label
(`role_dev`, `role_prod`, `role_vpn` - set in `infra/modules/<host>/main.tf`).
- `group_vars/` - `all.yml` (shared: `deploy_user`, SSH key path, `HCLOUD_TOKEN`
lookup), `role_dev.yml` / `role_prod.yml` / `role_vpn.yml` (per-role vars,
e.g. `dev_runner_count`).
- `roles/bootstrap/` - Docker install, `deploy` user creation, SSH hardening,
unattended-upgrades. Replaces `infra/scripts/bootstrap.sh.tftpl`.
- `roles/deploy/` - copies `services/<host>/` to the server, looks up the
host's data volume directly from the Hetzner API and renders `.env`
(`DATA_DIR=...`) and, for `dev`, `Runners/docker-compose.yml`.
- `playbooks/` - `bootstrap.yml`, `deploy.yml`, `spinup.yml`, `spindown.yml`,
and `site.yml` (all three in order).
## Prerequisites
```sh
cd ansible
ansible-galaxy collection install -r requirements.yml
export HCLOUD_TOKEN=your-hetzner-api-token # never commit this
```
`ansible_ssh_private_key_file` (see `group_vars/all.yml`) defaults to
`~/.ssh/id_ed25519` - override with `ANSIBLE_SSH_PRIVATE_KEY_FILE` if you use a
different key. It must match one of the SSH keys OpenTofu installed on the
servers (`var.ssh_key_names`).
## Usage
Same chicken-and-egg as OpenTofu's own first-apply ordering (see
`../readme.md`): `dev`/`prod`'s firewalls only accept SSH from `vpn`'s own
public IP, so you must bring `vpn` up and connect to it before `dev`/`prod`
are reachable at all.
```sh
# 1. vpn first - its firewall accepts SSH from var.allowed_ssh_source_ips directly
ansible-playbook playbooks/bootstrap.yml -l role_vpn
ansible-playbook playbooks/deploy.yml -l role_vpn
ansible-playbook playbooks/spinup.yml -l role_vpn
# 2. connect to the VPN you just started with an OpenVPN client - now dev/prod
# are reachable, since your egress IP matches their firewall rule
ansible-playbook playbooks/bootstrap.yml -l role_dev,role_prod
ansible-playbook playbooks/deploy.yml -l role_dev,role_prod
ansible-playbook playbooks/spinup.yml -l role_dev,role_prod
# ...or once already bootstrapped and connected, do everything in one go:
ansible-playbook playbooks/site.yml -l role_dev,role_prod
```
Re-run `deploy.yml` + `spinup.yml` any time a compose file changes or
`dev_runner_count` in `group_vars/role_dev.yml` is bumped - `spinup.sh` only
starts/updates what's changed, same as before.
`spindown.yml` runs `spindown.sh`, which does a full `docker system/volume
prune -a` on the host - it prompts for a `yes` confirmation before running,
since the dynamic inventory makes it trivial to target multiple hosts at once
(`-l role_dev,role_prod`).
Inspect what the dynamic inventory currently sees:
```sh
ansible-inventory --graph
ansible-inventory --list
```