digitalboard.core/roles/homarr/README.md
Simon Bärlocher 518d80ec71
feat(services): multi-domain routing, split-horizon and OIDC hardening
Bundle of cross-role changes for the gymb services deployment:

- Traefik routers: OR-combine opnform/homarr/bookstack Host rules with new
  *_extra_domains (internal *.int.* FQDNs for a DMZ reverseproxy), and emit
  tls.certresolver only when traefik_cert_mode == acme (drawio, homarr,
  opnform, send).
- Split-horizon: bookstack_extra_hosts / opnform_extra_hosts add container
  /etc/hosts overrides so containers reach the IdP public FQDN over the LAN.
- bookstack: assert the OIDC issuer resolves concretely (reject "//v2.0"),
  allowing non-Entra IdPs that override bookstack_oidc_issuer.
- homarr: derive the bcrypt salt from the password digest so the admin hash
  is idempotent — no spurious template changes / container restarts.
- opnform: PATCH an existing OIDC connection instead of skipping (applies
  corrected inventory on re-run); add OIDC_FORCE_LOGIN (enabled only after
  bootstrap) and an optional direct-SSO ingress entrypoint.

Docs: READMEs and meta/argument_specs.yml updated for all new variables.
2026-05-27 16:18:29 +02:00

244 lines
No EOL
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# homarr
Deploy [Homarr](https://github.com/homarr-labs/homarr) as a self-contained
Docker Compose stack behind Traefik, with seeded admin user, OIDC group
and customizable application tiles.
## What this role does
- Deploys the official Homarr container with Traefik labels
- Seeds the SQLite database with:
- server settings (locale, analytics, crawling, default board)
- a default board with the three layouts (desktop/tablet/mobile)
- a local admin user with bcrypt-hashed password
- OIDC and credentials admin groups with full permissions
- application tiles defined in `homarr_apps`, auto-laid-out across all
three screen sizes via the bundled `homarr_compute_layouts` filter
- Skips the onboarding wizard so the instance is usable right after deploy
- Restarts the container via handler when the seed or compose file changes
## What this role does NOT do
- Does not configure OIDC end-to-end — set `homarr_oidc_*` variables and
configure the corresponding client in your identity provider
- Does not migrate existing Homarr databases — only seeds empty ones
- Does not create users beyond the single local admin (OIDC users are
provisioned on first login)
## Required variables
Provide via OpenBao, Ansible Vault, or extra-vars. **Never commit real
secrets to version control.**
| Variable | Format | Generate with |
|---|---|---|
| `homarr_secret_encryption_key` | 64-char hex string | `openssl rand -hex 32` |
| `homarr_admin_password` | strong password | `openssl rand -base64 24` |
| `homarr_oidc_client_secret` | from your identity provider | — |
The `assert` task at the top of the role will fail fast if the encryption
key is missing or malformed.
## Configurable variables
See `defaults/main.yml` for the full list. Most useful overrides:
| Variable | Default | Purpose |
|---|---|---|
| `homarr_domain` | `homarr.local.test` | Traefik Host rule |
| `homarr_extra_domains` | `[]` | Extra Host-rule hostnames (OR-combined), e.g. internal `*.int.*` FQDN |
| `homarr_base_url` | `https://home.local.test` | NEXTAUTH_URL / BASE_URL |
| `homarr_auth_providers` | `credentials` | `credentials`, `oidc`, or both |
| `homarr_oidc_issuer` | empty | Identity provider issuer URL |
| `homarr_oidc_client_id` | empty | OIDC client id |
| `homarr_oidc_admin_group` | `homarr-admins` | Group granting admin role |
| `homarr_apps` | `[]` | List of application tiles, see below |
## Application tiles
`homarr_apps` is a list of tile definitions that are seeded into the
default board. Each entry needs:
| Field | Required | Description |
|---|---|---|
| `id` | yes | Unique slug, used as `app-<id>` and `item-<id>` |
| `name` | yes | Display name |
| `icon` | yes | Icon URL |
| `href` | yes | Click target |
| `width` | yes | Tile width in grid cells (110) |
| `description` | no | Tooltip / subtitle |
| `height` | no | Tile height (default `1`) |
The role validates that all `id` values are unique.
### Auto-layout
Tiles are packed left-to-right into three layouts:
- **Desktop**: 10 columns
- **Tablet**: 6 columns
- **Mobile**: 2 columns
When a tile does not fit the remaining width of a row, it wraps to the
next row. Tile width is clamped to the grid width (a tile with
`width: 8` becomes `width: 6` on tablet and `width: 2` on mobile).
### Example
```yaml
homarr_apps:
- id: nextcloud
name: Nextcloud
description: Cloud Storage & Collaboration
icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/nextcloud.png
href: https://cloud.example.com
width: 2
- id: keycloak
name: Keycloak
description: Identity & Access Management
icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/keycloak.png
href: https://auth.example.com
width: 2
```
## Layout filter plugin
The grid-packing algorithm that places tiles on the desktop, tablet
and mobile layouts lives in `filter_plugins/homarr_layout.py` rather
than inside the Jinja seed template. This keeps the SQL template
readable and lets the algorithm be unit-tested in isolation.
The filter is invoked once from `tasks/main.yml`:
```yaml
- name: Compute Homarr app layouts
ansible.builtin.set_fact:
homarr_layout: "{{ homarr_apps | homarr_compute_layouts }}"
```
This produces a `homarr_layout` fact with two keys, both consumed by
`templates/homarr_seed.sql.j2`:
| Key | Shape | Purpose |
|---|---|---|
| `apps` | list, same order as `homarr_apps` | each entry enriched with `desktop`, `tablet`, `mobile` sub-dicts of `{x, y, w, h}` |
| `section_height` | dict with `desktop`, `tablet`, `mobile` | minimum height of the parent section so all tiles fit |
The filter signature accepts custom column counts if Homarr ever
changes the breakpoint widths:
```jinja
{{ homarr_apps | homarr_compute_layouts(desktop_cols=12, tablet_cols=8, mobile_cols=4) }}
```
To debug a layout without running the full deploy, run the play with
`-vv` — the `Show computed app layouts` task dumps the full
`homarr_layout` fact.
### Running the filter tests
The filter is covered by unit tests in
`filter_plugins/tests/test_homarr_layout.py`:
```bash
pip install pytest ansible-core
pytest filter_plugins/tests/
```
15 tests cover packing, width clamping, height/section-height,
input validation and custom grid sizes.
## First login
After the role completes, log in at `{{ homarr_base_url }}` with:
- Username: value of `homarr_admin_username` (default `admin`)
- Password: value of `homarr_admin_password`
OIDC users are provisioned on first login if their identity provider
group matches `homarr_oidc_admin_group`. They receive admin permissions
automatically through the seeded `group-oidc-admins` group.
## Example playbook
```yaml
- name: Deploy Homarr service
hosts: homarr_servers
become: true
roles:
- digitalboard.core.homarr
```
With inventory variables:
```yaml
# inventories/<env>/group_vars/homarr_servers.yml
homarr_domain: home.digitalboard.ch
homarr_base_url: "https://home.digitalboard.ch"
homarr_auth_providers: "credentials,oidc"
homarr_oidc_issuer: "https://auth.digitalboard.ch/realms/Digitalboard"
homarr_oidc_client_id: "homarr-digitalboard"
homarr_oidc_client_name: "Digitalboard"
homarr_secret_encryption_key: >-
{{ lookup('community.hashi_vault.vault_kv2_get',
'digitalboard/homarr',
mount_point='kv').data.data.encryption_key }}
homarr_admin_password: >-
{{ lookup('community.hashi_vault.vault_kv2_get',
'digitalboard/homarr',
mount_point='kv').data.data.admin_password }}
homarr_oidc_client_secret: >-
{{ lookup('community.hashi_vault.vault_kv2_get',
'digitalboard/homarr',
mount_point='kv').data.data.oidc_client_secret }}
homarr_apps:
- id: nextcloud
name: Nextcloud
description: Cloud Storage
icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/nextcloud.png
href: https://cloud.digitalboard.ch
width: 2
- id: keycloak
name: Keycloak
description: Identity & Access Management
icon: https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/keycloak.png
href: https://auth.digitalboard.ch
width: 2
```
## Re-running the role
The role is idempotent for the typical re-run case:
- The seed only runs when no local admin user exists in the database
- The compose file and seed template are deployed via `template`,
which only changes content when the inputs change
- The restart handler only fires when one of those templates changes
If you need to re-seed an existing database (for example after deleting
the database file to apply schema changes), the role will detect the
fresh database and seed it again on the next run.
## Troubleshooting
**Login fails after deploy.** Verify that the bcrypt hash was written
correctly:
```bash
sqlite3 /srv/data/homarr/homarr/appdata/db/db.sqlite \
"SELECT id, name, email, length(password), provider FROM user;"
```
Expected: one row with `user-local-admin`, password length 60,
provider `credentials`.
**Encryption key validation fails.** The key must be exactly 64
characters and contain only hex digits (`[a-fA-F0-9]`). Both upper-
and lowercase are accepted.
**App tiles overlap.** Check `homarr_apps` for duplicate `id` values.
The role validates this, but if you bypass the check, the seed will
still run and Homarr will display only one of the duplicates.