Automate DKIM sync and add Hetzner resource labels
- Add dkim_sync.yml: generates DKIM keys for all mail_domains, writes keys to stack config (group_vars/all/dkim.yml), and publishes mail._domainkey TXT records via dns.yml — replaces manual vault editing - Remove dkim_keys from vault.yml.setup (public keys don't need encryption) - Add hcloud_labels to config.yml.setup and apply to server + SSH key in provision role, enabling project-level tagging of Hetzner resources - Fix setup.sh next steps: add missing bootstrap step, replace manual DKIM instructions with dkim_sync.yml - Update CLAUDE.md and README.md accordingly Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b38cd94fc8
commit
bd90a7e16f
8 changed files with 89 additions and 30 deletions
|
|
@ -40,12 +40,14 @@ Note: Inventory and vault password are set via `ANSIBLE_INVENTORY` and `ANSIBLE_
|
||||||
- `provision.yml` - Provision a cloud VM (Hetzner)
|
- `provision.yml` - Provision a cloud VM (Hetzner)
|
||||||
- `dns.yml` - Manage DNS zones/records via Hetzner DNS API
|
- `dns.yml` - Manage DNS zones/records via Hetzner DNS API
|
||||||
- `bootstrap.yml` - First-time server setup (run once as root before site.yml)
|
- `bootstrap.yml` - First-time server setup (run once as root before site.yml)
|
||||||
|
- `dkim_sync.yml` - Fetch DKIM keys from mailserver and publish to DNS (run once after first mail deploy)
|
||||||
|
|
||||||
**Full deployment order** (fresh server):
|
**Full deployment order** (fresh server):
|
||||||
1. `provision.yml` - create server, auto-writes IP to hosts.yml and config.yml
|
1. `provision.yml` - create server, auto-writes IP to hosts.yml and config.yml
|
||||||
2. `dns.yml` - create DNS records
|
2. `dns.yml` - create DNS records
|
||||||
3. `bootstrap.yml` - users, SSH hardening, packages, Docker (connects as root)
|
3. `bootstrap.yml` - users, SSH hardening, packages, Docker (connects as root)
|
||||||
4. `site.yml` - deploy all services
|
4. `site.yml` - deploy all services
|
||||||
|
5. `dkim_sync.yml` - generate DKIM keys, write to stack config, publish to DNS
|
||||||
|
|
||||||
**Playbook Execution Order** (via `site.yml`):
|
**Playbook Execution Order** (via `site.yml`):
|
||||||
1. networks.yml - Pre-create all Docker networks (must run before any service)
|
1. networks.yml - Pre-create all Docker networks (must run before any service)
|
||||||
|
|
|
||||||
17
README.md
17
README.md
|
|
@ -213,24 +213,13 @@ ansible-vault edit $LINDERHOF_DIR/group_vars/all/vault.yml
|
||||||
|
|
||||||
## after first mail deployment — DKIM
|
## after first mail deployment — DKIM
|
||||||
|
|
||||||
retrieve the generated DKIM public key and add it to the vault:
|
run `dkim_sync.yml` once after the first mail deployment — it generates DKIM keys for all mail domains, writes them to your stack config, and publishes the `mail._domainkey` DNS records automatically:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker exec mailserver cat /tmp/docker-mailserver/rspamd/dkim/<domain>/mail.pub
|
ansible-playbook playbooks/dkim_sync.yml
|
||||||
ansible-vault edit $LINDERHOF_DIR/group_vars/all/vault.yml
|
|
||||||
```
|
```
|
||||||
|
|
||||||
add under `dkim_keys`:
|
keys are stored in `$LINDERHOF_DIR/group_vars/all/dkim.yml` (plain file — DKIM public keys are not secret). safe to re-run; only generates keys for domains that don't have one yet.
|
||||||
```yaml
|
|
||||||
dkim_keys:
|
|
||||||
example.com: "v=DKIM1; k=rsa; p=..."
|
|
||||||
```
|
|
||||||
|
|
||||||
then re-run DNS — the `mail._domainkey` record is created automatically:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ansible-playbook playbooks/dns.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## common operations
|
## common operations
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,12 @@ enable_radicale: true
|
||||||
# ============================================================
|
# ============================================================
|
||||||
domain: $domain
|
domain: $domain
|
||||||
server_name: $server_name
|
server_name: $server_name
|
||||||
|
|
||||||
|
# Labels applied to all Hetzner cloud resources (server, SSH key).
|
||||||
|
# DNS resources do not support labels.
|
||||||
|
hcloud_labels:
|
||||||
|
managed-by: linderhof
|
||||||
|
stack: $stack_name
|
||||||
server_ip: $server_ip
|
server_ip: $server_ip
|
||||||
admin_user: $admin_user
|
admin_user: $admin_user
|
||||||
admin_shell: /bin/zsh
|
admin_shell: /bin/zsh
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,3 @@ restic_password: "$restic_password"
|
||||||
|
|
||||||
# fail2ban (optional — IPs/CIDRs to whitelist)
|
# fail2ban (optional — IPs/CIDRs to whitelist)
|
||||||
# fail2ban_ignoreip: "your-home-ip/32"
|
# fail2ban_ignoreip: "your-home-ip/32"
|
||||||
|
|
||||||
# DKIM public keys — add after first mail deployment:
|
|
||||||
# docker exec mailserver cat /tmp/docker-mailserver/rspamd/dkim/$domain/mail.pub
|
|
||||||
# Format: "v=DKIM1; k=rsa; p=<base64 public key>"
|
|
||||||
# dkim_keys:
|
|
||||||
# $domain: "v=DKIM1; k=rsa; p=..."
|
|
||||||
|
|
|
||||||
72
playbooks/dkim_sync.yml
Normal file
72
playbooks/dkim_sync.yml
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
---
|
||||||
|
# Fetch DKIM public keys from the running mailserver and publish them to DNS.
|
||||||
|
#
|
||||||
|
# Safe to re-run — only generates keys for domains that don't have one yet.
|
||||||
|
# Run after the first mail deployment:
|
||||||
|
# ansible-playbook playbooks/dkim_sync.yml
|
||||||
|
#
|
||||||
|
# What it does:
|
||||||
|
# 1. Generates DKIM keys for any domain missing one (skips existing keys)
|
||||||
|
# 2. Discovers each domain's key file at runtime via find — the algorithm prefix
|
||||||
|
# (rsa-2048-..., ed25519-...) may vary across docker-mailserver versions,
|
||||||
|
# but the *<domain>.public.dns.txt suffix is stable
|
||||||
|
# 3. Writes $LINDERHOF_DIR/group_vars/all/dkim.yml (plain file — DKIM keys are public)
|
||||||
|
# 4. Runs dns.yml to create/update mail._domainkey TXT records for all domains
|
||||||
|
|
||||||
|
- name: Fetch DKIM keys from mailserver
|
||||||
|
hosts: all
|
||||||
|
gather_facts: false
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Check for existing DKIM key files
|
||||||
|
# /srv/mail/config is the host-side mount of /tmp/docker-mailserver in the container
|
||||||
|
command: find /srv/mail/config/rspamd/dkim -name "*{{ item }}.public.dns.txt" -type f
|
||||||
|
loop: "{{ mail_domains }}"
|
||||||
|
register: dkim_existing
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Generate DKIM keys for domains without existing keys
|
||||||
|
command: docker exec mailserver setup config dkim
|
||||||
|
when: dkim_existing.results | selectattr('stdout', 'equalto', '') | list | length > 0
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
- name: Find DKIM DNS TXT key file for each domain
|
||||||
|
command: find /srv/mail/config/rspamd/dkim -name "*{{ item }}.public.dns.txt" -type f
|
||||||
|
loop: "{{ mail_domains }}"
|
||||||
|
register: dkim_file_paths
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Read DKIM public key for each domain
|
||||||
|
slurp:
|
||||||
|
src: "{{ item.stdout | trim }}"
|
||||||
|
loop: "{{ dkim_file_paths.results }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.item }}"
|
||||||
|
register: dkim_keys_raw
|
||||||
|
|
||||||
|
- name: Build dkim_keys dict
|
||||||
|
set_fact:
|
||||||
|
dkim_keys_collected: >-
|
||||||
|
{{ dkim_keys_collected | default({}) | combine({item.item.item: item.content | b64decode | trim}) }}
|
||||||
|
loop: "{{ dkim_keys_raw.results }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.item.item }}"
|
||||||
|
|
||||||
|
- name: Write dkim.yml to stack config directory
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
# Written to a separate dkim.yml rather than config.yml so this playbook can safely
|
||||||
|
# overwrite it without touching the hand-edited config. Ansible loads all files under
|
||||||
|
# group_vars/all/ automatically, so dkim_keys is available to all roles either way.
|
||||||
|
copy:
|
||||||
|
content: |
|
||||||
|
---
|
||||||
|
# DKIM public keys — written automatically by dkim_sync.yml, do not edit manually
|
||||||
|
dkim_keys:
|
||||||
|
{% for domain_name, key in dkim_keys_collected.items() %}
|
||||||
|
{{ domain_name }}: "{{ key }}"
|
||||||
|
{% endfor %}
|
||||||
|
dest: "{{ lookup('env', 'ANSIBLE_INVENTORY') | dirname }}/group_vars/all/dkim.yml"
|
||||||
|
|
||||||
|
- import_playbook: dns.yml
|
||||||
|
|
@ -4,13 +4,7 @@
|
||||||
# Zone definitions live in $LINDERHOF_DIR/group_vars/all/dns.yml
|
# Zone definitions live in $LINDERHOF_DIR/group_vars/all/dns.yml
|
||||||
# (generated from inventory/group_vars/all/dns.yml.setup by setup.sh).
|
# (generated from inventory/group_vars/all/dns.yml.setup by setup.sh).
|
||||||
#
|
#
|
||||||
# To add DKIM keys after first mail deployment:
|
# DKIM records are managed automatically by dkim_sync.yml — do not add manually.
|
||||||
# docker exec mailserver cat /tmp/docker-mailserver/rspamd/dkim/<domain>/mail.pub
|
|
||||||
# Then add to vault.yml:
|
|
||||||
# ansible-vault edit $LINDERHOF_DIR/group_vars/all/vault.yml
|
|
||||||
# dkim_keys:
|
|
||||||
# example.com: "v=DKIM1; k=rsa; p=..."
|
|
||||||
# And uncomment the mail._domainkey record in dns.yml.
|
|
||||||
#
|
#
|
||||||
# Usage: ansible-playbook playbooks/dns.yml
|
# Usage: ansible-playbook playbooks/dns.yml
|
||||||
- name: Manage DNS zones on Hetzner Cloud
|
- name: Manage DNS zones on Hetzner Cloud
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
hetzner.hcloud.ssh_key:
|
hetzner.hcloud.ssh_key:
|
||||||
name: "{{ admin_user }}"
|
name: "{{ admin_user }}"
|
||||||
public_key: "{{ admin_ssh_key }}"
|
public_key: "{{ admin_ssh_key }}"
|
||||||
|
labels: "{{ hcloud_labels }}"
|
||||||
api_token: "{{ hcloud_token }}"
|
api_token: "{{ hcloud_token }}"
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
|
|
@ -14,6 +15,7 @@
|
||||||
location: "{{ hcloud_location }}"
|
location: "{{ hcloud_location }}"
|
||||||
ssh_keys:
|
ssh_keys:
|
||||||
- "{{ admin_user }}"
|
- "{{ admin_user }}"
|
||||||
|
labels: "{{ hcloud_labels }}"
|
||||||
api_token: "{{ hcloud_token }}"
|
api_token: "{{ hcloud_token }}"
|
||||||
state: present
|
state: present
|
||||||
register: server_result
|
register: server_result
|
||||||
|
|
|
||||||
6
setup.sh
6
setup.sh
|
|
@ -242,6 +242,6 @@ echo " 2. Review $VAULT (ansible-vault edit)"
|
||||||
echo " 3. Review $DNS_CONFIG"
|
echo " 3. Review $DNS_CONFIG"
|
||||||
echo " 4. Provision a server: ansible-playbook playbooks/provision.yml"
|
echo " 4. Provision a server: ansible-playbook playbooks/provision.yml"
|
||||||
echo " 5. Update DNS: ansible-playbook playbooks/dns.yml"
|
echo " 5. Update DNS: ansible-playbook playbooks/dns.yml"
|
||||||
echo " 6. Deploy: ansible-playbook playbooks/site.yml"
|
echo " 6. Bootstrap server: ansible-playbook playbooks/site.yml --tags bootstrap"
|
||||||
echo " 7. After mail deploys, retrieve DKIM keys and add to vault.yml:"
|
echo " 7. Deploy: ansible-playbook playbooks/site.yml"
|
||||||
echo " docker exec mailserver cat /tmp/docker-mailserver/rspamd/dkim/$domain/mail.pub"
|
echo " 8. Sync DKIM keys to DNS: ansible-playbook playbooks/dkim_sync.yml"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue