reference-ansible/docs/inventories.md
Simon Bärlocher 2ba0c07cd3
docs(reference-ansible): add docs/ tree and document repo, playbooks, Makefile
Addresses the WKS PoC review (Notion 2026-05-26). All docs in English.
- README: purpose, docs table of contents, annotated repo tree
- docs/getting_started.md: prerequisites (WKS account, OIDC, SSH, VPN) + first deploy
- docs/ansible.md: playbook table, "Running Ansible", service parameters, cheatsheet
- docs/secrets.md: canonical Bao login (moved out of README) + demo defaults
- docs/operations.md: full Makefile reference
- docs/inventories.md: repo layout, topology, standard folder structure, walkthrough
- docs/testing.md: static checks, inventory resolution, smoke test / dry run
- remove ARCHITECTURE.md (architecture docs live externally)

Also includes the gymburgdorf inventory build-out (bookstack, homarr,
opnform, send) and scripts/bao-seed.sh. site.yml keeps a third traefik
play (traefik_servers minus the vagrant _dmz/_backend split) so the demo
inventories still configure their reverse proxy after the rebase onto main.
2026-05-28 11:20:54 +02:00

8.4 KiB

Repo layout & inventories

← Documentation index

Repo layout and role origin

reference-ansible/
├── Makefile                     # deploy targets, OIDC login, OBJC fork workaround
├── ansible.cfg                  # collections_path, remote_user=root, hashi_vault auth_method=token
├── requirements.yml             # community.hashi_vault + digitalboard.core (Git)
├── playbooks/site.yml           # play sequence (see ansible.md)
├── scripts/bao-seed.sh          # seed/merge OpenBao secrets per inventory
├── docs/                        # this documentation
├── collections/                 # ← installed by `make install`, gitignored
│   └── ansible_collections/
│       └── digitalboard/core/
│           └── roles/           # 🔑 THE ROLES LIVE HERE, NOT in the repo root
└── inventories/
    ├── demo-gymburgdorf/        # reference inventory of this documentation
    ├── demo-mbazürich/
    ├── demo-phbern/
    └── vagrant/                 # local test inventory with its own topology

Important: There is no roles/ directory in the repo root. All roles come from the digitalboard.core collection (see requirements.yml), installed via make install into ./collections/. Plays reference them by FQCN digitalboard.core.<role>.

Available inventories

Inventory Purpose
demo-gymburgdorf/ Demo tenant — recommended as the template for new tenants
demo-mbazürich/ Demo tenant
demo-phbern/ Demo tenant
vagrant/ local test VMs; incompatible group topology with the demo inventories

Inventory topology (demo-gymburgdorf)

flowchart LR
    classDef dmz fill:#fee2e2,stroke:#991b1b,color:#000
    classDef app fill:#dcfce7,stroke:#166534,color:#000
    classDef stor fill:#dbeafe,stroke:#1e40af,color:#000
    classDef turn fill:#fef9c3,stroke:#854d0e,color:#000

    subgraph ALL["group: all_servers"]
        direction LR
        subgraph DMZ["DMZ 172.16.9.0/24"]
            RP["<b>reverseproxy</b><br/>172.16.9.111<br/>traefik_mode: dmz"]:::dmz
            TURN["<b>turn</b><br/>172.16.9.112<br/>(no role in site.yml yet)"]:::turn
        end
        subgraph BE["Backend 172.16.19.0/24<br/>group: backend_servers"]
            APP["<b>application</b><br/>172.16.19.101<br/>traefik_mode: backend<br/>+ authentik, authentik_outpost_ldap,<br/>  nextcloud, collabora, drawio, …"]:::app
            ST["<b>storage</b><br/>172.16.19.102<br/>traefik_mode: backend<br/>+ garage (S3)"]:::stor
        end
    end

    RP -.HTTPS in, HTTP out.-> APP
    RP -.HTTPS in, HTTP out.-> ST

Group memberships (from hosts.yml):

Group Members Purpose
all_servers reverseproxy, application, storage, turn base role for all hosts
traefik_servers children: all_servers (all 4 hosts) Traefik everywhere; DMZ/backend via traefik_mode
backend_servers application, storage sets traefik_mode: backend via group_var
garage_servers storage single-host wrapper for the Garage role
nextcloud_servers, collabora_servers, drawio_servers, authentik_servers, authentik_outpost_ldap_servers, send_servers, opnform_servers, homarr_servers, bookstack_servers only application each single-host wrappers

Difference from the vagrant inventory: vagrant structures Traefik differently — via the children groups traefik_servers_dmz and traefik_servers_backend instead of via backend_servers + host_vars override. The two topologies are structurally incompatible; a 1:1 mapping is not possible. For new tenants, therefore, take demo-gymburgdorf as the template.

Standard folder structure of an inventory entry

A fully built-out inventory follows this layout (example demo-gymburgdorf). Currently only this inventory is built out; demo-mbazürich and demo-phbern so far contain only hosts.yml.

inventories/demo-<kunde>/
├── hosts.yml                          # REQUIRED — hosts, IPs, group topology
├── group_vars/
│   ├── all/
│   │   ├── vault.yml                  # REQUIRED — vault_addr, vault_mount (Bao)
│   │   ├── ansible.yml                # ansible_python_interpreter etc.
│   │   └── docker.yml                 # docker_registry_mirrors
│   ├── traefik_servers/
│   │   └── traefik.yml                # ACME/TSIG, TLS — applies to ALL Traefik instances
│   └── backend_servers/
│       └── traefik.yml                # traefik_mode: backend (default for app + storage)
└── host_vars/
    ├── reverseproxy/
    │   └── traefik.yml                # traefik_mode: dmz + DMZ-specific ACME overrides
    ├── application/
    │   ├── main.yml                   # comment only: which services run here
    │   ├── traefik.yml                # traefik_dmz_exposed_services (what the DMZ routes)
    │   └── <service>.yml              # one file per service (nextcloud, authentik, …)
    └── storage/
        ├── main.yml                   # same as above
        ├── traefik.yml                # traefik_extra_hosts + traefik_dmz_exposed_services
        └── garage.yml                 # service vars for garage

Conventions:

  • hosts.yml is the only hard required file. Vars are optional — if one is missing, the role defaults from digitalboard.core take effect. A new inventory therefore starts minimally with only hosts.yml (just like demo-mbazürich/demo-phbern).
  • group_vars/all/vault.yml is effectively required as soon as Bao lookups are supposed to work — without vault_mount/vault_addr the secret lookups fail.
  • One file per service under host_vars/<host>/<service>.yml. The file name is free (Ansible loads all YAMLs in the directory); by convention it is named like the role. Which variables belong where: ansible.md § Where parameters belong.
  • main.yml per host is pure documentation — a comment indicating which services run on the host. Carries no productive vars.
  • host_vars/<host>/traefik.yml declares via traefik_dmz_exposed_services which local services the DMZ Traefik should make reachable from outside. The DMZ reads this list via hostvars[<backend>] and renders its routers from it. A new service exposed externally = a new entry here. Mechanics: ansible.md § traefik.

Walkthrough: Creating a new demo tenant

Recommended template: demo-gymburgdorf (not vagrant, because its group topology is incompatible).

  1. Copy the inventory:

    cp -r inventories/demo-gymburgdorf inventories/demo-<kunde>
    
  2. Adjust hosts.yml: IPs, hostnames per host.

  3. group_vars/all/vault.yml — set vault_mount to the new tenant mount (demo-<kunde>).

  4. group_vars/traefik_servers/traefik.yml — point traefik_acme_dns_zone and the acme-tsig lookup paths to the new zone / the new Bao path.

  5. Go through host_vars/application/*.yml and host_vars/storage/*.yml: FQDNs to the new domain pattern (e.g. *.<kunde>.souveredu.ch), Bao lookup paths to demo-<kunde>/data/….

  6. Prepare OpenBao (out-of-band, not via Ansible):

    • Create a new KV-v2 mount demo-<kunde>.
    • Write secrets: acme-tsig, authentik, nextcloud, garage, … — conveniently via make seed_bao_<kunde> (see scripts/bao-seed.sh and secrets.md § Demo-Only-Defaults).
    • Policy for the deploy token: read on demo-<kunde>/data/*.
  7. DNS: Create the TSIG update zone (demo-<kunde>._acme.digitalboard.ch) at ns1.digitalboard.ch, CNAMEs _acme-challenge.*.<kunde>.<tld> pointing there.

  8. Makefile — add a new target modeled on deploy_site_demo_gymburgdorf and add it to deploy_site_demo; likewise a seed_bao_<kunde> target.

  9. Smoke test: ansible all -i inventories/demo-<kunde>/hosts.yml -m ping.

  10. Deploy: Bao login + make deploy_site_demo_<kunde>.