Bundle of cross-role changes for the gymb services deployment: - Traefik routers: OR-combine opnform/homarr/bookstack Host rules with new *_extra_domains (internal *.int.* FQDNs for a DMZ reverseproxy), and emit tls.certresolver only when traefik_cert_mode == acme (drawio, homarr, opnform, send). - Split-horizon: bookstack_extra_hosts / opnform_extra_hosts add container /etc/hosts overrides so containers reach the IdP public FQDN over the LAN. - bookstack: assert the OIDC issuer resolves concretely (reject "//v2.0"), allowing non-Entra IdPs that override bookstack_oidc_issuer. - homarr: derive the bcrypt salt from the password digest so the admin hash is idempotent — no spurious template changes / container restarts. - opnform: PATCH an existing OIDC connection instead of skipping (applies corrected inventory on re-run); add OIDC_FORCE_LOGIN (enabled only after bootstrap) and an optional direct-SSO ingress entrypoint. Docs: READMEs and meta/argument_specs.yml updated for all new variables.
229 lines
7 KiB
YAML
229 lines
7 KiB
YAML
#SPDX-License-Identifier: MIT-0
|
|
---
|
|
# tasks file for bookstack
|
|
|
|
# =====================================================================
|
|
# 1. VALIDATE REQUIRED SECRETS
|
|
# =====================================================================
|
|
|
|
- name: Assert required secrets are set
|
|
ansible.builtin.assert:
|
|
that:
|
|
- bookstack_db_root_password | length > 0
|
|
- bookstack_db_password | length > 0
|
|
- bookstack_admin_password | length > 0
|
|
- (not bookstack_oidc_enabled) or (bookstack_oidc_client_id | length > 0)
|
|
- (not bookstack_oidc_enabled) or (bookstack_oidc_client_secret | length > 0)
|
|
# Issuer URL must resolve to something concrete. The Entra default
|
|
# interpolates bookstack_entra_tenant_id; an unset tenant leaves
|
|
# "//v2.0" in the URL. Allow non-Entra IdPs (Authentik, Keycloak)
|
|
# that override bookstack_oidc_issuer directly.
|
|
- (not bookstack_oidc_enabled) or
|
|
(bookstack_oidc_issuer | length > 0 and
|
|
'//v2.0' not in bookstack_oidc_issuer)
|
|
fail_msg: >-
|
|
One or more required secrets are unset. Provide them via OpenBao
|
|
lookup, Ansible Vault or --extra-vars. See README for the full list.
|
|
quiet: true
|
|
|
|
# =====================================================================
|
|
# 2. PREPARATION: Packages, directories, APP_KEY
|
|
# =====================================================================
|
|
|
|
- name: Ensure required packages are installed
|
|
ansible.builtin.package:
|
|
name:
|
|
- python3-docker
|
|
- python3-requests
|
|
state: present
|
|
|
|
- name: Create docker compose directory
|
|
ansible.builtin.file:
|
|
path: "{{ bookstack_docker_compose_dir }}"
|
|
state: directory
|
|
mode: '0755'
|
|
|
|
- name: Create BookStack data directories
|
|
ansible.builtin.file:
|
|
path: "{{ item }}"
|
|
state: directory
|
|
owner: "{{ bookstack_puid }}"
|
|
group: "{{ bookstack_pgid }}"
|
|
mode: '0755'
|
|
loop:
|
|
- "{{ bookstack_docker_volume_dir }}"
|
|
- "{{ bookstack_appdata_dir }}"
|
|
- "{{ bookstack_db_data_dir }}"
|
|
- "{{ bookstack_backup_dir }}"
|
|
|
|
- name: Verify Traefik network exists
|
|
community.docker.docker_network_info:
|
|
name: "{{ bookstack_traefik_network }}"
|
|
register: _traefik_net
|
|
failed_when: not _traefik_net.exists
|
|
|
|
- name: Check whether APP_KEY has been generated before
|
|
ansible.builtin.stat:
|
|
path: "{{ bookstack_docker_volume_dir }}/.app_key"
|
|
register: _app_key_file
|
|
|
|
- name: Generate persistent APP_KEY on first run
|
|
ansible.builtin.shell: |
|
|
set -o pipefail
|
|
umask 077
|
|
echo "base64:$(openssl rand -base64 32)" > {{ bookstack_docker_volume_dir }}/.app_key
|
|
args:
|
|
executable: /bin/bash
|
|
creates: "{{ bookstack_docker_volume_dir }}/.app_key"
|
|
when:
|
|
- not _app_key_file.stat.exists
|
|
- bookstack_app_key | length == 0
|
|
|
|
- name: Write inventory-provided APP_KEY
|
|
ansible.builtin.copy:
|
|
content: "{{ bookstack_app_key }}\n"
|
|
dest: "{{ bookstack_docker_volume_dir }}/.app_key"
|
|
mode: '0600'
|
|
when:
|
|
- not _app_key_file.stat.exists
|
|
- bookstack_app_key | length > 0
|
|
no_log: true
|
|
|
|
- name: Read APP_KEY back into a fact
|
|
ansible.builtin.slurp:
|
|
src: "{{ bookstack_docker_volume_dir }}/.app_key"
|
|
register: _app_key_slurp
|
|
no_log: true
|
|
|
|
- name: Register APP_KEY fact
|
|
ansible.builtin.set_fact:
|
|
bookstack_resolved_app_key: "{{ _app_key_slurp.content | b64decode | trim }}"
|
|
no_log: true
|
|
|
|
# =====================================================================
|
|
# 3. DEPLOY: Render compose, bring stack up
|
|
# =====================================================================
|
|
|
|
- name: Render docker-compose.yml for BookStack
|
|
ansible.builtin.template:
|
|
src: docker-compose.yml.j2
|
|
dest: "{{ bookstack_docker_compose_dir }}/docker-compose.yml"
|
|
mode: '0640'
|
|
notify: restart bookstack
|
|
|
|
- name: Start BookStack containers
|
|
community.docker.docker_compose_v2:
|
|
project_src: "{{ bookstack_docker_compose_dir }}"
|
|
state: present
|
|
pull: always
|
|
wait: true
|
|
|
|
# =====================================================================
|
|
# 4. CONFIGURE: Wait for app and seed initial admin user
|
|
# =====================================================================
|
|
|
|
- name: Wait for BookStack to be ready
|
|
ansible.builtin.command:
|
|
cmd: docker exec {{ bookstack_service_name }} curl -sf -o /dev/null -w "%{http_code}" http://localhost/login
|
|
register: _bookstack_health
|
|
retries: 30
|
|
delay: 5
|
|
until: _bookstack_health.stdout == "200"
|
|
changed_when: false
|
|
|
|
- name: Wait for BookStack migrations to be complete
|
|
community.docker.docker_container_exec:
|
|
container: "{{ bookstack_service_name }}-db"
|
|
argv:
|
|
- mariadb
|
|
- --protocol=tcp
|
|
- -h
|
|
- 127.0.0.1
|
|
- -u
|
|
- "{{ bookstack_db_user }}"
|
|
- "-p{{ bookstack_db_password }}"
|
|
- "{{ bookstack_db_name }}"
|
|
- -Nse
|
|
- "SHOW TABLES LIKE 'users';"
|
|
register: _users_table
|
|
retries: 30
|
|
delay: 5
|
|
until: _users_table.stdout | trim == 'users'
|
|
changed_when: false
|
|
no_log: true
|
|
|
|
- name: Check whether the initial admin already exists
|
|
community.docker.docker_container_exec:
|
|
container: "{{ bookstack_service_name }}-db"
|
|
argv:
|
|
- mariadb
|
|
- --protocol=tcp
|
|
- -h
|
|
- 127.0.0.1
|
|
- -u
|
|
- "{{ bookstack_db_user }}"
|
|
- "-p{{ bookstack_db_password }}"
|
|
- "{{ bookstack_db_name }}"
|
|
- -Nse
|
|
- "SELECT COUNT(*) FROM users WHERE email = '{{ bookstack_admin_email }}';"
|
|
register: _admin_exists
|
|
changed_when: false
|
|
no_log: true
|
|
|
|
- name: Create initial admin user
|
|
community.docker.docker_container_exec:
|
|
container: "{{ bookstack_service_name }}"
|
|
argv:
|
|
- php
|
|
- "{{ bookstack_artisan_path }}"
|
|
- bookstack:create-admin
|
|
- "--email={{ bookstack_admin_email }}"
|
|
- "--name={{ bookstack_admin_name }}"
|
|
- "--password={{ bookstack_admin_password }}"
|
|
when: (_admin_exists.stdout | trim | int) == 0
|
|
no_log: true
|
|
|
|
# =====================================================================
|
|
# 5. BACKUP: systemd timer for daily DB + uploads dump
|
|
# =====================================================================
|
|
|
|
- name: Render backup script
|
|
ansible.builtin.template:
|
|
src: backup.sh.j2
|
|
dest: /usr/local/bin/bookstack-backup.sh
|
|
owner: root
|
|
group: root
|
|
mode: '0750'
|
|
when: bookstack_backup_enabled | bool
|
|
|
|
- name: Render backup systemd service
|
|
ansible.builtin.template:
|
|
src: bookstack-backup.service.j2
|
|
dest: /etc/systemd/system/bookstack-backup.service
|
|
mode: '0644'
|
|
when: bookstack_backup_enabled | bool
|
|
notify: reload systemd
|
|
|
|
- name: Render backup systemd timer
|
|
ansible.builtin.template:
|
|
src: bookstack-backup.timer.j2
|
|
dest: /etc/systemd/system/bookstack-backup.timer
|
|
mode: '0644'
|
|
when: bookstack_backup_enabled | bool
|
|
notify: reload systemd
|
|
|
|
- name: Enable and start backup timer
|
|
ansible.builtin.systemd:
|
|
name: bookstack-backup.timer
|
|
enabled: true
|
|
state: started
|
|
daemon_reload: true
|
|
when: bookstack_backup_enabled | bool
|
|
|
|
- name: Disable backup timer when feature is off
|
|
ansible.builtin.systemd:
|
|
name: bookstack-backup.timer
|
|
enabled: false
|
|
state: stopped
|
|
when: not (bookstack_backup_enabled | bool)
|
|
failed_when: false
|