digitalboard.core/roles/bookstack
Simon Bärlocher 9cbfab7080
docs(bookstack): add meta/argument_specs.yml
47 typed options covering the full defaults file plus the OIDC and
backup-timer subsystems. The three secrets the role asserts on
(db_root_password, db_password, admin_password) are marked
required: true so ansible refuses the play with a clear error before
the validate task even runs.

Loads cleanly through ansible-core's ArgumentSpecValidator with 100%
defaults/spec coverage. Matches the spec convention used by traefik,
authentik, drawio, garage, nextcloud, opnform, coturn, talk and send.
2026-05-26 16:16:35 +02:00
..
defaults feat(bookstack): add role for self-hosted BookStack deployment 2026-05-26 16:16:35 +02:00
handlers feat(bookstack): add role for self-hosted BookStack deployment 2026-05-26 16:16:35 +02:00
meta docs(bookstack): add meta/argument_specs.yml 2026-05-26 16:16:35 +02:00
tasks feat(bookstack): add role for self-hosted BookStack deployment 2026-05-26 16:16:35 +02:00
templates feat(bookstack): add role for self-hosted BookStack deployment 2026-05-26 16:16:35 +02:00
tests feat(bookstack): add role for self-hosted BookStack deployment 2026-05-26 16:16:35 +02:00
vars feat(bookstack): add role for self-hosted BookStack deployment 2026-05-26 16:16:35 +02:00
README.md feat(bookstack): add role for self-hosted BookStack deployment 2026-05-26 16:16:35 +02:00

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 Entra ID App Registration ID (if OIDC on)
bookstack_oidc_client_secret Entra ID client secret (if OIDC on)
bookstack_entra_tenant_id Entra tenant UUID (if OIDC on)

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_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