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.
179 lines
8.4 KiB
Markdown
179 lines
8.4 KiB
Markdown
<!-- markdownlint-disable MD013 MD060 MD051 -->
|
|
# Repo layout & inventories
|
|
|
|
[← Documentation index](README.md)
|
|
|
|
## Repo layout and role origin
|
|
|
|
```text
|
|
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](../requirements.yml)), installed via
|
|
> `make install` into `./collections/`. Plays reference them by
|
|
> FQCN `digitalboard.core.<role>`.
|
|
|
|
## Available inventories
|
|
|
|
| Inventory | Purpose |
|
|
| --- | --- |
|
|
| [`demo-gymburgdorf/`](../inventories/demo-gymburgdorf/) | Demo tenant — **recommended as the template for new tenants** |
|
|
| [`demo-mbazürich/`](../inventories/demo-mbazürich/) | Demo tenant |
|
|
| [`demo-phbern/`](../inventories/demo-phbern/) | Demo tenant |
|
|
| [`vagrant/`](../inventories/vagrant/) | local test VMs; **incompatible group topology** with the demo inventories |
|
|
|
|
## Inventory topology (`demo-gymburgdorf`)
|
|
|
|
```mermaid
|
|
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](../inventories/demo-gymburgdorf/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`.
|
|
|
|
```text
|
|
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](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](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:**
|
|
|
|
```bash
|
|
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](../scripts/bao-seed.sh) and
|
|
[secrets.md § Demo-Only-Defaults](secrets.md#demo-only-defaults--must-be-overridden)).
|
|
- 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>`.
|