digitalboard.core/roles/homarr/tasks/main.yml
Tobias Wüst 422b196831
Chore: add admin user and seed staging
added creation of the admin user, the basic homeboard and all basic setup tasks.
Todo: Cleanup
2026-04-07 16:58:28 +02:00

396 lines
No EOL
13 KiB
YAML

#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.
============================================================