fix(homarr): salt column, bcrypt newline, transaction safety

This commit is contained in:
Tobias Wüst 2026-05-12 23:15:06 +02:00
parent 23ea8dafc9
commit c060d6136a
Signed by: Tobias-Wuest
GPG key ID: 2D8992B0F4CA97E8

View file

@ -3,7 +3,22 @@
# tasks file for homarr # tasks file for homarr
# ===================================================================== # =====================================================================
# 1. VORBEREITUNG: Pakete und Verzeichnisse VOR Container-Start # 0. VALIDATION
# =====================================================================
- name: Validate encryption key
ansible.builtin.assert:
that:
- homarr_secret_encryption_key | length == 64
- homarr_secret_encryption_key is match('^[a-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
# =====================================================================
# 1. PREPARATION: packages and directories before container start
# ===================================================================== # =====================================================================
- name: Ensure required packages are installed - name: Ensure required packages are installed
@ -11,7 +26,6 @@
name: name:
- sqlite3 - sqlite3
- python3-docker - python3-docker
- python3-bcrypt
state: present state: present
- name: Create docker compose directory - name: Create docker compose directory
@ -37,7 +51,7 @@
register: db_exists register: db_exists
# ===================================================================== # =====================================================================
# 2. CONTAINER STARTEN # 2. START CONTAINER
# ===================================================================== # =====================================================================
- name: Create docker-compose file for homarr - name: Create docker-compose file for homarr
@ -52,7 +66,7 @@
state: present state: present
# ===================================================================== # =====================================================================
# 3. AUF DATENBANK WARTEN # 3. WAIT FOR DATABASE
# ===================================================================== # =====================================================================
- name: Wait for database to be created by Homarr - name: Wait for database to be created by Homarr
@ -63,71 +77,69 @@
when: not db_exists.stat.exists when: not db_exists.stat.exists
- name: Wait for database schema to be initialized - name: Wait for database schema to be initialized
ansible.builtin.shell: | ansible.builtin.command:
i=0 cmd: sqlite3 "{{ homarr_db_dir }}" "SELECT name FROM sqlite_master WHERE type='table' AND name='board';"
while [ $i -lt 30 ]; do
if sqlite3 "{{ homarr_db_dir }}" "SELECT name FROM sqlite_master WHERE type='table' AND name='board';" 2>/dev/null | grep -q board; then
exit 0
fi
sleep 2
i=$((i + 1))
done
exit 1
register: schema_check register: schema_check
until: schema_check.stdout == "board"
retries: 30
delay: 2
changed_when: false changed_when: false
when: not db_exists.stat.exists when: not db_exists.stat.exists
- name: Ensure python3-bcrypt is installed # =====================================================================
ansible.builtin.package: # 4. GENERATE BCRYPT HASH (on controller, not on target)
name: python3-bcrypt # =====================================================================
state: present
- name: Generate bcrypt hash for admin password - name: Generate bcrypt hash for admin password
ansible.builtin.shell: | ansible.builtin.shell:
python3 -c " cmd: python3 -c "import bcrypt, sys; print(bcrypt.hashpw(sys.stdin.read().encode(), bcrypt.gensalt(rounds=10)).decode())"
import bcrypt stdin: "{{ homarr_admin_password }}"
password = '{{ homarr_admin_password }}'.encode() stdin_add_newline: false
salt = bcrypt.gensalt(rounds=10) delegate_to: localhost
hashed = bcrypt.hashpw(password, salt) become: false
print(salt.decode()) register: bcrypt_result
print(hashed.decode())
"
register: bcrypt_output
changed_when: false changed_when: false
no_log: true no_log: true
- name: Set bcrypt hash fact
ansible.builtin.set_fact:
homarr_bcrypt_hash: "{{ bcrypt_result.stdout }}"
no_log: true
# ===================================================================== # =====================================================================
# 4. DATENBANK SEEDEN (nur wenn Onboarding noch nicht abgeschlossen) # 5. SEED DATABASE (only if local admin user does not exist yet)
# ===================================================================== # =====================================================================
- name: Check if onboarding is already completed - name: Check if local admin user exists
ansible.builtin.shell: | ansible.builtin.command:
sqlite3 "{{ homarr_db_dir }}" "SELECT step FROM onboarding WHERE step='finish';" 2>/dev/null cmd: sqlite3 "{{ homarr_db_dir }}" "SELECT id FROM user WHERE id='user-local-admin';"
register: onboarding_status register: admin_exists
changed_when: false changed_when: false
failed_when: false failed_when: false
- name: Seed Homarr database - name: Seed Homarr database
ansible.builtin.shell: | ansible.builtin.shell: |
sqlite3 "{{ homarr_db_dir }}" << 'SEEDSQL' sqlite3 "{{ homarr_db_dir }}" << 'SEEDSQL'
BEGIN TRANSACTION;
-- SERVER SETTINGS -- SERVER SETTINGS
INSERT OR REPLACE INTO serverSetting (setting_key, value) INSERT OR REPLACE INTO serverSetting (setting_key, value)
VALUES VALUES
('analytics', '{"json": {"enableGeneral": false, "enableWidgetData": false, "enableIntegrationData": false}}'), ('analytics', '{"json": {"enableGeneral": false, "enableWidgetData": false, "enableIntegrationData": false, "enableUserData": false}}'),
('culture', '{"json": {"defaultLocale": "de"}}'), ('culture', '{"json": {"defaultLocale": "de"}}'),
('crawling', '{"json": {"crawlingEnabled": false}}'), ('crawling', '{"json": {"crawlingEnabled": false}}'),
('board', '{"json": {"defaultBoardId": "board-default"}}'); ('board', '{"json": {"homeBoardId": "board-default", "mobileHomeBoardId": "board-default", "enableStatusByDefault": true, "forceDisableStatus": false, "defaultBoardId": "board-default"}}');
-- ONBOARDING ÜBERSPRINGEN -- SKIP ONBOARDING
UPDATE onboarding SET step = 'finish', previous_step = 'settings'; UPDATE onboarding SET step = 'finish', previous_step = 'settings';
-- ===================================================================== -- =================================================================
-- GRUPPEN (müssen VOR groupMember existieren) -- GROUPS (must exist before groupMember)
-- ===================================================================== -- =================================================================
-- OIDC-ADMIN GRUPPE -- OIDC admin group
INSERT OR IGNORE INTO "group" (id, name, owner_id, position) INSERT OR IGNORE INTO "group" (id, name, owner_id, position)
VALUES ('group-oidc-admins', '{{ oidc_admin_group | default("homarr-admins") }}', NULL, 0); VALUES ('group-oidc-admins', '{{ homarr_oidc_admin_group }}', NULL, 0);
INSERT OR IGNORE INTO groupPermission (group_id, permission) INSERT OR IGNORE INTO groupPermission (group_id, permission)
VALUES VALUES
@ -137,7 +149,7 @@
('group-oidc-admins', 'integration-create'), ('group-oidc-admins', 'integration-create'),
('group-oidc-admins', 'integration-full-access'); ('group-oidc-admins', 'integration-full-access');
-- CREDENTIALS-ADMIN GRUPPE -- Credentials admin group
INSERT OR IGNORE INTO "group" (id, name, owner_id, position) INSERT OR IGNORE INTO "group" (id, name, owner_id, position)
VALUES ('group-credentials-admin', 'credentials-admin', NULL, 1); VALUES ('group-credentials-admin', 'credentials-admin', NULL, 1);
@ -149,31 +161,29 @@
('group-credentials-admin', 'integration-create'), ('group-credentials-admin', 'integration-create'),
('group-credentials-admin', 'integration-full-access'); ('group-credentials-admin', 'integration-full-access');
-- ===================================================================== -- =================================================================
-- LOKALER ADMIN USER (Passwort wird via CLI gesetzt) -- LOCAL ADMIN USER
-- ===================================================================== -- =================================================================
INSERT OR IGNORE INTO user (id, name, email, password, salt, email_verified, provider) INSERT OR IGNORE INTO user (id, name, email, password, email_verified, provider)
VALUES ( VALUES (
'user-local-admin', 'user-local-admin',
'{{ homarr_admin_username | default("admin") }}', '{{ homarr_admin_username }}',
'{{ homarr_admin_email | default("admin@digitalboard.ch") }}', '{{ homarr_admin_email }}',
'{{ bcrypt_output.stdout_lines[1] }}', '{{ homarr_bcrypt_hash }}',
'{{ bcrypt_output.stdout_lines[0] }}',
1, 1,
'credentials' 'credentials'
); );
-- ADMIN-USER DEN GRUPPEN ZUWEISEN (Gruppen existieren jetzt) -- Assign admin user to groups
INSERT OR IGNORE INTO groupMember (group_id, user_id) INSERT OR IGNORE INTO groupMember (group_id, user_id)
VALUES VALUES
('group-credentials-admin', 'user-local-admin'), ('group-credentials-admin', 'user-local-admin'),
('group-oidc-admins', 'user-local-admin'); ('group-oidc-admins', 'user-local-admin');
-- ===================================================================== -- =================================================================
-- BOARD -- BOARD
-- ===================================================================== -- =================================================================
INSERT OR IGNORE INTO board ( INSERT OR IGNORE INTO board (
id, name, is_public, id, name, is_public,
@ -183,8 +193,8 @@
) )
VALUES ( VALUES (
'board-default', 'board-default',
'{{ default_board_name | default("Dashboard") }}', '{{ homarr_default_board_name }}',
{% if default_board_public | default(true) %}1{% else %}0{% endif %}, {% if homarr_default_board_public %}1{% else %}0{% endif %},
'#fa5252', '#fa5252',
'#fd7e14', '#fd7e14',
100, 100,
@ -195,18 +205,18 @@
0 0
); );
-- LAYOUTS -- Layouts
INSERT OR IGNORE INTO layout (id, name, board_id, column_count, breakpoint) INSERT OR IGNORE INTO layout (id, name, board_id, column_count, breakpoint)
VALUES VALUES
('layout-desktop', 'Desktop', 'board-default', 10, 0), ('layout-desktop', 'Desktop', 'board-default', 10, 0),
('layout-tablet', 'Tablet', 'board-default', 6, 768), ('layout-tablet', 'Tablet', 'board-default', 6, 768),
('layout-mobile', 'Mobile', 'board-default', 2, 480); ('layout-mobile', 'Mobile', 'board-default', 2, 480);
-- HOME BOARD FÜR ADMIN SETZEN (Board existiert jetzt) -- Set home board for admin user (board exists now)
UPDATE user SET home_board_id = 'board-default', mobile_home_board_id = 'board-default' UPDATE user SET home_board_id = 'board-default', mobile_home_board_id = 'board-default'
WHERE id = 'user-local-admin'; WHERE id = 'user-local-admin';
-- SEKTION -- Section
DELETE FROM section_layout WHERE section_id = 'section-apps'; DELETE FROM section_layout WHERE section_id = 'section-apps';
DELETE FROM item_layout WHERE section_id = 'section-apps'; DELETE FROM item_layout WHERE section_id = 'section-apps';
DELETE FROM section WHERE id = 'section-apps'; DELETE FROM section WHERE id = 'section-apps';
@ -218,7 +228,7 @@
'empty', 'empty',
0, 0,
0, 0,
'Anwendungen', 'Applications',
'{"json": {}}' '{"json": {}}'
); );
@ -228,15 +238,15 @@
('section-apps', 'layout-tablet', NULL, 0, 0, 6, 4), ('section-apps', 'layout-tablet', NULL, 0, 0, 6, 4),
('section-apps', 'layout-mobile', NULL, 0, 0, 2, 6); ('section-apps', 'layout-mobile', NULL, 0, 0, 2, 6);
-- BOARD-BERECHTIGUNG -- Board permissions
INSERT OR IGNORE INTO boardGroupPermission (board_id, group_id, permission) INSERT OR IGNORE INTO boardGroupPermission (board_id, group_id, permission)
VALUES VALUES
('board-default', 'group-oidc-admins', 'full-access'), ('board-default', 'group-oidc-admins', 'full-access'),
('board-default', 'group-credentials-admin', 'full-access'); ('board-default', 'group-credentials-admin', 'full-access');
-- ===================================================================== -- =================================================================
-- APPS -- APPS
-- ===================================================================== -- =================================================================
-- Nextcloud -- Nextcloud
INSERT OR IGNORE INTO app (id, name, description, icon_url, href) INSERT OR IGNORE INTO app (id, name, description, icon_url, href)
@ -312,85 +322,12 @@
('item-mailman', 'section-apps', 'layout-desktop', 4, 0, 2, 1), ('item-mailman', 'section-apps', 'layout-desktop', 4, 0, 2, 1),
('item-mailman', 'section-apps', 'layout-tablet', 4, 0, 2, 1), ('item-mailman', 'section-apps', 'layout-tablet', 4, 0, 2, 1),
('item-mailman', 'section-apps', 'layout-mobile', 0, 1, 1, 1); ('item-mailman', 'section-apps', 'layout-mobile', 0, 1, 1, 1);
COMMIT;
SEEDSQL SEEDSQL
args: args:
executable: /bin/bash executable: /bin/bash
register: seed_result register: seed_result
changed_when: seed_result.rc == 0 changed_when: seed_result.rc == 0
when: onboarding_status.stdout is not defined or 'finish' not in onboarding_status.stdout when: admin_exists.stdout == ""
notify: restart homarr
# =====================================================================
# 5. RESTART, HEALTH-CHECK, DANN CLI
# =====================================================================
- name: Restart Homarr to apply database changes
ansible.builtin.shell:
cmd: docker compose restart
chdir: "{{ homarr_docker_compose_dir }}"
when: seed_result is changed
- name: Wait for Homarr to be ready
ansible.builtin.shell:
cmd: docker compose exec -T {{ homarr_service_name }} wget -qO /dev/null "http://localhost:7575" 2>&1
chdir: "{{ homarr_docker_compose_dir }}"
retries: 30
delay: 5
register: homarr_ready
until: homarr_ready.rc == 0
changed_when: false
- name: Display completion message
ansible.builtin.debug:
msg: |
============================================================
Homarr deployed!
URL: {{ homarr_base_url }}
Admin-User: {{ homarr_admin_username }}
Admin-Passwort: {{ homarr_admin_password }}
(Bitte sofort ändern!)
============================================================
when: not db_exists.stat.exists
# =====================================================================
# 6. ABSCHLUSS
# =====================================================================
- name: Display admin credentials
ansible.builtin.debug:
msg: |
============================================
LOKALER ADMIN PASSWORT (bitte sofort ändern!)
{{ admin_password_output.stdout }}
============================================
when:
- admin_password_output is defined
- admin_password_output.stdout is defined
- admin_password_output.stdout | length > 0
- name: Display completion message
ansible.builtin.debug:
msg: |
============================================================
Homarr wurde erfolgreich deployed!
============================================================
URL: {{ homarr_base_url }}
OIDC Konfiguration:
- Issuer: {{ oidc_issuer }}
- Client ID: {{ oidc_client_id }}
- Admin-Gruppe: {{ oidc_admin_group }}
Nächste Schritte:
1. Stelle sicher, dass in Keycloak die Gruppe "{{ oidc_admin_group }}" existiert
2. Füge deinen User zur Gruppe "{{ oidc_admin_group }}" hinzu
3. Öffne {{ homarr_base_url }}
4. Du solltest automatisch via OIDC eingeloggt werden
Das Standard-Board "{{ default_board_name }}" wurde erstellt mit:
- Nextcloud App
- Keycloak App
- Mailman App
Weitere Apps kannst du direkt in der Homarr UI hinzufügen.
============================================================