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.
154 lines
6.4 KiB
Markdown
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
|