3.3 KiB
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 theirrolelabel (role_dev,role_prod,role_vpn- set ininfra/modules/<host>/main.tf).group_vars/-all.yml(shared:deploy_user, SSH key path,HCLOUD_TOKENlookup),role_dev.yml/role_prod.yml/role_vpn.yml(per-role vars, e.g.dev_runner_count).roles/bootstrap/- Docker install,deployuser creation, SSH hardening, unattended-upgrades. Replacesinfra/scripts/bootstrap.sh.tftpl.roles/deploy/- copiesservices/<host>/to the server, looks up the host's data volume directly from the Hetzner API and renders.env(DATA_DIR=...) and, fordev,Runners/docker-compose.yml.playbooks/-bootstrap.yml,deploy.yml,spinup.yml,spindown.yml, andsite.yml(all three in order).
Prerequisites
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.
# 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:
ansible-inventory --graph
ansible-inventory --list