#SPDX-License-Identifier: MIT-0 --- # tasks file for homarr # ===================================================================== # 1. VORBEREITUNG: Pakete und Verzeichnisse VOR Container-Start # ===================================================================== - name: Ensure required packages are installed ansible.builtin.package: name: - sqlite3 - python3-docker - python3-bcrypt 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_dir }}" register: db_exists # ===================================================================== # 2. CONTAINER STARTEN # ===================================================================== - 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. AUF DATENBANK WARTEN # ===================================================================== - name: Wait for database to be created by Homarr ansible.builtin.wait_for: path: "{{ homarr_db_dir }}" state: present timeout: 60 when: not db_exists.stat.exists - name: Wait for database schema to be initialized ansible.builtin.shell: | i=0 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 changed_when: false when: not db_exists.stat.exists - name: Ensure python3-bcrypt is installed ansible.builtin.package: name: python3-bcrypt state: present - name: Generate bcrypt hash for admin password ansible.builtin.shell: | python3 -c " import bcrypt password = '{{ homarr_admin_password }}'.encode() salt = bcrypt.gensalt(rounds=10) hashed = bcrypt.hashpw(password, salt) print(salt.decode()) print(hashed.decode()) " register: bcrypt_output changed_when: false no_log: true # ===================================================================== # 4. DATENBANK SEEDEN (nur wenn Onboarding noch nicht abgeschlossen) # ===================================================================== - name: Check if onboarding is already completed ansible.builtin.shell: | sqlite3 "{{ homarr_db_dir }}" "SELECT step FROM onboarding WHERE step='finish';" 2>/dev/null register: onboarding_status changed_when: false failed_when: false - name: Seed Homarr database ansible.builtin.shell: | sqlite3 "{{ homarr_db_dir }}" << 'SEEDSQL' -- SERVER SETTINGS INSERT OR REPLACE INTO serverSetting (setting_key, value) VALUES ('analytics', '{"json": {"enableGeneral": false, "enableWidgetData": false, "enableIntegrationData": false}}'), ('culture', '{"json": {"defaultLocale": "de"}}'), ('crawling', '{"json": {"crawlingEnabled": false}}'), ('board', '{"json": {"defaultBoardId": "board-default"}}'); -- ONBOARDING ÜBERSPRINGEN UPDATE onboarding SET step = 'finish', previous_step = 'settings'; -- ===================================================================== -- GRUPPEN (müssen VOR groupMember existieren) -- ===================================================================== -- OIDC-ADMIN GRUPPE INSERT OR IGNORE INTO "group" (id, name, owner_id, position) VALUES ('group-oidc-admins', '{{ oidc_admin_group | default("homarr-admins") }}', NULL, 0); INSERT OR IGNORE INTO groupPermission (group_id, permission) VALUES ('group-oidc-admins', 'admin'), ('group-oidc-admins', 'board-create'), ('group-oidc-admins', 'board-full-access'), ('group-oidc-admins', 'integration-create'), ('group-oidc-admins', 'integration-full-access'); -- CREDENTIALS-ADMIN GRUPPE INSERT OR IGNORE INTO "group" (id, name, owner_id, position) VALUES ('group-credentials-admin', 'credentials-admin', NULL, 1); INSERT OR IGNORE INTO groupPermission (group_id, permission) VALUES ('group-credentials-admin', 'admin'), ('group-credentials-admin', 'board-create'), ('group-credentials-admin', 'board-full-access'), ('group-credentials-admin', 'integration-create'), ('group-credentials-admin', 'integration-full-access'); -- ===================================================================== -- LOKALER ADMIN USER (Passwort wird via CLI gesetzt) -- ===================================================================== INSERT OR IGNORE INTO user (id, name, email, password, salt, email_verified, provider) VALUES ( 'user-local-admin', '{{ homarr_admin_username | default("admin") }}', '{{ homarr_admin_email | default("admin@digitalboard.ch") }}', '{{ bcrypt_output.stdout_lines[1] }}', '{{ bcrypt_output.stdout_lines[0] }}', 1, 'credentials' ); -- ADMIN-USER DEN GRUPPEN ZUWEISEN (Gruppen existieren jetzt) INSERT OR IGNORE INTO groupMember (group_id, user_id) VALUES ('group-credentials-admin', 'user-local-admin'), ('group-oidc-admins', 'user-local-admin'); -- ===================================================================== -- BOARD -- ===================================================================== INSERT OR IGNORE INTO board ( id, name, is_public, primary_color, secondary_color, opacity, background_image_attachment, background_image_repeat, background_image_size, item_radius, disable_status ) VALUES ( 'board-default', '{{ default_board_name | default("Dashboard") }}', {% if default_board_public | default(true) %}1{% else %}0{% endif %}, '#fa5252', '#fd7e14', 100, 'fixed', 'no-repeat', 'cover', 'lg', 0 ); -- LAYOUTS INSERT OR IGNORE INTO layout (id, name, board_id, column_count, breakpoint) VALUES ('layout-desktop', 'Desktop', 'board-default', 10, 0), ('layout-tablet', 'Tablet', 'board-default', 6, 768), ('layout-mobile', 'Mobile', 'board-default', 2, 480); -- HOME BOARD FÜR ADMIN SETZEN (Board existiert jetzt) UPDATE user SET home_board_id = 'board-default', mobile_home_board_id = 'board-default' WHERE id = 'user-local-admin'; -- SEKTION DELETE FROM section_layout WHERE section_id = 'section-apps'; DELETE FROM item_layout WHERE section_id = 'section-apps'; DELETE FROM section WHERE id = 'section-apps'; INSERT INTO section (id, board_id, kind, x_offset, y_offset, name, options) VALUES ( 'section-apps', 'board-default', 'empty', 0, 0, 'Anwendungen', '{"json": {}}' ); INSERT OR REPLACE INTO section_layout (section_id, layout_id, parent_section_id, x_offset, y_offset, width, height) VALUES ('section-apps', 'layout-desktop', NULL, 0, 0, 10, 3), ('section-apps', 'layout-tablet', NULL, 0, 0, 6, 4), ('section-apps', 'layout-mobile', NULL, 0, 0, 2, 6); -- BOARD-BERECHTIGUNG INSERT OR IGNORE INTO boardGroupPermission (board_id, group_id, permission) VALUES ('board-default', 'group-oidc-admins', 'full-access'), ('board-default', 'group-credentials-admin', 'full-access'); -- ===================================================================== -- APPS -- ===================================================================== -- Nextcloud INSERT OR IGNORE INTO app (id, name, description, icon_url, href) VALUES ( 'app-nextcloud', 'Nextcloud', 'Cloud Storage & Collaboration', 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/nextcloud.png', 'https://cloud.digitalboard.ch' ); INSERT OR IGNORE INTO item (id, board_id, kind, options, advanced_options) VALUES ( 'item-nextcloud', 'board-default', 'app', '{"json": {"appId": "app-nextcloud"}}', '{"json": {}}' ); INSERT OR REPLACE INTO item_layout (item_id, section_id, layout_id, x_offset, y_offset, width, height) VALUES ('item-nextcloud', 'section-apps', 'layout-desktop', 0, 0, 2, 1), ('item-nextcloud', 'section-apps', 'layout-tablet', 0, 0, 2, 1), ('item-nextcloud', 'section-apps', 'layout-mobile', 0, 0, 1, 1); -- Keycloak INSERT OR IGNORE INTO app (id, name, description, icon_url, href) VALUES ( 'app-keycloak', 'Keycloak', 'Identity & Access Management', 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/keycloak.png', 'https://auth.digitalboard.ch' ); INSERT OR IGNORE INTO item (id, board_id, kind, options, advanced_options) VALUES ( 'item-keycloak', 'board-default', 'app', '{"json": {"appId": "app-keycloak"}}', '{"json": {}}' ); INSERT OR REPLACE INTO item_layout (item_id, section_id, layout_id, x_offset, y_offset, width, height) VALUES ('item-keycloak', 'section-apps', 'layout-desktop', 2, 0, 2, 1), ('item-keycloak', 'section-apps', 'layout-tablet', 2, 0, 2, 1), ('item-keycloak', 'section-apps', 'layout-mobile', 1, 0, 1, 1); -- Mailman INSERT OR IGNORE INTO app (id, name, description, icon_url, href) VALUES ( 'app-mailman', 'Mailman', 'Mailing List Manager', 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/mailman.png', 'https://lists.digitalboard.ch' ); INSERT OR IGNORE INTO item (id, board_id, kind, options, advanced_options) VALUES ( 'item-mailman', 'board-default', 'app', '{"json": {"appId": "app-mailman"}}', '{"json": {}}' ); INSERT OR REPLACE INTO item_layout (item_id, section_id, layout_id, x_offset, y_offset, width, height) VALUES ('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-mobile', 0, 1, 1, 1); SEEDSQL args: executable: /bin/bash register: seed_result changed_when: seed_result.rc == 0 when: onboarding_status.stdout is not defined or 'finish' not in onboarding_status.stdout # ===================================================================== # 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. ============================================================