Deploy BookStack with linuxserver.io images behind Traefik, including Entra ID OIDC SSO support and a daily backup timer. Stack: - lscr.io/linuxserver/bookstack:version-v26.03.3 - lscr.io/linuxserver/mariadb:11.4.9 - Traefik labels for websecure entrypoint on internal network - Healthcheck via mariadb-admin ping (LSIO image lacks healthcheck.sh) Features: - Persistent APP_KEY generated on first run, stored in volume dir - Optional OIDC SSO via Microsoft Entra ID (configurable per-instance) - Idempotent admin user creation with DB-based existence check - Daily systemd timer backup (DB dump + uploads tar + APP_KEY) with configurable retention Implementation notes: - DB queries use --protocol=tcp with the app user because root@localhost uses unix_socket auth in the LSIO MariaDB image (no password) and root@% does not exist - docker_container_exec uses argv: (list) instead of command: (string) to avoid argument-splitting issues - Migration-wait task ensures users table exists before admin check, since /login returns 200 before Laravel migrations complete - no_log: true on all tasks that reference DB or admin passwords - artisan absolute path (/app/www/artisan) because LSIO image WORKDIR is not the app directory Adds bookstack route to DMZ Traefik service registry.
164 lines
No EOL
5.3 KiB
YAML
164 lines
No EOL
5.3 KiB
YAML
#SPDX-License-Identifier: MIT-0
|
|
---
|
|
# tasks file for homarr
|
|
|
|
# =====================================================================
|
|
# 0. VALIDATION
|
|
# =====================================================================
|
|
|
|
- name: Validate encryption key
|
|
ansible.builtin.assert:
|
|
that:
|
|
- homarr_secret_encryption_key | length == 64
|
|
- homarr_secret_encryption_key is match('^[a-fA-F0-9]+$')
|
|
fail_msg: >-
|
|
homarr_secret_encryption_key must be a 64-character hex string.
|
|
Generate with: openssl rand -hex 32
|
|
Provide via OpenBao, Ansible Vault or extra-vars.
|
|
success_msg: Encryption key validation passed
|
|
|
|
- name: Validate OIDC configuration when enabled
|
|
ansible.builtin.assert:
|
|
that:
|
|
- homarr_oidc_client_secret | length > 0
|
|
fail_msg: >-
|
|
homarr_oidc_client_secret must be set when 'oidc' is in homarr_auth_providers.
|
|
Set via OpenBao or remove 'oidc' from homarr_auth_providers.
|
|
when: "'oidc' in homarr_auth_providers"
|
|
|
|
- name: Validate homarr_apps have unique ids
|
|
ansible.builtin.assert:
|
|
that:
|
|
- homarr_apps | map(attribute='id') | list | length ==
|
|
homarr_apps | map(attribute='id') | unique | list | length
|
|
fail_msg: >-
|
|
homarr_apps contains duplicate ids.
|
|
Each app must have a unique 'id'. Got:
|
|
{{ homarr_apps | map(attribute='id') | list }}
|
|
success_msg: All app ids are unique
|
|
when: homarr_apps | length > 0
|
|
|
|
# =====================================================================
|
|
# 1. PREPARATION: packages and directories before container start
|
|
# =====================================================================
|
|
|
|
- name: Ensure required packages are installed
|
|
ansible.builtin.package:
|
|
name:
|
|
- sqlite3
|
|
- python3-docker
|
|
state: present
|
|
|
|
- name: Create docker compose directory
|
|
ansible.builtin.file:
|
|
path: "{{ homarr_docker_compose_dir }}"
|
|
state: directory
|
|
mode: '0755'
|
|
|
|
- name: Create Homarr data directories
|
|
ansible.builtin.file:
|
|
path: "{{ item }}"
|
|
state: directory
|
|
owner: "1000"
|
|
group: "1000"
|
|
mode: "0755"
|
|
loop:
|
|
- "{{ homarr_appdata_dir }}"
|
|
- "{{ homarr_appdata_dir }}/db"
|
|
|
|
- name: Check if database already exists
|
|
ansible.builtin.stat:
|
|
path: "{{ homarr_db }}"
|
|
register: db_exists
|
|
|
|
# =====================================================================
|
|
# 2. START CONTAINER
|
|
# =====================================================================
|
|
|
|
- name: Create docker-compose file for homarr
|
|
ansible.builtin.template:
|
|
src: docker-compose.yml.j2
|
|
dest: "{{ homarr_docker_compose_dir }}/docker-compose.yml"
|
|
mode: '0644'
|
|
|
|
- name: Start homarr containers
|
|
community.docker.docker_compose_v2:
|
|
project_src: "{{ homarr_docker_compose_dir }}"
|
|
state: present
|
|
|
|
# =====================================================================
|
|
# 3. WAIT FOR DATABASE
|
|
# =====================================================================
|
|
|
|
- name: Wait for database to be created by Homarr
|
|
ansible.builtin.wait_for:
|
|
path: "{{ homarr_db }}"
|
|
state: present
|
|
timeout: 60
|
|
when: not db_exists.stat.exists
|
|
|
|
- name: Wait for database schema to be initialized
|
|
ansible.builtin.command:
|
|
cmd: sqlite3 "{{ homarr_db }}" "SELECT name FROM sqlite_master WHERE type='table' AND name='board';"
|
|
register: schema_check
|
|
until: schema_check.stdout == "board"
|
|
retries: 30
|
|
delay: 2
|
|
changed_when: false
|
|
when: not db_exists.stat.exists
|
|
|
|
# =====================================================================
|
|
# 4. GENERATE BCRYPT HASH (on controller, not on target)
|
|
# =====================================================================
|
|
|
|
- name: Generate bcrypt hash for admin password
|
|
ansible.builtin.shell:
|
|
cmd: python3 -c "import bcrypt, sys; print(bcrypt.hashpw(sys.stdin.read().encode(), bcrypt.gensalt(rounds=10)).decode())"
|
|
stdin: "{{ homarr_admin_password }}"
|
|
stdin_add_newline: false
|
|
delegate_to: localhost
|
|
become: false
|
|
register: bcrypt_result
|
|
changed_when: false
|
|
no_log: true
|
|
|
|
- name: Set bcrypt hash fact
|
|
ansible.builtin.set_fact:
|
|
homarr_bcrypt_hash: "{{ bcrypt_result.stdout }}"
|
|
no_log: true
|
|
|
|
# =====================================================================
|
|
# 5. COMPUTE APP LAYOUTS
|
|
# =====================================================================
|
|
# Packing is done by the homarr_compute_layouts filter plugin (Python)
|
|
# rather than inline Jinja, so the seed template stays readable and the
|
|
# packing algorithm can be unit-tested in isolation.
|
|
|
|
- name: Compute Homarr app layouts
|
|
ansible.builtin.set_fact:
|
|
homarr_layout: "{{ homarr_apps | digitalboard.core.homarr_compute_layouts }}"
|
|
|
|
- name: Show computed app layouts
|
|
ansible.builtin.debug:
|
|
var: homarr_layout
|
|
verbosity: 1
|
|
|
|
# =====================================================================
|
|
# 6. SEED DATABASE (only if local admin user does not exist yet)
|
|
# =====================================================================
|
|
|
|
- name: Check if local admin user exists
|
|
ansible.builtin.command:
|
|
cmd: sqlite3 "{{ homarr_db }}" "SELECT id FROM user WHERE id='user-local-admin';"
|
|
register: admin_exists
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Seed Homarr database
|
|
ansible.builtin.command:
|
|
cmd: sqlite3 "{{ homarr_db }}"
|
|
stdin: "{{ lookup('template', 'homarr_seed.sql.j2') }}"
|
|
register: seed_result
|
|
changed_when: seed_result.rc == 0
|
|
when: admin_exists.stdout == ""
|
|
notify: restart homarr |