- authentik: address the rewrite service by compose service name instead of a network alias on the public FQDN, which shadowed extra_hosts pins and broke OIDC discovery for c-ares-based (Node) resolvers - homarr: add homarr_extra_hosts to pin the IdP FQDN to a LAN IP so OIDC discovery stays in-network while the issuer matches the browser-facing URL - opnform: add opnform_oidc_sso_redirect_root to 302 the root URL to the SSO path (deep-links untouched, /login?bypass=1 break-glass); restart ingress via container restart so envsubst re-renders nginx.conf - nextcloud: make the UserConfig sed workaround fail loud on upstream drift instead of silently skipping (nextcloud/server#59629) - gitignore: exclude the local .ansible/ collection cache
251 lines
8.4 KiB
Markdown
251 lines
8.4 KiB
Markdown
# 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 | — |
|
||
|
||
`homarr_oidc_client_secret` is only required when `oidc` is in
|
||
`homarr_auth_providers`; the role asserts it then. The encryption key is
|
||
always required — the `assert` task at the top of the role fails fast if it
|
||
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_extra_hosts` | `[]` | Container `/etc/hosts` overrides (`host:ip`) — pin IdP FQDN to LAN IP |
|
||
| `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 (1–10) |
|
||
| `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 | digitalboard.core.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 gains `desktop`/`tablet`/`mobile` 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 | digitalboard.core.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.
|
||
|
||
## License
|
||
|
||
MIT-0
|