digitalboard.core/roles/bookstack/README.md
Simon Bärlocher 03bf0efe44
docs(collection): document all roles and fix metadata drift
Replace ansible-galaxy init placeholders across the collection and
correct documentation that drifted from the code, after a multi-agent
review of every role README against its defaults, tasks and templates.

Collection level:
- README: role table for all 16 roles, requirements and role-ordering
- galaxy.yml: declare community.docker and community.general deps,
  real description/tags/urls; normalize license to MIT-0
- meta/runtime.yml: requires_ansible '>=2.15.0'
- plugins/README: document the homarr_layout filter and
  garage_credentials lookup instead of scaffold boilerplate

Per-role meta/main.yml and README for the placeholder roles
(389ds, authentik, authentik_outpost_ldap, base, collabora, drawio,
garage, homarr, httpbin, keycloak, nextcloud, opencloud, traefik).

Correctness fixes found during review:
- keycloak: wrong domain default, drop invented keycloak_cert_resolver,
  document the provisioning feature
- garage: root_domain is .s3.<first-entry>, not the bare domain
- opnform: jwt/front_api secrets use `openssl rand -hex 32`; align the
  validation fail_msg in tasks/main.yml accordingly
- send: S3 example references garage_s3_domains[0] (was singular)
- opencloud: document required opencloud_wopi_domain

License normalized to MIT-0 across galaxy.yml, role meta and READMEs to
match the SPDX headers.
2026-05-27 22:33:42 +02:00

154 lines
6.4 KiB
Markdown

# Ansible Role: bookstack
Deploys [BookStack](https://www.bookstackapp.com/) as a self-contained Docker
Compose stack behind Traefik, with its own MariaDB container, OIDC SSO
(Entra ID by default) and a daily systemd-timer driven backup of database
and uploads.
## Requirements
- Docker Engine + Compose plugin on the target host
- Traefik already running, with the external network referenced by
`bookstack_traefik_network` (default: `proxy`)
- `community.docker` collection on the controller
- DNS for `bookstack_domain` pointing at the Traefik host
## Required variables
The role asserts these are set; the play fails fast if any is empty:
| Variable | Description |
|---|---|
| `bookstack_db_root_password` | MariaDB root password |
| `bookstack_db_password` | MariaDB user password |
| `bookstack_admin_password` | Initial local admin password |
| `bookstack_oidc_client_id` | OIDC client ID (if OIDC on) |
| `bookstack_oidc_client_secret` | OIDC client secret (if OIDC on) |
When OIDC is on, the role also asserts that `bookstack_oidc_issuer`
resolves to a concrete URL. For Entra ID this means setting
`bookstack_entra_tenant_id` (the default issuer interpolates it; an unset
tenant leaves `//v2.0` and fails the assert). For other IdPs (Authentik,
Keycloak) set `bookstack_oidc_issuer` directly instead.
Provide via OpenBao lookup, Ansible Vault or `--extra-vars`. Never commit
real secrets.
## Optional variables
See `defaults/main.yml`. Frequently overridden:
- `bookstack_domain`, `bookstack_base_url`
- `bookstack_extra_domains` (extra Host-rule hostnames, e.g. an internal
`*.int.*` FQDN for a DMZ reverseproxy)
- `bookstack_extra_hosts` (container `/etc/hosts` overrides for
split-horizon IdP access; entries as `host:ip`)
- `bookstack_image`, `bookstack_db_image` (pin in production)
- `bookstack_oidc_enabled` (set `false` to disable OIDC entirely)
- `bookstack_oidc_auto_initiate` (`true` redirects straight to IdP)
- `bookstack_oidc_user_to_groups` (`true` syncs roles from Entra groups)
- `bookstack_backup_enabled`, `bookstack_backup_schedule`,
`bookstack_backup_retention_days`
## Entra ID app registration
1. Azure Portal → Entra ID → App registrations → New registration
2. Redirect URI (Web): `https://<bookstack_domain>/oidc/callback`
3. Front-channel logout URL: `https://<bookstack_domain>/logout`
4. Certificates & secrets → New client secret →
`bookstack_oidc_client_secret`
5. For group sync (`bookstack_oidc_user_to_groups: true`):
- Token configuration → Add groups claim → Security groups
- In BookStack, create roles whose **External Auth ID** equals the
Entra group Object ID, so the mapping resolves on first login.
## What the role does
| Phase | Action |
|---|---|
| Validate | `assert` all required secrets are set |
| Prepare | install packages, create volume dirs, generate persistent `APP_KEY`, verify Traefik network |
| Deploy | render `docker-compose.yml`, pull images, bring stack up |
| Configure | wait for the app, create the initial local admin via `php artisan bookstack:create-admin` (idempotent) |
| Backup | render `/usr/local/bin/bookstack-backup.sh` + systemd timer (daily 03:00, 14-day retention) |
## Example playbook
```yaml
- name: Deploy BookStack service
hosts: bookstack_servers
become: true
roles:
- digitalboard.core.bookstack
```
With inventory variables:
```yaml
# group_vars/bookstack_servers.yml
bookstack_domain: wiki.digitalboard.ch
bookstack_base_url: "https://wiki.digitalboard.ch"
bookstack_entra_tenant_id: "{{ lookup('community.hashi_vault.vault_kv2_get',
'digitalboard/bookstack',
mount_point='kv').data.data.tenant_id }}"
bookstack_oidc_client_id: "{{ lookup('community.hashi_vault.vault_kv2_get',
'digitalboard/bookstack',
mount_point='kv').data.data.client_id }}"
bookstack_oidc_client_secret: "{{ lookup('community.hashi_vault.vault_kv2_get',
'digitalboard/bookstack',
mount_point='kv').data.data.client_secret }}"
bookstack_db_root_password: "{{ lookup('community.hashi_vault.vault_kv2_get',
'digitalboard/bookstack',
mount_point='kv').data.data.db_root_password }}"
bookstack_db_password: "{{ lookup('community.hashi_vault.vault_kv2_get',
'digitalboard/bookstack',
mount_point='kv').data.data.db_password }}"
bookstack_admin_password: "{{ lookup('community.hashi_vault.vault_kv2_get',
'digitalboard/bookstack',
mount_point='kv').data.data.admin_password }}"
```
## Backup / restore
Backups land in `{{ bookstack_backup_dir }}` (default
`/srv/data/bookstack/backup`) with three files per run:
- `bookstack-db-<stamp>.sql.gz` — mariadb-dump
- `bookstack-files-<stamp>.tar.gz` — uploads, attachments
- `bookstack-appkey-<stamp>.txt` — APP_KEY (required for restore!)
Manual trigger: `systemctl start bookstack-backup.service`
Timer status: `systemctl list-timers bookstack-backup.timer`
Restore procedure:
1. Stop the stack: `docker compose down` in `bookstack_docker_compose_dir`
2. Restore the APP_KEY: copy the `.txt` content to
`{{ bookstack_docker_volume_dir }}/.app_key` (the key MUST match or
encrypted DB values become unreadable)
3. Start only the DB container, then load the dump:
```bash
gunzip -c bookstack-db-<stamp>.sql.gz \
| docker exec -i bookstack-db \
mariadb -u root -p"<root-pw>" bookstack
```
4. Extract the files: `tar -xzf bookstack-files-<stamp>.tar.gz -C
{{ bookstack_appdata_dir }}/www/`
5. Bring the stack back up: `docker compose up -d`
## Notes
- `bookstack_oidc_auto_initiate: false` (default) shows a login page
with an SSO button alongside the local login form. With `true`, users
go straight to the IdP — the local admin then has to use
`https://<domain>/login?email_login=1`.
- `bookstack_oidc_user_to_groups: true` only makes sense once BookStack
roles with the correct **External Auth IDs** (= Entra group Object
IDs) exist; otherwise users lose their role assignment on every login.
- Image tags default to pinned versions; bump them deliberately rather
than chasing `latest`.
- BookStack officially supports MySQL/MariaDB only — no PostgreSQL.
## License
MIT-0