95 lines
3.4 KiB
Markdown
95 lines
3.4 KiB
Markdown
|
|
# `caddy_site`
|
||
|
|
|
||
|
|
Writes one Caddy site file into `{{ caddy_sites_dir }}`, makes sure the main
|
||
|
|
Caddyfile imports that directory, validates the result, and reloads Caddy once.
|
||
|
|
|
||
|
|
Replaces the four-task block that was copy-pasted into 10 playbooks.
|
||
|
|
|
||
|
|
Runs on any host in the `[caddy]` group — `edge` (vipy), `monitoring`
|
||
|
|
(watchtower) and `vpn_control` (spacey).
|
||
|
|
|
||
|
|
## Usage
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
- ansible.builtin.include_role:
|
||
|
|
name: caddy_site
|
||
|
|
vars:
|
||
|
|
caddy_site_name: forgejo # -> forgejo.conf
|
||
|
|
caddy_site_domain: "{{ forgejo_domain }}"
|
||
|
|
caddy_site_upstream: "localhost:{{ forgejo_port }}"
|
||
|
|
```
|
||
|
|
|
||
|
|
Use `include_role`, not a `roles:` block, so the call stays in task order next
|
||
|
|
to the tasks it depends on. Variables passed this way are scoped to the include
|
||
|
|
and do not leak into later calls — so **every call must pass everything it
|
||
|
|
needs**; nothing carries over.
|
||
|
|
|
||
|
|
## Shapes
|
||
|
|
|
||
|
|
Pick exactly one of `caddy_site_upstream`, `caddy_site_root`, `caddy_site_body`.
|
||
|
|
|
||
|
|
| Want | Set |
|
||
|
|
|---|---|
|
||
|
|
| `reverse_proxy host:port` | `caddy_site_upstream` |
|
||
|
|
| static `root *` + `file_server` | `caddy_site_root` |
|
||
|
|
| anything else | `caddy_site_body` (raw, indented 4 for you) |
|
||
|
|
|
||
|
|
`caddy_site_upstream` accepts two modifiers, which add a block to the
|
||
|
|
`reverse_proxy`:
|
||
|
|
|
||
|
|
- `caddy_site_headers_up: {"X-Forwarded-Host": "..."}`
|
||
|
|
- `caddy_site_resolvers: "100.100.100.100"` — Tailscale MagicDNS
|
||
|
|
|
||
|
|
and `caddy_site_basic_auth` wraps the site in a `basic_auth` block.
|
||
|
|
|
||
|
|
## `caddy_site_basic_auth` is a LIST, not a dict
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
caddy_site_basic_auth:
|
||
|
|
- user: "{{ datum_dashboard_username }}"
|
||
|
|
hash: "{{ datum_dashboard_password_hash }}"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Ansible does not template dictionary keys.** With `{ "{{ user }}": "hash" }`
|
||
|
|
the value is rendered and the key is not, so the literal string
|
||
|
|
`{{ datum_dashboard_username }}` lands in the config file. Found while building
|
||
|
|
this role; the `assert` refuses a mapping so it cannot happen again.
|
||
|
|
|
||
|
|
## Secrets and `--diff`
|
||
|
|
|
||
|
|
Rendered site files can carry credentials — `datum-gateway.conf` holds a bcrypt
|
||
|
|
hash — and `--diff` prints rendered content. The template task therefore sets
|
||
|
|
`diff: "{{ caddy_site_reveal | bool }}"`, default `false`, so `--diff` runs are
|
||
|
|
safe everywhere. Pass `-e caddy_site_reveal=true` to see what moved on a site
|
||
|
|
you know is not secret.
|
||
|
|
|
||
|
|
## Validation
|
||
|
|
|
||
|
|
`validate: "caddy validate --adapter caddyfile --config %s"` runs against the
|
||
|
|
rendered temp file before it is moved into place. Verified on vipy that a single
|
||
|
|
site fragment validates cleanly (rc=0, `Valid configuration`) and that a
|
||
|
|
malformed one is rejected (rc=1, with the syntax error and line number). A
|
||
|
|
failed validate leaves the live file untouched, so a broken config can no longer
|
||
|
|
reach a running Caddy.
|
||
|
|
|
||
|
|
What it cannot catch is a conflict with the global `/etc/caddy/Caddyfile`.
|
||
|
|
|
||
|
|
## The reload is a handler
|
||
|
|
|
||
|
|
`Reload caddy` fires **once, at the end of the play**, however many sites
|
||
|
|
notified it. The code this replaced ran `command: systemctl reload caddy`
|
||
|
|
immediately, mid-play. If a later task in the same play needs the new config to
|
||
|
|
be live, flush first:
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
- ansible.builtin.meta: flush_handlers
|
||
|
|
```
|
||
|
|
|
||
|
|
## Known intentional difference
|
||
|
|
|
||
|
|
The `resolvers` block is commented `# Use Tailscale MagicDNS to resolve the
|
||
|
|
upstream hostname` in every case. `datum-gateway` previously said `# Resolve via
|
||
|
|
Tailscale MagicDNS`. Migrating it therefore rewrites one comment line, which
|
||
|
|
Caddy ignores. Every other site renders byte-identical to what its playbook
|
||
|
|
produced.
|