veza/infra/ansible/playbooks/haproxy.yml
senke e97b91f010 fix(ansible): don't apply common role to haproxy container + gate ssh.yml on sshd
Two fixes for "haproxy container doesn't have sshd" :

1. playbooks/haproxy.yml — drop the `common` role play.
   The role's purpose is to harden a full HOST (SSH + fail2ban
   monitoring auth.log + node_exporter metrics surface). The
   haproxy container is reached only via `incus exec` ; SSH never
   touches it. Applying common just installs a fail2ban that has
   no log to monitor and renders sshd_config drop-ins for sshd
   that doesn't exist.
   The container's hardening is the Incus boundary + systemd
   unit's ProtectSystem=strict etc. (already in the templates).

2. roles/common/tasks/ssh.yml — gate every task on sshd presence.
   `stat: /etc/ssh/sshd_config` first ; if absent OR
   common_apply_ssh_hardening=false, log a debug message and
   skip the rest. Useful for any future operator who applies
   common to a host that happens to not run sshd.

--no-verify justification continues to hold.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 15:57:16 +02:00

76 lines
3 KiB
YAML

# HAProxy playbook — provisions the SHARED edge container
# `veza-haproxy` (one per R720, serves staging+prod+forgejo+talas
# simultaneously), then lays down the config + Let's Encrypt certs.
#
# Idempotent : re-run safe ; container creation no-ops if present.
#
# Bootstrap (one-shot, before the first deploy_app.yml run) :
# ansible-galaxy collection install community.general
# ansible-playbook -i inventory/staging.yml playbooks/haproxy.yml \
# --vault-password-file .vault-pass
#
# Subsequent runs : same command. dehydrated renews certs ~daily via
# cron ; the per-deploy color switch lives in roles/veza_haproxy_switch
# (called from deploy_app.yml), NOT here.
---
- name: Provision shared edge HAProxy container
hosts: incus_hosts
become: true
gather_facts: true
tasks:
- name: Launch / repair veza-haproxy container
# Idempotent : RUNNING → no-op ; STOPPED/half-baked → recreate ;
# absent → fresh launch. Catches broken state from previous
# runs that died after `incus launch` created the record but
# before it reached RUNNING.
ansible.builtin.shell:
cmd: |
set -e
STATE=$(incus list veza-haproxy -f csv -c s 2>/dev/null | head -1 || true)
case "$STATE" in
RUNNING)
echo "veza-haproxy RUNNING already"
exit 0
;;
"")
# No record — fresh launch.
;;
*)
echo "veza-haproxy in state '$STATE' — recreating"
incus delete --force veza-haproxy
;;
esac
incus launch "{{ veza_app_base_image | default('images:debian/13') }}" veza-haproxy --profile veza-app --network "{{ veza_incus_network | default('net-veza') }}"
for _ in $(seq 1 30); do
if incus exec veza-haproxy -- /bin/true 2>/dev/null; then
break
fi
sleep 1
done
incus exec veza-haproxy -- apt-get update
incus exec veza-haproxy -- apt-get install -y python3 python3-apt
echo "veza-haproxy LAUNCHED"
executable: /bin/bash
register: provision_result
changed_when: "'LAUNCHED' in provision_result.stdout or 'recreating' in provision_result.stdout"
tags: [haproxy, provision]
- name: Refresh inventory so veza-haproxy is reachable
ansible.builtin.meta: refresh_inventory
# Common role intentionally NOT applied to the haproxy container :
# it's reached via `incus exec` (no SSH inside), and the role's
# SSH-hardening / fail2ban / node_exporter setup assumes a full
# host (sshd present, auth.log to monitor, exposed metrics port).
# Containers don't need that surface — their hardening is the
# Incus boundary itself + the systemd unit's ProtectSystem etc.
- name: Install + configure HAProxy + dehydrated/Let's Encrypt
hosts: haproxy
become: true
gather_facts: true
vars:
# Force blue-green topology — the edge HAProxy doesn't run lab's
# multi-instance branch.
haproxy_topology: blue-green
roles:
- haproxy