From 23ea8dafc9b4ca40bcdef8b2bec4a53a7ab0ff3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20W=C3=BCst?= Date: Tue, 7 Apr 2026 16:58:28 +0200 Subject: [PATCH] Chore: add admin user and seed staging added creation of the admin user, the basic homeboard and all basic setup tasks. Todo: Cleanup --- roles/homarr/defaults/main.yml | 31 +- roles/homarr/tasks/main.yml | 383 ++++++++++++++++++- roles/homarr/templates/docker-compose.yml.j2 | 18 +- 3 files changed, 424 insertions(+), 8 deletions(-) diff --git a/roles/homarr/defaults/main.yml b/roles/homarr/defaults/main.yml index c5dccef..78b32ab 100644 --- a/roles/homarr/defaults/main.yml +++ b/roles/homarr/defaults/main.yml @@ -7,17 +7,44 @@ docker_compose_base_dir: /etc/docker/compose docker_volume_base_dir: /srv/data # homarr-specific configuration +homarr_base_path: /srv/data/homarr homarr_service_name: homarr homarr_docker_compose_dir: "{{ docker_compose_base_dir }}/{{ homarr_service_name }}" homarr_docker_volume_dir: "{{ docker_volume_base_dir }}/{{ homarr_service_name }}" +homarr_appdata_dir: "{{ homarr_docker_volume_dir }}/{{ homarr_service_name }}/appdata" +homarr_db_dir: "{{ homarr_appdata_dir }}/db/db.sqlite" # Service configuration homarr_domain: "homarr.local.test" homarr_image: "ghcr.io/homarr-labs/homarr:latest" -homarr_secret_encription_key: "CHANGE_ME" +homarr_secret_encryption_key: "4fc2f54f54be3f4439b728da81b743fb0ee6317fd1a24f4096611f68019fa5a7" homarr_port: 7575 homarr_use_docker: false +# URL – wird für BASE_URL, NEXTAUTH_URL und die Completion-Message verwendet +homarr_base_url: "https://home.local.test" + +# OIDC Konfiguration +oidc_issuer: "https://auth.digitalboard.ch/realms/Digitalboard" +oidc_client_id: "homarr-digitalboard" +oidc_client_name: "Digitalboard" +oidc_scopes: "openid profile email groups" +oidc_groups_attribute: "groups" +oidc_client_secret: "mein-test-secret-aus-keycloak" +oidc_auto_login: "false" + +# OIDC Admin-Gruppe (muss in Keycloak existieren) +oidc_admin_group: "homarr-admins" + +# Board Konfiguration +default_board_name: "Home" +default_board_public: true + # Traefik configuration homarr_traefik_network: "proxy" -homarr_use_ssl: true \ No newline at end of file +homarr_use_ssl: true + +# Lokaler Admin +homarr_admin_username: "admin" +homarr_admin_email: "admin@digitalboard.ch" +homarr_admin_password: "ChangeMe123!" \ No newline at end of file diff --git a/roles/homarr/tasks/main.yml b/roles/homarr/tasks/main.yml index 17c3bf5..f8dd3df 100644 --- a/roles/homarr/tasks/main.yml +++ b/roles/homarr/tasks/main.yml @@ -1,14 +1,47 @@ #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 - file: + 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 - template: + ansible.builtin.template: src: docker-compose.yml.j2 dest: "{{ homarr_docker_compose_dir }}/docker-compose.yml" mode: '0644' @@ -16,4 +49,348 @@ - name: Start homarr containers community.docker.docker_compose_v2: project_src: "{{ homarr_docker_compose_dir }}" - state: present \ No newline at end of file + 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. + ============================================================ \ No newline at end of file diff --git a/roles/homarr/templates/docker-compose.yml.j2 b/roles/homarr/templates/docker-compose.yml.j2 index 7992c7c..b953ed6 100644 --- a/roles/homarr/templates/docker-compose.yml.j2 +++ b/roles/homarr/templates/docker-compose.yml.j2 @@ -2,17 +2,29 @@ # Homarr - A simple, yet powerful dashboard for your server. # #---------------------------------------------------------------------# services: - homarr: + {{ homarr_service_name }}: container_name: {{ homarr_service_name }} image: {{ homarr_image }} restart: unless-stopped volumes: {% if homarr_use_docker %} - - /var/run/docker.sock:/var/run/docker.sock # Optional, only if you want docker integration + - /var/run/docker.sock:/var/run/docker.sock {% endif %} - {{ homarr_docker_volume_dir }}/homarr/appdata:/appdata environment: - - SECRET_ENCRYPTION_KEY={{ homarr_secret_encryption_key }} + TZ: "Europe/Zurich" + BASE_URL: "{{ homarr_base_url }}" + NEXTAUTH_URL: "{{ homarr_base_url }}" + SECRET_ENCRYPTION_KEY: "{{ homarr_secret_encryption_key }}" + # Auth: Credentials + OIDC + AUTH_PROVIDERS: "credentials,oidc" + AUTH_OIDC_ISSUER: "{{ oidc_issuer }}" + AUTH_OIDC_CLIENT_ID: "{{ oidc_client_id }}" + AUTH_OIDC_CLIENT_SECRET: "{{ oidc_client_secret }}" + AUTH_OIDC_CLIENT_NAME: "{{ oidc_client_name | default('Keycloak') }}" + AUTH_OIDC_SCOPE_OVERWRITE: "{{ oidc_scopes | default('openid email profile groups') }}" + AUTH_OIDC_GROUPS_ATTRIBUTE: "{{ oidc_groups_attribute | default('groups') }}" + AUTH_OIDC_AUTO_LOGIN: "{{ oidc_auto_login | default('false') }}" networks: - {{ homarr_traefik_network }} labels: