78 lines
3.3 KiB
Markdown
78 lines
3.3 KiB
Markdown
# 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
|
|
```
|