Compare commits

...
Author SHA1 Message Date
ajnart 9699255180 fix(wud): authenticate HTTP requests and validate container access 2026-09-12 10:50:27 +02:00
Thomas 2049c6ade0 fix: tighten v2 setup and release safeguards (#6824) 2026-09-12 10:47:35 +02:00
ajnart e21a0bb6da fix: refresh OpenAPI widget kinds 2026-09-12 10:46:39 +02:00
Thomas 61f22c8e64 fix: show board removal confirmation in a dialog (#6829) 2026-09-12 10:36:36 +02:00
leafyelin 6cca757493 feat(plugins): add llama.cpp server integration and widget (#6765) 2026-09-10 20:06:13 +02:00
Thomas 1102ad8342 fix: protect demo users from deletion and password changes (#6819) 2026-09-10 19:52:47 +02:00
Thomas 7c00c18a42 docs(mcp): restore custom widget authoring guidance (#6817) 2026-09-10 19:24:55 +02:00
Thomas ea3dac752f fix(mcp): harden tool schemas and dispatch (#6815) 2026-09-10 19:19:47 +02:00
Thomas f994d665d2 ci: remove automatic test gates and trim test suites (#6816)
* ci: remove automatic test gates and trim test suites

* docs: remove CI audit report
2026-09-10 19:14:31 +02:00
Thomas 3358b6337f fix(mcp): use JSON-compatible invitation dates (#6814) 2026-09-10 17:53:38 +02:00
Thomas 61d3fd5f08 fix(board): restore container grid geometry (#6808) 2026-09-09 15:04:27 +02:00
Thomas 3aac106156 docs: correct v2 beta installation guidance (#6809) 2026-09-09 12:09:14 +02:00
Thomas 9d857377c1 fix: refine release v2 interactions (#6805) 2026-09-08 11:36:19 +02:00
Thomas 923905c529 fix: keep bookmark density consistent (#6804)
* fix: keep bookmark density consistent

* fix: prioritize bookmark titles in compact rows

* fix: preserve compact bookmark text fallback
2026-09-08 10:39:41 +02:00
Thomas a4a903d7c3 feat: polish release v2 experience (#6800) 2026-09-07 19:17:37 +02:00
Thomas 925b0fb801 fix: improve bookmark widget density (#6802)
* fix: improve bookmark widget density

* fix: render dense bookmark grids as icons

* fix: honor bookmark column density
2026-09-07 13:47:11 +02:00
ajnart 6db26a0d99 docs: restore release article hierarchy 2026-09-06 20:41:55 +02:00
ajnart bbcbd031fe docs: clarify Homarr provider terms 2026-09-06 20:37:52 +02:00
ajnart 7024a57f56 docs: tighten Homarr 2.0 release article 2026-09-06 19:57:44 +02:00
Thomas 5121356113 Merge pull request #6794 from homarr-labs/feat/refocus-homarr-v2-article
docs: refocus Homarr 2.0 article
2026-09-06 19:20:05 +02:00
ajnart 4f2ab59345 docs: refocus Homarr 2.0 article 2026-09-06 19:19:23 +02:00
Thomas 73bf68c9a8 Merge pull request #6792 from homarr-labs/feat/v2-release-client-ids
fix(ci): use GitHub App client IDs
2026-09-06 18:05:28 +02:00
ajnart dea1de5fee fix(ci): use GitHub App client IDs 2026-09-06 17:57:35 +02:00
Thomas 2bd538e685 Merge pull request #6791 from homarr-labs/feat/publish-homarr-v2-blog
docs: publish Homarr 2.0 article
2026-09-06 17:52:05 +02:00
ajnart f0c39ea0d2 docs: publish Homarr 2.0 article 2026-09-06 17:51:36 +02:00
Thomas 8a0cf15bf7 docs: finalize Homarr 2.0 release article (#6790) 2026-09-06 17:48:08 +02:00
Thomas f1c898efe2 docs: add Homarr 2.0 release article (#6789) 2026-09-06 15:39:54 +02:00
Thomas 724dee55c5 fix(weather): handle disabled external connections (#6780) 2026-09-06 15:35:06 +02:00
Thomas 50a3bcce69 fix(ci): restore release automation (#6770)
* fix(ci): restore release automation

* ci: remove obsolete documentation release dispatch

* ci: report production deployment failures
2026-09-06 15:32:52 +02:00
Thomasandajnart ca7be8f3ad fix(integrations): support UniFi controller default ports (#6787)
* fix(integrations): use Unifi default port 8443 when no port is given

The node-unifi library hardcodes https:// in its base URL and the
Unifi controller API only speaks HTTPS. When a user adds the
integration with a bare http:// or https:// URL, getPortFromUrl
returned 80 or 443, and the library attempted a TLS handshake on
the wrong port. The server's plain HTTP response caused
'EPROTO ... wrong version number'.

Derive the port from the user's explicit port, or fall back to 8443
(the integration's defaultPort). Closes #6207.

* style: auto-fix lint and format

* fix(integrations): support UniFi controller default ports

* docs(unifi): resolve review leftovers

---------

Co-authored-by: ajnart <ajnart@users.noreply.github.com>
2026-09-06 15:32:45 +02:00
134ef48848 fix(integrations): preserve Nextcloud subpaths (#6786)
* fix(integrations): preserve Nextcloud subpaths

* Keep existing Nextcloud DAV endpoints intact

---------

Co-authored-by: Artur <145776561+arturict@users.noreply.github.com>
Co-authored-by: arturict <arturict@users.noreply.github.com>
2026-09-06 15:32:37 +02:00
98aa6f01f2 feat(auth): support nested OIDC claim paths (#6785)
* feat(auth): support nested OIDC claim paths

* test(auth): cover OIDC profile name fallbacks

* test(auth): cover missing OIDC username fallback

* test(auth): preserve OIDC provider guard

---------

Co-authored-by: Artur <145776561+arturict@users.noreply.github.com>
Co-authored-by: arturict <arturict@users.noreply.github.com>
2026-09-06 15:32:31 +02:00
Thomas 4c88030b0f fix(downloads): preserve name column width (#6784) 2026-09-06 15:32:25 +02:00
Thomas 0c11c2785e docs(helm): sync current chart metadata (#6783) 2026-09-06 15:32:20 +02:00
Thomas 96a6926b2b fix(docker): normalize container CPU usage (#6782) 2026-09-06 15:32:15 +02:00
Thomas f89487231f fix(unraid): use byte-based memory metrics (#6781) 2026-09-06 15:32:09 +02:00
Thomas a62d11b5cd fix(mcp): honor public OAuth origin (#6779) 2026-09-06 15:31:57 +02:00
Thomas 28e242417d fix(backup): prompt re-login after restore (#6778) 2026-09-06 15:31:51 +02:00
Thomas 0ffa95c505 docs: make environment examples safe and current (#6777)
* docs: make SQLite environment example runnable

* docs: remove unsafe source secret examples

* docs(auth): remove unsafe SSO environment values

* docs(auth): clarify OIDC credential placeholders
2026-09-06 15:31:40 +02:00
Thomas b708f687fa docs: add v1 to v2 upgrade guide (#6774)
* docs: add v1 to v2 upgrade guide

* docs: simplify v2 backup guidance
2026-09-06 15:31:33 +02:00
Thomas 4fd9b04c61 fix(deps): patch high production advisories (#6788) 2026-09-06 15:27:14 +02:00
Thomas 378332c8da docs: update contribution checklist for v2 (#6773) 2026-09-06 14:31:57 +02:00
Thomas 3d9b8e8ae6 fix(docs): generate valid integration links (#6772) 2026-09-06 14:31:48 +02:00
Thomas f7b194fc9f fix(auth): patch critical Auth.js vulnerabilities (#6775) 2026-09-06 14:25:09 +02:00
Thomas eb20b97d5f fix(deps): patch critical protobufjs vulnerability (#6776) 2026-09-06 14:25:00 +02:00
439 changed files with 15870 additions and 10516 deletions

No files matched your search

+1 -1
View File
@@ -37,4 +37,4 @@ Orient from the current checkout before editing. Treat package manifests, export
- Cron jobs: `packages/cron-jobs/src/jobs/`
- App routes: `apps/nextjs/src/app/[locale]/`
When a code change affects users, invoke `documentation-sync`. When exposing tRPC through MCP, invoke `mcp-integration`.
Invoke `documentation-sync` when hidden capabilities or information not evident to an advanced user change, or existing guidance becomes inaccurate; follow the Documentation Sync criteria in `AGENTS.md`. When exposing tRPC through MCP, invoke `mcp-integration`.
@@ -32,24 +32,23 @@ Import only from an exported package entrypoint. Do not deep-import `@homarr/com
## Root exports
| Area | Public exports | Purpose |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| Objects | `objectKeys`, `objectEntries`, `hashObjectBase64` | Typed key/entry iteration and stable query-key hashing |
| Strings | `capitalize`, `isNullOrWhitespace`, `bestMatch`, `normalizeImageName`, `getImageMatchRank` | Text normalization and matching |
| Cookies | `parseCookies`, `setClientCookie` | Cookie parsing and browser cookie serialization |
| Arrays | `splitToNChunks`, `splitToChunksWithNItems` | Split by number of groups or maximum group size |
| Dates | `formatDuration`, `toValidDate`, `isDateWithin` | Duration formatting, safe date coercion, relative-window checks |
| Timing | `Stopwatch` | Elapsed-time measurement and formatting |
| React hooks | `useTimeAgo`, `useIntegrationConnected` | Relative time and integration freshness state |
| URLs | `removeTrailingSlash`, `extractBaseUrlFromHeaders`, `getPortFromUrl`, `isAbsoluteUrl`, `SAFE_NEW_TAB_REL`, `getSafeApplicationUrl`, `getSafeAppHref` | URL normalization, safe navigation, ports, and new-tab policy |
| Numbers and bytes | `formatNumber`, `randomInt`, `formatBytes`, `formatBytesPair`, `formatByteRate`, `metricToImperial`, `imperialToMetric` | Compact numbers, byte displays, rates, and unit conversion |
| Legacy bytes | `humanFileSize` | Deprecated; use the `formatBytes*` helpers in new code |
| Byte types | `ByteUnitSystem`, `FormatBytesOptions` | Options for byte formatting |
| Errors | `extractErrorMessage`, `FlattenError` | Unknown-error messages and flattenable domain errors |
| Theme | `getMantineColor` | Resolve a Mantine color and shade |
| Functions | `isFunction` | Unknown-value function guard |
| Grid | `GridAlgorithmItem`, `generateResponsiveGridFor` | Responsive nested-section grid placement |
| IDs | `createId` | CUID2 ID creation |
| Area | Public exports | Purpose |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| Objects | `objectKeys`, `objectEntries`, `isRecord`, `hashObjectBase64` | Typed key/entry iteration, record guards, and stable query-key hashing |
| Strings | `capitalize`, `isNullOrWhitespace`, `bestMatch`, `normalizeImageName`, `getImageMatchRank` | Text normalization and matching |
| Cookies | `parseCookies`, `setClientCookie` | Cookie parsing and browser cookie serialization |
| Arrays | `splitToNChunks`, `splitToChunksWithNItems` | Split by number of groups or maximum group size |
| Dates | `formatDuration`, `toValidDate`, `isDateWithin` | Duration formatting, safe date coercion, relative-window checks |
| Timing | `Stopwatch` | Elapsed-time measurement and formatting |
| React hooks | `useTimeAgo`, `useIntegrationConnected` | Relative time and integration freshness state |
| URLs | `removeTrailingSlash`, `extractBaseUrlFromHeaders`, `getPortFromUrl`, `isAbsoluteUrl`, `SAFE_NEW_TAB_REL`, `getSafeApplicationUrl`, `getSafeAppHref` | URL normalization, safe navigation, ports, and new-tab policy |
| Numbers and bytes | `formatNumber`, `formatBytes`, `formatBytesPair`, `formatByteRate`, `formatBitRate`, `defaultByteUnitSystem`, `metricToImperial` | Compact numbers, byte displays, byte/bit rates, and unit conversion |
| Byte types | `ByteUnitSystem`, `FormatBytesOptions`, `FormatBitRateOptions` | Options for byte and bit-rate formatting |
| Errors | `extractErrorMessage`, `FlattenError` | Unknown-error messages and flattenable domain errors |
| Theme | `getMantineColor` | Resolve a Mantine color and shade |
| Functions | `isFunction` | Unknown-value function guard |
| Grid | `GridAlgorithmItem`, `generateResponsiveGridFor` | Responsive nested-section grid placement |
| IDs | `createId` | CUID2 ID creation |
The root deliberately does not re-export `types`, `server`, `client`, or `env`; use their named entrypoints.
+9 -5
View File
@@ -1,13 +1,17 @@
---
name: documentation-sync
description: Keep Homarr's Docusaurus documentation aligned with user-facing code. Use when adding or changing integrations, widgets, APIs, environment variables, CLI commands, authentication, permissions, cron jobs, settings, or UI behavior that users must understand.
description: Write concise Homarr docs for advanced users, covering only hidden capabilities and information not evident from the UI. Use when that information changes or existing guidance becomes inaccurate; skip obvious controls and routine fixes.
---
# Documentation Sync
Update `apps/docs/` in the same change as user-facing behavior. Inspect adjacent pages and the docs types before copying a pattern; the source and docs evolve together.
Apply the audience and scope rules in `AGENTS.md` first. Before adding a passage, identify what an advanced user could not learn directly from the interface. If there is no such information, omit it.
## Map the change
For hidden capabilities such as the advanced widget feature, explain discovery, activation, and non-obvious behavior. For visible standard controls such as Delete, omit narration of what the label already conveys. Use the shortest explanation that preserves necessary technical detail; remove redundant prose in the passage being edited.
When an update is warranted, inspect adjacent pages for structure and types, not as a verbosity target. The mappings below locate needed information; they do not require a page for every feature or code change.
## Locate a needed update
| Code change | Documentation target |
| ----------------------------------- | -------------------------------------------------------------------- |
@@ -40,11 +44,11 @@ For a widget:
- Add or update `apps/docs/docs/widgets/<slug>/index.ts` with the local `WidgetDefinition` pattern.
- Add or update `index.mdx` with the established `WidgetHeader`, `WidgetConfig`, and `AddingWidget` components as applicable.
- Document options, required integrations, permissions, empty states, and changed behavior that affect setup or use.
- Explain only non-obvious option semantics, integration prerequisites, permission constraints, or behavior that the interface does not make clear.
## Completion criteria
1. Describe the shipped behavior, not an implementation plan.
1. Every passage adds information an advanced user cannot reasonably infer from the interface. Remove obvious, repetitive, or unnecessary prose.
2. Keep names, defaults, paths, screenshots, links, and prerequisites consistent with code.
3. Update every affected page and remove superseded guidance.
4. Run the narrowest useful docs validation. Use `pnpm turbo build --filter=@homarr/docs` when links, MDX, generated definitions, or navigation can fail; otherwise run the docs package formatter on touched files.
+12 -7
View File
@@ -1,22 +1,25 @@
# Since .env is gitignored, you can use .env.example to build a new `.env` file when you clone the repo.
# Keep this file up-to-date when you add new variables to \`.env\`.
# Keep this file up-to-date when you add new variables to `.env`.
# This file will be committed to version control, so make sure not to have any secrets in it.
# If you are cloning this repo, create a copy of this file named `.env` and populate it with your secrets.
# The below secret is not used anywhere but required for Auth.js (Would encrypt JWTs and Mail hashes, both not used)
AUTH_SECRET="supersecret"
# Generate two different 64-character hex values with `openssl rand -hex 32` before starting Homarr.
# Never reuse these values or commit the populated `.env` file.
AUTH_SECRET=
# The below secret is used to encrypt integration secrets in the database.
# It should be a 32-byte string, generated by running `openssl rand -hex 32` on Unix
# or starting the project without any (which will show a randomly generated one).
SECRET_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
# Homarr will reject an empty or invalid value and print a randomly generated candidate.
SECRET_ENCRYPTION_KEY=
LOG_LEVEL='info'
# This is how you can use the sqlite driver:
DB_DRIVER='better-sqlite3'
DB_URL='FULL_PATH_TO_YOUR_SQLITE_DB_FILE'
# Set an absolute writable SQLite path so migrations and the app use the same file.
# Their working directories differ; do not use a relative path here.
# Container images use /appdata/db/db.sqlite by default instead.
DB_URL=
# Those are the two ways to use the mysql2 driver:
# 1. Using the URL format:
@@ -63,6 +66,8 @@ UNSAFE_ENABLE_MOCK_INTEGRATION=true
# Public URL contract. Values must be absolute HTTP(S) URLs without credentials,
# query strings, or fragments. Trailing slashes are normalized at startup.
# Public Homarr origin used by MCP OAuth metadata and redirects behind proxies.
# BASE_URL=https://homarr.example.com
HOMARR_WEBSITE_URL=https://homarr.dev
# PocketBase API used by Homarr and by the standalone documentation site.
# Defaults to HOMARR_WEBSITE_URL.
+6 -6
View File
@@ -4,14 +4,14 @@
<h3>Homarr</h3>
</div>
**Thank you for your contribution. Please ensure that your pull request meets the following pull request:**
**Thank you for your contribution. Please ensure that your pull request meets the following requirements:**
- [ ] Builds without warnings or errors (`pnpm build`, autofix with `pnpm format:fix`)
- [ ] Pull request targets `dev` branch
- [ ] Relevant checks pass without warnings or errors
- [ ] Pull request targets the intended base branch (`dev` by default; `release/v2` for v2 release work)
- [ ] Commits follow the [conventional commits guideline](https://www.conventionalcommits.org/en/v1.0.0/)
- [ ] No shorthand variable names are used (eg. `x`, `y`, `i` or any abbrevation)
- [ ] Documentation is up to date. Create a pull request [here](https://github.com/homarr-labs/documentation/).
- [ ] When using AI; No temp files are checked in, the code style follows the rest of the project
- [ ] No shorthand variable names are used (for example, `x`, `y`, `i`, or abbreviations)
- [ ] User-facing changes include matching documentation updates under `apps/docs`
- [ ] When using AI, no temporary files are checked in and the code style follows the rest of the project
**When contributing a new integration or widget:**
@@ -20,7 +20,7 @@ jobs:
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
client-id: ${{ vars.RENOVATE_APPROVE_CLIENT_ID }}
permission-pull-requests: write # required to enable auto-merge
- name: Install GitHub CLI
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
client-id: ${{ vars.RENOVATE_APPROVE_CLIENT_ID }}
permission-pull-requests: write # required to approve a pull request and enable auto-merge
- name: Install GitHub CLI
+10 -57
View File
@@ -97,17 +97,16 @@ jobs:
- name: Validate Custom Widget bundle budget
run: |
pnpm --filter nextjs build
pnpm turbo run build --filter=@homarr/nextjs
pnpm check:custom-widget-bundle
- name: Run unit tests
run: pnpm test
container:
name: Container and E2E
name: Container build
# Same-repository previews already build amd64; only build a fallback when no digest is available.
needs: [fast-gate, preview-amd64]
if: |
!cancelled() && needs.fast-gate.result == 'success' &&
needs.preview-amd64.result != 'failure' && needs.preview-amd64.outputs.digest == '' &&
(
github.event_name == 'merge_group' ||
(github.event_name == 'push' && github.ref == 'refs/heads/release/v2') ||
@@ -115,10 +114,9 @@ jobs:
(github.event_name == 'workflow_dispatch' && (inputs.operation == 'validate' || inputs.operation == 'publish'))
)
runs-on: ubuntu-latest
timeout-minutes: 30
timeout-minutes: 20
permissions:
contents: read
packages: read
steps:
- uses: actions/checkout@v7
if: "!env.ACT"
@@ -126,23 +124,10 @@ jobs:
persist-credentials: false
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
- name: Setup
uses: ./tooling/github/setup
- name: Set up Docker Buildx
if: "!env.ACT"
if: needs.preview-amd64.outputs.digest == '' && !env.ACT
uses: docker/setup-buildx-action@v4
- name: Reuse amd64 preview image
if: needs.preview-amd64.outputs.digest != '' && !env.ACT
env:
DIGEST: ${{ needs.preview-amd64.outputs.digest }}
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
docker pull "${PREVIEW_IMAGE}@${DIGEST}"
docker tag "${PREVIEW_IMAGE}@${DIGEST}" homarr-ci
docker tag "${PREVIEW_IMAGE}@${DIGEST}" homarr-e2e
- name: Build container once
if: needs.preview-amd64.outputs.digest == '' && !env.ACT
uses: docker/build-push-action@v7
@@ -152,9 +137,7 @@ jobs:
platforms: linux/amd64
load: true
push: false
tags: |
homarr-ci
homarr-e2e
tags: homarr-ci
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
@@ -182,37 +165,7 @@ jobs:
fi
done
DOCKER_BUILDKIT=1 docker build --platform linux/amd64 \
--tag homarr-ci --tag homarr-e2e "${secret_args[@]}" .
- name: Cache Playwright browsers
if: "!env.ACT"
id: playwright-cache
uses: actions/cache@v6
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install Playwright Chromium
if: steps.playwright-cache.outputs.cache-hit != 'true' || env.ACT
run: pnpm exec playwright install --with-deps chromium
- name: Install Playwright system deps
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: pnpm exec playwright install-deps chromium
- name: Run E2E tests
env:
NODE_ENV: development
CI: "true"
run: pnpm exec vitest e2e --retry 1 --maxWorkers 4
- name: Upload E2E artifacts
if: failure() && !env.ACT
uses: actions/upload-artifact@v7
with:
name: playwright-report
path: e2e/test-results/
retention-days: 7
--tag homarr-ci "${secret_args[@]}" .
preview-amd64:
name: Build amd64 preview image
@@ -320,10 +273,10 @@ jobs:
publish-preview:
name: Publish multi-platform preview image
needs: [container, preview-amd64, preview-arm64]
needs: [fast-gate, preview-amd64, preview-arm64]
if: |
!cancelled() &&
needs.container.result == 'success' &&
needs.fast-gate.result == 'success' &&
needs.preview-amd64.result == 'success' &&
needs.preview-arm64.result == 'success' &&
(
@@ -22,7 +22,7 @@ jobs:
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.CROWDIN_APP_PRIVATE_KEY }}
client-id: ${{ secrets.CROWDIN_APP_ID }}
client-id: ${{ vars.CROWDIN_CLIENT_ID }}
permission-contents: write # required to commit to crowdin branch
permission-pull-requests: write # required to create pull request
+21 -10
View File
@@ -16,10 +16,10 @@ permissions:
jobs:
create-and-merge-pr:
runs-on: ubuntu-latest
timeout-minutes: 2
timeout-minutes: 10
steps:
- name: Discord notification
if: ${{ github.event.inputs.send-notifications }}
if: ${{ inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
@@ -37,11 +37,22 @@ jobs:
branch: next
- name: Create pull request
id: create-pull-request
run: 'gh pr create --title "chore(release): release ${{ steps.semver.outputs.next }}-beta" --body "Detected change to be ``${{ steps.semver.outputs.bump }}``<br/>Bump version from ``${{ steps.semver.outputs.current }}`` to ``${{ steps.semver.outputs.next }}-beta``" --base beta --head next --label automerge'
run: |
pr_url="$(gh pr list --base beta --head next --state open --json url --jq '.[0].url // empty')"
if [[ -z "$pr_url" ]]; then
pr_url="$(gh pr create \
--title "chore(release): release ${{ steps.semver.outputs.next }}-beta" \
--body "Detected change to be ${{ steps.semver.outputs.bump }}<br/>Bump version from ${{ steps.semver.outputs.current }} to ${{ steps.semver.outputs.next }}-beta" \
--base beta \
--head next \
--label automerge)"
fi
echo "url=$pr_url" >> "$GITHUB_OUTPUT"
echo "pr_number=${pr_url##*/}" >> "$GITHUB_OUTPUT"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
- name: Discord notification
if: ${{ github.event.inputs.send-notifications }}
if: ${{ inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
@@ -52,19 +63,19 @@ jobs:
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
client-id: ${{ vars.RENOVATE_APPROVE_CLIENT_ID }}
permission-pull-requests: write
- name: Approve PR
env:
GITHUB_TOKEN: ${{ steps.obtainApprovalToken.outputs.token }}
run: |
gh pr review --approve --body "Automatically approved by GitHub Action"
gh pr review "${{ steps.create-pull-request.outputs.url }}" --approve --body "Automatically approved by GitHub Action"
- name: Obtain token
id: obtainMergeToken
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
client-id: ${{ vars.RENOVATE_MERGE_CLIENT_ID }}
permission-contents: write # write to beta branch (due to merge)
permission-pull-requests: write # merge pull request
- id: automerge
@@ -82,21 +93,21 @@ jobs:
MERGE_REQUIRED_APPROVALS: 0 # do not require approvals
- name: Merged Discord notification
if: ${{ steps.automerge.outputs.mergeResult == 'merged' && github.event.inputs.send-notifications }}
if: ${{ steps.automerge.outputs.mergeResult == 'merged' && inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
with:
args: "Merged PR ${{ steps.create-pull-request.outputs.url }} for release ${{ steps.semver.outputs.next }}-beta"
- name: Major Bump Discord notification
if: ${{ steps.semver.outputs.bump == 'major' && github.event.inputs.send-notifications }}
if: ${{ steps.semver.outputs.bump == 'major' && inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
with:
args: "The release PR must be manually merged because the next version is a major version: ${{ steps.create-pull-request.outputs.url }} for release ${{ steps.semver.outputs.next }}-beta"
- name: Discord Fail Notification
if: failure() && github.event.inputs.send-notifications
if: ${{ failure() && inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
+26 -3
View File
@@ -40,6 +40,7 @@ jobs:
version: ${{ steps.read-semver.outputs.version || steps.version-fallback.outputs.version }}
git_ref: ${{ steps.read-git-ref.outputs.ref || steps.read-semver.outputs.version || github.ref }}
skipped: ${{ env.SKIP_RELEASE }}
upstream_sync: ${{ steps.update-upstream.outcome }}
steps:
- run: echo "Manual dispatch redeploys the latest existing release without creating a new one"
if: env.SKIP_RELEASE == 'true'
@@ -54,7 +55,7 @@ jobs:
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
client-id: ${{ vars.RENOVATE_MERGE_CLIENT_ID }}
permission-contents: write # required to commit package.json & changelog changes, merge them to dev / next and publish the release
- uses: actions/checkout@v7
@@ -115,8 +116,10 @@ jobs:
run: |
echo "ref=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Update dev / next branch
id: update-upstream
if: env.SKIP_RELEASE == 'false'
continue-on-error: true # Prevent pipeline from failing when merge fails
# Finish publishing the already-created release, then fail in the publish job if synchronization did not work.
continue-on-error: true
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
UPSTREAM_BRANCH: ${{ github.ref_name == 'main' && 'dev' || 'next' }}
@@ -274,7 +277,7 @@ jobs:
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
client-id: ${{ vars.RENOVATE_MERGE_CLIENT_ID }}
permission-contents: write # required to publish the release
- name: Log in to the Container registry
@@ -311,6 +314,12 @@ jobs:
run: |
gh release edit --repo ${{ github.repository }} ${{ needs.release.outputs.version }} --draft=false
- name: Verify dev / next synchronization
if: needs.release.outputs.skipped == 'false' && needs.release.outputs.upstream_sync != 'success'
run: |
echo "::error::The released commit was not synchronized back to the upstream development branch."
exit 1
- name: Discord notification
if: github.event_name != 'workflow_dispatch' || inputs['send-notifications']
env:
@@ -318,3 +327,17 @@ jobs:
uses: Ilshidur/action-discord@0.4.0
with:
args: "Successfully deployed images for branch **${{ github.ref_name }}**. Tagged as **${{env.NEXT_VERSION}}**."
notify-failure:
name: Notify deployment failure
if: ${{ always() && (failure() || cancelled()) && (github.event_name != 'workflow_dispatch' || inputs['send-notifications']) }}
needs: [release, build-amd64, build-arm64, extract-asset-amd64, extract-asset-arm64, publish]
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- name: Discord failure notification
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
with:
args: "The deployment for branch **${{ github.ref_name }}** failed or was cancelled: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
+31 -11
View File
@@ -19,10 +19,10 @@ permissions:
jobs:
create-and-merge-pr:
runs-on: ubuntu-latest
timeout-minutes: 2
timeout-minutes: 10
steps:
- name: Discord notification
if: ${{ github.event.inputs.send-notifications }}
if: ${{ github.event_name == 'schedule' || inputs.send-notifications }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
@@ -38,13 +38,30 @@ jobs:
with:
token: ${{ github.token }}
branch: dev
noNewCommitBehavior: silent
noVersionBumpBehavior: silent
- name: No release needed
if: ${{ steps.semver.outputs.bump == '' || steps.semver.outputs.bump == 'none' }}
run: echo "No release-worthy commits were found since ${{ steps.semver.outputs.current }}." >> "$GITHUB_STEP_SUMMARY"
- name: Create pull request
id: create-pull-request
run: 'gh pr create --title "chore(release): automatic release ${{ steps.semver.outputs.next }}" --body "**This is an automatic release**.<br/>Manual action may be required for major bumps.<br/>Detected change to be ``${{ steps.semver.outputs.bump }}``<br/>Bump version from ``${{ steps.semver.outputs.current }}`` to ``${{ steps.semver.outputs.next }}``" --base main --head dev --label automerge'
if: ${{ steps.semver.outputs.bump != '' && steps.semver.outputs.bump != 'none' }}
run: |
pr_url="$(gh pr list --base main --head dev --state open --json url --jq '.[0].url // empty')"
if [[ -z "$pr_url" ]]; then
pr_url="$(gh pr create \
--title "chore(release): automatic release ${{ steps.semver.outputs.next }}" \
--body "**This is an automatic release**.<br/>Manual action may be required for major bumps.<br/>Detected change to be ``${{ steps.semver.outputs.bump }}``<br/>Bump version from ``${{ steps.semver.outputs.current }}`` to ``${{ steps.semver.outputs.next }}``" \
--base main \
--head dev \
--label automerge)"
fi
echo "url=$pr_url" >> "$GITHUB_OUTPUT"
echo "pr_number=${pr_url##*/}" >> "$GITHUB_OUTPUT"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
- name: Discord notification
if: ${{ github.event.inputs.send-notifications }}
if: ${{ steps.create-pull-request.outcome == 'success' && (github.event_name == 'schedule' || inputs.send-notifications) }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
@@ -52,26 +69,29 @@ jobs:
args: "Created a release PR ${{ steps.create-pull-request.outputs.url }} for version ${{ steps.semver.outputs.next }} (new behaviour: ${{ steps.semver.outputs.bump }})"
- name: Obtain token
id: obtainApprovalToken
if: ${{ steps.create-pull-request.outcome == 'success' }}
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
client-id: ${{ vars.RENOVATE_APPROVE_CLIENT_ID }}
permission-pull-requests: write
- name: Approve PR
if: ${{ steps.create-pull-request.outcome == 'success' }}
env:
GITHUB_TOKEN: ${{ steps.obtainApprovalToken.outputs.token }}
run: |
gh pr review --approve --body "Automatically approved by GitHub Action"
gh pr review "${{ steps.create-pull-request.outputs.url }}" --approve --body "Automatically approved by GitHub Action"
- name: Obtain token
id: obtainMergeToken
if: ${{ steps.create-pull-request.outcome == 'success' }}
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
client-id: ${{ vars.RENOVATE_MERGE_CLIENT_ID }}
permission-contents: write # write to main branch (due to merge)
permission-pull-requests: write # merge pull request
- id: automerge
if: ${{ steps.semver.outputs.bump != 'major' }}
if: ${{ steps.create-pull-request.outcome == 'success' && steps.semver.outputs.bump != 'major' }}
name: automerge
uses: "pascalgn/automerge-action@v0.16.4"
env:
@@ -85,21 +105,21 @@ jobs:
MERGE_REQUIRED_APPROVALS: 0 # do not require approvals
- name: Merged Discord notification
if: ${{ steps.automerge.outputs.mergeResult == 'merged' && github.event.inputs.send-notifications }}
if: ${{ steps.automerge.outputs.mergeResult == 'merged' && (github.event_name == 'schedule' || inputs.send-notifications) }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
with:
args: "Merged PR ${{ steps.create-pull-request.outputs.url }} for release ${{ steps.semver.outputs.next }}"
- name: Major Bump Discord notification
if: ${{ steps.semver.outputs.bump == 'major' && github.event.inputs.send-notifications }}
if: ${{ steps.semver.outputs.bump == 'major' && (github.event_name == 'schedule' || inputs.send-notifications) }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
with:
args: "The release PR must be manually merged because the next version is a major version: ${{ steps.create-pull-request.outputs.url }} for release ${{ steps.semver.outputs.next }}"
- name: Discord Fail Notification
if: failure() && github.event.inputs.send-notifications
if: ${{ failure() && (github.event_name == 'schedule' || inputs.send-notifications) }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@0.4.0
+2 -30
View File
@@ -26,36 +26,8 @@ jobs:
- name: Copy env
run: cp .env.example .env
- name: Start dev server
run: pnpm --filter @homarr/nextjs dev &
env:
TURBO_TELEMETRY_DISABLED: 1
DISABLE_TELEMETRY: 1
- name: Wait for server and fetch spec
run: |
set -euo pipefail
for i in $(seq 1 30); do
if curl -fsS http://localhost:3000/api/openapi -o /tmp/openapi.json 2>/dev/null; then
echo "Server is up, spec fetched"
break
fi
echo "Waiting for server... ($i/30)"
sleep 3
done
if [ ! -f /tmp/openapi.json ]; then
echo "::error::Server failed to start within timeout"
exit 1
fi
jq -e '.openapi and .paths' /tmp/openapi.json > /dev/null || {
echo "::error::Fetched file is not a valid OpenAPI spec"
exit 1
}
cp /tmp/openapi.json apps/docs/static/api/open-api-schema.json
echo "OpenAPI spec updated ($(jq '.paths | length' /tmp/openapi.json) paths)"
- name: Generate and validate OpenAPI spec
run: pnpm scripts:update-openapi
- name: Commit updated spec
uses: EndBug/add-and-commit@v10
+1 -34
View File
@@ -5,39 +5,6 @@ on:
types: [released]
jobs:
trigger-docs-release:
name: Trigger Documentation Release
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- name: Obtain token
id: obtainToken
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.HOMARR_DOCS_RELEASE_APP_PRIVATE_KEY }}
client-id: ${{ vars.HOMARR_DOCS_RELEASE_APP_ID }}
owner: homarr-labs
repositories: |
documentation
permission-contents: write # required to dispatch repository workflow
- name: Trigger documentation release
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
SOURCE_TAG: ${{ github.event.release.tag_name }}
run: |
curl -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/repos/homarr-labs/documentation/dispatches \
-d @- <<EOF
{
"event_type": "trigger-release",
"client_payload": {
"tag": "${SOURCE_TAG}"
}
}
EOF
update-bug-report-template:
name: Update Bug Report Template
runs-on: ubuntu-latest
@@ -48,7 +15,7 @@ jobs:
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
client-id: ${{ vars.RENOVATE_MERGE_CLIENT_ID }}
permission-contents: write # required to commit to branch
permission-pull-requests: write # required to create pr & enable automerge
- name: Checkout code
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
client-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
client-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_CLIENT_ID }}
permission-contents: write # required to commit to branch
permission-pull-requests: write # required to create pr & enable automerge
@@ -24,7 +24,7 @@ jobs:
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
client-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
client-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_CLIENT_ID }}
permission-contents: write # required to commit to branch
permission-pull-requests: write # required to create pr & enable automerge
- name: Checkout code
+29 -12
View File
@@ -2,8 +2,36 @@ name: Workshop
on:
pull_request:
paths:
- ".github/workflows/workshop.yml"
- "apps/workshop/**"
- "apps/docs/**"
- "packages/custom-widgets/**"
- "packages/definitions/**"
- "packages/workshop/**"
- "tooling/typescript/**"
- "patches/**"
- "package.json"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "turbo.json"
- ".npmrc"
push:
branches: [dev, v2, release/v2, main]
paths:
- ".github/workflows/workshop.yml"
- "apps/workshop/**"
- "apps/docs/**"
- "packages/custom-widgets/**"
- "packages/definitions/**"
- "packages/workshop/**"
- "tooling/typescript/**"
- "patches/**"
- "package.json"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "turbo.json"
- ".npmrc"
permissions:
contents: read
@@ -15,21 +43,10 @@ concurrency:
jobs:
validate:
runs-on: ubuntu-24.04
timeout-minutes: 30
timeout-minutes: 5
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version: 24.18.0
- uses: pnpm/action-setup@v6
with:
run_install: false
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @homarr/workshop typecheck
- run: pnpm --filter @homarr/docs typecheck
- run: docker compose --env-file apps/workshop/.env.staging.example -f apps/workshop/docker-compose.staging.yml config --quiet
- run: pnpm test:workshop
- run: pnpm test:workshop-image
publish-amd64:
if: github.event_name == 'push' && (github.ref_name == 'v2' || github.ref_name == 'release/v2' || github.ref_name == 'main')
+6 -1
View File
@@ -10,7 +10,12 @@
"CHANGELOG.md",
"apps/docs/.docusaurus/**",
"apps/docs/build/**",
"apps/docs/blog/**",
"apps/docs/blog/2022/**",
"apps/docs/blog/2023/**",
"apps/docs/blog/2024/**",
"apps/docs/blog/2025/**",
"apps/docs/blog/authors.yml",
"apps/docs/static/api/open-api-schema.json",
"packages/custom-widgets/src/core/component-catalog.generated.json",
"apps/docs/static/custom-widgets/component-catalog-v1.json"
]
+8 -2
View File
@@ -59,7 +59,13 @@ homarr/
The documentation site lives at `apps/docs/` (Docusaurus 3, `@homarr/docs`).
When modifying user-facing code, you MUST also update the corresponding documentation:
Write for smart, curious, advanced users. Documentation should contain only information they cannot reasonably discover or understand directly from the interface: hidden capabilities, non-obvious behavior, prerequisites, constraints, configuration contracts, and migration requirements.
Keep docs concise. Every paragraph must add knowledge beyond visible labels and standard UI conventions. Explain how to discover and use a hidden advanced widget feature; omit explanations that the Delete button deletes an item in edit mode. Avoid UI walkthroughs, exhaustive control descriptions, and repetition. When editing a passage, remove obvious or redundant prose rather than preserving verbosity as a precedent.
Update docs only when a change introduces or alters that non-obvious information, or makes existing guidance inaccurate. Routine fixes and self-explanatory UI changes need no docs update. A new feature alone does not require a page; apply the same reader-value test.
When documentation is needed, use these locations:
- New integration → `apps/docs/docs/integrations/<slug>/index.mdx` + `index.ts`
- New widget → `apps/docs/docs/widgets/<slug>/index.mdx` + `index.ts`
@@ -99,6 +105,6 @@ When modifying user-facing code, you MUST also update the corresponding document
Portable skills live in `.agents/skills/`. Read the relevant `SKILL.md` before working in that domain; detailed references are loaded only when needed. Claude-compatible discovery is provided through `.claude/skills`.
- `codebase-context` — architecture, package boundaries, and shared utilities
- `documentation-sync` — required user-facing documentation updates
- `documentation-sync` — documentation for changes users need explained
- `mcp-integration` — safe tRPC-to-MCP exposure
- `homarr-custom-widget` — safe Custom JSX v2 authoring
Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@@ -0,0 +1,159 @@
---
authors:
- ajnart
tags: [homarr, update, version, assistant, custom widgets, dashboards, docker]
description: Homarr 2.0 introduces Custom Widgets and Workshop, rebuilt board editing, Assistant tooling, responsive layouts, onboarding, and more.
image: ./img/homarr-v2-dashboard.webp
draft: false
---
# Homarr 2.0.0 - Workshop, Custom Widgets and a rebuilt board
First, thank you.
Many of you used the v2 demo when it was still rough, found bugs and came back to test the next build. Others translated Homarr, answered questions, contributed code or simply kept using it. We would not be shipping this without you.
We are also planning a small batch of **free, limited-edition Homarr swag** for Workshop contributors and a few randomly picked community members. It is not for sale and it is not for profit. No Homarr merch empire, sorry.
Homarr 2.0 builds on the dashboard you already use. Here is what changed.
![A populated Homarr 2.0 dashboard with compact widgets, an Assistant, and an application rail](./img/homarr-v2-dashboard.webp)
<!-- truncate -->
Update your Homarr image as usual. Homarr runs database migrations automatically when the updated container starts; no manual database migration or separate v2 upgrade procedure is needed. Workshop uses the production service by default, with no preview URL configuration required.
## Custom Widgets v2 and Workshop
### Build almost any widget
**This is the big one.** We think it is the most important Homarr update we have made.
Custom Widgets v2 lets you build widgets from JSX, API requests, actions and typed settings. Write one yourself or describe it to Assistant, then test it in the workbench. We think this is enough to make almost any dashboard widget—even that oddly specific component only three people need. Those three people no longer have to wait for us.
This was partly inspired by [Google's upcoming Create My Widget feature](https://blog.google/products-and-platforms/platforms/android/gemini-intelligence/): describe a widget in normal language and let AI make it. Coding knowledge is optional. Knowing what the API does still helps; AI has not fixed bad documentation. Yet.
![The Custom Widget v2 workbench after a live exchange-rate request successfully renders its preview](./img/custom-widget-workbench.webp)
A `homarr-custom-widget-v2` definition can use several fixed-origin API sources. Credentials are encrypted separately and stay out of exports and Workshop submissions. Actions support `GET`, `POST`, `PUT`, `PATCH` and `DELETE`; requests have host checks, timeouts, size limits, permissions and rate limits. Custom Widgets remain **Beta** in 2.0.
### Share widgets and Custom CSS
**Workshop is the other half.** Publish and install Custom Widgets, then update, vote on, comment on and report submissions. Workshop is not only for widgets: you can share your **Custom CSS** there too. Deployment URLs and credentials stay on your Homarr instance.
![The Community Workshop catalog inside Custom Widget management](./img/community-workshop.webp)
:::warning Workshop is moderated
AI makes widgets easy to produce. It does not make the first thing it coughs up worth publishing. **Do not dump untested slop into Workshop.** Test it, add a useful screenshot and explain its setup. Workshop is moderated.
:::
### The ultimate Homarr update
We have called this the **"ultimate Homarr update"**. Not because work stops, but because every new service or widget idea no longer needs a full Homarr release; the community can build and share it instead.
## Board editing, rebuilt
### Drag and drop
We threw out the old drag-and-drop system and rebuilt it on [`dnd-kit`](https://dndkit.com/). Moves and eight-direction resizes are transactional: Homarr previews the result, checks collisions and only saves a valid layout. Bad moves roll back instead of wrecking the board. In short: less fighting with your own dashboard.
![A Calendar tile moving across the rebuilt Homarr 2.0 board grid](./img/drag-and-drop.gif)
### Goodbye Groups and sections, bonjour Containers
Groups and dynamic sections did nearly the same job, so **Containers replace both**. They hold apps, widgets and other Containers. Hold <kbd>Ctrl</kbd> or <kbd>Command</kbd> to move several items at once.
### One board, several layouts
Boards also have Base and Mobile layouts, plus optional breakpoints. **Reset from Base** creates a Mobile starting point without duplicating the board.
## Homarr Assistant
### Work with live Homarr data
Assistant reads live Homarr data and has tools for boards, apps, integrations, widgets, Docker and supported services. It can answer questions, make approved changes and build Custom Widgets. It follows your permissions: reads can run automatically, while changes require approval by default.
![The Homarr Assistant expanded over a live dashboard with health, exploration, media, and board actions](./img/assistant-control-surface.webp)
Open it with <kbd>Shift</kbd> + <kbd>A</kbd>. <kbd>Cmd</kbd>/<kbd>Ctrl</kbd> + <kbd>/</kbd> searches it.
### The Homarr provider
:::note
Assistant ships with a **"Homarr provider"**. It is free, will never offer paid plans, and is paid for by [Ajnart](https://github.com/ajnart) using the ad revenue from this website.
It only uses zero-data-retention (ZDR) providers. [The provider is open source](https://github.com/homarr-labs/homarr/blob/release/v2/apps/workshop/homarr_provider.go), and neither prompts nor responses are logged. We are not selling your data. Pinky promise.
:::
### External assistants through MCP
External assistants can use the same permission-checked tools through `/api/mcp`, with an API key or OAuth 2.1 with PKCE.
## The rest (there is a lot)
### Brand-new onboarding
A six-stage setup studio handles the first admin, core settings, Docker discovery, apps, integrations and the first board.
![The Homarr 2.0 setup studio on its Essentials step, with all six setup stages shown](./img/onboarding-studio.webp)
### Board switcher
Boards now live in a searchable gallery with keyboard navigation. Open it from the header or press <kbd>Shift</kbd> + <kbd>C</kbd>.
![A visual board card inside the searchable Homarr board switcher](./img/board-switcher.webp)
### Command menu and keybinds
<kbd>Cmd</kbd>/<kbd>Ctrl</kbd> + <kbd>K</kbd> finds resources, pages, settings, users, groups and commands. More
keybinds cover Assistant, editing, selection and advanced widget views.
### Customizable header
Pick which buttons appear, put them on the left, center or right, and change their order.
![The Header studio showing left, center, and right zones plus available controls](./img/header-studio.webp)
### Instance branding
Change the name, logo, favicon, colors, corner styles and login page. Global CSS applies instance-wide; board CSS can override it. Only give that power to admins you trust. CSS remains CSS.
![Instance branding controls for application identity, palette, corner style, and global CSS](./img/instance-branding.webp)
### Widgets
Add content without leaving the board. Settings have live previews, and supported widgets have advanced views. Weather and Clock were redesigned; Air Quality, Countdown and Timer are new.
### Docker and Podman
`DOCKER_ENDPOINTS` supports several hosts. Assisted setup and container labels can create apps, integrations, Containers, widgets and health checks.
![Assisted Docker setup listing discovered services that can become apps or integrations](./img/docker-assisted-setup.webp)
Homepage-compatible labels work too.
### Permissions
Groups use a new Viewer, Editor and Admin matrix. The API, Assistant and MCP follow the same rules. LDAP can create users on first sign-in; OIDC can link verified accounts and synchronize groups.
### Performance
Board startup work runs in parallel, heavy screens load when opened, and identical integration requests share work instead of doing it twice.
## Compatibility notes
- Legacy Custom JSX definitions enter a migration state. Homarr keeps encrypted credentials and an archived copy until the v2 definition validates.
- Mobile layouts place rail content in the main flow instead of displaying rails.
- Custom Widgets remain Beta.
- Assistant and MCP do not bypass permissions.
## Links
- Try the [live demo](https://demo.homarr.dev/).
- See [installation and update instructions](/docs/getting-started/installation/docker).
- Browse [Workshop](https://homarr.dev/workshop).
- Read the guides for [Boards](/docs/management/boards), [Custom Widgets](/docs/management/custom-widgets), [Assistant](/docs/management/assistant), [MCP](/docs/management/mcp) and [Docker labels](/docs/integrations/docker-labels).
@@ -90,7 +90,7 @@ homarr run dev
homarr run --detach dev
homarr run --pr 6441
homarr run --pr 6441 --demo
homarr --pr 6441 --env WORKSHOP_WEB_URL=https://app-v2.preview.homarr.dev/
homarr --pr 6441 --env WORKSHOP_WEB_URL=https://workshop.example.com/workshop
```
- `--pr <number>`: Pull and run a PR image from GHCR (shorthand: `homarr --pr <number>`).
@@ -148,13 +148,18 @@ homarr images rm feature
# List data volumes with owner PR/tag and in-use status
homarr data
# Delete data volume (safely stops and removes owning container first)
# Preview deleting a data volume and its owning container
homarr data rm homarr_feature_data
# Permanently delete the data after reviewing the preview
homarr data rm homarr_feature_data --force
# Remove stopped containers (prompts without --force)
homarr prune --force
```
Forced data deletion removes the derived owner container only when it mounts the exact selected volume. A same-named container that does not own that volume stops the operation.
### Instance Operations & Diagnostics
```bash
@@ -14,4 +14,4 @@ homarr fix-usernames
This will convert all credential usernames to lowercase and update the database accordingly.
![](./img/screenshot-cli.png)
![Terminal output confirming credential usernames were converted to lowercase](./img/screenshot-cli.png)
Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

+26 -1
View File
@@ -4,6 +4,7 @@ The Homarr image includes a recovery CLI for operations that cannot be completed
application API; use the [API](/docs/management/api) or [MCP endpoint](/docs/management/mcp) for automation.
Back up the database and stop Homarr before running a command that changes data.
Failed commands print an error and return a non-zero exit code, so shell scripts can stop safely.
## Run the recovery CLI
@@ -14,7 +15,31 @@ docker exec -it <container-id> /bin/bash
homarr <command>
```
In QNAP Container Station, open the Homarr container's **Execute** action and select `/bin/bash`.
### QNAP Container Station
1. Open **Container Station**.
![Container Station in the QNAP application menu](./img/334116069-e7ec4726-3df7-4dc6-a3e0-b745e7eef17f.png)
2. Select **Containers**.
![Containers menu in QNAP Container Station](./img/334116205-aa09ce40-0701-4f7d-b9f9-b431b9d207c5.png)
3. Select the Homarr container.
![Homarr container selected in QNAP Container Station](./img/334116303-714b5e07-b9de-4ec0-a2bc-def7ee38750f.png)
4. Select **Execute**.
![Execute action for the Homarr container in QNAP Container Station](./img/334116411-df1482f9-3aa9-4686-b77f-e0dca09e60b2.png)
5. Select `/bin/bash`, then select **Execute**.
![Bash selected in the QNAP execute console](./img/334116518-e1ddb515-99c7-4159-81bf-76e889d1e808.png)
6. Run `homarr <command>` in the console.
![Bash console open for Homarr in QNAP Container Station](./img/334116642-08c4f204-5cd0-4e4e-a785-0142e1b279ca.png)
See the pages in this section for available commands.
@@ -10,10 +10,10 @@ Using this command, you can forcefully reset the password of a user.
It will be set to a random value and the hash for the user will be updated.
All sessions for that user will be terminated and the user must re-login.
![](./img/screenshot-cli.png)
![Reset-password command help showing that username is required](./img/screenshot-cli.png)
Using the `--username` parameter, you can specify who you want to reset the password for.
Executing the command will create a new random password, update the database and display the new password.
All sessions for this user will be terminated and they must re-login.
![](./img/screenshot-cli-result.png)
![Terminal output confirming a password reset and session deletion](./img/screenshot-cli-result.png)
@@ -31,9 +31,13 @@ pnpm install --frozen-lockfile
### Run the Homarr application
1. Copy `.env.example` to `.env`.
2. Set `DB_URL` to an absolute writable SQLite file path.
3. Keep Redis and the optional local databases running in one terminal.
4. Apply migrations and start Next.js in another terminal.
2. Run `openssl rand -hex 32` twice. Set `AUTH_SECRET` and `SECRET_ENCRYPTION_KEY` to the two different generated
values in `.env`; the example leaves both empty. Keep this file private and preserve `SECRET_ENCRYPTION_KEY`
when reusing a database.
3. Set `DB_URL` to an absolute writable SQLite file path. Migration and application commands run from different
workspace directories, so a relative path can point them at different databases.
4. Keep Redis and the optional local databases running in one terminal.
5. Apply migrations and start Next.js in another terminal.
```sh
cp .env.example .env
@@ -57,6 +61,20 @@ Homarr becomes the instance administrator.
Use `pnpm docker:dev:up` instead when you only need Redis in the background. Running the migration also seeds the
default data; use `pnpm db:seed` to seed it explicitly later.
`pnpm dev` starts only Next.js. When your work needs scheduled tasks or live subscriptions, run the relevant service
in a separate terminal alongside it:
```sh
pnpm --filter @homarr/tasks dev
```
```sh
pnpm --filter @homarr/websocket dev
```
The WebSocket service listens on port 3001. Production startup embeds both services, so these extra commands are
only needed for development.
Workshop contributors can set up PocketBase and Docusaurus with the dedicated
[Workshop development guide](./workshop).
@@ -80,5 +98,27 @@ Workshop contributors can set up PocketBase and Docusaurus with the dedicated
- `pnpm typecheck` — typecheck all workspaces.
- `pnpm test` — unit and contract suites.
- `pnpm build` — production monorepo build.
- `docker build -t homarr .` — build a production Docker image.
- `docker run -p 7575:7575 -e SECRET_ENCRYPTION_KEY='your_64_character_hex_string' homarr:<name>` — run it.
- `docker build -t homarr:local .` — build a production Docker image.
- `docker run -p 7575:7575 -e SECRET_ENCRYPTION_KEY='your_64_character_hex_string' homarr:local` — run it.
## CI and focused validation
The main CI workflow runs lint, typechecking, affected workspace builds, workspace configuration checks, OpenAPI validation, and Custom Widget architecture and bundle checks. Unit and E2E suites do not run in CI. Preview images publish after the Fast gate and both architecture builds succeed; browser tests do not block publication. The container job builds an amd64 image when no preview image was built.
Workshop CI runs only when its workflow or Docker build inputs change. It validates Compose configuration and publishes production images on its configured release branches. Its Docker integration and image tests are manual; workspace typechecks run in the main Fast gate.
Choose the smallest relevant suite when changing behavior:
| Command | Scope | Requirements |
| ------------------------------------------------- | -------------------------------------------------------------- | --------------------------------------------------------------------- |
| `pnpm test <file>` | Focused unit or contract tests | Workspace dependencies |
| `pnpm test` | All unit and contract tests, without Docker integration suites | Workspace dependencies |
| `pnpm test:coverage` | Unit and contract tests with coverage | Workspace dependencies |
| `pnpm test:integration <file>` | Docker-backed service and database compatibility tests | Docker |
| `pnpm test:e2e <file>` | Browser and container scenarios | Docker; Playwright Chromium and system dependencies for browser tests |
| `pnpm test:docs-screenshots` | Regenerate Assistant documentation screenshots | Same browser prerequisites |
| `pnpm test:workshop` / `pnpm test:workshop-image` | Workshop integration / production-image validation | Docker |
Omit `<file>` to run all tests in that command's scope. E2E and screenshot commands use a local image tagged `homarr-e2e`, or an image selected with `HOMARR_E2E_IMAGE`. The default commands run once without automatic retries; coverage is opt-in. The screenshot generator is excluded from both unit and E2E commands.
Keep behavioral coverage for authorization, migrations, persistence, and widget execution. Avoid tests that only search source code for particular component names, JSX, CSS, or documentation wording.
@@ -30,7 +30,8 @@ Using the `PUID` and `PGID` will require you to set the correct permissions on t
| `PUID` | User ID to run the container as | Any valid user ID | `0` |
| `PGID` | Group ID to run the container as | Any valid group ID | `0` |
| `LOG_LEVEL` | Log level to use | `debug` / `info` / `warn` / `error` | `info` |
| `NO_EXTERNAL_CONNECTION` | Disables Homarr's internet-backed requests, including update checks, weather, and RSS feeds | `true` or `false` | `false` |
| `BASE_URL` | Public Homarr origin used by MCP OAuth metadata and redirects. Set this when a reverse proxy does not preserve the public host and protocol. | HTTP(S) origin without a path | - |
| `NO_EXTERNAL_CONNECTION` | Disables Homarr's internet-backed requests, including update checks, weather, location search, and RSS feeds | `true` or `false` | `false` |
| `ENABLE_DNS_CACHING` | Enables dns caching. Enabled by default, but if you experience issues with IPv6 or static IPs, see [#4006](https://github.com/homarr-labs/homarr/issues/4006) or disable this flag | `true` or `false` | `true` |
| `HOMARR_DISABLE_IPV6` | Disables the IPv6 listener of the internal nginx proxy, even when the host supports IPv6. IPv6 is normally disabled automatically when the host has no IPv6 configured. See [#4596](https://github.com/homarr-labs/homarr/issues/4596) | `true` or `false` | `false` |
| `HOMARR_WEBSITE_URL` | Main Homarr documentation/site base used by runtime links. Must be an absolute HTTP(S) URL. | HTTP(S) URL | `https://homarr.dev` |
@@ -53,20 +53,6 @@ Commands at a grid boundary or at the tile's minimum size are ignored and announ
On touch screens, press and hold a tile before moving it; a quick tap does not start a drag. Resize handles accept direct touch input. During a pointer or touch move or resize, `Esc` cancels the interaction and restores the layout from before it started.
## Board editing
When a tile is focused in edit mode:
| Shortcut | Description |
| -------------------- | --------------------------------------------------------------------------- |
| `Enter` or `Space` | Starts or stops keyboard editing |
| Arrow keys | Moves the focused tile within its current canvas, rail, or Container |
| `Shift` + Arrow keys | Resizes from the tile's lower-right corner: Left/Up shrink; Right/Down grow |
| `Esc` | Stops keyboard editing without undoing its changes |
Keyboard moves and resizes apply immediately. To move an item between a canvas, rail, or Container, drag it there or use **Move / resize item** for precise numeric placement.
Commands at a grid boundary or at the tile's minimum size are ignored and announced instead of moving or shrinking the tile.
:::tip
Here `MOD` refers to the modifier key, which is `Ctrl` on Windows and `Cmd` on macOS.
+1 -1
View File
@@ -40,7 +40,7 @@ services:
volumes:
- ./homarr/appdata:/appdata
environment:
- BASE_URL=your.internal.dns.address.here.com
- BASE_URL=https://your.internal.dns.address.here.com
- SECRET_ENCRYPTION_KEY=your_64_character_hex_string # <--- can be generated with `openssl rand -hex 32`
networks:
- proxy
@@ -202,9 +202,9 @@ Homarr supports multiple authentication options, from internal userbase (credent
| ``AUTH_OIDC_CLIENT_NAME`` | Display name of provider (in login screen) | OIDC |
| ``AUTH_OIDC_AUTO_LOGIN`` | Automatically redirect to OIDC login | false |
| ``AUTH_OIDC_SCOPE_OVERWRITE`` | Overwrite default scopes (openid, profile, email) | openid email profile groups |
| ``AUTH_OIDC_GROUPS_ATTRIBUTE`` | Attribute used for groups (roles) claim | groups |
| ``AUTH_OIDC_GROUPS_ATTRIBUTE`` | Attribute used for groups (roles) claim. Dot-separated paths such as `resource_access.homarr.roles` are supported. | groups |
| ``AUTH_OIDC_GROUPS_LOCAL_MANAGEMENT`` | Stop syncing group memberships of OIDC users from the groups claim and manage them locally via the group members page instead | false |
| ``AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE`` | Overwrite name attribute. By default it will use preferred_username if it does not contain a `@` and otherwise name. | --- |
| ``AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE`` | Overwrite name attribute. Dot-separated paths are supported. By default it will use preferred_username if it does not contain a `@` and otherwise name. | --- |
| ``AUTH_OIDC_FORCE_USERINFO`` | Force userinfo endpoint to be used for user information. | false |
| ``AUTH_OIDC_ENABLE_DANGEROUS_ACCOUNT_LINKING`` | Enable account linking for OIDC provider. This will link the OIDC accounts by email. Be sure that you have verified emails to prevent stealing accounts. | false |
| ``AUTH_OIDC_ENABLE_DANGEROUS_CREDENTIALS_LINKING`` | Link an OIDC identity to an existing credentials user with the same email. The credentials login and existing user data are preserved. Requires the OIDC profile to contain `email_verified: true`. | false |
@@ -244,18 +244,21 @@ Homarr supports multiple authentication options, from internal userbase (credent
- Create a group (e.g., `homarr-admins`) and assign users who require admin access.
**2. Example .env file:**
Generate `SECRET_ENCRYPTION_KEY` with `openssl rand -hex 32`; do not copy an example key between installs.
```env
OIDC_CLIENT_ID=identificationid #OIDC client ID
OIDC_CLIENT_SECRET=secretsecretsecret #OIDC client secret
OIDC_CLIENT_ID=<REPLACE_WITH_OIDC_CLIENT_ID>
OIDC_CLIENT_SECRET=<REPLACE_WITH_OIDC_CLIENT_SECRET>
OIDC_SLUG=homarr #Application slug in Authentik
AUTH_DOMAIN=auth.example.com #Authentik FQDN
ADMIN_GROUP=homarr-admins #Authentik group for Homarr admins
HOMARR_FQDN=homarr.example.com #Homarr FQDN
SECRET_ENCRYPTION_KEY=encryptencryptencrypt #Homarr encryption key
SECRET_ENCRYPTION_KEY=<REPLACE_WITH_64_HEX_CHARACTERS>
```
**3. Example Docker Compose configuration:**
```yaml {14-26}
```yaml {14-24}
services:
homarr:
image: ghcr.io/homarr-labs/homarr:latest
@@ -269,8 +272,6 @@ Homarr supports multiple authentication options, from internal userbase (credent
environment:
- TZ=America/Los_Angeles
- SECRET_ENCRYPTION_KEY=${SECRET_ENCRYPTION_KEY}
- BASE_URL=https://${HOMARR_FQDN}
- NEXTAUTH_URL=https://${HOMARR_FQDN}
- AUTH_PROVIDERS=oidc #(optional: include ',credentials' to keep local accounts as fallback)
- AUTH_OIDC_CLIENT_ID=${OIDC_CLIENT_ID}
- AUTH_OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET}
@@ -322,15 +323,18 @@ Homarr supports multiple authentication options, from internal userbase (credent
- Record the `Client-ID` and `Client Key` (secret).
**2. Example .env file (Homarr):**
Generate `SECRET_ENCRYPTION_KEY` with `openssl rand -hex 32`; do not copy an example key between installs.
```env
OIDC_CLIENT_ID=identificationid #Google Client-ID
OIDC_CLIENT_SECRET=secretsecretsecret #Google Client Key (secret)
OIDC_CLIENT_ID=<REPLACE_WITH_GOOGLE_CLIENT_ID>
OIDC_CLIENT_SECRET=<REPLACE_WITH_GOOGLE_CLIENT_SECRET>
HOMARR_FQDN=homarr.example.com #Homarr FQDN
SECRET_ENCRYPTION_KEY=encryptencryptencrypt #Homarr encryption key
SECRET_ENCRYPTION_KEY=<REPLACE_WITH_64_HEX_CHARACTERS>
```
**3. Example Docker Compose configuration (Homarr):**
```yaml {16-22}
```yaml {14-20}
services:
homarr:
image: ghcr.io/homarr-labs/homarr:latest
@@ -344,8 +348,6 @@ Homarr supports multiple authentication options, from internal userbase (credent
environment:
- TZ=Europe/Berlin
- SECRET_ENCRYPTION_KEY=${SECRET_ENCRYPTION_KEY}
- BASE_URL=https://${HOMARR_FQDN}
- NEXTAUTH_URL=https://${HOMARR_FQDN}
- AUTH_PROVIDERS=oidc #(optional: include ',credentials' to keep local accounts as fallback)
- AUTH_OIDC_CLIENT_ID=${OIDC_CLIENT_ID}
- AUTH_OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET}
+9 -8
View File
@@ -1,31 +1,32 @@
---
title: Frequently Asked Questions
description: Answers to common Homarr questions about installation, customization, connectivity, support, and commercial use.
tags:
- Community
- Help
---
### Can Homarr run on a Raspberry Pi?
## Can Homarr run on a Raspberry Pi?
Yes, when the device uses a supported `linux/arm64` operating system. `linux/arm/v7` is not supported. See
[Prerequisites](/docs/getting-started).
### Can I use custom app icons?
## Can I use custom app icons?
Yes. Use a direct image URL, one of Homarr's icon sources, or upload an image. See [Icons](/docs/advanced/icons).
### Can I create widgets?
## Can I create widgets?
Yes. [Custom Widgets](/docs/management/custom-widgets) use server-side API requests and safe JSX. They can be written in
the workbench or created with an MCP-connected agent.
### Can Homarr be exposed to the internet?
## Can Homarr be exposed to the internet?
Homarr supports authenticated and public deployments. Terminate HTTPS at a reverse proxy, keep Homarr updated, and grant
public board access only when anonymous viewing is intended. See [Board access control](/docs/management/boards#access-control)
and [Single sign-on](/docs/advanced/single-sign-on).
### Where are the logs?
## Where are the logs?
Use **Management → Tools → Logs** for retained and live application logs. Container startup failures and older events are
available through the deployment platform, for example `docker container logs homarr`.
@@ -33,18 +34,18 @@ available through the deployment platform, for example `docker container logs ho
Use the browser developer console only for client-side failures. Review logs before sharing them because URLs and other
deployment details may be present.
### Why can Homarr not reach a service behind Gluetun?
## Why can Homarr not reach a service behind Gluetun?
Gluetun's firewall blocks traffic that is not explicitly allowed. Configure its
[`FIREWALL_OUTBOUND_SUBNETS`](https://github.com/qdm12/gluetun-wiki/blob/main/setup/options/firewall.md) for the relevant
LAN or Docker subnet, and use an address reachable from the Homarr container.
### Where do I report a bug or request a feature?
## Where do I report a bug or request a feature?
Use the [GitHub issue templates](https://github.com/homarr-labs/homarr/issues/new/choose). For community support, see
[Get in touch](/docs/community/get-in-touch).
### Can Homarr be used commercially?
## Can Homarr be used commercially?
Yes. Homarr is licensed under the [Apache License 2.0](/docs/community/license). Commercial users can support development
through [OpenCollective](/docs/community/donate).
@@ -19,7 +19,7 @@ If onboarding returns after a restart, check that `/appdata` is writable and per
A **board** is a dashboard page containing apps, widgets, and Containers. Boards can be public or restricted to users
and groups. Each user can choose a home board.
![Board management with board previews](./img/after-installation-boards.png)
![Board management with board previews](./img/after-installation-boards.webp)
See [Boards](/docs/management/boards) for responsive layouts, access control, appearance, and home-board settings.
@@ -28,7 +28,7 @@ See [Boards](/docs/management/boards) for responsive layouts, access control, ap
An **app** is a saved shortcut: a name, URL, icon, and optional status check. Add an App tile or a Bookmarks widget to
show apps on a board.
![App management with saved shortcuts](./img/after-installation-apps.png)
![App management with saved shortcuts](./img/after-installation-apps.webp)
See [Apps](/docs/management/apps) or [Icons](/docs/advanced/icons).
@@ -37,7 +37,7 @@ See [Apps](/docs/management/apps) or [Icons](/docs/advanced/icons).
An **integration** is a server-side connection from Homarr to a supported service such as Sonarr, Home Assistant, or
Docker. Integrations provide data and actions to widgets. They can also be linked to apps.
![Integration catalog with compatible widgets](./img/after-installation-integrations.png)
![Integration catalog with compatible widgets](./img/after-installation-integrations.webp)
See [Managing integrations](/docs/management/integrations) and the
[integration catalog](/docs/category/integrations).
@@ -47,7 +47,7 @@ See [Managing integrations](/docs/management/integrations) and the
A **widget** is a board tile. Some widgets work on their own; others require one or more integrations. Configuration,
supported integrations, and non-obvious limitations are documented on each widget page.
![Board in edit mode with the add-content menu](./img/after-installation-widgets.png)
![Board in edit mode with the add-content menu](./img/after-installation-widgets.webp)
See the [widget catalog](/docs/category/widgets).
@@ -56,7 +56,7 @@ See the [widget catalog](/docs/category/widgets).
A **Container** groups board items and can be nested. A **rail** reserves a fixed area at the left or right of a
responsive layout. Both are configured in board edit mode.
![Board layout settings with fixed sidebars](./img/after-installation-containers-rails.png)
![Board layout settings with fixed sidebars](./img/after-installation-containers-rails.webp)
See [Boards](/docs/management/boards#containers-and-rails).
@@ -65,7 +65,7 @@ See [Boards](/docs/management/boards#containers-and-rails).
Users inherit permissions and default boards from their groups. Resource-specific access can further restrict boards
and integrations.
![Group permission settings](./img/after-installation-users-permissions.png)
![Group permission settings](./img/after-installation-users-permissions.webp)
See [Users and groups](/docs/management/users) and [single sign-on](/docs/advanced/single-sign-on).
Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 527 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

@@ -24,6 +24,10 @@ see [Managing integrations](/docs/management/integrations#configuration).
### Update
Update the image and recreate the container with the same configuration. Homarr runs database migrations automatically
at startup, including when moving from v1 to v2; no separate upgrade procedure is needed. Keep the `/appdata` mount and
`SECRET_ENCRYPTION_KEY` unchanged. Workshop connects to the production service by default.
```sh
docker compose pull
docker compose up -d
@@ -2,9 +2,9 @@
<img src="https://raw.githubusercontent.com/homarr-labs/charts/refs/heads/main/charts/homarr/icon.svg" align="right" width="92" alt="homarr logo">
![Version: 8.28.1](https://img.shields.io/badge/Version-8.28.1-informational?style=flat)
![Version: 8.28.2](https://img.shields.io/badge/Version-8.28.2-informational?style=flat)
![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat)
![AppVersion: v1.76.1](https://img.shields.io/badge/AppVersion-v1.76.1-informational?style=flat)
![AppVersion: v1.76.2](https://img.shields.io/badge/AppVersion-v1.76.2-informational?style=flat)
A Helm chart to deploy homarr for Kubernetes
@@ -437,7 +437,7 @@ All available values are listed on the [artifacthub](https://artifacthub.io/pack
| httproute.rules[0].matches[0].path.value | string | `"/"` | Path value to match |
| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
| image.repository | string | `"ghcr.io/homarr-labs/homarr"` | Image repository |
| image.tag | string | `"v1.76.1"` | Overrides the image tag whose default is the chart appVersion |
| image.tag | string | `"v1.76.2"` | Overrides the image tag whose default is the chart appVersion |
| imagePullSecrets | list | `[]` | Secrets for Docker registry |
| ingress.annotations | object | `{}` | Ingress annotations |
| ingress.enabled | bool | `false` | Enable ingress |
@@ -2,4 +2,4 @@
Pika Pods is a hosting platform for open source apps. You can deploy Homarr on Pika Pods with just a few clicks:
[_![Deploy on PikaPods](https://www.pikapods.com/static/run-button.svg)_](https://www.pikapods.com/pods?run=homarr-v1)
[_![Deploy on PikaPods](https://www.pikapods.com/static/run-button.svg)_](https://www.pikapods.com/pods?run=homarr)
@@ -21,8 +21,8 @@ This method is only useful, when you want to develop on Homarr or extend it with
## Prerequisites
- [Node.js](https://nodejs.org/en/download/)
- [pnpm](https://pnpm.io/installation)
- [Node.js](https://nodejs.org/en/download/) 24.18 or newer
- [pnpm](https://pnpm.io/installation) at the version pinned in the repository's `package.json`
- [Redis](https://redis.io/download)
## Steps
@@ -38,13 +38,14 @@ This method is only useful, when you want to develop on Homarr or extend it with
Install all dependencies using <code>pnpm install</code>
</li>
<li>
Copy the `.env.example` file to `.env` and replace `DB_URL` with the full path you want to use for the sqlite
database file. For example `DB_URL='/home/username/homarr/db.sqlite`
Copy `.env.example` to `.env` and set `DB_URL` to an absolute writable path, such as
`DB_URL='/home/username/homarr/db.sqlite'`. Migration and application commands run from different workspace
directories, so a relative path can point them at different databases.
</li>
<li>
Replace `AUTH_SECRET` and `SECRET_ENCRYPTION_KEY` both with a secure random string by using `openssl rand -hex 32`
Run `openssl rand -hex 32` twice and set `AUTH_SECRET` and `SECRET_ENCRYPTION_KEY` to the two different generated
values. Keep both values secret and preserve `SECRET_ENCRYPTION_KEY` with your backups.
</li>
<li>Replace `CRON_JOB_API_KEY` with a secure random string by using `openssl rand -base64 32`</li>
<li>Run `pnpm run db:migration:sqlite:run` and wait that it completes</li>
<li>Build the source using `pnpm build`</li>
<li>
@@ -30,7 +30,7 @@ You can install Homarr directly from your Unraid Dashboard, no terminal required
Don't know whether you have the plugin installed or not? Search for this tab in the navigation:
![](img/unraid-navigation-app-manager.webp)
![Unraid navigation with the Apps tab highlighted](img/unraid-navigation-app-manager.webp)
:::
#### Installing
@@ -41,7 +41,7 @@ After you've installed the Community Apps Plugin, you can install Homarr from th
2. Search for "Homarr" and click on the result.
3. Click on "Install" and adjust the settings to your liking.
![](img/unraid-apps-homarr.webp)
![Homarr result in Unraid Community Apps with installation actions](img/unraid-apps-homarr.webp)
After you've installed Homarr, you can find it under the tab "Docker".
+1 -1
View File
@@ -5,6 +5,6 @@
"link": {
"type": "generated-index",
"title": "Integrations",
"description": "Integration are a principal part of homarr, it's where you can connect your other apps to interact with them with widgets or other capabilities, Homarr currently integrates with the following applications:"
"description": "Connect Homarr to supported services so widgets can display data and run actions. Choose an integration to see its setup requirements and compatible widgets."
}
}
@@ -0,0 +1,55 @@
---
title: "llama.cpp"
description: "Connect Homarr to a llama.cpp llama-server to monitor your local LLM: health, loaded model, generation speed and request status."
hide_title: true
---
import { IntegrationHeader } from "@site/src/components/integrations/header";
import { IntegrationCapabilites } from "@site/src/components/integrations/widgets";
import { AddingIntegration } from "@site/src/components/integrations/adding";
import { IntegrationSecrets } from "@site/src/components/integrations/secrets";
import { llamacppIntegration } from ".";
import { llamacppWidget } from "@site/docs/widgets/llama-cpp";
<IntegrationHeader integration={llamacppIntegration} categories={["System Monitoring"]} />
The llama.cpp integration lets Homarr connect to a [llama-server](https://github.com/ggml-org/llama.cpp) instance so you can see the health of your local LLM, which model is loaded, and how fast it is generating — all from your dashboard.
### Widgets & Capabilities
<IntegrationCapabilites items={[{ widget: llamacppWidget }]} />
### Adding the integration
<AddingIntegration />
:::info URL configuration
Use the root URL of your `llama-server`, for example `http://192.168.1.50:8080`. Homarr calls the `/health`, `/v1/models`, `/metrics` and `/slots` endpoints under that URL automatically.
:::
:::tip Metrics
Generation speed and token counters are read from the Prometheus endpoint at `/metrics`. Start `llama-server` with the `--metrics` flag so these values are available:
```shell
llama-server --model /path/to/model.gguf --metrics
```
Without `--metrics` the widget still shows health, model information, and active-request speed from `/slots`; metrics-backed counters and aggregate speed stay empty.
:::
:::info Context and per-request speed
Context (KV cache) usage and the in-flight request data are read from the `/slots` endpoint, which is available on every recent llama-server build and requires no extra flags. The per-request generation speed shown while a request is running is derived by tracking the request's decoded token count across polls, so it reflects the average speed of the request that is currently being generated.
:::
### Secrets
llama-server does not require authentication by default. The integration only needs the URL of your server.
<IntegrationSecrets
secrets={[
{
credentials: [],
steps: [],
},
]}
/>
@@ -0,0 +1,9 @@
import { IntegrationDefinition } from "@site/src/types";
export const llamacppIntegration: IntegrationDefinition = {
name: "llama.cpp",
description:
"Connect to a local llama.cpp llama-server to show model, throughput and request status on your dashboard.",
iconUrl: "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/llama-cpp.svg",
path: "../../integrations/llama-cpp",
};
@@ -16,7 +16,7 @@ import { networkControllerStatusWidget } from "@site/docs/widgets/network-contro
<IntegrationHeader integration={unifiControllerIntegration} categories={["Network controller"]} />
This integration is using the [node-unifi](https://www.npmjs.com/package/node-unifi) library under the hood to connect to the Unifi Controller.
Therfore it should support CloudKey Gen1, CloudKey Gen2, UnifiOS-based UDM-Pro controllers as well as self-hosted UniFi controllers.
Therefore it should support CloudKey Gen1, CloudKey Gen2, UnifiOS-based UDM-Pro controllers as well as self-hosted UniFi controllers.
### Widgets & Capabilities
@@ -28,6 +28,14 @@ Therfore it should support CloudKey Gen1, CloudKey Gen2, UnifiOS-based UDM-Pro c
<AddingIntegration />
Use the local HTTPS address of your controller:
- UniFi OS consoles such as Dream Router, Dream Machine, Cloud Gateway, and CloudKey use `https://<host>` (port 443).
- Self-hosted UniFi Network Servers normally use `https://<host>:8443`.
- If you omit the port, Homarr tries port 443 first and then port 8443 when the first port cannot be reached.
Use a local administrator account. Cloud-only accounts and accounts with multi-factor authentication are not supported by the underlying client.
### Secrets
<IntegrationSecrets
@@ -24,12 +24,14 @@ The What's Up Docker integration lets Homarr connect to your [WUD](https://githu
<AddingIntegration />
:::info URL configuration
Use the root URL of your WUD instance, for example `https://wud.example.com`. Homarr calls the `/api/app` and `/api/containers` endpoints under that URL automatically.
Use the root URL of your WUD instance, for example `https://wud.example.com`. Homarr uses `/api/containers` under that URL for both connection testing and widget data.
:::
### Secrets
WUD does not require authentication by default. If your instance is protected with [Basic Authentication](https://github.com/getwud/wud/blob/main/docs/configuration/authentications/basic/README.md), you can provide a username and password.
WUD 9 requires authentication. Enter your WUD username and password in Homarr; the initial admin credentials configured with `WUD_AUTH_ADMIN_USER` and `WUD_AUTH_ADMIN_PASSWORD` work with Basic authentication. Older WUD versions without authentication can leave both fields empty.
Credentials are sent to the configured URL over HTTP or HTTPS. Use HTTPS outside a trusted internal network.
<IntegrationSecrets
secrets={[
@@ -40,7 +42,7 @@ WUD does not require authentication by default. If your instance is protected wi
{
credentials: ["username", "password"],
steps: [
"Open your WUD instance's configuration and configure a WUD_AUTH_BASIC_* user as described in the WUD documentation",
"Use your WUD account credentials; older versions can use a configured WUD_AUTH_BASIC_* user",
"Enter that username and password in Homarr — Basic authentication is handled automatically",
],
},
+22 -1
View File
@@ -18,7 +18,7 @@ Create an API key from the **Authentication** tab. Its value has the form `<id>.
Send it in the `ApiKey` header:
```sh
curl -H 'ApiKey: <id>.<token>' https://homarr.example.com/api/health/live
curl -H 'ApiKey: <id>.<token>' https://homarr.example.com/api/info
```
The same header authenticates tRPC requests under `/api/trpc`. Without a key, browser requests can use the Homarr
@@ -27,6 +27,27 @@ session cookie.
API keys do not expire automatically and act as the user that created them. Every request uses that user's group and
resource permissions.
## Invitation dates
`POST /api/invites` and the tRPC `invite.createInvite` procedure accept `expirationDate` as an ISO 8601 timestamp with a
timezone, for example `2026-12-01T18:00:00Z`. Homarr validates the timestamp and stores it as a date. tRPC callers should
send the ISO string rather than a JavaScript `Date` object.
## Health probes
The liveness endpoint does not require authentication:
```sh
curl https://homarr.example.com/api/health/live
```
It returns JSON with overall and per-component status and latency. HTTP `200` means every dependency is healthy; HTTP
`500` identifies an unhealthy component without exposing its internal error. Health responses are not cached.
## Pagination
General paginated endpoints accept at most 100 records per page, and shared search endpoints accept a limit of at most 100. Request subsequent pages instead of using a larger value.
## Permission behavior
Unauthorized operations return `FORBIDDEN`. List endpoints filter records on the server when resource-specific access
+3 -2
View File
@@ -30,8 +30,9 @@ needed to restore encrypted integration credentials.
Select a Homarr backup ZIP. The browser previews entity counts, board names, and required database migrations before
the archive is uploaded.
Restoring replaces the current SQLite database and cannot be undone. After a successful restore, the Next.js process
restarts and the page reloads when Homarr is ready.
Restoring replaces the current SQLite database and cannot be undone. It also invalidates the current database session.
After a successful management restore, Homarr waits for the restarted server and sends the administrator to sign in
again. The first-run onboarding restore keeps its existing board destination.
A backup can be restored on another Homarr instance. When its `SECRET_ENCRYPTION_KEY` differs, Homarr re-encrypts stored
integration secrets for the target instance. Version metadata is used to apply compatible migrations.
@@ -36,6 +36,11 @@ current Base layout.
A **Container** groups apps, widgets, and nested Containers. It can have a label, collapse control, app-opening action,
border color, and CSS classes.
Items can occupy the outermost rows and columns of a Container. The drop preview shows the position where the item
will remain when released.
Dragging an item onto another item of the same size swaps their positions. Displaced items snap directly to their
previewed positions without a movement animation and stay there when you release the pointer.
A **rail** reserves one to three columns at the left or right of a non-Mobile layout. Rails stay fixed while the page
scrolls and store their contents separately for each layout.
@@ -51,6 +56,8 @@ layout.
- Press Escape during a pointer or touch interaction to restore the item to its previous position.
The add-content menu can create an app, integration, widget, or Container without leaving the board.
When adding apps, click one app to add it immediately, or choose **Select multiple** to add several at once. `Ctrl`/`Cmd`
click remains available as a shortcut.
## Board settings
@@ -17,7 +17,7 @@ The color of the icon indicates how long it is valid.
![The Trusted certificates management page](../img/certificates.png)
![](./img/screenshot.png)
![Trusted certificates page listing a certificate's subject, filename, and expiry](./img/screenshot.png)
| Color | Meaning |
| --------- | --------------------------- |
@@ -34,7 +34,7 @@ Navigate to `Management` > `Tools` > `Certificates`.
To add a certificate click on the "Add certificate" button. The upload form expands directly on the certificates page.
![](./img/add-button.png)
![Add certificate button on the Trusted certificates page](./img/add-button.png)
Then select the certificate file you want to add and click the "Add" button. You can cancel to collapse the form without
leaving the page.
@@ -43,7 +43,7 @@ leaving the page.
To delete a certificate you can click on the trash icon in the certificate card.
![](./img/card.png)
![Certificate card with subject, filename, expiry date, and delete action](./img/card.png)
The first click arms the action and the second click confirms the deletion in place; no confirmation dialog is opened.
@@ -29,6 +29,8 @@ encrypted separately and are not included in exports, prompts, browser data, or
Administrators can create, import, duplicate, enable, export, publish, and delete definitions under
**Management → Custom Widgets**. Adding or configuring one on a board also requires permission to modify that board.
Use **Actions → Configure sources** to update deployment-specific URLs, network access, and encrypted credentials
without reopening the authoring workbench.
Once placed, normal board permissions control rendering and requests. Queries can run for anonymous viewers on public
boards; actions require authentication and the permission declared by the request.
@@ -55,5 +57,5 @@ Legacy definitions show **Migration required**. The migration prompt excludes cr
describe the visible behavior separately, import the returned v2 definition, and verify it before removing the archived
version.
Back up the database and `SECRET_ENCRYPTION_KEY` before upgrading or rolling back. Multi-replica installations must use
the same Redis service so cached responses invalidate consistently.
Keep `SECRET_ENCRYPTION_KEY` unchanged when updating Homarr so existing credentials remain readable. Multi-replica
installations must use the same Redis service so cached responses invalidate consistently.
+38 -30
View File
@@ -8,18 +8,17 @@ tags:
# Model Context Protocol (MCP)
Homarr exposes a stateless [Model Context Protocol](https://modelcontextprotocol.io) server at `/api/mcp`. It maps the
current Homarr API to tools for compatible AI clients.
Connect an AI client to Homarr to manage boards, apps, integrations, and other services through MCP tools.
Homarr uses MCP v2 (protocol `2026-07-28`) at `/api/mcp`, with compatibility for 2025 Streamable HTTP clients.
Requests are stateless: each request authenticates independently, without an MCP session ID.
![The AI / MCP management tab](./img/mcp.png)
The endpoint implements MCP `2026-07-28` and accepts 2025 Streamable HTTP clients through a compatibility path. The old
`/api/mcp/mcp` route remains as an alias; use `/api/mcp` for new clients.
## Connect a client
## Configure a client
Create an API key under **Management → Tools → API → Authentication**, then add the endpoint and header to the client's
MCP configuration:
Create an API key under **Management → Tools → API → Authentication**. Add this configuration to your client,
replacing the URL and API key:
```json
{
@@ -34,7 +33,10 @@ MCP configuration:
}
```
Clients without Streamable HTTP support can use `mcp-remote` over STDIO:
Use the complete key, including the ID, dot, and token. Keep it private and use HTTPS for remote connections.
The **AI / MCP** tab on Homarr's API page provides your endpoint, configuration, and available tools.
For clients that only support STDIO, use `mcp-remote`:
```json
{
@@ -47,21 +49,30 @@ Clients without Streamable HTTP support can use `mcp-remote` over STDIO:
}
```
The **AI / MCP** tab on Homarr's API page shows the current endpoint, configuration, and generated tool catalog.
### OAuth
## Authentication and permissions
Clients with OAuth support can authorize through OAuth 2.1 with PKCE instead of an API key.
Homarr publishes discovery metadata under `/.well-known/`.
MCP supports API keys and OAuth 2.1 with PKCE. API keys are simpler for direct client configuration; OAuth is available
for clients that support dynamic authorization.
If your reverse proxy does not preserve the public host and protocol, set `BASE_URL` to your public origin,
such as `https://homarr.example.com`, without a path.
Tools use the permissions of the API key owner or authorized user. Creating a key does not grant additional access.
Custom Widget and Workshop authoring tools, prompts, and resources are visible only to administrators.
## Permissions and tools
Use HTTPS when the endpoint crosses an untrusted network.
Tools use the permissions of the API key owner or OAuth user. Queries read data; mutations change it.
Inspect the **AI / MCP** tab or your client's tool list for available actions.
## Verify the endpoint
Custom Widget and Workshop authoring tools, prompts, and resources require administrator access.
For Custom Widgets, install the official skill and follow the preview-and-evidence workflow described in
[Connect an agent](./custom-widgets/agent-authoring). Secret values are configured in Homarr and are never returned
through MCP.
The 2025 compatibility path accepts a compact `tools/list` request:
The built-in [Assistant](./assistant) uses the same tool catalog with the current Homarr session and asks for approval
before mutations by default.
## Verify the connection
This request lists tools through the older-client compatibility path. It checks authentication and tool discovery:
```sh
curl -X POST \
@@ -72,17 +83,14 @@ curl -X POST \
https://homarr.example.com/api/mcp
```
The removed HTTP+SSE `/sse` and `/message` transports return `410 Gone`.
A successful discovery or initialization response alone does not verify that tools can be listed.
## Tools and resources
## Troubleshooting
The generated catalog includes available board, app, integration, Docker, Kubernetes, media, DNS, download, and other
Homarr procedures. Queries are read-only; mutations change state. The exact catalog depends on the release and current
user permissions, so inspect `tools/list` or the table in Homarr instead of relying on a static list.
For Custom Widgets, install the official skill and follow the preview-and-evidence workflow described in
[Connect an agent](./custom-widgets/agent-authoring). Secret values are configured in Homarr and are never returned
through MCP.
The built-in [Assistant](./assistant) uses the same tool catalog with the current Homarr session and asks for approval
before mutations by default.
- **`invalid_token`**: copy the complete API key in `<id>.<token>` format.
- **Missing tools**: check the user's permissions. Invalid tool schemas and duplicate names are omitted; check server
logs for `MCP tool omitted from catalog` and report the diagnostic with your Homarr version. Exclude API keys.
- **`Date cannot be represented in JSON Schema`**: update Homarr. Older versions could fail to list all tools because
of an unsupported date schema.
- **`410 Gone` on `/sse` or `/message`**: use `/api/mcp` with a Streamable HTTP client. The old `/api/mcp/mcp` URL remains
an alias.
+1 -1
View File
@@ -11,6 +11,6 @@ tags:
Using the media feature, you can upload images for custom icons to Homarr.
Images will be stored in the database. They can be selected in the [icon picker](/docs/advanced/icons).
![](./img/screenshot.png)
![Media page with search, user visibility filter, upload action, and an uploaded file](./img/screenshot.png)
![The Media management page](../img/media.png)
@@ -22,7 +22,7 @@ You can add any search engine that has a search URL that accepts a search term a
To create a custom search engine, you can click the button at the top of the page.
![](./img/create-search-engine.png)
![New search engine form with name, shortcut, icon, and URL template fields](./img/create-search-engine.png)
### Name
+8 -2
View File
@@ -13,8 +13,14 @@ A **user** is a Homarr identity with preferences, sessions, group memberships, a
use local credentials, LDAP, or OIDC; see [Single sign-on](/docs/advanced/single-sign-on).
Manage users under **Management → Users → Manage**. Administrators can change a user's profile, language, home boards,
search preferences, week start, accessibility preference, and local password. Deleting a user does not delete boards
they created.
search preferences, data-size units (binary KiB/MiB or decimal KB/MB), week start, accessibility preference, and local
password. Decimal units are the default. Change them from the user form or **Command menu (Ctrl/Cmd + K) →
Preferences → Data size units**. Byte rates follow the same preference; bit rates stay decimal and use labels such as
Mb/s. Deleting a user does not delete boards they created.
In demo mode (`DEMO_MODE=true`), user deletion, password changes, and local username changes are disabled for everyone,
including administrators, even when `DEMO_READ_ONLY=false` allows other changes. With demo editing enabled, other
profile preferences remain editable. This applies to the UI and API.
![The Users management page](../img/users.png)
+2 -2
View File
@@ -5,7 +5,7 @@
"link": {
"type": "generated-index",
"title": "Widgets",
"description": "Widgets allow you to show data on your board or control applications. Click on one of the widgets below for further information:",
"image": "/img/docusaurus.png"
"description": "Widgets display data or provide actions on a board. Choose a widget to see its configuration, supported integrations, and limitations.",
"image": "/img/pictures/widgets/widget-dark.jpg"
}
}
@@ -15,6 +15,12 @@ import { bookmarksWidget } from ".";
The Bookmarks widget keeps direct URLs and Homarr Apps in one ordered list. URLs do not require an App record; Homarr
uses the site's favicon when available. Choose adaptive, compact-grid, or other layouts from the widget configuration.
Vertical uses consistent compact rows and keeps bookmark names visible whenever the widget is wide enough, regardless
of how many bookmarks are configured. Adaptive and grid layouts reduce card height and spacing to show as many
bookmarks as possible before scrolling. Horizontal uses a compact, centered scrolling row with narrower cards in
one-row widgets. Horizontal, vertical, compact-grid, and icon-only layouts keep their gaps tight. Compact grid also
preserves its configured columns when the dashboard is scaled. Bookmark cards, icons, text, and gaps keep the same
visual density when a low-column dashboard is enlarged to fill the screen.
### Screenshots
+3 -2
View File
@@ -16,7 +16,8 @@ export const bookmarksWidget: WidgetDefinition = {
},
{
name: "Layout",
description: "How bookmarks use the available widget space.",
description:
"How bookmarks use the available widget space. Vertical keeps compact rows and stable title visibility.",
values: { type: "select", options: ["Adaptive", "Vertical", "Horizontal", "Grid", "Compact grid", "Icons"] },
defaultValue: "Adaptive",
},
@@ -28,7 +29,7 @@ export const bookmarksWidget: WidgetDefinition = {
},
{
name: "Card spacing",
description: "Space between bookmark cards.",
description: "Space between bookmark cards. Horizontal, compact-grid, and icon-only layouts cap larger gaps.",
values: { type: "select", options: ["Extra small", "Small", "Medium", "Large", "Extra large"] },
defaultValue: "Extra small",
},
+3 -2
View File
@@ -66,8 +66,9 @@ export const downloadsWidget: WidgetDefinition = {
defaultValue: "yes",
},
{
name: "Hide completed torrent under this threshold (in kiB/s)",
description: "This will hide completed torrent entries whose upload speed is below the specified threshold.",
name: "Hide completed torrent under this threshold",
description:
"This hides completed torrents whose upload speed is below the threshold. The input uses KiB/s with binary units and KB/s with decimal units.",
values: "Any number above 0, 0 to disable",
defaultValue: "0",
},
@@ -0,0 +1,43 @@
---
title: "llama.cpp"
description: "Displays the health, loaded model and generation speed of a local llama.cpp llama-server."
hide_title: true
---
import { WidgetHeader } from "@site/src/components/widgets/header";
import { AddingWidget } from "@site/src/components/widgets/adding";
import { WidgetConfig } from "@site/src/components/widgets/configuration";
import { WidgetIntegrations } from "@site/src/components/widgets/integrations";
import { llamacppWidget } from ".";
import { llamacppIntegration } from "@site/docs/integrations/llama-cpp";
<WidgetHeader widget={llamacppWidget} categories={["System Monitoring"]} />
This widget shows the live status of your local [llama.cpp](https://github.com/ggml-org/llama.cpp) `llama-server`: whether it is online, which model is loaded (with quantization, context size, file size and parameter count), the generation speed in tokens per second, and how many requests are being processed. When the server is started with `--metrics`, it also reports total token counts and the prompt cache hit rate.
### Generation speed
The widget picks the most meaningful speed for the current state (hover the speed for an explanation of which one is shown):
- **While a request is being generated** — the average speed of that request since it started, derived from the token count reported by `/slots`.
- **While the server is busy but the per-request data is unavailable** — the live speed from the `/metrics` rate gauge.
- **While the server is idle** — the average generation speed since the server started (cumulative tokens ÷ seconds), so the value stays meaningful instead of dropping to zero.
### Supported Integrations
<WidgetIntegrations
items={[
{
integration: llamacppIntegration,
note: "Displays health, model details, generation speed and request status",
},
]}
/>
### Adding the widget
<AddingWidget />
### Configuration
<WidgetConfig configuration={llamacppWidget.configuration} />
+37
View File
@@ -0,0 +1,37 @@
import { WidgetDefinition } from "@site/src/types";
import { IconCpu } from "@tabler/icons-react";
export const llamacppWidget: WidgetDefinition = {
icon: IconCpu,
name: "llama.cpp",
description: "Shows the health, loaded model and generation speed of a local llama.cpp llama-server.",
path: "../../widgets/llama-cpp",
configuration: {
items: [
{
name: "Show title",
description: "Displays the llama.cpp label and status badge at the top of the widget",
values: { type: "boolean" },
defaultValue: "yes",
},
{
name: "Show model info",
description: "Displays the loaded model name, quantization, context size and file size",
values: { type: "boolean" },
defaultValue: "yes",
},
{
name: "Show cache hit rate",
description: "Displays the prompt cache hit rate as a percentage bar",
values: { type: "boolean" },
defaultValue: "yes",
},
{
name: "Show context usage",
description: "Displays the context (KV cache) token usage as a progress bar",
values: { type: "boolean" },
defaultValue: "yes",
},
],
},
};
+1 -5
View File
@@ -19,11 +19,7 @@ export const systemDisksWidget: WidgetDefinition = {
description: "How disk usage values are shown",
values: {
type: "select",
options: [
"Percentage (e.g., 76%)",
"Absolute values (e.g., 800GB / 1TB)",
"Free space percentage (e.g., 24% free)",
],
options: ["Percentage (e.g., 76%)", "Absolute values", "Free space percentage (e.g., 24% free)"],
},
defaultValue: "Percentage (e.g., 76%)",
},
+3 -2
View File
@@ -31,5 +31,6 @@ adds hourly temperature and precipitation charts and the complete seven-day fore
Weather and location data come from Open-Meteo. Active weather refreshes every ten minutes; identical location requests
can reuse a server response for five minutes. Failed refreshes keep the last successful data with an error indicator.
Set `NO_EXTERNAL_CONNECTION=true` to disable this and other internet-backed requests. Allowlist
`https://open-meteo.com` when outbound access is restricted.
Set `NO_EXTERNAL_CONNECTION=true` to disable this and other internet-backed requests. Weather data and the city search
inside the widget configuration are unavailable while it is enabled. Allowlist `https://open-meteo.com` when outbound
access is restricted.
-7
View File
@@ -34,17 +34,13 @@
"@mdx-js/react": "^3.1.1",
"@nivo/line": "^0.99.0",
"@scalar/api-reference-react": "catalog:",
"@shadcn/react": "catalog:",
"@tabler/icons-react": "catalog:",
"@tailwindcss/postcss": "^4.3.2",
"class-variance-authority": "^0.7.1",
"clsx": "catalog:",
"cmdk": "catalog:",
"date-fns": "catalog:",
"dayjs": "catalog:",
"docusaurus-plugin-image-zoom": "^3.0.1",
"embla-carousel-react": "catalog:",
"input-otp": "catalog:",
"leader-line-new": "^1.1.9",
"next-themes": "catalog:",
"pocketbase": "catalog:",
@@ -52,10 +48,7 @@
"posthog-docusaurus": "^2.0.5",
"prism-react-renderer": "^2.4.1",
"react": "catalog:",
"react-day-picker": "catalog:",
"react-dom": "catalog:",
"react-resizable-panels": "catalog:",
"recharts": "3.8.0",
"sonner": "catalog:",
"swiper": "^14.0.5",
"tailwind-merge": "^3.6.0",
-144
View File
@@ -24,147 +24,3 @@ export function Carbon() {
</>
);
}
// Variant 1: Strict re-initialization – remove prior scripts and injected nodes, then load cover
export function CarbonCoverStrict() {
const ref = React.useRef<HTMLDivElement>(null!);
const location = useLocation();
if (process.env.NODE_ENV === "development") {
return null;
}
useEffect(() => {
const serve = "CWBDTKQM";
const placement = "homarrdev";
// Clear our local container
ref.current.innerHTML = "";
// Remove any globally injected Carbon ad containers
Array.from(document.querySelectorAll('[id^="carbonads"], .carbonads')).forEach(
(el) => el.parentElement && el.parentElement.removeChild(el),
);
// Remove existing script if present
const existing = document.getElementById("_carbonads_js");
if (existing && existing.parentElement) existing.parentElement.removeChild(existing);
// Reset possible global handle
try {
delete (window as any)._carbonads;
} catch {}
// Inject fresh cover script into our container
const s = document.createElement("script");
s.id = "_carbonads_js";
s.src = `https://cdn.carbonads.com/carbon.js?serve=${serve}&placement=${placement}&format=cover`;
ref.current.appendChild(s);
}, [location]);
return (
<>
<style>
{`
#carbon-responsive-wrap * { margin: initial; padding: initial; }
`}
</style>
<div
ref={ref}
id="carbonads"
data-selector="carbonads"
className="bg-background flex flex-col m-4 space-y-2 carbonads argos-ignore"
/>
</>
);
}
// Variant 2: Observer – load cover then keep only the latest injected ad node
export function CarbonCoverObserver() {
const ref = React.useRef<HTMLDivElement>(null!);
const location = useLocation();
if (process.env.NODE_ENV === "development") {
return null;
}
useEffect(() => {
const serve = "CWBDTKQM";
const placement = "homarrdev";
ref.current.innerHTML = "";
const s = document.createElement("script");
s.id = "_carbonads_js";
s.src = `https://cdn.carbonads.com/carbon.js?serve=${serve}&placement=${placement}&format=cover`;
ref.current.appendChild(s);
// Observe body and ensure we keep only the most recent carbon container
const trim = () => {
const nodes = Array.from(document.querySelectorAll('[id^="carbonads"]')) as HTMLElement[];
if (nodes.length > 1) {
// Keep the last (most recently inserted); remove the rest
nodes.slice(0, -1).forEach((n) => n.parentElement && n.parentElement.removeChild(n));
}
};
const observer = new MutationObserver(() => trim());
observer.observe(document.body, { childList: true, subtree: true });
// Initial pass
trim();
return () => observer.disconnect();
}, [location]);
return (
<>
<style>
{`
#carbon-responsive-wrap * { margin: initial; padding: initial; }
`}
</style>
<div
ref={ref}
id="carbonads"
data-selector="carbonads"
className="bg-background flex flex-col m-4 space-y-2 carbonads argos-ignore"
/>
</>
);
}
// Variant 3: CSS-only – hide the first instance so only the second (cover) remains visible
export function CarbonCoverCssOnlySecond() {
const ref = React.useRef<HTMLDivElement>(null!);
const location = useLocation();
if (process.env.NODE_ENV === "development") {
return null;
}
useEffect(() => {
const serve = "CWBDTKQM";
const placement = "homarrdev";
ref.current.innerHTML = "";
const s = document.createElement("script");
s.id = "_carbonads_js";
s.src = `https://cdn.carbonads.com/carbon.js?serve=${serve}&placement=${placement}&format=cover`;
ref.current.appendChild(s);
}, [location]);
return (
<>
<style>
{`
/* Hide the first injected container; show subsequent one (cover) */
#carbon-responsive-wrap { display: none; }
#carbon-responsive-wrap * { margin: initial; padding: initial; }
`}
</style>
<div
ref={ref}
id="carbonads"
data-selector="carbonads"
className="bg-background flex flex-col m-4 space-y-2 carbonads argos-ignore"
/>
</>
);
}
@@ -18,7 +18,16 @@ export const DragAndDropShowcase = () => {
className={"rounded-3xl overflow-hidden border-8 border-red-500 border-solid"}
style={{ aspectRatio: "17.6/9" }}
>
<video className={"w-full"} src={video} autoPlay loop muted />
<video
className={"w-full"}
src={video}
controls
playsInline
preload="metadata"
aria-label="Silent demonstration of moving and resizing Homarr dashboard widgets"
>
<track kind="captions" src="/media/homepage-drag-and-drop.vtt" srcLang="en" label="English" />
</video>
</div>
</SectionContainer>
</div>
Loaded 100 of 439 files, more files were not shown because too many files have changed in this diff. Show more