Files
factory/vibe/guidebooks/factory-provisioning/ansible/inventory.md
Gabriel Radureau dbe32161dc docs(vibe): add factory-provisioning guidebook (Ansible + OpenTofu)
Deep, code-grounded tree-docs guidebook under vibe/guidebooks/factory-provisioning/,
explored from the actual playbooks/roles and tofu code:

- Hub: the two provisioning engines (operator-run Ansible vs CI-applied OpenTofu),
  a green-field bring-up flow, master index, maintenance rule.
- ansible/ sub-tree: ordered pages 01-system .. 06-recover, an inventory & variables
  concept page, and a Tier-1/Tier-2 roles reference (hashicorp_vault, step_ca,
  crowdsec, pihole, deploy_docker_compose + the gitea_* family and helpers).
- opentofu/ sub-tree: factory-iac (Cloudflare/OVH/GCP/Gitea/Vault edge +
  cloudflare_token module), postgres-iac (per-app DB/role/pgbouncer lookup),
  ci-apply-flow (Gitea OIDC-JWT -> Vault -> auto-approve apply).

Cross-linked bidirectionally with the lab-ecosystem guidebook and the safe-env
ADR/PRD (the sandbox rehearses exactly these engines). 14 mermaid diagrams
MCP-validated; zero dead links. Authored by the Lab Cartographer cohort.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 21:11:51 +02:00

10 KiB

vibe > Guidebooks > Factory provisioning > Ansible > Inventory & variables

Inventory & variables

Note

Status: active · Last Updated: 2026-06-23 Upstream: Ansible sub-hub · Lab ecosystem · 01 factory Downstream: Roles reference Related: Secrets & Vault · Storage & recovery · Naming conventions · ADR-0001 safe prod-like environment · PRD · isolation boundary

The inventory is the single source of truth for which machines exist and which service each machine runs. It is a directory inventory — inventory/hosts.yml plus a layered group_vars/ tree — passed to every playbook with -i ansible/arcodange/factory/inventory.

Important

This inventory describes live production. The three IPs 192.168.1.201-203 are the real Pis that run the public CMS, the Dolibarr ERP, and business email. A playbook pointed at this inventory mutates prod. The safe-environment work treats this file as the prod blast-radius and requires a separate sandbox inventory + a prod-IP guard before any sandbox apply — see the ADR-0001 and the first row of the PRD isolation boundary.


Hosts

Defined in inventory/hosts.yml. Three physical Pis are each reachable two ways — over the LAN (the canonical path) and through an internet port-forward managed at the firewall — plus the control node as localhost.

Host ansible_host preferred_ip Port Reach
pi1 pi1.home 192.168.1.201 22 LAN
pi2 pi2.home 192.168.1.202 22 LAN
pi3 pi3.home 192.168.1.203 22 LAN
internetPi1 rg-evry.changeip.co 51022 WAN port-forward → pi1
internetPi2 rg-evry.changeip.co 52022 WAN port-forward → pi2
internetPi3 rg-evry.changeip.co 53022 WAN port-forward → pi3
localhost (local connection) control node

Note

The internetPiN entries share one DNS name (rg-evry.changeip.co) and differ only by SSH port (5N022). The hosts file documents the choice of changeip.co over arcodange.duckdns.org: changeip is managed directly with the firewall rather than depending on a DuckDNS registry update, so the forward is stable. preferred_ip is a custom hostvar (not a connection variable) — roles read it to build DNS records, the Gitea SSH domain, and the Pi-hole local-DNS table.


Groups

Groups map machines to roles. The membership is small and deliberate; read the table as "this service runs on these hosts".

Group Members Defined as What it is for
raspberries pi1, pi2, pi3 + internetPi1-3 explicit hosts Every Pi, LAN and WAN handles. Carries the shared ansible_user: pi.
local localhost, pi1, pi2, pi3 explicit hosts The control-node-facing group; localhost runs kubectl/tofu/docker tasks that talk to the cluster.
postgres pi2 explicit host The single PostgreSQL node. pi2 is the database host.
gitea pi2 (via children: postgres) child of postgres Gitea co-locates with its database, so the group simply inherits postgres. groups.gitea[0] resolves to pi2 everywhere.
pihole pi1, pi3 explicit hosts The HA DNS pair (Pi-hole + Gravity Sync).
step_ca pi1, pi2, pi3 explicit hosts Every Pi runs a step-ca node (primary pi1, standbys pi2/pi3).
all everything (children: raspberries) implicit + child Ansible's universal group; group_vars/all/ applies to all hosts.

Tip

Because gitea is a child of postgres and postgres has exactly one host, every reference to groups.gitea[0] (the Gitea container, the API base URL http://{{ groups.gitea[0] }}:3000, the SSH domain) points at pi2. Move Postgres and Gitea follows automatically.


Connection variables

Variable Where set Value / effect
ansible_user raspberries.vars pi — the SSH login on every Pi.
ansible_ssh_extra_args per-host (pi1/pi2/pi3) -o StrictHostKeyChecking=no — Pis get reimaged, so host-key churn is expected; the check is disabled rather than forcing known_hosts edits.
ansible_port internetPiN 51022 / 52022 / 53022 — the firewall's per-Pi SSH forwards.
ansible_connection localhost local — run on the control node, no SSH.
ansible_python_interpreter localhost "{{ ansible_playbook_python }}" — uses the uv-managed venv's Python, no hardcoded path.

The control-node tooling chain (scp_if_ssh = True) is set in ansible.cfg; the collections_path lives there too.


group_vars/ layering

Variables are split by group so each service owns its own file. The path group_vars/<group>/<file>.yml is auto-loaded for every host in <group>.

File Scope Declares
all/common.yml all hosts user_home — the control user's $HOME, looked up from the environment.
all/ssh.yml all hosts SSH-public-key discovery: first_found over id_ed25519_arcodange.pubid_ed25519.pubid_rsa.pub, then splits the file into ssh_public_key, ssh_key_title, ssh_key_algorithm. Roles push this key to authorized hosts.
all/gitea.yml all hosts gitea_secret_propagation_users: [arcodange] — user namespaces that must also receive org-level Gitea Action secrets (see the gitea_secret role).
gitea/gitea.yml gitea gitea_version: 1.25.5, the gitea_database triple, and the full Gitea Docker Compose: Postgres backend (postgres:5432), the smtps/orange.fr mailer, SSH on 2222:22, ROOT_URL https://gitea.arcodange.lab/, registration disabled. SSH domain is built from hostvars[groups.gitea[0]].preferred_ip.
gitea/gitea_vault.yml gitea VAULTED. The gitea_vault.* map — GITEA__mailer__PASSWD (consumed by the compose above) plus the github_api_token / gitlab_api_token read by the mirror roles.
postgres/postgres.yml postgres The Postgres Docker Compose — postgres:16.3-alpine, 5432:5432, data under /home/pi/arcodange/docker_composes/postgres/data — plus the pgbouncer auth-user block.
step_ca/step_ca.yml step_ca step_ca_primary: pi1, step_ca_fqdn: ssl-ca.arcodange.lab, the step user/home/dir, and step_ca_listen_address: ":8443".
step_ca/step_ca_vault.yml step_ca VAULTED. vault_step_ca_password (the CA root password) and vault_step_ca_jwk_password (the cert-manager JWK provisioner password).

Note

Encrypted files are conventionally suffixed _vault.yml. They are normal group_vars files whose contents are ansible-vault-encrypted; non-vault siblings hold the plaintext structure that references the vaulted keys (e.g. gitea/gitea.yml interpolates gitea_vault.GITEA__mailer__PASSWD).


The vault model

Two distinct mechanisms share the word "vault" here — keep them apart:

  1. ansible-vault encrypts the *_vault.yml files at rest in git (AES256). Decryption happens transparently at playbook runtime.
  2. The vault password itself is never on disk. ANSIBLE_VAULT_PASSWORD_FILE points at a tiny executable that fetches the password from the K8s secret arcodange-ansible-vault in the kube-system namespace:
kubectl get secret -n kube-system arcodange-ansible-vault \
  --template='{{index .data.pass | base64decode}}'

So decrypting any *_vault.yml requires kubectl access to the live cluster — the cluster is the key custodian. The setup recipe (and the kubectl create secret to seed it) lives in ansible/README.md; how this fits the broader secret hierarchy is in Secrets & Vault.

Caution

This is not HashiCorp Vault. HashiCorp Vault (vault.arcodange.lab) is a separate, cluster-resident service installed by the hashicorp_vault role in the 04 · Tools stage. The arcodange-ansible-vault K8s secret only holds the ansible-vault password and is also read by the Gitea CI runners for the mailer.


Why this page matters for safe-prod

The variables above bind Ansible directly to live infrastructure: the host IPs, the prod Vault address, the prod Postgres superuser, and the prod Gitea forge. The safe-environment design maps each of these to a sandbox control — a parallel inventory/sandbox/hosts.yml with VM/cloud hosts, a pre-task guard that aborts on any 192.168.1.201-203 target unless i_mean_prod=true, and per-service overrides — detailed in the PRD isolation boundary. Until that lands, assume every run is a prod run.