90 lines
3.9 KiB
Markdown
90 lines
3.9 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, 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. Persistent service data lives in named Docker volumes backed up
|
|
to S3, not on a Hetzner volume - see `../readme.md`.
|
|
|
|
## 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,
|
|
`backup_s3_*` S3 backup credentials), `role_dev.yml` / `role_prod.yml` /
|
|
`role_vpn.yml` (per-role vars, e.g. `service_group`).
|
|
- `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 and renders `.env`
|
|
(S3 backup credentials, see `../readme.md`) 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
|
|
export BACKUP_S3_BUCKET=your-bucket-name
|
|
export BACKUP_S3_ACCESS_KEY_ID=your-access-key-id
|
|
export BACKUP_S3_SECRET_ACCESS_KEY=your-secret-access-key
|
|
export BACKUP_S3_ENDPOINT=your-s3-endpoint # omit for real AWS S3
|
|
```
|
|
|
|
`../control.sh`'s **Set variables** menu does the same thing interactively and
|
|
persists your answers to `../.control.env` (gitignored) so you don't have to
|
|
re-export them every session.
|
|
|
|
`deploy_user` (see `group_vars/all.yml`) defaults to `deploy` - override with
|
|
the `DEPLOY_USER` env var if you want a different non-root user created on each
|
|
host. `ansible_ssh_private_key_file` (same file) 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 - `spinup.sh`
|
|
only starts/updates what's changed, same as before. `dev` always runs three
|
|
Gitea Actions runners (fixed in
|
|
`roles/deploy/templates/runners-docker-compose.yml.j2`).
|
|
|
|
`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
|
|
```
|