2026-04-29 12:50:27 +00:00
|
|
|
# Prod inventory — single R720 (self-hosted Incus) at v1.0 launch,
|
|
|
|
|
# Hetzner debordement post-launch. ROADMAP_V1.0_LAUNCH.md §2 documents
|
|
|
|
|
# the COMPRESSED HA stance : real multi-host HA arrives v1.1+ ; v1.0
|
|
|
|
|
# ships single-host with EC4+2 MinIO + PgAutoFailover colocated.
|
feat(infra): Ansible IaC scaffolding — common + incus_host roles (Day 5 v1.0.9)
Day 5 of ROADMAP_V1.0_LAUNCH.md §Semaine 1: turn the manual
host-setup steps into an idempotent playbook so subsequent days
(W2 Postgres HA, W2 PgBouncer, W2 OTel collector, W3 Redis
Sentinel, W3 MinIO distributed, W4 HAProxy) can each land as a
self-contained role on top of this baseline.
Layout (full tree under infra/ansible/):
ansible.cfg pinned defaults — inventory path,
ControlMaster=auto so the SSH handshake
is paid once per playbook run
inventory/{lab,staging,prod}.yml
three environments. lab is the R720's
local Incus container (10.0.20.150),
staging is Hetzner (TODO until W2
provisions the box), prod is R720
(TODO until DNS at EX-5 lands).
group_vars/all.yml shared defaults — SSH whitelist,
fail2ban thresholds, unattended-upgrades
origins, node_exporter version pin.
playbooks/site.yml entry point. Two plays:
1. common (every host)
2. incus_host (incus_hosts group)
roles/common/ idempotent baseline:
ssh.yml — drop-in
/etc/ssh/sshd_config.d/50-veza-
hardening.conf, validates with
`sshd -t` before reload, asserts
ssh_allow_users non-empty before
apply (refuses to lock out the
operator).
fail2ban.yml — sshd jail tuned to
group_vars (defaults bantime=1h,
findtime=10min, maxretry=5).
unattended_upgrades.yml — security-
only origins, Automatic-Reboot
pinned to false (operator owns
reboot windows for SLO-budget
alignment, cf W2 day 10).
node_exporter.yml — pinned to
1.8.2, runs as a systemd unit
on :9100. Skips download when
--version already matches.
roles/incus_host/ zabbly upstream apt repo + incus +
incus-client install. First-time
`incus admin init --preseed` only when
`incus list` errors (i.e. the host
has never been initialised) — re-runs
on initialised hosts are no-ops.
Configures incusbr0 / 10.99.0.1/24
with NAT + default storage pool.
Acceptance verified locally (full --check needs SSH to the lab
host which is offline-only from this box, so the user runs that
step):
$ cd infra/ansible
$ ansible-playbook -i inventory/lab.yml playbooks/site.yml --syntax-check
playbook: playbooks/site.yml ← clean
$ ansible-playbook -i inventory/lab.yml playbooks/site.yml --list-tasks
21 tasks across 2 plays, all tagged. ← partial applies work
Conventions enforced from the start:
- Every task has tags so `--tags ssh,fail2ban` partial applies
are always possible.
- Sub-task files (ssh.yml, fail2ban.yml, etc.) so the role
main.yml stays a directory of concerns, not a wall of tasks.
- Validators run before reload (sshd -t for sshd_config). The
role refuses to apply changes that would lock the operator out.
- Comments answer "why" — task names + module names already
say "what".
Next role on the stack: postgres_ha (W2 day 6) — pg_auto_failover
monitor + primary + replica in 2 Incus containers.
SKIP_TESTS=1 — IaC YAML, no app code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 16:16:38 +00:00
|
|
|
#
|
2026-04-29 12:50:27 +00:00
|
|
|
# Topology mirrors staging.yml (same shape, different prefix +
|
|
|
|
|
# different network — see group_vars/prod.yml). Phase-2 (post v1.1)
|
|
|
|
|
# flips `veza-prod` to a non-R720 host without changing any other
|
|
|
|
|
# part of this file.
|
|
|
|
|
#
|
|
|
|
|
# Naming : every container ends up `veza-<component>[-<color>]` because
|
|
|
|
|
# group_vars/prod.yml sets veza_container_prefix=veza- (the established
|
|
|
|
|
# convention — staging is prefixed, prod is bare).
|
feat(infra): Ansible IaC scaffolding — common + incus_host roles (Day 5 v1.0.9)
Day 5 of ROADMAP_V1.0_LAUNCH.md §Semaine 1: turn the manual
host-setup steps into an idempotent playbook so subsequent days
(W2 Postgres HA, W2 PgBouncer, W2 OTel collector, W3 Redis
Sentinel, W3 MinIO distributed, W4 HAProxy) can each land as a
self-contained role on top of this baseline.
Layout (full tree under infra/ansible/):
ansible.cfg pinned defaults — inventory path,
ControlMaster=auto so the SSH handshake
is paid once per playbook run
inventory/{lab,staging,prod}.yml
three environments. lab is the R720's
local Incus container (10.0.20.150),
staging is Hetzner (TODO until W2
provisions the box), prod is R720
(TODO until DNS at EX-5 lands).
group_vars/all.yml shared defaults — SSH whitelist,
fail2ban thresholds, unattended-upgrades
origins, node_exporter version pin.
playbooks/site.yml entry point. Two plays:
1. common (every host)
2. incus_host (incus_hosts group)
roles/common/ idempotent baseline:
ssh.yml — drop-in
/etc/ssh/sshd_config.d/50-veza-
hardening.conf, validates with
`sshd -t` before reload, asserts
ssh_allow_users non-empty before
apply (refuses to lock out the
operator).
fail2ban.yml — sshd jail tuned to
group_vars (defaults bantime=1h,
findtime=10min, maxretry=5).
unattended_upgrades.yml — security-
only origins, Automatic-Reboot
pinned to false (operator owns
reboot windows for SLO-budget
alignment, cf W2 day 10).
node_exporter.yml — pinned to
1.8.2, runs as a systemd unit
on :9100. Skips download when
--version already matches.
roles/incus_host/ zabbly upstream apt repo + incus +
incus-client install. First-time
`incus admin init --preseed` only when
`incus list` errors (i.e. the host
has never been initialised) — re-runs
on initialised hosts are no-ops.
Configures incusbr0 / 10.99.0.1/24
with NAT + default storage pool.
Acceptance verified locally (full --check needs SSH to the lab
host which is offline-only from this box, so the user runs that
step):
$ cd infra/ansible
$ ansible-playbook -i inventory/lab.yml playbooks/site.yml --syntax-check
playbook: playbooks/site.yml ← clean
$ ansible-playbook -i inventory/lab.yml playbooks/site.yml --list-tasks
21 tasks across 2 plays, all tagged. ← partial applies work
Conventions enforced from the start:
- Every task has tags so `--tags ssh,fail2ban` partial applies
are always possible.
- Sub-task files (ssh.yml, fail2ban.yml, etc.) so the role
main.yml stays a directory of concerns, not a wall of tasks.
- Validators run before reload (sshd -t for sshd_config). The
role refuses to apply changes that would lock the operator out.
- Comments answer "why" — task names + module names already
say "what".
Next role on the stack: postgres_ha (W2 day 6) — pg_auto_failover
monitor + primary + replica in 2 Incus containers.
SKIP_TESTS=1 — IaC YAML, no app code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 16:16:38 +00:00
|
|
|
all:
|
|
|
|
|
hosts:
|
|
|
|
|
veza-prod:
|
2026-04-30 12:39:39 +00:00
|
|
|
# Same R720 as staging at v1.0 — separate Incus network keeps
|
|
|
|
|
# blast radius contained. Move to a dedicated host post-v1.1.
|
|
|
|
|
ansible_host: srv-102v
|
|
|
|
|
ansible_user: senke
|
feat(infra): Ansible IaC scaffolding — common + incus_host roles (Day 5 v1.0.9)
Day 5 of ROADMAP_V1.0_LAUNCH.md §Semaine 1: turn the manual
host-setup steps into an idempotent playbook so subsequent days
(W2 Postgres HA, W2 PgBouncer, W2 OTel collector, W3 Redis
Sentinel, W3 MinIO distributed, W4 HAProxy) can each land as a
self-contained role on top of this baseline.
Layout (full tree under infra/ansible/):
ansible.cfg pinned defaults — inventory path,
ControlMaster=auto so the SSH handshake
is paid once per playbook run
inventory/{lab,staging,prod}.yml
three environments. lab is the R720's
local Incus container (10.0.20.150),
staging is Hetzner (TODO until W2
provisions the box), prod is R720
(TODO until DNS at EX-5 lands).
group_vars/all.yml shared defaults — SSH whitelist,
fail2ban thresholds, unattended-upgrades
origins, node_exporter version pin.
playbooks/site.yml entry point. Two plays:
1. common (every host)
2. incus_host (incus_hosts group)
roles/common/ idempotent baseline:
ssh.yml — drop-in
/etc/ssh/sshd_config.d/50-veza-
hardening.conf, validates with
`sshd -t` before reload, asserts
ssh_allow_users non-empty before
apply (refuses to lock out the
operator).
fail2ban.yml — sshd jail tuned to
group_vars (defaults bantime=1h,
findtime=10min, maxretry=5).
unattended_upgrades.yml — security-
only origins, Automatic-Reboot
pinned to false (operator owns
reboot windows for SLO-budget
alignment, cf W2 day 10).
node_exporter.yml — pinned to
1.8.2, runs as a systemd unit
on :9100. Skips download when
--version already matches.
roles/incus_host/ zabbly upstream apt repo + incus +
incus-client install. First-time
`incus admin init --preseed` only when
`incus list` errors (i.e. the host
has never been initialised) — re-runs
on initialised hosts are no-ops.
Configures incusbr0 / 10.99.0.1/24
with NAT + default storage pool.
Acceptance verified locally (full --check needs SSH to the lab
host which is offline-only from this box, so the user runs that
step):
$ cd infra/ansible
$ ansible-playbook -i inventory/lab.yml playbooks/site.yml --syntax-check
playbook: playbooks/site.yml ← clean
$ ansible-playbook -i inventory/lab.yml playbooks/site.yml --list-tasks
21 tasks across 2 plays, all tagged. ← partial applies work
Conventions enforced from the start:
- Every task has tags so `--tags ssh,fail2ban` partial applies
are always possible.
- Sub-task files (ssh.yml, fail2ban.yml, etc.) so the role
main.yml stays a directory of concerns, not a wall of tasks.
- Validators run before reload (sshd -t for sshd_config). The
role refuses to apply changes that would lock the operator out.
- Comments answer "why" — task names + module names already
say "what".
Next role on the stack: postgres_ha (W2 day 6) — pg_auto_failover
monitor + primary + replica in 2 Incus containers.
SKIP_TESTS=1 — IaC YAML, no app code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 16:16:38 +00:00
|
|
|
ansible_python_interpreter: /usr/bin/python3
|
|
|
|
|
children:
|
|
|
|
|
incus_hosts:
|
|
|
|
|
hosts:
|
|
|
|
|
veza-prod:
|
refactor(bootstrap): everything via Ansible — no NOPASSWD, no SSH plumbing
Rearchitecture after operator pushback : the previous design did
too much in bash (SSH-streaming script chunks, manual sudo dance,
NOPASSWD requirement). Ansible is the right tool. The shell
scripts are now thin orchestrators handling the chicken-and-egg
of vault + Forgejo CI provisioning, then calling ansible-playbook.
Key principles :
1. NO NOPASSWD sudo on the R720. --ask-become-pass interactive,
password held in ansible memory only for the run.
2. Two parallel scripts — one per host, fully self-contained.
3. Both run the SAME Ansible playbooks (bootstrap_runner.yml +
haproxy.yml). Difference is the inventory.
Files (new + replaced) :
ansible.cfg
pipelining=True → False. Required for --ask-become-pass to
work reliably ; the previous setting raced sudo's prompt and
timed out at 12s.
playbooks/bootstrap_runner.yml (new)
The Incus-host-side bootstrap, ported from the old
scripts/bootstrap/bootstrap-remote.sh. Three plays :
Phase 1 : ensure veza-app + veza-data profiles exist ;
drop legacy empty veza-net profile.
Phase 2 : forgejo-runner gets /var/lib/incus/unix.socket
attached as a disk device, security.nesting=true,
/usr/bin/incus pushed in as /usr/local/bin/incus,
smoke-tested.
Phase 3 : forgejo-runner registered with `incus,self-hosted`
label (idempotent — skips if already labelled).
Each task uses Ansible idioms (`incus_profile`, `incus_command`
where they exist, `command:` with `failed_when` and explicit
state-checking elsewhere). no_log on the registration token.
inventory/local.yml (new)
Inventory for `bootstrap-r720.sh` — connection: local instead
of SSH+become. Same group structure as staging.yml ;
container groups use community.general.incus connection
plugin (the local incus binary, no remote).
inventory/{staging,prod}.yml (modified)
Added `forgejo_runner` group (target of bootstrap_runner.yml
phase 3, reached via community.general.incus from the host).
scripts/bootstrap/bootstrap-local.sh (rewritten)
Five phases : preflight, vault, forgejo, ansible, summary.
Phase 4 calls a single `ansible-playbook` with both
bootstrap_runner.yml + haproxy.yml in sequence.
--ask-become-pass : ansible prompts ONCE for sudo, holds in
memory, reuses for every become: true task.
scripts/bootstrap/bootstrap-r720.sh (new)
Symmetric to bootstrap-local.sh but runs as root on the R720.
No SSH preflight, no --ask-become-pass (already root).
Same Ansible playbooks, inventory/local.yml.
scripts/bootstrap/verify-r720.sh (new — replaces verify-remote)
Read-only checks of R720 state. Run as root locally on the R720.
scripts/bootstrap/verify-local.sh (modified)
Cross-host SSH check now fits the env-var-driven SSH_TARGET
pattern (R720_USER may be empty if the alias has User=).
scripts/bootstrap/{bootstrap-remote.sh, verify-remote.sh,
verify-remote-ssh.sh} (DELETED)
Replaced by playbooks/bootstrap_runner.yml + verify-r720.sh.
README.md (rewritten)
Documents the parallel-script architecture, the
no-NOPASSWD-sudo design choice (--ask-become-pass), each
phase's needs, and a refreshed troubleshooting list.
State files unchanged in shape :
laptop : .git/talas-bootstrap/local.state
R720 : /var/lib/talas/r720-bootstrap.state
--no-verify justification continues to hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 13:12:26 +00:00
|
|
|
# forgejo-runner container (target of bootstrap_runner.yml phase 3).
|
|
|
|
|
forgejo_runner:
|
|
|
|
|
hosts:
|
|
|
|
|
forgejo-runner:
|
|
|
|
|
vars:
|
|
|
|
|
ansible_connection: community.general.incus
|
2026-04-30 13:42:44 +00:00
|
|
|
ansible_incus_remote: "{{ veza_incus_remote_name | default('srv-102v') }}"
|
refactor(bootstrap): everything via Ansible — no NOPASSWD, no SSH plumbing
Rearchitecture after operator pushback : the previous design did
too much in bash (SSH-streaming script chunks, manual sudo dance,
NOPASSWD requirement). Ansible is the right tool. The shell
scripts are now thin orchestrators handling the chicken-and-egg
of vault + Forgejo CI provisioning, then calling ansible-playbook.
Key principles :
1. NO NOPASSWD sudo on the R720. --ask-become-pass interactive,
password held in ansible memory only for the run.
2. Two parallel scripts — one per host, fully self-contained.
3. Both run the SAME Ansible playbooks (bootstrap_runner.yml +
haproxy.yml). Difference is the inventory.
Files (new + replaced) :
ansible.cfg
pipelining=True → False. Required for --ask-become-pass to
work reliably ; the previous setting raced sudo's prompt and
timed out at 12s.
playbooks/bootstrap_runner.yml (new)
The Incus-host-side bootstrap, ported from the old
scripts/bootstrap/bootstrap-remote.sh. Three plays :
Phase 1 : ensure veza-app + veza-data profiles exist ;
drop legacy empty veza-net profile.
Phase 2 : forgejo-runner gets /var/lib/incus/unix.socket
attached as a disk device, security.nesting=true,
/usr/bin/incus pushed in as /usr/local/bin/incus,
smoke-tested.
Phase 3 : forgejo-runner registered with `incus,self-hosted`
label (idempotent — skips if already labelled).
Each task uses Ansible idioms (`incus_profile`, `incus_command`
where they exist, `command:` with `failed_when` and explicit
state-checking elsewhere). no_log on the registration token.
inventory/local.yml (new)
Inventory for `bootstrap-r720.sh` — connection: local instead
of SSH+become. Same group structure as staging.yml ;
container groups use community.general.incus connection
plugin (the local incus binary, no remote).
inventory/{staging,prod}.yml (modified)
Added `forgejo_runner` group (target of bootstrap_runner.yml
phase 3, reached via community.general.incus from the host).
scripts/bootstrap/bootstrap-local.sh (rewritten)
Five phases : preflight, vault, forgejo, ansible, summary.
Phase 4 calls a single `ansible-playbook` with both
bootstrap_runner.yml + haproxy.yml in sequence.
--ask-become-pass : ansible prompts ONCE for sudo, holds in
memory, reuses for every become: true task.
scripts/bootstrap/bootstrap-r720.sh (new)
Symmetric to bootstrap-local.sh but runs as root on the R720.
No SSH preflight, no --ask-become-pass (already root).
Same Ansible playbooks, inventory/local.yml.
scripts/bootstrap/verify-r720.sh (new — replaces verify-remote)
Read-only checks of R720 state. Run as root locally on the R720.
scripts/bootstrap/verify-local.sh (modified)
Cross-host SSH check now fits the env-var-driven SSH_TARGET
pattern (R720_USER may be empty if the alias has User=).
scripts/bootstrap/{bootstrap-remote.sh, verify-remote.sh,
verify-remote-ssh.sh} (DELETED)
Replaced by playbooks/bootstrap_runner.yml + verify-r720.sh.
README.md (rewritten)
Documents the parallel-script architecture, the
no-NOPASSWD-sudo design choice (--ask-become-pass), each
phase's needs, and a refreshed troubleshooting list.
State files unchanged in shape :
laptop : .git/talas-bootstrap/local.state
R720 : /var/lib/talas/r720-bootstrap.state
--no-verify justification continues to hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 13:12:26 +00:00
|
|
|
ansible_python_interpreter: /usr/bin/python3
|
refactor(ansible): single edge HAProxy — multi-env + Forgejo + Talas
The 12-record DNS plan ($1 per record at the registrar but only one
public R720 IP) forces the obvious : a single HAProxy on :443 must
serve staging.veza.fr + veza.fr + www.veza.fr + talas.fr +
www.talas.fr + forgejo.talas.group all at once. Per-env haproxies
were a phase-1 simplification that doesn't survive contact with
DNS reality.
Topology after :
veza-haproxy (one container, R720 public 443)
├── ACL host_staging → staging_{backend,stream,web}_pool
│ → veza-staging-{component}-{blue|green}.lxd
├── ACL host_prod → prod_{backend,stream,web}_pool
│ → veza-{component}-{blue|green}.lxd
├── ACL host_forgejo → forgejo_backend → 10.0.20.105:3000
│ (Forgejo container managed outside the deploy pipeline)
└── ACL host_talas → talas_vitrine_backend
(placeholder 503 until the static site lands)
Changes :
inventory/{staging,prod}.yml :
Both `haproxy:` group now points to the SAME container
`veza-haproxy` (no env prefix). Comment makes the contract
explicit so the next reader doesn't try to split it back.
group_vars/all/main.yml :
NEW : haproxy_env_prefixes (per-env container prefix mapping).
NEW : haproxy_env_public_hosts (per-env Host-header mapping).
NEW : haproxy_forgejo_host + haproxy_forgejo_backend.
NEW : haproxy_talas_hosts + haproxy_talas_vitrine_backend.
NEW : haproxy_letsencrypt_* (moved from env files — the edge
is shared, the LE config is shared too. Else the env
that ran the haproxy role last would clobber the
domain set).
group_vars/{staging,prod}.yml :
Strip the haproxy_letsencrypt_* block (now in all/main.yml).
Comment points readers there.
roles/haproxy/templates/haproxy.cfg.j2 :
The `blue-green` topology branch rebuilt around per-env
backends (`<env>_backend_api`, `<env>_stream_pool`,
`<env>_web_pool`) plus standalone `forgejo_backend`,
`talas_vitrine_backend`, `default_503`.
Frontend ACLs : `host_<env>` (hdr(host) -i ...) selects
which env's backends to use ; path ACLs (`is_api`,
`is_stream_seg`, etc.) refine within the env.
Sticky cookie name suffixed `_<env>` so a user logged
into staging doesn't carry the cookie into prod.
Per-env active color comes from haproxy_active_colors map
(built by veza_haproxy_switch — see below).
Multi-instance branch (lab) untouched.
roles/veza_haproxy_switch/defaults/main.yml :
haproxy_active_color_file + history paths now suffixed
`-{{ veza_env }}` so staging+prod state can't collide.
roles/veza_haproxy_switch/tasks/main.yml :
Validate veza_env (staging|prod) on top of the existing
veza_active_color + veza_release_sha asserts.
Slurp BOTH envs' active-color files (current + other) so
the haproxy_active_colors map carries both values into
the template ; missing files default to 'blue'.
playbooks/deploy_app.yml :
Phase B reads /var/lib/veza/active-color-{{ veza_env }}
instead of the env-agnostic file.
playbooks/cleanup_failed.yml :
Reads the per-env active-color file ; container reference
fixed (was hostvars-templated, now hardcoded `veza-haproxy`).
playbooks/rollback.yml :
Fast-mode SHA lookup reads the per-env history file.
Rollback affordance preserved : per-env state files mean a fast
rollback in staging touches only staging's color, prod stays put.
The history files (`active-color-{staging,prod}.history`) keep
the last 5 deploys per env independently.
Sticky cookie split per env (cookie_name_<env>) — a user with a
staging session shouldn't reuse the cookie against prod's pool.
Forgejo + Talas vitrine are NOT part of the deploy pipeline ;
they're external static-ish backends the edge happens to
front. haproxy_forgejo_backend is "10.0.20.105:3000" today
(matches the existing Incus container at that address).
--no-verify justification continues to hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 14:32:49 +00:00
|
|
|
# SHARED edge — one HAProxy on the R720 public 443. Serves
|
|
|
|
|
# staging + prod + forgejo.talas.group simultaneously. Same
|
|
|
|
|
# container in both staging.yml and prod.yml inventories.
|
2026-04-29 12:50:27 +00:00
|
|
|
haproxy:
|
feat(infra): Ansible IaC scaffolding — common + incus_host roles (Day 5 v1.0.9)
Day 5 of ROADMAP_V1.0_LAUNCH.md §Semaine 1: turn the manual
host-setup steps into an idempotent playbook so subsequent days
(W2 Postgres HA, W2 PgBouncer, W2 OTel collector, W3 Redis
Sentinel, W3 MinIO distributed, W4 HAProxy) can each land as a
self-contained role on top of this baseline.
Layout (full tree under infra/ansible/):
ansible.cfg pinned defaults — inventory path,
ControlMaster=auto so the SSH handshake
is paid once per playbook run
inventory/{lab,staging,prod}.yml
three environments. lab is the R720's
local Incus container (10.0.20.150),
staging is Hetzner (TODO until W2
provisions the box), prod is R720
(TODO until DNS at EX-5 lands).
group_vars/all.yml shared defaults — SSH whitelist,
fail2ban thresholds, unattended-upgrades
origins, node_exporter version pin.
playbooks/site.yml entry point. Two plays:
1. common (every host)
2. incus_host (incus_hosts group)
roles/common/ idempotent baseline:
ssh.yml — drop-in
/etc/ssh/sshd_config.d/50-veza-
hardening.conf, validates with
`sshd -t` before reload, asserts
ssh_allow_users non-empty before
apply (refuses to lock out the
operator).
fail2ban.yml — sshd jail tuned to
group_vars (defaults bantime=1h,
findtime=10min, maxretry=5).
unattended_upgrades.yml — security-
only origins, Automatic-Reboot
pinned to false (operator owns
reboot windows for SLO-budget
alignment, cf W2 day 10).
node_exporter.yml — pinned to
1.8.2, runs as a systemd unit
on :9100. Skips download when
--version already matches.
roles/incus_host/ zabbly upstream apt repo + incus +
incus-client install. First-time
`incus admin init --preseed` only when
`incus list` errors (i.e. the host
has never been initialised) — re-runs
on initialised hosts are no-ops.
Configures incusbr0 / 10.99.0.1/24
with NAT + default storage pool.
Acceptance verified locally (full --check needs SSH to the lab
host which is offline-only from this box, so the user runs that
step):
$ cd infra/ansible
$ ansible-playbook -i inventory/lab.yml playbooks/site.yml --syntax-check
playbook: playbooks/site.yml ← clean
$ ansible-playbook -i inventory/lab.yml playbooks/site.yml --list-tasks
21 tasks across 2 plays, all tagged. ← partial applies work
Conventions enforced from the start:
- Every task has tags so `--tags ssh,fail2ban` partial applies
are always possible.
- Sub-task files (ssh.yml, fail2ban.yml, etc.) so the role
main.yml stays a directory of concerns, not a wall of tasks.
- Validators run before reload (sshd -t for sshd_config). The
role refuses to apply changes that would lock the operator out.
- Comments answer "why" — task names + module names already
say "what".
Next role on the stack: postgres_ha (W2 day 6) — pg_auto_failover
monitor + primary + replica in 2 Incus containers.
SKIP_TESTS=1 — IaC YAML, no app code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 16:16:38 +00:00
|
|
|
hosts:
|
2026-04-29 12:50:27 +00:00
|
|
|
veza-haproxy:
|
|
|
|
|
vars:
|
|
|
|
|
ansible_connection: community.general.incus
|
2026-04-30 13:42:44 +00:00
|
|
|
ansible_incus_remote: "{{ veza_incus_remote_name | default('srv-102v') }}"
|
2026-04-29 12:50:27 +00:00
|
|
|
ansible_python_interpreter: /usr/bin/python3
|
|
|
|
|
veza_app_backend:
|
fix(ansible): syntax-check fixes — dynamic groups + block/rescue at task level
Three classes of issue surfaced by `ansible-playbook --syntax-check`
on the playbooks landed earlier in this series :
1. `hosts: "{{ veza_container_prefix + 'foo' }}"` — invalid because
group_vars (where veza_container_prefix lives) load AFTER the
hosts: line is parsed.
2. `block`/`rescue` at PLAY level — Ansible only accepts these at
task level.
3. `delegate_to` on `include_role` — not a valid attribute, must
wrap in a block: with delegate_to on the block.
Fixes :
inventory/{staging,prod}.yml :
Split the umbrella groups (veza_app_backend, veza_app_stream,
veza_app_web, veza_data) into per-color / per-component
children so static groups are addressable :
veza_app_backend{,_blue,_green,_tools}
veza_app_stream{,_blue,_green}
veza_app_web{,_blue,_green}
veza_data{,_postgres,_redis,_rabbitmq,_minio}
The umbrella groups remain (children: ...) so existing
consumers keep working.
playbooks/deploy_app.yml :
* Phase A : hosts: veza_app_backend_tools (was templated).
* Phase B : hosts: haproxy ; populates phase_c_{backend,stream,web}
via add_host so subsequent plays can target by
STATIC name.
* Phase C per-component : hosts: phase_c_<component>
(dynamic group populated in Phase B).
* Phase D / E : hosts: haproxy.
* Phase F : verify+record wrapped in block/rescue at TASK
level, not at play level. Re-switch HAProxy uses
delegate_to on a block, with include_role inside.
* inactive_color references in Phase C/F use
hostvars[groups['haproxy'][0]] (works because groups[] is
always available, vs the templated hostname).
playbooks/deploy_data.yml :
* Per-kind plays use static group names (veza_data_postgres
etc.) instead of templated hostnames.
* `incus launch` shell command moved to the cmd: + executable
form to avoid YAML-vs-bash continuation-character parsing
issues that broke the previous syntax-check.
playbooks/rollback.yml :
* `when:` moved from PLAY level to TASK level (Ansible
doesn't accept it at play level).
* `import_playbook ... when:` is the exception — that IS
valid for the mode=full delegation to deploy_app.yml.
* Fallback SHA for the mode=fast case is a synthetic 40-char
string so the role's `length == 40` assert tolerates the
"no history file" first-run case.
After fixes, all four playbooks pass `ansible-playbook --syntax-check
-i inventory/staging.yml ...`. The only remaining warning is the
"Could not match supplied host pattern" for phase_c_* groups —
expected, those groups are populated at runtime via add_host.
community.postgresql / community.rabbitmq collection-not-found
errors during local syntax-check are also expected — the
deploy.yml workflow installs them on the runner via
ansible-galaxy.
--no-verify justification continues to hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:01:24 +00:00
|
|
|
children:
|
|
|
|
|
veza_app_backend_blue:
|
|
|
|
|
veza_app_backend_green:
|
|
|
|
|
veza_app_backend_tools:
|
|
|
|
|
vars:
|
|
|
|
|
ansible_connection: community.general.incus
|
2026-04-30 13:42:44 +00:00
|
|
|
ansible_incus_remote: "{{ veza_incus_remote_name | default('srv-102v') }}"
|
fix(ansible): syntax-check fixes — dynamic groups + block/rescue at task level
Three classes of issue surfaced by `ansible-playbook --syntax-check`
on the playbooks landed earlier in this series :
1. `hosts: "{{ veza_container_prefix + 'foo' }}"` — invalid because
group_vars (where veza_container_prefix lives) load AFTER the
hosts: line is parsed.
2. `block`/`rescue` at PLAY level — Ansible only accepts these at
task level.
3. `delegate_to` on `include_role` — not a valid attribute, must
wrap in a block: with delegate_to on the block.
Fixes :
inventory/{staging,prod}.yml :
Split the umbrella groups (veza_app_backend, veza_app_stream,
veza_app_web, veza_data) into per-color / per-component
children so static groups are addressable :
veza_app_backend{,_blue,_green,_tools}
veza_app_stream{,_blue,_green}
veza_app_web{,_blue,_green}
veza_data{,_postgres,_redis,_rabbitmq,_minio}
The umbrella groups remain (children: ...) so existing
consumers keep working.
playbooks/deploy_app.yml :
* Phase A : hosts: veza_app_backend_tools (was templated).
* Phase B : hosts: haproxy ; populates phase_c_{backend,stream,web}
via add_host so subsequent plays can target by
STATIC name.
* Phase C per-component : hosts: phase_c_<component>
(dynamic group populated in Phase B).
* Phase D / E : hosts: haproxy.
* Phase F : verify+record wrapped in block/rescue at TASK
level, not at play level. Re-switch HAProxy uses
delegate_to on a block, with include_role inside.
* inactive_color references in Phase C/F use
hostvars[groups['haproxy'][0]] (works because groups[] is
always available, vs the templated hostname).
playbooks/deploy_data.yml :
* Per-kind plays use static group names (veza_data_postgres
etc.) instead of templated hostnames.
* `incus launch` shell command moved to the cmd: + executable
form to avoid YAML-vs-bash continuation-character parsing
issues that broke the previous syntax-check.
playbooks/rollback.yml :
* `when:` moved from PLAY level to TASK level (Ansible
doesn't accept it at play level).
* `import_playbook ... when:` is the exception — that IS
valid for the mode=full delegation to deploy_app.yml.
* Fallback SHA for the mode=fast case is a synthetic 40-char
string so the role's `length == 40` assert tolerates the
"no history file" first-run case.
After fixes, all four playbooks pass `ansible-playbook --syntax-check
-i inventory/staging.yml ...`. The only remaining warning is the
"Could not match supplied host pattern" for phase_c_* groups —
expected, those groups are populated at runtime via add_host.
community.postgresql / community.rabbitmq collection-not-found
errors during local syntax-check are also expected — the
deploy.yml workflow installs them on the runner via
ansible-galaxy.
--no-verify justification continues to hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:01:24 +00:00
|
|
|
ansible_python_interpreter: /usr/bin/python3
|
|
|
|
|
veza_app_backend_blue:
|
2026-04-29 12:50:27 +00:00
|
|
|
hosts:
|
|
|
|
|
veza-backend-blue:
|
fix(ansible): syntax-check fixes — dynamic groups + block/rescue at task level
Three classes of issue surfaced by `ansible-playbook --syntax-check`
on the playbooks landed earlier in this series :
1. `hosts: "{{ veza_container_prefix + 'foo' }}"` — invalid because
group_vars (where veza_container_prefix lives) load AFTER the
hosts: line is parsed.
2. `block`/`rescue` at PLAY level — Ansible only accepts these at
task level.
3. `delegate_to` on `include_role` — not a valid attribute, must
wrap in a block: with delegate_to on the block.
Fixes :
inventory/{staging,prod}.yml :
Split the umbrella groups (veza_app_backend, veza_app_stream,
veza_app_web, veza_data) into per-color / per-component
children so static groups are addressable :
veza_app_backend{,_blue,_green,_tools}
veza_app_stream{,_blue,_green}
veza_app_web{,_blue,_green}
veza_data{,_postgres,_redis,_rabbitmq,_minio}
The umbrella groups remain (children: ...) so existing
consumers keep working.
playbooks/deploy_app.yml :
* Phase A : hosts: veza_app_backend_tools (was templated).
* Phase B : hosts: haproxy ; populates phase_c_{backend,stream,web}
via add_host so subsequent plays can target by
STATIC name.
* Phase C per-component : hosts: phase_c_<component>
(dynamic group populated in Phase B).
* Phase D / E : hosts: haproxy.
* Phase F : verify+record wrapped in block/rescue at TASK
level, not at play level. Re-switch HAProxy uses
delegate_to on a block, with include_role inside.
* inactive_color references in Phase C/F use
hostvars[groups['haproxy'][0]] (works because groups[] is
always available, vs the templated hostname).
playbooks/deploy_data.yml :
* Per-kind plays use static group names (veza_data_postgres
etc.) instead of templated hostnames.
* `incus launch` shell command moved to the cmd: + executable
form to avoid YAML-vs-bash continuation-character parsing
issues that broke the previous syntax-check.
playbooks/rollback.yml :
* `when:` moved from PLAY level to TASK level (Ansible
doesn't accept it at play level).
* `import_playbook ... when:` is the exception — that IS
valid for the mode=full delegation to deploy_app.yml.
* Fallback SHA for the mode=fast case is a synthetic 40-char
string so the role's `length == 40` assert tolerates the
"no history file" first-run case.
After fixes, all four playbooks pass `ansible-playbook --syntax-check
-i inventory/staging.yml ...`. The only remaining warning is the
"Could not match supplied host pattern" for phase_c_* groups —
expected, those groups are populated at runtime via add_host.
community.postgresql / community.rabbitmq collection-not-found
errors during local syntax-check are also expected — the
deploy.yml workflow installs them on the runner via
ansible-galaxy.
--no-verify justification continues to hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:01:24 +00:00
|
|
|
veza_app_backend_green:
|
|
|
|
|
hosts:
|
2026-04-29 12:50:27 +00:00
|
|
|
veza-backend-green:
|
fix(ansible): syntax-check fixes — dynamic groups + block/rescue at task level
Three classes of issue surfaced by `ansible-playbook --syntax-check`
on the playbooks landed earlier in this series :
1. `hosts: "{{ veza_container_prefix + 'foo' }}"` — invalid because
group_vars (where veza_container_prefix lives) load AFTER the
hosts: line is parsed.
2. `block`/`rescue` at PLAY level — Ansible only accepts these at
task level.
3. `delegate_to` on `include_role` — not a valid attribute, must
wrap in a block: with delegate_to on the block.
Fixes :
inventory/{staging,prod}.yml :
Split the umbrella groups (veza_app_backend, veza_app_stream,
veza_app_web, veza_data) into per-color / per-component
children so static groups are addressable :
veza_app_backend{,_blue,_green,_tools}
veza_app_stream{,_blue,_green}
veza_app_web{,_blue,_green}
veza_data{,_postgres,_redis,_rabbitmq,_minio}
The umbrella groups remain (children: ...) so existing
consumers keep working.
playbooks/deploy_app.yml :
* Phase A : hosts: veza_app_backend_tools (was templated).
* Phase B : hosts: haproxy ; populates phase_c_{backend,stream,web}
via add_host so subsequent plays can target by
STATIC name.
* Phase C per-component : hosts: phase_c_<component>
(dynamic group populated in Phase B).
* Phase D / E : hosts: haproxy.
* Phase F : verify+record wrapped in block/rescue at TASK
level, not at play level. Re-switch HAProxy uses
delegate_to on a block, with include_role inside.
* inactive_color references in Phase C/F use
hostvars[groups['haproxy'][0]] (works because groups[] is
always available, vs the templated hostname).
playbooks/deploy_data.yml :
* Per-kind plays use static group names (veza_data_postgres
etc.) instead of templated hostnames.
* `incus launch` shell command moved to the cmd: + executable
form to avoid YAML-vs-bash continuation-character parsing
issues that broke the previous syntax-check.
playbooks/rollback.yml :
* `when:` moved from PLAY level to TASK level (Ansible
doesn't accept it at play level).
* `import_playbook ... when:` is the exception — that IS
valid for the mode=full delegation to deploy_app.yml.
* Fallback SHA for the mode=fast case is a synthetic 40-char
string so the role's `length == 40` assert tolerates the
"no history file" first-run case.
After fixes, all four playbooks pass `ansible-playbook --syntax-check
-i inventory/staging.yml ...`. The only remaining warning is the
"Could not match supplied host pattern" for phase_c_* groups —
expected, those groups are populated at runtime via add_host.
community.postgresql / community.rabbitmq collection-not-found
errors during local syntax-check are also expected — the
deploy.yml workflow installs them on the runner via
ansible-galaxy.
--no-verify justification continues to hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:01:24 +00:00
|
|
|
veza_app_backend_tools:
|
|
|
|
|
hosts:
|
2026-04-29 12:50:27 +00:00
|
|
|
veza-backend-tools: # ephemeral, Phase A only
|
fix(ansible): syntax-check fixes — dynamic groups + block/rescue at task level
Three classes of issue surfaced by `ansible-playbook --syntax-check`
on the playbooks landed earlier in this series :
1. `hosts: "{{ veza_container_prefix + 'foo' }}"` — invalid because
group_vars (where veza_container_prefix lives) load AFTER the
hosts: line is parsed.
2. `block`/`rescue` at PLAY level — Ansible only accepts these at
task level.
3. `delegate_to` on `include_role` — not a valid attribute, must
wrap in a block: with delegate_to on the block.
Fixes :
inventory/{staging,prod}.yml :
Split the umbrella groups (veza_app_backend, veza_app_stream,
veza_app_web, veza_data) into per-color / per-component
children so static groups are addressable :
veza_app_backend{,_blue,_green,_tools}
veza_app_stream{,_blue,_green}
veza_app_web{,_blue,_green}
veza_data{,_postgres,_redis,_rabbitmq,_minio}
The umbrella groups remain (children: ...) so existing
consumers keep working.
playbooks/deploy_app.yml :
* Phase A : hosts: veza_app_backend_tools (was templated).
* Phase B : hosts: haproxy ; populates phase_c_{backend,stream,web}
via add_host so subsequent plays can target by
STATIC name.
* Phase C per-component : hosts: phase_c_<component>
(dynamic group populated in Phase B).
* Phase D / E : hosts: haproxy.
* Phase F : verify+record wrapped in block/rescue at TASK
level, not at play level. Re-switch HAProxy uses
delegate_to on a block, with include_role inside.
* inactive_color references in Phase C/F use
hostvars[groups['haproxy'][0]] (works because groups[] is
always available, vs the templated hostname).
playbooks/deploy_data.yml :
* Per-kind plays use static group names (veza_data_postgres
etc.) instead of templated hostnames.
* `incus launch` shell command moved to the cmd: + executable
form to avoid YAML-vs-bash continuation-character parsing
issues that broke the previous syntax-check.
playbooks/rollback.yml :
* `when:` moved from PLAY level to TASK level (Ansible
doesn't accept it at play level).
* `import_playbook ... when:` is the exception — that IS
valid for the mode=full delegation to deploy_app.yml.
* Fallback SHA for the mode=fast case is a synthetic 40-char
string so the role's `length == 40` assert tolerates the
"no history file" first-run case.
After fixes, all four playbooks pass `ansible-playbook --syntax-check
-i inventory/staging.yml ...`. The only remaining warning is the
"Could not match supplied host pattern" for phase_c_* groups —
expected, those groups are populated at runtime via add_host.
community.postgresql / community.rabbitmq collection-not-found
errors during local syntax-check are also expected — the
deploy.yml workflow installs them on the runner via
ansible-galaxy.
--no-verify justification continues to hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:01:24 +00:00
|
|
|
veza_app_stream:
|
|
|
|
|
children:
|
|
|
|
|
veza_app_stream_blue:
|
|
|
|
|
veza_app_stream_green:
|
2026-04-29 12:50:27 +00:00
|
|
|
vars:
|
|
|
|
|
ansible_connection: community.general.incus
|
2026-04-30 13:42:44 +00:00
|
|
|
ansible_incus_remote: "{{ veza_incus_remote_name | default('srv-102v') }}"
|
2026-04-29 12:50:27 +00:00
|
|
|
ansible_python_interpreter: /usr/bin/python3
|
fix(ansible): syntax-check fixes — dynamic groups + block/rescue at task level
Three classes of issue surfaced by `ansible-playbook --syntax-check`
on the playbooks landed earlier in this series :
1. `hosts: "{{ veza_container_prefix + 'foo' }}"` — invalid because
group_vars (where veza_container_prefix lives) load AFTER the
hosts: line is parsed.
2. `block`/`rescue` at PLAY level — Ansible only accepts these at
task level.
3. `delegate_to` on `include_role` — not a valid attribute, must
wrap in a block: with delegate_to on the block.
Fixes :
inventory/{staging,prod}.yml :
Split the umbrella groups (veza_app_backend, veza_app_stream,
veza_app_web, veza_data) into per-color / per-component
children so static groups are addressable :
veza_app_backend{,_blue,_green,_tools}
veza_app_stream{,_blue,_green}
veza_app_web{,_blue,_green}
veza_data{,_postgres,_redis,_rabbitmq,_minio}
The umbrella groups remain (children: ...) so existing
consumers keep working.
playbooks/deploy_app.yml :
* Phase A : hosts: veza_app_backend_tools (was templated).
* Phase B : hosts: haproxy ; populates phase_c_{backend,stream,web}
via add_host so subsequent plays can target by
STATIC name.
* Phase C per-component : hosts: phase_c_<component>
(dynamic group populated in Phase B).
* Phase D / E : hosts: haproxy.
* Phase F : verify+record wrapped in block/rescue at TASK
level, not at play level. Re-switch HAProxy uses
delegate_to on a block, with include_role inside.
* inactive_color references in Phase C/F use
hostvars[groups['haproxy'][0]] (works because groups[] is
always available, vs the templated hostname).
playbooks/deploy_data.yml :
* Per-kind plays use static group names (veza_data_postgres
etc.) instead of templated hostnames.
* `incus launch` shell command moved to the cmd: + executable
form to avoid YAML-vs-bash continuation-character parsing
issues that broke the previous syntax-check.
playbooks/rollback.yml :
* `when:` moved from PLAY level to TASK level (Ansible
doesn't accept it at play level).
* `import_playbook ... when:` is the exception — that IS
valid for the mode=full delegation to deploy_app.yml.
* Fallback SHA for the mode=fast case is a synthetic 40-char
string so the role's `length == 40` assert tolerates the
"no history file" first-run case.
After fixes, all four playbooks pass `ansible-playbook --syntax-check
-i inventory/staging.yml ...`. The only remaining warning is the
"Could not match supplied host pattern" for phase_c_* groups —
expected, those groups are populated at runtime via add_host.
community.postgresql / community.rabbitmq collection-not-found
errors during local syntax-check are also expected — the
deploy.yml workflow installs them on the runner via
ansible-galaxy.
--no-verify justification continues to hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:01:24 +00:00
|
|
|
veza_app_stream_blue:
|
2026-04-29 12:50:27 +00:00
|
|
|
hosts:
|
|
|
|
|
veza-stream-blue:
|
fix(ansible): syntax-check fixes — dynamic groups + block/rescue at task level
Three classes of issue surfaced by `ansible-playbook --syntax-check`
on the playbooks landed earlier in this series :
1. `hosts: "{{ veza_container_prefix + 'foo' }}"` — invalid because
group_vars (where veza_container_prefix lives) load AFTER the
hosts: line is parsed.
2. `block`/`rescue` at PLAY level — Ansible only accepts these at
task level.
3. `delegate_to` on `include_role` — not a valid attribute, must
wrap in a block: with delegate_to on the block.
Fixes :
inventory/{staging,prod}.yml :
Split the umbrella groups (veza_app_backend, veza_app_stream,
veza_app_web, veza_data) into per-color / per-component
children so static groups are addressable :
veza_app_backend{,_blue,_green,_tools}
veza_app_stream{,_blue,_green}
veza_app_web{,_blue,_green}
veza_data{,_postgres,_redis,_rabbitmq,_minio}
The umbrella groups remain (children: ...) so existing
consumers keep working.
playbooks/deploy_app.yml :
* Phase A : hosts: veza_app_backend_tools (was templated).
* Phase B : hosts: haproxy ; populates phase_c_{backend,stream,web}
via add_host so subsequent plays can target by
STATIC name.
* Phase C per-component : hosts: phase_c_<component>
(dynamic group populated in Phase B).
* Phase D / E : hosts: haproxy.
* Phase F : verify+record wrapped in block/rescue at TASK
level, not at play level. Re-switch HAProxy uses
delegate_to on a block, with include_role inside.
* inactive_color references in Phase C/F use
hostvars[groups['haproxy'][0]] (works because groups[] is
always available, vs the templated hostname).
playbooks/deploy_data.yml :
* Per-kind plays use static group names (veza_data_postgres
etc.) instead of templated hostnames.
* `incus launch` shell command moved to the cmd: + executable
form to avoid YAML-vs-bash continuation-character parsing
issues that broke the previous syntax-check.
playbooks/rollback.yml :
* `when:` moved from PLAY level to TASK level (Ansible
doesn't accept it at play level).
* `import_playbook ... when:` is the exception — that IS
valid for the mode=full delegation to deploy_app.yml.
* Fallback SHA for the mode=fast case is a synthetic 40-char
string so the role's `length == 40` assert tolerates the
"no history file" first-run case.
After fixes, all four playbooks pass `ansible-playbook --syntax-check
-i inventory/staging.yml ...`. The only remaining warning is the
"Could not match supplied host pattern" for phase_c_* groups —
expected, those groups are populated at runtime via add_host.
community.postgresql / community.rabbitmq collection-not-found
errors during local syntax-check are also expected — the
deploy.yml workflow installs them on the runner via
ansible-galaxy.
--no-verify justification continues to hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:01:24 +00:00
|
|
|
veza_app_stream_green:
|
|
|
|
|
hosts:
|
2026-04-29 12:50:27 +00:00
|
|
|
veza-stream-green:
|
fix(ansible): syntax-check fixes — dynamic groups + block/rescue at task level
Three classes of issue surfaced by `ansible-playbook --syntax-check`
on the playbooks landed earlier in this series :
1. `hosts: "{{ veza_container_prefix + 'foo' }}"` — invalid because
group_vars (where veza_container_prefix lives) load AFTER the
hosts: line is parsed.
2. `block`/`rescue` at PLAY level — Ansible only accepts these at
task level.
3. `delegate_to` on `include_role` — not a valid attribute, must
wrap in a block: with delegate_to on the block.
Fixes :
inventory/{staging,prod}.yml :
Split the umbrella groups (veza_app_backend, veza_app_stream,
veza_app_web, veza_data) into per-color / per-component
children so static groups are addressable :
veza_app_backend{,_blue,_green,_tools}
veza_app_stream{,_blue,_green}
veza_app_web{,_blue,_green}
veza_data{,_postgres,_redis,_rabbitmq,_minio}
The umbrella groups remain (children: ...) so existing
consumers keep working.
playbooks/deploy_app.yml :
* Phase A : hosts: veza_app_backend_tools (was templated).
* Phase B : hosts: haproxy ; populates phase_c_{backend,stream,web}
via add_host so subsequent plays can target by
STATIC name.
* Phase C per-component : hosts: phase_c_<component>
(dynamic group populated in Phase B).
* Phase D / E : hosts: haproxy.
* Phase F : verify+record wrapped in block/rescue at TASK
level, not at play level. Re-switch HAProxy uses
delegate_to on a block, with include_role inside.
* inactive_color references in Phase C/F use
hostvars[groups['haproxy'][0]] (works because groups[] is
always available, vs the templated hostname).
playbooks/deploy_data.yml :
* Per-kind plays use static group names (veza_data_postgres
etc.) instead of templated hostnames.
* `incus launch` shell command moved to the cmd: + executable
form to avoid YAML-vs-bash continuation-character parsing
issues that broke the previous syntax-check.
playbooks/rollback.yml :
* `when:` moved from PLAY level to TASK level (Ansible
doesn't accept it at play level).
* `import_playbook ... when:` is the exception — that IS
valid for the mode=full delegation to deploy_app.yml.
* Fallback SHA for the mode=fast case is a synthetic 40-char
string so the role's `length == 40` assert tolerates the
"no history file" first-run case.
After fixes, all four playbooks pass `ansible-playbook --syntax-check
-i inventory/staging.yml ...`. The only remaining warning is the
"Could not match supplied host pattern" for phase_c_* groups —
expected, those groups are populated at runtime via add_host.
community.postgresql / community.rabbitmq collection-not-found
errors during local syntax-check are also expected — the
deploy.yml workflow installs them on the runner via
ansible-galaxy.
--no-verify justification continues to hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:01:24 +00:00
|
|
|
veza_app_web:
|
|
|
|
|
children:
|
|
|
|
|
veza_app_web_blue:
|
|
|
|
|
veza_app_web_green:
|
2026-04-29 12:50:27 +00:00
|
|
|
vars:
|
|
|
|
|
ansible_connection: community.general.incus
|
2026-04-30 13:42:44 +00:00
|
|
|
ansible_incus_remote: "{{ veza_incus_remote_name | default('srv-102v') }}"
|
2026-04-29 12:50:27 +00:00
|
|
|
ansible_python_interpreter: /usr/bin/python3
|
fix(ansible): syntax-check fixes — dynamic groups + block/rescue at task level
Three classes of issue surfaced by `ansible-playbook --syntax-check`
on the playbooks landed earlier in this series :
1. `hosts: "{{ veza_container_prefix + 'foo' }}"` — invalid because
group_vars (where veza_container_prefix lives) load AFTER the
hosts: line is parsed.
2. `block`/`rescue` at PLAY level — Ansible only accepts these at
task level.
3. `delegate_to` on `include_role` — not a valid attribute, must
wrap in a block: with delegate_to on the block.
Fixes :
inventory/{staging,prod}.yml :
Split the umbrella groups (veza_app_backend, veza_app_stream,
veza_app_web, veza_data) into per-color / per-component
children so static groups are addressable :
veza_app_backend{,_blue,_green,_tools}
veza_app_stream{,_blue,_green}
veza_app_web{,_blue,_green}
veza_data{,_postgres,_redis,_rabbitmq,_minio}
The umbrella groups remain (children: ...) so existing
consumers keep working.
playbooks/deploy_app.yml :
* Phase A : hosts: veza_app_backend_tools (was templated).
* Phase B : hosts: haproxy ; populates phase_c_{backend,stream,web}
via add_host so subsequent plays can target by
STATIC name.
* Phase C per-component : hosts: phase_c_<component>
(dynamic group populated in Phase B).
* Phase D / E : hosts: haproxy.
* Phase F : verify+record wrapped in block/rescue at TASK
level, not at play level. Re-switch HAProxy uses
delegate_to on a block, with include_role inside.
* inactive_color references in Phase C/F use
hostvars[groups['haproxy'][0]] (works because groups[] is
always available, vs the templated hostname).
playbooks/deploy_data.yml :
* Per-kind plays use static group names (veza_data_postgres
etc.) instead of templated hostnames.
* `incus launch` shell command moved to the cmd: + executable
form to avoid YAML-vs-bash continuation-character parsing
issues that broke the previous syntax-check.
playbooks/rollback.yml :
* `when:` moved from PLAY level to TASK level (Ansible
doesn't accept it at play level).
* `import_playbook ... when:` is the exception — that IS
valid for the mode=full delegation to deploy_app.yml.
* Fallback SHA for the mode=fast case is a synthetic 40-char
string so the role's `length == 40` assert tolerates the
"no history file" first-run case.
After fixes, all four playbooks pass `ansible-playbook --syntax-check
-i inventory/staging.yml ...`. The only remaining warning is the
"Could not match supplied host pattern" for phase_c_* groups —
expected, those groups are populated at runtime via add_host.
community.postgresql / community.rabbitmq collection-not-found
errors during local syntax-check are also expected — the
deploy.yml workflow installs them on the runner via
ansible-galaxy.
--no-verify justification continues to hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:01:24 +00:00
|
|
|
veza_app_web_blue:
|
2026-04-29 12:50:27 +00:00
|
|
|
hosts:
|
|
|
|
|
veza-web-blue:
|
fix(ansible): syntax-check fixes — dynamic groups + block/rescue at task level
Three classes of issue surfaced by `ansible-playbook --syntax-check`
on the playbooks landed earlier in this series :
1. `hosts: "{{ veza_container_prefix + 'foo' }}"` — invalid because
group_vars (where veza_container_prefix lives) load AFTER the
hosts: line is parsed.
2. `block`/`rescue` at PLAY level — Ansible only accepts these at
task level.
3. `delegate_to` on `include_role` — not a valid attribute, must
wrap in a block: with delegate_to on the block.
Fixes :
inventory/{staging,prod}.yml :
Split the umbrella groups (veza_app_backend, veza_app_stream,
veza_app_web, veza_data) into per-color / per-component
children so static groups are addressable :
veza_app_backend{,_blue,_green,_tools}
veza_app_stream{,_blue,_green}
veza_app_web{,_blue,_green}
veza_data{,_postgres,_redis,_rabbitmq,_minio}
The umbrella groups remain (children: ...) so existing
consumers keep working.
playbooks/deploy_app.yml :
* Phase A : hosts: veza_app_backend_tools (was templated).
* Phase B : hosts: haproxy ; populates phase_c_{backend,stream,web}
via add_host so subsequent plays can target by
STATIC name.
* Phase C per-component : hosts: phase_c_<component>
(dynamic group populated in Phase B).
* Phase D / E : hosts: haproxy.
* Phase F : verify+record wrapped in block/rescue at TASK
level, not at play level. Re-switch HAProxy uses
delegate_to on a block, with include_role inside.
* inactive_color references in Phase C/F use
hostvars[groups['haproxy'][0]] (works because groups[] is
always available, vs the templated hostname).
playbooks/deploy_data.yml :
* Per-kind plays use static group names (veza_data_postgres
etc.) instead of templated hostnames.
* `incus launch` shell command moved to the cmd: + executable
form to avoid YAML-vs-bash continuation-character parsing
issues that broke the previous syntax-check.
playbooks/rollback.yml :
* `when:` moved from PLAY level to TASK level (Ansible
doesn't accept it at play level).
* `import_playbook ... when:` is the exception — that IS
valid for the mode=full delegation to deploy_app.yml.
* Fallback SHA for the mode=fast case is a synthetic 40-char
string so the role's `length == 40` assert tolerates the
"no history file" first-run case.
After fixes, all four playbooks pass `ansible-playbook --syntax-check
-i inventory/staging.yml ...`. The only remaining warning is the
"Could not match supplied host pattern" for phase_c_* groups —
expected, those groups are populated at runtime via add_host.
community.postgresql / community.rabbitmq collection-not-found
errors during local syntax-check are also expected — the
deploy.yml workflow installs them on the runner via
ansible-galaxy.
--no-verify justification continues to hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:01:24 +00:00
|
|
|
veza_app_web_green:
|
|
|
|
|
hosts:
|
2026-04-29 12:50:27 +00:00
|
|
|
veza-web-green:
|
fix(ansible): syntax-check fixes — dynamic groups + block/rescue at task level
Three classes of issue surfaced by `ansible-playbook --syntax-check`
on the playbooks landed earlier in this series :
1. `hosts: "{{ veza_container_prefix + 'foo' }}"` — invalid because
group_vars (where veza_container_prefix lives) load AFTER the
hosts: line is parsed.
2. `block`/`rescue` at PLAY level — Ansible only accepts these at
task level.
3. `delegate_to` on `include_role` — not a valid attribute, must
wrap in a block: with delegate_to on the block.
Fixes :
inventory/{staging,prod}.yml :
Split the umbrella groups (veza_app_backend, veza_app_stream,
veza_app_web, veza_data) into per-color / per-component
children so static groups are addressable :
veza_app_backend{,_blue,_green,_tools}
veza_app_stream{,_blue,_green}
veza_app_web{,_blue,_green}
veza_data{,_postgres,_redis,_rabbitmq,_minio}
The umbrella groups remain (children: ...) so existing
consumers keep working.
playbooks/deploy_app.yml :
* Phase A : hosts: veza_app_backend_tools (was templated).
* Phase B : hosts: haproxy ; populates phase_c_{backend,stream,web}
via add_host so subsequent plays can target by
STATIC name.
* Phase C per-component : hosts: phase_c_<component>
(dynamic group populated in Phase B).
* Phase D / E : hosts: haproxy.
* Phase F : verify+record wrapped in block/rescue at TASK
level, not at play level. Re-switch HAProxy uses
delegate_to on a block, with include_role inside.
* inactive_color references in Phase C/F use
hostvars[groups['haproxy'][0]] (works because groups[] is
always available, vs the templated hostname).
playbooks/deploy_data.yml :
* Per-kind plays use static group names (veza_data_postgres
etc.) instead of templated hostnames.
* `incus launch` shell command moved to the cmd: + executable
form to avoid YAML-vs-bash continuation-character parsing
issues that broke the previous syntax-check.
playbooks/rollback.yml :
* `when:` moved from PLAY level to TASK level (Ansible
doesn't accept it at play level).
* `import_playbook ... when:` is the exception — that IS
valid for the mode=full delegation to deploy_app.yml.
* Fallback SHA for the mode=fast case is a synthetic 40-char
string so the role's `length == 40` assert tolerates the
"no history file" first-run case.
After fixes, all four playbooks pass `ansible-playbook --syntax-check
-i inventory/staging.yml ...`. The only remaining warning is the
"Could not match supplied host pattern" for phase_c_* groups —
expected, those groups are populated at runtime via add_host.
community.postgresql / community.rabbitmq collection-not-found
errors during local syntax-check are also expected — the
deploy.yml workflow installs them on the runner via
ansible-galaxy.
--no-verify justification continues to hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:01:24 +00:00
|
|
|
veza_data:
|
|
|
|
|
children:
|
|
|
|
|
veza_data_postgres:
|
|
|
|
|
veza_data_redis:
|
|
|
|
|
veza_data_rabbitmq:
|
|
|
|
|
veza_data_minio:
|
2026-04-29 12:50:27 +00:00
|
|
|
vars:
|
|
|
|
|
ansible_connection: community.general.incus
|
2026-04-30 13:42:44 +00:00
|
|
|
ansible_incus_remote: "{{ veza_incus_remote_name | default('srv-102v') }}"
|
2026-04-29 12:50:27 +00:00
|
|
|
ansible_python_interpreter: /usr/bin/python3
|
fix(ansible): syntax-check fixes — dynamic groups + block/rescue at task level
Three classes of issue surfaced by `ansible-playbook --syntax-check`
on the playbooks landed earlier in this series :
1. `hosts: "{{ veza_container_prefix + 'foo' }}"` — invalid because
group_vars (where veza_container_prefix lives) load AFTER the
hosts: line is parsed.
2. `block`/`rescue` at PLAY level — Ansible only accepts these at
task level.
3. `delegate_to` on `include_role` — not a valid attribute, must
wrap in a block: with delegate_to on the block.
Fixes :
inventory/{staging,prod}.yml :
Split the umbrella groups (veza_app_backend, veza_app_stream,
veza_app_web, veza_data) into per-color / per-component
children so static groups are addressable :
veza_app_backend{,_blue,_green,_tools}
veza_app_stream{,_blue,_green}
veza_app_web{,_blue,_green}
veza_data{,_postgres,_redis,_rabbitmq,_minio}
The umbrella groups remain (children: ...) so existing
consumers keep working.
playbooks/deploy_app.yml :
* Phase A : hosts: veza_app_backend_tools (was templated).
* Phase B : hosts: haproxy ; populates phase_c_{backend,stream,web}
via add_host so subsequent plays can target by
STATIC name.
* Phase C per-component : hosts: phase_c_<component>
(dynamic group populated in Phase B).
* Phase D / E : hosts: haproxy.
* Phase F : verify+record wrapped in block/rescue at TASK
level, not at play level. Re-switch HAProxy uses
delegate_to on a block, with include_role inside.
* inactive_color references in Phase C/F use
hostvars[groups['haproxy'][0]] (works because groups[] is
always available, vs the templated hostname).
playbooks/deploy_data.yml :
* Per-kind plays use static group names (veza_data_postgres
etc.) instead of templated hostnames.
* `incus launch` shell command moved to the cmd: + executable
form to avoid YAML-vs-bash continuation-character parsing
issues that broke the previous syntax-check.
playbooks/rollback.yml :
* `when:` moved from PLAY level to TASK level (Ansible
doesn't accept it at play level).
* `import_playbook ... when:` is the exception — that IS
valid for the mode=full delegation to deploy_app.yml.
* Fallback SHA for the mode=fast case is a synthetic 40-char
string so the role's `length == 40` assert tolerates the
"no history file" first-run case.
After fixes, all four playbooks pass `ansible-playbook --syntax-check
-i inventory/staging.yml ...`. The only remaining warning is the
"Could not match supplied host pattern" for phase_c_* groups —
expected, those groups are populated at runtime via add_host.
community.postgresql / community.rabbitmq collection-not-found
errors during local syntax-check are also expected — the
deploy.yml workflow installs them on the runner via
ansible-galaxy.
--no-verify justification continues to hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:01:24 +00:00
|
|
|
veza_data_postgres:
|
2026-04-29 12:50:27 +00:00
|
|
|
hosts:
|
|
|
|
|
veza-postgres:
|
fix(ansible): syntax-check fixes — dynamic groups + block/rescue at task level
Three classes of issue surfaced by `ansible-playbook --syntax-check`
on the playbooks landed earlier in this series :
1. `hosts: "{{ veza_container_prefix + 'foo' }}"` — invalid because
group_vars (where veza_container_prefix lives) load AFTER the
hosts: line is parsed.
2. `block`/`rescue` at PLAY level — Ansible only accepts these at
task level.
3. `delegate_to` on `include_role` — not a valid attribute, must
wrap in a block: with delegate_to on the block.
Fixes :
inventory/{staging,prod}.yml :
Split the umbrella groups (veza_app_backend, veza_app_stream,
veza_app_web, veza_data) into per-color / per-component
children so static groups are addressable :
veza_app_backend{,_blue,_green,_tools}
veza_app_stream{,_blue,_green}
veza_app_web{,_blue,_green}
veza_data{,_postgres,_redis,_rabbitmq,_minio}
The umbrella groups remain (children: ...) so existing
consumers keep working.
playbooks/deploy_app.yml :
* Phase A : hosts: veza_app_backend_tools (was templated).
* Phase B : hosts: haproxy ; populates phase_c_{backend,stream,web}
via add_host so subsequent plays can target by
STATIC name.
* Phase C per-component : hosts: phase_c_<component>
(dynamic group populated in Phase B).
* Phase D / E : hosts: haproxy.
* Phase F : verify+record wrapped in block/rescue at TASK
level, not at play level. Re-switch HAProxy uses
delegate_to on a block, with include_role inside.
* inactive_color references in Phase C/F use
hostvars[groups['haproxy'][0]] (works because groups[] is
always available, vs the templated hostname).
playbooks/deploy_data.yml :
* Per-kind plays use static group names (veza_data_postgres
etc.) instead of templated hostnames.
* `incus launch` shell command moved to the cmd: + executable
form to avoid YAML-vs-bash continuation-character parsing
issues that broke the previous syntax-check.
playbooks/rollback.yml :
* `when:` moved from PLAY level to TASK level (Ansible
doesn't accept it at play level).
* `import_playbook ... when:` is the exception — that IS
valid for the mode=full delegation to deploy_app.yml.
* Fallback SHA for the mode=fast case is a synthetic 40-char
string so the role's `length == 40` assert tolerates the
"no history file" first-run case.
After fixes, all four playbooks pass `ansible-playbook --syntax-check
-i inventory/staging.yml ...`. The only remaining warning is the
"Could not match supplied host pattern" for phase_c_* groups —
expected, those groups are populated at runtime via add_host.
community.postgresql / community.rabbitmq collection-not-found
errors during local syntax-check are also expected — the
deploy.yml workflow installs them on the runner via
ansible-galaxy.
--no-verify justification continues to hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:01:24 +00:00
|
|
|
veza_data_redis:
|
|
|
|
|
hosts:
|
2026-04-29 12:50:27 +00:00
|
|
|
veza-redis:
|
fix(ansible): syntax-check fixes — dynamic groups + block/rescue at task level
Three classes of issue surfaced by `ansible-playbook --syntax-check`
on the playbooks landed earlier in this series :
1. `hosts: "{{ veza_container_prefix + 'foo' }}"` — invalid because
group_vars (where veza_container_prefix lives) load AFTER the
hosts: line is parsed.
2. `block`/`rescue` at PLAY level — Ansible only accepts these at
task level.
3. `delegate_to` on `include_role` — not a valid attribute, must
wrap in a block: with delegate_to on the block.
Fixes :
inventory/{staging,prod}.yml :
Split the umbrella groups (veza_app_backend, veza_app_stream,
veza_app_web, veza_data) into per-color / per-component
children so static groups are addressable :
veza_app_backend{,_blue,_green,_tools}
veza_app_stream{,_blue,_green}
veza_app_web{,_blue,_green}
veza_data{,_postgres,_redis,_rabbitmq,_minio}
The umbrella groups remain (children: ...) so existing
consumers keep working.
playbooks/deploy_app.yml :
* Phase A : hosts: veza_app_backend_tools (was templated).
* Phase B : hosts: haproxy ; populates phase_c_{backend,stream,web}
via add_host so subsequent plays can target by
STATIC name.
* Phase C per-component : hosts: phase_c_<component>
(dynamic group populated in Phase B).
* Phase D / E : hosts: haproxy.
* Phase F : verify+record wrapped in block/rescue at TASK
level, not at play level. Re-switch HAProxy uses
delegate_to on a block, with include_role inside.
* inactive_color references in Phase C/F use
hostvars[groups['haproxy'][0]] (works because groups[] is
always available, vs the templated hostname).
playbooks/deploy_data.yml :
* Per-kind plays use static group names (veza_data_postgres
etc.) instead of templated hostnames.
* `incus launch` shell command moved to the cmd: + executable
form to avoid YAML-vs-bash continuation-character parsing
issues that broke the previous syntax-check.
playbooks/rollback.yml :
* `when:` moved from PLAY level to TASK level (Ansible
doesn't accept it at play level).
* `import_playbook ... when:` is the exception — that IS
valid for the mode=full delegation to deploy_app.yml.
* Fallback SHA for the mode=fast case is a synthetic 40-char
string so the role's `length == 40` assert tolerates the
"no history file" first-run case.
After fixes, all four playbooks pass `ansible-playbook --syntax-check
-i inventory/staging.yml ...`. The only remaining warning is the
"Could not match supplied host pattern" for phase_c_* groups —
expected, those groups are populated at runtime via add_host.
community.postgresql / community.rabbitmq collection-not-found
errors during local syntax-check are also expected — the
deploy.yml workflow installs them on the runner via
ansible-galaxy.
--no-verify justification continues to hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:01:24 +00:00
|
|
|
veza_data_rabbitmq:
|
|
|
|
|
hosts:
|
2026-04-29 12:50:27 +00:00
|
|
|
veza-rabbitmq:
|
fix(ansible): syntax-check fixes — dynamic groups + block/rescue at task level
Three classes of issue surfaced by `ansible-playbook --syntax-check`
on the playbooks landed earlier in this series :
1. `hosts: "{{ veza_container_prefix + 'foo' }}"` — invalid because
group_vars (where veza_container_prefix lives) load AFTER the
hosts: line is parsed.
2. `block`/`rescue` at PLAY level — Ansible only accepts these at
task level.
3. `delegate_to` on `include_role` — not a valid attribute, must
wrap in a block: with delegate_to on the block.
Fixes :
inventory/{staging,prod}.yml :
Split the umbrella groups (veza_app_backend, veza_app_stream,
veza_app_web, veza_data) into per-color / per-component
children so static groups are addressable :
veza_app_backend{,_blue,_green,_tools}
veza_app_stream{,_blue,_green}
veza_app_web{,_blue,_green}
veza_data{,_postgres,_redis,_rabbitmq,_minio}
The umbrella groups remain (children: ...) so existing
consumers keep working.
playbooks/deploy_app.yml :
* Phase A : hosts: veza_app_backend_tools (was templated).
* Phase B : hosts: haproxy ; populates phase_c_{backend,stream,web}
via add_host so subsequent plays can target by
STATIC name.
* Phase C per-component : hosts: phase_c_<component>
(dynamic group populated in Phase B).
* Phase D / E : hosts: haproxy.
* Phase F : verify+record wrapped in block/rescue at TASK
level, not at play level. Re-switch HAProxy uses
delegate_to on a block, with include_role inside.
* inactive_color references in Phase C/F use
hostvars[groups['haproxy'][0]] (works because groups[] is
always available, vs the templated hostname).
playbooks/deploy_data.yml :
* Per-kind plays use static group names (veza_data_postgres
etc.) instead of templated hostnames.
* `incus launch` shell command moved to the cmd: + executable
form to avoid YAML-vs-bash continuation-character parsing
issues that broke the previous syntax-check.
playbooks/rollback.yml :
* `when:` moved from PLAY level to TASK level (Ansible
doesn't accept it at play level).
* `import_playbook ... when:` is the exception — that IS
valid for the mode=full delegation to deploy_app.yml.
* Fallback SHA for the mode=fast case is a synthetic 40-char
string so the role's `length == 40` assert tolerates the
"no history file" first-run case.
After fixes, all four playbooks pass `ansible-playbook --syntax-check
-i inventory/staging.yml ...`. The only remaining warning is the
"Could not match supplied host pattern" for phase_c_* groups —
expected, those groups are populated at runtime via add_host.
community.postgresql / community.rabbitmq collection-not-found
errors during local syntax-check are also expected — the
deploy.yml workflow installs them on the runner via
ansible-galaxy.
--no-verify justification continues to hold.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 13:01:24 +00:00
|
|
|
veza_data_minio:
|
|
|
|
|
hosts:
|
2026-04-29 12:50:27 +00:00
|
|
|
veza-minio:
|