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.
8.4 KiB
Repo layout & inventories
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 thedigitalboard.corecollection (see requirements.yml), installed viamake installinto./collections/. Plays reference them by FQCNdigitalboard.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
vagrantinventory:vagrantstructures Traefik differently — via the children groupstraefik_servers_dmzandtraefik_servers_backendinstead of viabackend_servers+host_varsoverride. The two topologies are structurally incompatible; a 1:1 mapping is not possible. For new tenants, therefore, takedemo-gymburgdorfas 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.ymlis the only hard required file. Vars are optional — if one is missing, the role defaults fromdigitalboard.coretake effect. A new inventory therefore starts minimally with onlyhosts.yml(just likedemo-mbazürich/demo-phbern).group_vars/all/vault.ymlis effectively required as soon as Bao lookups are supposed to work — withoutvault_mount/vault_addrthe 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.ymlper host is pure documentation — a comment indicating which services run on the host. Carries no productive vars.host_vars/<host>/traefik.ymldeclares viatraefik_dmz_exposed_serviceswhich local services the DMZ Traefik should make reachable from outside. The DMZ reads this list viahostvars[<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).
-
Copy the inventory:
cp -r inventories/demo-gymburgdorf inventories/demo-<kunde> -
Adjust
hosts.yml: IPs, hostnames per host. -
group_vars/all/vault.yml— setvault_mountto the new tenant mount (demo-<kunde>). -
group_vars/traefik_servers/traefik.yml— pointtraefik_acme_dns_zoneand theacme-tsiglookup paths to the new zone / the new Bao path. -
Go through
host_vars/application/*.ymlandhost_vars/storage/*.yml: FQDNs to the new domain pattern (e.g.*.<kunde>.souveredu.ch), Bao lookup paths todemo-<kunde>/data/…. -
Prepare OpenBao (out-of-band, not via Ansible):
- Create a new KV-v2 mount
demo-<kunde>. - Write secrets:
acme-tsig,authentik,nextcloud,garage, … — conveniently viamake seed_bao_<kunde>(see scripts/bao-seed.sh and secrets.md § Demo-Only-Defaults). - Policy for the deploy token: read on
demo-<kunde>/data/*.
- Create a new KV-v2 mount
-
DNS: Create the TSIG update zone (
demo-<kunde>._acme.digitalboard.ch) atns1.digitalboard.ch, CNAMEs_acme-challenge.*.<kunde>.<tld>pointing there. -
Makefile — add a new target modeled on
deploy_site_demo_gymburgdorfand add it todeploy_site_demo; likewise aseed_bao_<kunde>target. -
Smoke test:
ansible all -i inventories/demo-<kunde>/hosts.yml -m ping. -
Deploy: Bao login +
make deploy_site_demo_<kunde>.