digitalboard.core/roles/bookstack/README.md
Simon Bärlocher 3236ca332f
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 23:12:24 +02:00

6.4 KiB

Ansible Role: bookstack

Deploys BookStack 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

- name: Deploy BookStack service
  hosts: bookstack_servers
  become: true
  roles:
    - digitalboard.core.bookstack

With inventory variables:

# 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:
    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