Remove incompatible persisted widget query data before resetting a crashed widget so it can fetch fresh data instead of re-rendering the same stale payload.
* fix(immich): use thumbnail endpoint instead of original for album carousel
Switches from /api/assets/:id/original to /api/assets/:id/thumbnail to fix
HEIC image incompatibility (thumbnails are always JPEG), reduce bandwidth,
and improve loading speed.
Closes#6388
* working on immich
* Improve image proxy error handling
* fix(image-proxy): use trusted fetch response type
* docs(immich): require Asset View permission
The pnpm fetch docs recommend `pnpm install -r --offline` without
--frozen-lockfile. The --offline flag already enforces no registry
access, making --frozen-lockfile redundant.
https://pnpm.io/cli/fetch
PR #6296 introduced pnpm fetch in the Dockerfile for Docker cache
optimization. However, pnpm fetch crashes with ENOENT when
patchedDependencies are configured with nodeLinker: hoisted because
applyPatchToDir tries to chdir into node_modules/<pkg> which doesn't
exist during fetch (only the content-addressable store is populated).
Fix: temporarily switch nodeLinker to isolated during pnpm fetch so
patches apply inside the virtual store (node_modules/.pnpm/...) which
IS created by pnpm fetch. Restore hoisted before the install step so
the final node_modules layout stays flat.
Refs: pnpm/pnpm#5268, pnpm/pnpm#8726
* fix: context menu refresh stays open with loading spinner, cache age reacts to refetch
* style: auto-fix lint and format
* refactor: pass isFetching as prop to WidgetCacheAge to avoid duplicate useIsFetching
---------
Co-authored-by: ajnart <ajnart@users.noreply.github.com>
Next.js 16.2.9 does not fully support TypeScript 7 (tsgo) as the
primary typescript package. The Go-native compiler has no in-process
JavaScript API — lib/typescript.js is missing — which causes Next.js's
hasNecessaryDependencies check to fail and the build to SIGABRT with
CI=true. Vercel is working on native tsgo integration expected in Q3
2026 alongside TypeScript 7 stable.
The Mantine Indicator component (used by the Proxmox cluster health
widget for VM/LXC status dots) sits at z-index 200, which is higher
than the AppShellHeader's default 100. This caused the status dots
to bleed above the header when scrolling.
Set the header z-index to 201 so it stacks above Indicator content
without conflicting with modal/popover layers (300+).
Closes#6194
* fix(widget-secrets): add missing widget_secret migration and add error notifications
The widget_secret table was defined in all three Drizzle schema files but
no SQL migration was ever generated to create it, causing setSecret to
fail with 'no such table: widget_secret' in production (drizzle-kit push
in development hid the problem).
- Add migration SQL for sqlite (0040), mysql (0042), postgresql (0008)
- Update journal entries for all three dialects
- Add onError handlers to setSecret/deleteSecret mutations so errors
are shown as red notifications instead of silently swallowed
Fixes#6186
* fix(widget-secrets): add missing drizzle snapshots for merge-url and widget_secret migrations
The merge_custom_widget_url migration (0039/0041/0007) was committed
without a corresponding snapshot, breaking drizzle-kit generate for
all future migrations. This adds:
- 0039/0041/0007 snapshots reflecting the merge-url changes
(base_url+endpoint -> url, add enabled column)
- 0040/0042/0008 snapshots reflecting the widget_secret table
All snapshot chains now match journal entries (41/43/9 entries each).
The refactor from integration-based to option-based config changed the
useQuery enabled condition from 'integrationIds.length > 0' (always true
when an integration was linked) to 'query.length > 0'. The combination
of the debounced search value and the enabled toggle inside the nested
useOptions callback caused the query to never fire, making stations
unsearchable in the edit form.
Removing the enabled guard lets the query always run. The 300ms debounce
already prevents excessive requests.
* docs(helm): Update Helm chart documentation
* feat(widgets): add right-click context menu with auto-generated toggle actions
- Add WidgetContextMenu component using Mantine 9.3 Menu.ContextMenu
- Extend WidgetDefinition with contextActions API for widget-specific actions
- Auto-generate context menu toggle items for switch-type widget options
- Add 'Search' context action to media-requests widgets (list + stats)
- Keep existing dots-button menu for discoverability in edit mode
- Add widgetStateRef to WidgetComponentProps for client-side-only state
- Add skipContextMenu opt-out on switch option definitions
* style: auto-fix lint and format
* fix(widgets): address CodeRabbit review — badge in context menu target, edit-only pending gate
- Move title badge inside WidgetContextMenu wrapper so right-clicking the title overlay also triggers the context menu
- Remove full-menu hide during integration loading; only disable 'Edit' action while isPending
- Add isPending to allActions useMemo dependency array
* style: auto-fix lint and format
* fix(widgets): gate context menu behind authenticated session
- Only show right-click context menu for logged-in users
- Unauthenticated users see plain children without menu wrapper
- Matches existing BoardItemMenu session gating pattern
* style: auto-fix lint and format
* chore: revert unrelated sitemap change from auto-fix
* style: auto-fix lint and format
* fix(context-menu): persist widget option changes to server
Toggle switches and edit modal changes from the context menu now call
saveBoard mutation to persist to DB. Previously only the client cache
was updated, losing changes on reload. Removes unnecessary invalidation
that caused cascading refetch loops.
* style: auto-fix lint and format
* fix: use explicit React type imports instead of global namespace
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
CodeRabbit review addressed:
- Removed the env-var gating (simpler: just remove source maps entirely)
- CI health check now fails fast if container never becomes healthy
Changes vs origin/main:
- next.config.ts: remove productionBrowserSourceMaps and serverSourceMaps
(they default to false, restoring pre-v1.68.0 behavior)
- Dockerfile: remove .map copy step and NODE_OPTIONS=--enable-source-maps
- CI: add memory-regression job (<700MB guardrail)
Closes#6123
Production source maps (--enable-source-maps) caused a ~50% memory increase
(600MB to 1000MB) in v1.68.0. Node.js loads all .map files into memory when
this flag is enabled, and Next.js server chunks generate hundreds of map files.
Changes:
- Guard productionBrowserSourceMaps and serverSourceMaps behind
HOMARR_ENABLE_SOURCE_MAPS env var (default off)
- Guard .map copy step and NODE_OPTIONS in Dockerfile behind
ENABLE_SOURCE_MAPS build arg (default false)
- run.sh checks HOMARR_ENABLE_SOURCE_MAPS before setting --enable-source-maps
- Add Docker-based memory regression CI job (<700MB threshold)
Closes#6123
* Add 'Columns to show' label in Docker stats
* feat: Add column translation keys for Docker widget
* feat: Enhance table with column visibility state
Added column visibility state management for the table.
* Add 'noColumns' error message to en.json
- Paginate GitHub releases via api.paginate to avoid missing tags beyond page 1
- Use tag_name instead of name for GitLab releases (name is a display label)
- Remove ArchiveTeam Warrior from widget-configured note in docs
- Fix test assertion to use objectContaining for error shape with message
- Wrap setSecret delete+insert in a cross-DB transaction
- Add MCP metadata to all widget secret procedures and register in mcp.ts
- Show configured tokens even when no repos use that provider
- Only clear token input after mutation succeeds, with loading state
Every error now includes a diagnostic message:
- noMatchingVersion: shows the regex used, how many tags were
checked, and sample tag names so users can see why matching failed
- noReleasesFound: distinguishes "no releases at all" from "no match"
- invalidIdentifier: shows the problematic identifier string
- unexpected: includes HTTP status codes from provider APIs
getLatestRelease returns { release } | { noMatch: reason } instead
of Release | null, carrying the diagnostic through.
Wrap each repository fetch in try/catch so one failure (Redis,
network, etc.) doesn't crash the entire query batch. Failed repos
now return a proper error response instead of causing all repos
to show "No response from provider".
The releases widget was crashing because throwIfActionForbiddenAsync
or the widgetSecrets query would throw before DB migrations are run.
Wrap in try/catch so the widget works without tokens in that case.
- Fix npm scoped package support (filter time metadata keys, make
description optional, exclude falsy releases from version selection)
- Fix timetable date formatting (use Intl.DateTimeFormat for stable
MM/DD/YYYY output) and named zod import
- Guard migration 0000 against unknown provider keys
- Fix migration 0003 to delete integration links by integrationId
- Reset immich album carousel index when photo list changes
- Add bounded limit to timetable router input
- Remove stale misspelled noProviderSeleceted translation key
- Fix provider name visibility in release widget settings
- Update docs: add ArchiveTeam Warrior note, fix typos, correct
showPhotoInfo default
The widget-only integrations refactor incorrectly removed the
ArchiveTeam Warrior integration entirely. It only needed to be
excluded from auto-seeding (by removing defaultUrl), not deleted.
This restores the integration class, reverts the widget/router/handler
to use integration middleware, and removes archiveTeamWarrior from the
widget-only-integrations migration.
The MantineReactTable defaults table cell backgrounds to
var(--mantine-color-body) which is opaque. This makes the
downloads widget table block appear solid while the footer
and other widgets correctly show the board's background.
Added a styles.css targeting table elements to force
background-color: transparent, following the same pattern
used by the beszel-system-table widget. Also added
bg=transparent to mantinePaperProps for the Paper wrapper.
Adds a Docker button to the user avatar dropdown menu (admin-only)
that opens a 1400px modal containing the same Docker container table
as /manage/tools/docker.
Closes#2165
* fix(manage/about): bypass self-fetch to prevent ECONNREFUSED crash
The /manage/about page fetched its own API routes over HTTP
(http://$HOSTNAME:3000/api/about/...). When the server could not
reach itself (misconfigured HOSTNAME, restricted container networking,
reverse-proxy setups), fetch threw ECONNREFUSED and the whole page
crashed with a full-page Next.js error (issue #6008).
All four routes were force-static serving static JSON / a direct fs
read, so the HTTP round-trip was unnecessary. Replace the fetches
with direct JSON imports and a direct getDependenciesAsync() call,
eliminating the self-network dependency entirely.
Also add a manage-level error.tsx boundary (Next.js 16 convention) so
that any future runtime error in a manage page renders a friendly,
localised fallback inside the existing layout (sidebar stays usable)
instead of the ugly default full-page error.
Exploration draft by an LLM.
Refs #6008
* style: auto-fix lint and format
* refactor: use @static-data path alias, simplify error ternary
- Add @static-data/* tsconfig path alias to replace 7-level relative
imports (../../../../../../../static-data/...) with clean alias
imports across the about page and all 3 API route handlers.
- Simplify error.tsx: replace ternary with && short-circuit.
- Preserve CI lint bot's .sort() -> .toSorted() fix.
Refs #6008
Beszel only stores stats at 1m, 10m, 20m, 120m, 480m intervals.
The 1440m type doesn't exist, so queries for 1w/30d returned empty
results. Use 480m (8h) with appropriate perPage (21 for 1w, 90 for 30d).
* fix(beszel): harden cache layer and prevent blank widgets
- Add debug logging across Beszel integration, request handler, and tRPC
router (auth, fetch, SSE, per-procedure entry/exit) gated behind
LOG_LEVEL=debug
- cached-request-handler: retry with backoff, isValid predicate to skip
caching invalid data, in-flight dedup (single-flight per channel) to
avoid thundering herd after cache invalidation
- redis channel: set-then-publish so storage is always consistent even
when pubsub publish fails
- Beszel handlers: opt into fallbackToStaleOnError and retry so brief
upstream hiccups don't throw away good cached data
- beszel-system-stats: add refetchInterval matching each time period's
cache TTL so historical charts refresh in the background
- tRPC router: switch Promise.all -> Promise.allSettled for getSystems
and getAlerts; getSystemStats returns an error object instead of
throwing. Each result carries an optional error field so one failed
integration no longer nukes the whole widget
- Widgets: add explicit isPending loader, empty state for grid and
stats, and a shared per-integration error indicator (tooltip with
failed integration names). Alerts and table now only show their
empty state after loading completes
- Translation: add empty.noSystems key to beszelSystemGrid and
beszelSystemStats
Addresses #6030
* style: auto-fix lint and format
---------
Co-authored-by: ajnart <ajnart@users.noreply.github.com>
* feat(analytics): migrate from Umami to PostHog server-side analytics
Replace @umami/node with posthog-node for all server-side telemetry.
Add singleton PostHog client, feature tracking POC, DAU heartbeat,
and tRPC analytics router. Cron job is now locked (no manual trigger,
no custom interval). Update consent copy to explain what we collect
and why.
* style: auto-fix lint and format
* refactor(analytics): simplify + add client-side posthog-js init
- Inline constants.ts and track.ts into client.ts (single source)
- Remove dead exports (shutdownPostHogAsync, getPostHogClient)
- Flatten all DB queries into one Promise.all (no waterfall)
- Derive integration/widget totals from group-by (drop redundant counts)
- Add posthog-js with dynamic import, gated on enableGeneral
- Move Analytics component to body, pass consent from server settings
* style: auto-fix lint and format
* feat(cron): add hidden flag to cron jobs, hide analytics from task panel
The analytics job runs silently in the background — users don't need
to see or interact with it. Hidden jobs are filtered out of getAllAsync
so they never appear in the management UI.
* refactor(analytics): simplify to single toggle, always collect all data
Remove granular enableWidgetData/enableIntegrationData/enableUserData
toggles. Analytics is now a single on/off switch. When enabled, all
anonymous usage data is collected in one flat Promise.all call.
Side effect: existing users with enableGeneral=true who had granular
flags off will now send all data. This is by design — the granular
controls added complexity without meaningful privacy benefit since
the data is already anonymous counts.
* style: auto-fix lint and format
* Calculate and display total CPU and memory usage
Added total CPU and memory usage calculations for containers.
* Refactor Docker component layout
Refactor component layout and remove redundant Group elements.
* Create test.yaml
* Add files via upload
* Delete packages/icons/icons directory
Accidental
* Add total CPU and memory strings to English translations
* Add English translations and size refactor
* Fix typo in search label in en.json
* chore(docs): update OpenAPI spec
* Remove color styling from CPU and memory usage text
* Enhance Docker component with footer and toolbar updates
Updated createColumns function to include totals and relativeTime parameters for footer display. Adjusted table configuration to enable bottom toolbar based on isTiny flag.
* Fix formatting and add missing newline at end of file
* Enable sticky header and footer in component
Enable sticky header and add footer row properties for sticky positioning.
* Refactor table component properties
* Refactor table columns and fix footers
* Revert "chore(docs): update OpenAPI spec"
This reverts commit 26f3c809d9.
---------
Co-authored-by: Bergasha <Bergasha@users.noreply.github.com>
Co-authored-by: Manuel <30572287+manuel-rw@users.noreply.github.com>
Stalled torrents have time=0 (infinite/never), which raw numeric sort
places between negative (completed) and positive (active) values.
Add a custom sortingFn on the time column that maps 0 to Infinity so
stalled entries always sort last in ascending order and first in
descending order.
Closes#5416
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
- Remove ternary operators from new code (widget-integration-select,
TabsContainer, mock data, members page)
- Remove stale /docs/management/tools/backup from sitemap type
- Add value prop to error-state Select in beszel-system-stats
- Add generation guard to prevent late SSE events from writing stale
data after systemId/integration changes
- Sort integrationIds for stable key (avoids unnecessary resubscribes)
- Include integrationKey and systemId in flush interval deps
- Preserve current form value in WidgetIntegrationSelectInput when it's
missing from loaded options (matches WidgetDynamicSelectInput pattern)
react-jsx-parser auto-generates random keys on every render, causing
full remounts that reset uncontrolled Tabs to defaultValue. Fix by:
- Adding disableKeyGeneration to preserve React reconciliation
- Memoizing bindings to avoid unnecessary re-parses
- Converting TabsContainer to controlled Tabs with useState
- Validating defaultTab against actual panel values
- Clearing stale parseErrors when template/data changes
The Gotify integration (merged from dev) declares a documentationUrl but
had no docs page, failing the docs-coverage validation plugin once gotify
was added to the integration doc-slug map.
Merging dev introduced the Gotify integration and ArchiveTeam Warrior
widget/integration, which require entries in the doc-slug maps and the
generated documentation sitemap. Also remove a misplaced non-null
assertion in the group members page flagged by oxlint.
- Throw TRPCError instead of returning null when no integration
- Sort beszelRouter import alphabetically in mcp.ts
- Replace ternary with lookup table for background color
- Mention integration_all in MCP server instructions
- Add .default("1h") to timePeriod so MCP clients can omit it
- Include "1m" in the description's valid timePeriod values
- Clarify that getSystemStats uses only the first integrationId
- Use MantineSize type instead of plain string for itemRadius
Register beszelRouter in the MCP server with .meta({ mcp: enabled })
on getSystems, getAlerts, and getSystemStats procedures. AI assistants
can now query Beszel-monitored systems, view alerts, and fetch
historical metrics through the MCP protocol.
Ref #5968
The inner Card components used Mantine's default opaque background and
hard-coded radius, clashing with board styling. Now uses board.opacity
and board.itemRadius with the same pattern as system-resources widgets.
Closes#5968
* feat(integrations): add Gotify notifications integration
Closes#4186
> "It would be really useful if Homarr could integrate with Gotify —
> view notifications from Gotify right inside the Homarr dashboard,
> maybe as a widget or a small panel. A widget that displays the latest
> messages from Gotify (with title, message, time)."
> — #4186: feat: Gotify Integration - display notifications in Homarr
- `gotify/gotify-schema.ts` — Zod schema validating the `GET /message`
API response from the Gotify server, including id, date, title,
message body, and optional priority fields.
- `gotify/gotify-integration.ts` — `GotifyIntegration` class extending
the base `Integration` and implementing `INotificationsIntegration`.
Authenticates via HTTP Basic Auth (username + password) and fetches
the latest 100 messages from the `/message` endpoint. Connection
testing hits `/health`.
- `index.ts` — exports `GotifyIntegration` from the package public API.
- `base/creator.ts` — registers `GotifyIntegration` in the integration
creator map so it can be instantiated at runtime.
- `integration.ts` — registers the `gotify` integration definition with:
- `secretKinds: [["username", "password"]]` (Basic Auth credentials)
- `category: ["notifications"]` (picked up automatically by the
Notifications widget via `getIntegrationKindsByCategory`)
- Icon sourced from `homarr-labs/dashboard-icons`
- Documentation URL pointing to `/docs/integrations/gotify`
- `docs/homarr-docs-sitemap.ts` — adds `/docs/integrations/gotify` to
the `HomarrDocumentationPath` union type so the documentation link
passes the TypeScript path validation check.
Because `gotify` is registered under the `notifications` category, the
existing Notifications widget automatically supports it with no widget
code changes required. Users add the integration via Settings →
Integrations, supply their Gotify server URL, username, and password,
then attach it to a Notifications widget on any board.
* refactor(integrations): inline json parse and add error handling in Gotify integration
Address PR review feedback: remove intermediate `as unknown` cast by inlining
response.json() directly, and use safeParseAsync to handle deserialization
failures gracefully instead of throwing unhandled.
On macOS (and some Glances configurations) the /api/4/all endpoint
omits the quicklook key, causing a Zod invalid_type error and crashing
the system health widget. Mark quicklook as .optional() in allSchema
and add a null-safe fallback (`?? "Unknown"`) for cpuModelName.
Fixes#5728
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Consolidate 5 stat mapper useCallbacks and 6 series useMemos into
single mappers/series objects. Extract statsWhenShown helper for
repeated visibility guard pattern. Deduplicate SSE record envelope
construction. Simplify prepareRecords with if/else. Remove unused
gridColumns ternary with Number() indexing.
Replace all ternary operators with if/else, &&/||, or early returns.
Remove unused noAnimation constant that caused lint error.
Fix variable shadowing in tooltip deduplication loop.
- Decouple 1Hz SSE ingestion from render rate: accumulate data in a
ref and flush to React state every 3s instead of every tick
- First data point flushes immediately to avoid blank screen
- Reduce MAX_BUFFER from 60 to 30 (halves SVG reconciliation work)
- Gate chart data hooks behind visibility flags so disabled charts
skip all array processing
Keep showing existing charts during brief WebSocket reconnections
instead of flashing the error UI. The error state only shows when
no data was ever received (genuine initial connection failure).
- Set withDots={false} to eliminate thousands of hidden SVG Dot nodes
- Disable Recharts animation (isAnimationActive: false) during data updates
- Merge ChartPanel + BeszelAreaChart into BeszelChartPanel to fix
React.memo being defeated by children prop creating new refs
- Memoize yAxisProps inside BeszelAreaChart instead of rebuilding inline
- Hoist chartStyle, panelStyle, CPU_Y_AXIS_DOMAIN to module constants
- Memoize subscription input/enabled in useLiveStats
- Replace O(n) find() with Map lookup in useDockerChartData hot path
- Stabilize useContainerNames with ref comparison so it returns the
same array reference when container names haven't changed
- Memoize containerSeries (was computed inline every render)
- Hoist all system chart series arrays into useMemo
- Hoist activeDotProps/dotProps to module-level constants
- Wrap BeszelAreaChart and ChartPanel in React.memo
When SSE connection fails, the error UI now shows a specific message
and a button to switch back to historical view, so users don't need
to reload the page to recover.
- Restore timePeriod union cast to fix typecheck (TS2769)
- Clear live data on subscription error so error UI always shows
- Emit error when no integration is configured for live subscription
- Add error state to system select input when query fails
- Surface subscription errors in useLiveStats via onError + error state
- Show error UI in live mode when SSE connection fails
- Abort SSE reader on subscribeTopic failure instead of silent log
- Guard mock realtime against pre-aborted signal to prevent interval leak
- Remove dead includeDocker from subscription input schema
- Gate stats/live queries on systems loading state to prevent stale ID
requests during initial load
- Simplify useLiveStats: functional setState, stable integrationKey dep,
remove unused includeDocker param
- Extract prepareRecords helper to deduplicate chart ordering logic
- Remove stale timePeriod type cast
Replace plain text systemId option with a dedicated beszelSystem factory
type backed by a searchable Select that fetches available systems from
the integration. Shows proper states for no integration, loading, and
no systems found.
- Skip toReversed() for live data (already chronological from buffer)
- Reset live buffer on systemId/integration change to avoid stale data
- Throw on unexpected SSE stream end so subscription emits error
- Delete unused time-period.ts config (duplicate of server-side config)
Add "Live" option to time period selector that uses the existing
subscribeSystemStats tRPC subscription (PocketBase SSE) instead of
REST polling. A useLiveStats hook accumulates incoming data points
in a 60-entry rolling buffer. Charts use HH:mm:ss timestamps in
live mode.
- Add subscribeSystemStats tRPC subscription bridging PocketBase SSE
- Add subscribeRealtimeMetrics to BeszelIntegration with SSE parsing
- Add error handling: try/catch in subscription, response check in topic POST
- Add cacheDurationForInput to request handler for per-period cache TTL
- Add mock subscribeRealtimeMetrics with 1s interval for dev testing
Non-stacked charts produce duplicate Recharts payload entries (stroke +
fill). Deduplicate by dataKey keeping the first entry (correct color).
Also increase indicator borderRadius for a fully rounded pill.
BeszelAreaChart hardcoded type="stacked" for all charts, causing Docker
Network I/O to render blended colors that didn't match the tooltip.
- Make `type` prop configurable (default "default")
- Only stack Memory, Docker CPU, and Docker Memory charts
- Add systemId as a persisted widget option with Menu-based selector
- Replace Popover with Menu trigger="click-hover" for proper hover UX
- Show error state with Select when selected system is not found
- Remove 1-minute time period (live SSE not wired yet)
The SWC HTML minifier rejects slightly invalid HTML that browsers
tolerate, breaking the build on every page. Disable it while keeping
all other Docusaurus Faster optimizations.
Also fix broken relative links in after-the-installation.mdx and
donate.mdx by using absolute paths.
GitHub Pages redirects non-trailing-slash URLs with 301, causing
Algolia crawler to ignore all sitemap entries. Setting trailingSlash
to true aligns generated URLs with what GitHub Pages actually serves.
The postinstall codegen script crashes CI when homarr.dev/sitemap.xml
is unreachable. Now catches fetch errors and keeps the existing
committed homarr-docs-sitemap.ts instead of crashing.
* chore(deps): update dependency proxmox-api>undici to v7.27.1
* style: auto-fix lint and format
* chore: also update undici
---------
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Manuel <30572287+manuel-rw@users.noreply.github.com>
* fix(boards): add permissions to board summary schema to fix manage page crash
The getAllBoards query fetches userPermissions and groupPermissions
from the DB, but boardSummarySchema (the output validator) did not
include them. tRPC stripped the fields, causing a TypeError when
getBoardPermissionsAsync called .some() on undefined.
* fix(backup): cast Buffer to Uint8Array for NextResponse body
Node 24 narrowed the BodyInit type, making Buffer incompatible.
Wrapping in Uint8Array satisfies the type constraint.
* fix(lint): remove forbidden non-null assertions across backup and board code
Replace non-null assertions with safe guards, optional chaining, and
for-of iteration so oxlint passes in CI. Also remove unused Collapse import.
`import.meta.url` is undefined when esbuild bundles migrate.ts into
migrate.cjs, causing a TypeError at startup. Inline the seed JSON
data directly instead of reading from disk, which also fixes the
Docker image missing the seed files (not copied by Dockerfile).
* chore(integrations): add ws dependency for the truenas client
* feat(definitions): allow api key authentication for truenas
* fix(integrations): connect to truenas over trusted-certificate websocket with protocol autodetection
* test(integrations): cover truenas protocol detection and authentication
* refactor(integrations): avoid shorthand index variable in truenas mapping
* feat(integrations): add a websocket url helper to the integration base
* refactor(integrations): extract truenas connection handling into a client
createTheme was called without being imported, causing a runtime
ReferenceError on every page load. Use the already-imported theme
object from @homarr/ui which contains the full Mantine configuration.
* feat(board): persist category collapse state in localStorage for guests
Authenticated users already had their collapse state saved server-side.
This extends the same behaviour to unauthenticated (guest) users by
reading/writing localStorage keyed by section ID.
On mount, if the user is a guest, the stored value is applied to the
disclosure. On toggle, guests write to localStorage instead of calling
the protected API mutation.
Closes#3355
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: set localStorage value as useDisclosure default instead of useEffect
Replaces the useEffect that read localStorage after mount with a direct
initial value computation passed to useDisclosure. Removes the effect,
the open/close destructuring, and the useEffect import.
Note: initialCollapsed is computed during render rather than post-mount,
which may cause a hydration mismatch on SSR if localStorage differs from
the server-provided section.collapsed. The typeof window guard handles
the SSR case.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
When adding a widget from the "Choose item to add" modal, the widget
edit modal now opens immediately after placement, allowing the user to
configure options and integrations before the widget renders.
All available integrations for the widget are selected by default.
* ci: add --affected filtering and fix Turbo cache invalidation
- Remove globalDependencies **/.env which invalidated all caches on any .env change
- Move 40+ env vars from globalEnv to build-specific env (lint/format/typecheck don't need DB_URL etc.)
- Add per-task inputs with test file exclusions so test changes don't trigger build/typecheck
- Enable affectedUsingTaskInputs for task-level --affected detection
- Add --affected to all turbo commands in CI (lint, format, typecheck, build)
- Add fetch-depth: 0 for proper git history comparison
- Add Docker Buildx + GHA layer caching for E2E builds
* ci: add Cloudflare remote cache for Turborepo
Configure turborepo-remote-cache-cloudflare as the remote cache backend.
This commit can be reverted independently to disable remote caching.
* fix(ci): set TURBO_SCM_BASE for --affected to work in PRs
Turbo defaults to comparing against `main`, but PRs target `dev`.
Without explicit base/head refs, --affected falls back to running
all tasks. Use the PR base SHA for pull_requests, the before SHA
for pushes, and HEAD~1 as a last resort.
* perf(typecheck): exclude bundled .cjs/.css from tsc
websocket (835K lines) and tasks (755K lines) had bundled output
files being typechecked due to `include: ["."]` in their tsconfig.
Full typecheck drops from ~70s to ~26s.
* perf(turbo): scope topo task inputs to package.json only
The topo virtual task had no explicit inputs, defaulting to all files.
This caused any source change to cascade cache invalidation through
all dependent packages via ^topo. Since topo is only for dependency
ordering, its hash should only change when dependencies change.
* fix(turbo): disable remote cache signature verification
The cache lives on a private CF worker, signature verification
just adds noise without security benefit.
* fix(turbo): remove runtime env vars from build cache key
These env vars (DB_*, AUTH_*, DOCKER_*, etc.) are all runtime
configuration provided by users when running the Docker container.
They don't influence the build output since Next.js standalone mode
reads process.env at runtime and SKIP_ENV_VALIDATION=true skips
validation during build. Listing them in build.env caused unnecessary
cache busting in local dev.
Also restores coverage report comments per review feedback.
* fix(turbo): include test files in typecheck cache inputs
Test files must be typechecked — excluding them from typecheck inputs
caused changed tests to skip type validation.
* fix(deps): update dependency zod to ^4.3.5
* fix: omit can not be used with refine
* fix: convert intersection to object due to trpc-openapi issue
* fix: remove omit after refine
---------
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
* fix(deps): update dependency node-ical to ^0.23.1
* fix: date from node-ical represents utc value
---------
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
* feat: add GPU monitoring support via Glances integration
- Add GPU fields to SystemHealthMonitoring interface (gpuId, name,
memoryUtilization, processorUtilization, temperature, fanSpeed)
- Extend Glances integration to parse GPU data from /api/4/all endpoint
(supports NVIDIA, AMD, and Intel GPUs via Glances)
- Add GPU utilization ring to Health Monitoring widget with tooltip
showing VRAM usage, temperature, and fan speed
- Add GPU time-series chart to System Resources widget (grape color)
- Add GPU toggle option to both widgets
- Add English translations for GPU labels
- Add gpu: [] to all other integrations (dashdot, truenas, unraid,
openmediavault) for backward compatibility
- Add GPU mock data for development/testing
Closes community request from PR #5102 for GPU monitoring support.
* fix: implement GPU monitoring for dashdot integration and clarify OMV comment
- Add GPU load fetching via dashdot /load/gpu endpoint
- Add GPU info parsing from dashdot /info endpoint
- Add gpuLoadApi and gpu field to internalServerInfoApi schemas
- Add getCurrentGpuLoadAsync method to DashDotIntegration
- Map GPU load data to SystemHealthMonitoring gpu array
- Add explanatory comment for OMV's empty gpu array
Addresses review comments from manuel-rw on PR #5255
* Delete manifest.json
When Docker API returns `null` for container ports (instead of `[]`),
the Docker management page crashes. Added optional chaining on
`ports?.length` to handle this case.
Fixes#5311
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
description: Comprehensive Homarr codebase architecture and conventions reference
alwaysApply: true
---
# Homarr Codebase Context
Homarr is an open-source, self-hosted dashboard for managing homelab services. It integrates with 50+ self-hosted apps (media servers, download clients, DNS, NAS, etc.) and provides a drag-and-drop widget-based UI.
## Tech Stack
- **Monorepo**: pnpm workspaces + Turborepo
- **Framework**: Next.js (App Router, `output: "standalone"`) — T3 Stack
- **Language**: TypeScript throughout
- **ORM**: Drizzle (supports SQLite via better-sqlite3, MySQL, PostgreSQL)
- **API**: tRPC (HTTP + WebSocket subscriptions) with superjson + OpenAPI bridge via `trpc-to-openapi`
- **WebSocket subscriptions**: Client uses `wsLink` when `type === "subscription"`, connects to port 3001
### Multi-Database Support
Drizzle schemas exist in three parallel implementations:
- `packages/db/schema/sqlite.ts`
- `packages/db/schema/mysql.ts`
- `packages/db/schema/postgresql.ts`
Selected at runtime via `DB_DRIVER` env (`better-sqlite3` | `mysql2` | `node-postgres`). Migrations are per-engine in `packages/db/migrations/{sqlite,mysql,postgresql}/`.
### Widget System
Widgets are registered in `packages/widgets/src/index.tsx` as `widgetImports` (must satisfy `Record<WidgetKind, ...>`). Each widget:
- Has a `definition` created via `createWidgetDefinition(kind, { icon, createOptions, supportedIntegrations? })`
- Uses `optionsBuilder.from(factory => ({ ... }))` for typed options
- Loads dynamically via `next/dynamic`
- Fetches data via tRPC client hooks (backed by cron job caches)
### Integration System
Integrations are defined in `packages/definitions/src/integration.ts` (`integrationDefs`) and instantiated via `createIntegrationAsync` in `packages/integrations/src/base/creator.ts`. Each extends abstract `Integration` class with `testConnectionAsync` and typed methods.
### Cron Job System
Jobs defined in `packages/cron-jobs/src/jobs/`. Each job:
- Uses `createCronJob` with a cron expression
- Publishes results to Redis channels
- Widget subscriptions consume those channels via tRPC subscriptions
- Job management (start/stop/trigger) via `cronJobApi` HTTP client to port 3002
### Modals
Created with `createModal(component).withOptions({ defaultTitle })` from `@homarr/modals`. Opened via `useModalAction(SomeModal)`. Feature modals live in `packages/modals-collection/`.
### Forms
`useZodForm` from `@homarr/form` wraps Mantine `useForm` with zod validation. Shared form UIs in `packages/forms-collection/`.
### Routing (App Router)
- `apps/nextjs/src/app/[locale]/` — all UI routes under locale segment
Configured via `AUTH_PROVIDERS` env. Supports: `credentials` (local), `ldap`, `oidc`. Database sessions with httpOnly cookies. API key auth for programmatic access.
Docusaurus 3 site at `apps/docs/`, package name `@homarr/docs`.
- Run with `pnpm dev:docs` from root
- Build with `pnpm turbo build --filter=@homarr/docs`
- Uses Tailwind CSS 3 for custom pages (not Mantine)
- Imports `@homarr/definitions` for type-safe integration/widget metadata
- When adding or modifying widgets, integrations, cron jobs, API routes, env vars, or user-facing features, you MUST also update corresponding docs in `apps/docs/docs/`
Widgets sit inside board cards that have user-controlled opacity. The table must be fully transparent so the card background shows through. mantine-datatable defaults to `var(--mantine-color-body)` which is opaque.
function shouldIncludeColumn(accessor: string, ctx: ColumnContext): boolean {
if (!ctx.optionsColumnSet.has(accessor)) return false;
const check = columnVisibilityChecks[accessor];
if (check && !check(ctx)) return false;
return true;
}
```
---
## 4. Column Persistence (Drag/Resize → Server)
`useDataTableColumns` stores column order and widths in localStorage by default. For widgets, this must persist server-side so it works across devices.
### Hidden widget options
In `index.ts`, add hidden text options:
```typescript
columnOrder: factory.text({ defaultValue: "" }),
columnWidths: factory.text({ defaultValue: "" }),
```
With visibility override:
```typescript
{
columnOrder: { shouldHide: () => true },
columnWidths: { shouldHide: () => true },
}
```
### Translation keys required
Even hidden options need translation keys or the translation test fails:
On mount, seed `useDataTableColumns` with server-persisted values. Use a `hydrated` ref to prevent the persist effect from immediately writing defaults back:
**Key fix:** Use `closeOnItemClick={false}` if any menu item needs a two-step confirmation (like delete → confirm). Close the menu on scroll: `onScroll={() => { if (contextMenu) closeContextMenu(); }}`.
When a column has `ellipsis: true`, the tooltip target element can still extend beyond the cell bounds if inner elements don't clip. Fix:
```tsx
<Tooltip position="bottom-start" ...>
<Group style={{ overflow: "hidden" }}>
...
</Group>
</Tooltip>
```
Use `position="bottom-start"` instead of `position="right"` so the tooltip never escapes the widget horizontally.
### Dark mode tooltips
Mantine tooltips default to the theme's tooltip color which may be white in light mode and stay white in dark mode. Always set `color="dark"` on informational tooltips inside widgets.
### Division by zero
Guard all ratio calculations:
```typescript
if (item.size > 0) ratio = item.sent / item.size;
if (totalDown > 0) ratio = totalUp / totalDown;
```
### Composite idAccessor
When rows can come from multiple integrations, IDs may collide. Use a composite key:
description: How to expose tRPC procedures as MCP tools for AI clients
globs: packages/api/src/router/**/*.ts
alwaysApply: false
---
# MCP Integration Guide
Every new tRPC procedure that provides useful functionality should be exposed as an MCP tool so AI assistants (Claude, Cursor, etc.) can use it. This is a first-class feature of Homarr.
## Adding MCP to a procedure
Add `.meta({ mcp: { enabled: true, description: "..." } })` to the procedure chain:
```typescript
// Read-only tool (query)
myProcedure: protectedProcedure
.meta({ mcp: { enabled: true, description: "Get all items with their status and metadata" } })
The `.meta()` call must come **before** `.input()` and `.query()`/`.mutation()` in the chain.
## Writing good descriptions
The description is the **only thing** an AI sees to decide when and how to use the tool. Write it like you're explaining to a colleague who has never used Homarr.
**Must include:**
- What the tool returns or does
- Which integrations/services it works with (if applicable)
- How to get required IDs (e.g., "Use integration_all to get the integrationId")
- Permission requirements (if non-obvious)
**Good:**
```
"Get calendar events for upcoming and recent media releases. Fetches from all connected Sonarr (TV), Radarr (movies), Lidarr (music), and Readarr (books) integrations. Requires integrationIds from integration_all"
```
**Bad:**
```
"Get calendar events"
```
**For tools that return permission fields**, explain what they mean:
```
"List all integrations. Returns permissions.hasUseAccess (read) and permissions.hasInteractAccess (actions) — false means the API key lacks that permission, not an error"
```
## Registering in the MCP router
After adding `.meta()` to procedures, you must register the router in `packages/api/src/mcp.ts`. This file uses **eager imports** (no `lazy()`) because MCP tool extraction requires synchronous access to all procedure definitions.
```typescript
// packages/api/src/mcp.ts
import { myNewRouter } from "./router/my-new-feature";
export const mcpRouter = createTRPCRouter({
// ... existing routers
myFeature: myNewRouter,
});
```
**If your router is nested** (e.g., widget sub-routers), import and register the sub-router directly — do not import a parent that uses `lazy()`.
## What to expose vs. what to skip
**Expose:**
- Data queries (list, search, get by ID)
- Actions users would ask an AI to do (toggle, create, delete, approve)
- Status checks (health, stats, summaries)
**Skip:**
- Subscriptions (WebSocket-only, not supported by MCP)
Install [Act](https://nektosact.com/installation/index.html) and ensure Docker is running, then use:
```bash
pnpm ci:act:fast
pnpm ci:act:docker
pnpm ci:act:all
pnpm ci:act:cleanup
```
`fast` runs the quality gate. `docker` and `all` run the gate followed by the single container build, E2E suite, and memory regression test. `cleanup` validates the manual cleanup path without calling GitHub. The wrapper supports regular clones and Git worktrees, including non-default Docker contexts. Linux dependencies are kept in Docker volumes instead of overwriting host `node_modules`. The first run downloads the Act runner and Playwright browser dependencies.
Remote caching is optional locally. Copy `.github/act/.secrets.example` to `.github/act/.secrets`, populate it with rotated credentials, and run with:
The tag is replaced when the pull request is updated and deleted when it closes. Production Homarr images remain the supported multi-architecture releases. The `homarr-test` package must be configured as public in GHCR for unauthenticated testers.
## Dokploy preview cache
Keep `needs-demo` as the Dokploy preview label. Under the application's **Preview Build-time Secrets**, configure `TURBO_API`, `TURBO_TEAM`, `TURBO_TOKEN`, and `TURBO_REMOTE_CACHE_SIGNATURE_KEY`. Dokploy passes these to the Dockerfile as BuildKit secrets. Do not configure the token or signature key as Docker build arguments.
Rotate any credentials that have appeared in chat, logs, or committed configuration before enabling the cache.
if:github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987# Id of renovate bot and crowdin bot see https://api.github.com/users/homarr-renovate%5Bbot%5D and https://api.github.com/users/homarr-crowdin%5Bbot%5D and https://api.github.com/users/homarr-update-contributors%5Bbot%5D
# 158783068: homarr-renovate[bot]
# 190541745: homarr-crowdin[bot]
# 210161987: homarr-update-contributors[bot]
# 175486441: homarr-releases[bot]
# Extracted from https://api.github.com/users/<username>
args:"Beta release has been triggered: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
- name:Checkout repository
uses:actions/checkout@v7
with:
fetch-depth:0
- name:Get Next Version
id:semver
uses:ietf-tools/semver-action@v1
with:
token:${{ github.token }}
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'
env:
GITHUB_TOKEN:"${{ secrets.GITHUB_TOKEN }}"
- name:Discord notification
if:${{ github.event.inputs.send-notifications }}
env:
DISCORD_WEBHOOK:${{ secrets.DISCORD_WEBHOOK }}
uses:Ilshidur/action-discord@0.4.0
with:
args:"Created a release PR ${{ steps.create-pull-request.outputs.url }} for version ${{ steps.semver.outputs.next }}-beta (new behaviour: ${{ steps.semver.outputs.bump }})"
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"
args:"Automatic release has been triggered: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
- name:Checkout repository
uses:actions/checkout@v6
uses:actions/checkout@v7
with:
fetch-depth:0
- name:Get Next Version
@@ -38,22 +39,23 @@ jobs:
token:${{ github.token }}
branch:dev
- 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'
env:
GITHUB_TOKEN:"${{ secrets.GITHUB_TOKEN }}"
- name:Discord notification
if:${{ github.events.inputs.send-notifications }}
if:${{ github.event.inputs.send-notifications }}
env:
DISCORD_WEBHOOK:${{ secrets.DISCORD_WEBHOOK }}
uses:Ilshidur/action-discord@master
uses:Ilshidur/action-discord@0.4.0
with:
args:"Created a release PR ${{ steps.create-pull-request.outputs.url }} for version ${{ steps.semver.outputs.next }} (new behaviour: ${{ steps.semver.outputs.bump }})"
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 }}"
Remove widget-only services from Homarr's global Integration system.
The affected services are release providers and Search.ch. They are only used by specific widgets, so keeping them as global integrations added unnecessary setup, UI, database rows, docs pages, and integration package code.
ArchiveTeam Warrior remains a proper integration but is no longer auto-seeded on first install.
## Why
These services do not need shared integration state.
- Release providers are selected per Releases widget repository.
- Search.ch is only used by the Timetable widget.
Moving them into widget options makes configuration simpler and removes dead-weight integration code from `packages/integrations`.
## What Changed
### Release Providers
- Added release provider definitions in `packages/definitions/src/release-provider.ts`.
- Removed release providers from `IntegrationKind`.
- Moved release-fetching code out of `packages/integrations` and into `packages/request-handler/src/release-providers.ts`.
- Updated the Releases widget to store provider configuration directly in widget options:
-`provider`
-`identifier`
-`versionFilter`
-`providerUrl`
- Updated the Releases widget editor to use a provider select instead of an integration select.
- Updated Docker import mapping so `ghcr.io` maps to `gitHubContainerRegistry`.
- GitHub Container Registry now uses the public OCI registry API and accepts full image references such as `ghcr.io/homarr-labs/homarr:latest`.
### Timetable / Search.ch
- Removed Search.ch as an integration.
- Added a Timetable widget `baseUrl` option, defaulting to `https://search.ch`.
- Moved Search.ch request/parsing logic into `packages/request-handler/src/timetable.ts`.
- Updated the Timetable API to receive `baseUrl` directly from widget options.
### ArchiveTeam Warrior
- Kept as a proper integration (not removed).
- Removed `defaultUrl` from the integration definition to prevent auto-seeding on first install.
### Integration Cleanup
- Removed creators, exports, docs references, and implementation files for the deleted integrations.
- Removed unused dependencies from `packages/integrations/package.json`.
Release provider configuration lives on each Releases widget repository entry. The request handler resolves provider metadata and fetches releases directly from the provider-specific endpoint. GHCR uses anonymous OCI registry access for public packages.
Timetable passes its widget URL option directly to its API router and request handler. No global integration lookup is needed.
ArchiveTeam Warrior continues to use the standard integration system, but users must manually add the integration (it is not auto-created on install).
## Deliberate Non-Goals
- No provider secret/token support was added.
- No new secret storage model was introduced.
- No compatibility layer was kept in `packages/integrations`.
Widget options are client-visible, so storing provider credentials there would be wrong. If authenticated provider requests are needed later, they need a separate secret-storage design.
## Verification So Far
- Focused typechecks passed for affected packages.
- Live GHCR smoke checks passed for `ghcr.io/muchobien/pocketbase:latest` and `ghcr.io/homarr-labs/homarr:latest`.
-`pnpm test packages/integrations/test/pi-hole.spec.ts` passed earlier, but failed on rebased reruns because Pi-hole reported `domainsBeingBlocked` as `-2`/`0` instead of `> 1`.
The official [homarr.dev](https://homarr.dev) documentation site, built with [Docusaurus 3](https://docusaurus.io/) and living inside the Homarr monorepo.
## Why it's in the monorepo
Having documentation alongside the application code enables:
- **Two-way linking** — the docs app imports from `@homarr/definitions` for type-safe integration/widget metadata, and the main app references doc paths via generated sitemap types
- **Atomic PRs** — code changes and their documentation updates ship in the same pull request
- **Shared tooling** — same Node version, pnpm catalog, oxlint/oxfmt, CI pipeline
- **Build-time validation** — Docusaurus strict mode catches broken links and references at build time
-`index.mdx` — content using shared React components (`IntegrationHeader`, `WidgetHeader`, etc.)
## Importing from homarr packages
The docs app has `@homarr/definitions` as a workspace dependency. You can import integration kinds, widget kinds, and other definitions directly:
```typescript
import{IntegrationKind}from'@homarr/definitions';
```
This enables type-safe references to homarr domain concepts within documentation code.
## Search (Algolia DocSearch)
Search uses the `Docusaurus` Algolia index (crawler `cd77a285-2756-4557-bf21-ee703748df15`). A separate `markdown` index is built for LLM markdown indexing.
When making changes to the homarr codebase that affect user-facing behavior, **always update the corresponding documentation in this app**. See `.cursor/rules/documentation-sync.mdc` for the complete mapping of code changes to doc locations.
We are happy to announce that the documentation of Homarr has been migrated to Docusaurus - An opensource documentation tool.
The documentation has been re-written partly and includes now many animations, guides and additional crucial information.
We are still figuring things out and highly depend on your feedback.
<!-- truncate -->
## Why we migrated
The default GitHub Wiki feature is decent - but very confusing to read.
Our possibilites for searching the documentation in the Wiki are highly limited.
Additionally, Users are unable to contribute to the documentation if they have not sufficient permissions.
We could not review any chanegs made to the documentation. This is why we migrated to Docusaurus.
## How you can contribute
The documentation will soon be merged into the master branch of Homarr.
You'll find a ``doc/`` directory in the root of Homarr.
We'll obviously not include this in our Docker images.
In the future, everyone may modify the documentation by creating a pull request.
After we've reviewed your changes, we'll merge your PR and this documentation will automatically update.
## TL;DR
The documentation on GitHub Wiki will soon disappear. It will be replaced with this Docusaurus-powered website. You'll find much more information here with higher detail and searchability.
We've been working hard to offer Homarr in many languages. The next update of Homarr will be using [i18next](https://www.i18next.com/) to translate almost any part of your Dashboard.
### We need your help
 <br/>
Yes, we need **YOU**! We want to make Homarr accessible to as many people as possible. Thus, we need to translate all content into many different languages. We need your help for that! [You can help us now to translate Homarr into as many languages as possible](/docs/community/translations).
<!-- truncate -->
### About global translations / translations per user
We are aware that many people would like to configure translations globally or configure language per user. Currently, translations are stored in the Cookies, which will expire after some time (currently, it's set to 30 days). We will implement more granular language settings in future versions of Homarr, since it requires major changes to Homarr. Stay tuned! 😎
We'll continute to improve this system according to your feedback.
## Modules have been overhauled and renamed to widgets
Widgets are now much snappier and have been completely recoded.
This fixes some of the bugs a few users have previously experienced and improves overall customization.

## Fancy icon picker for speeding up your creation process
We've also added a new fancy icon picker, you can use to get access to any icons much faster.
We will add more icon sources in the future, as we made this icon picker very modular, and we plan to make local icons accessible via this picker very soon.
We've added a setting to disable editing completely. This will allow you to restrict edit access without password-protection.
use the `EDIT_MODE_PASSWORD` ENV variable to set a password for edit mode.
turn on `DISABLE_EDIT_MODE=TRUE` to disable edit mode on startup.
<!-- truncate -->
## 😎 Blazingly fast
We've implemented a new caching system, that will cache all data in your browser, meaning that Homarr will load within milliseconds, as if it was *powered by rust and blazingly fast* !!!
## Media request widgets (Overseerr & Jellyseerr)
We've implemented a new widget, that displays a history of all requests.
You can't approve from the widget yet, but we plan to add this in the future.
Since some have lost their changes, by refreshing the page in the edit mode. We've added a popup that will ask you to confirm you want to reload the page :
- Added mounting point ``/data`` for your database
- Require at least one administrator account on your instance. An onboarding process will automatically set this up.
- We've added analytics to the documentation and Homarr itself. We use [Umami Analytics](https://umami.is/) which collects anonymous data exclusively and protects your personal data rights. You can disable this using the environment variable ``DISABLE_ANALYTICS``.
One of the most requested features ever has been ___authentication___. We've completely *removed* the ``PASSWORD``, ``DISABLE_EDIT_MODE`` and ``EDIT_MODE_PASSWORD`` and replaced it by [a secure authentication system](https://next-auth.js.org/) that enables you to manage multiple users easily. We will allow for more detailed permission and user management soon. LDAP and OIDC integrations are currently also in progress and will arrive in a future version.
We have started work on this new version back in 2023 and have worked tirelessly for months on this brand-new dashboard.
This isn't our usual update. ``1.0`` doesn't use the same code from previous versions, but has been written from the ground up.
Therefore, this version contains an unusual amount of breaking changes.
This will be a one time occurrence and version > ``1.0`` will have less breaking changes.
This time more important than every; **read the breaking changes** list and **create a backup** before updating.
Some third party installation methods may take multiple days or weeks to update to the new version.
See [this guide on how to import your previous Homarr dashboard](/docs/getting-started/after-the-installation#importing-a-zip-from-version-before-100)
## Changes at a glance
- **Asynchronous integrations system**: In versions prior to ``1.0``, we fetched data upon each request synchronously.
This means that the user had to wait for the requests to finish so any data would appear.
A page reload will trigger another request - and worse, this will happen for every user accessing your dashboard.
In ``1.0``, we have implemented a new system that gets rid of this system and scales linearly.
It will fetch data in background, even if you are not on the page.
- **Permissions system**: You can now use detailed permissions to allow or prohibit users from certain actions.
For easier management, you can organize users into groups and manage them all at the same time.
- **Integration testing**: Starting from ``1.0``, we will require you to test the integration before you can use it.
This guarantees that you've configured your integration correctly and helps you find errors faster.
- **Re-imagined widgets**: All widgets have been rewritten & improved. They offer much better performance,
reliability and scalability compared to < ``1.0``.
- **Improved performance**: We have upgraded the underlying framework of Homarr and take advantage
of an in-memory message bus and websockets to deliver data near realtime to you.
<!-- truncate -->
## Breaking changes
:::warning
Please consult the breaking changes list & upgrade guide if you used third-party installation methods.
:::
- Docker Image
- Removed support for ``arm/v7`` -> upgrade to newer architectures as an alternative
- Renamed image from ``ghcr.io/ajnart/homarr`` to ``ghcr.io/homarr-labs/homarr``. We now use semantic versioning for our release numbers.
- Environment Variables
- New required env variable ``SECRET_ENCRYPTION_KEY`` used for encryption of secrets in the database
- Removed ``DISABLE_ANALYTICS``, can now be configured via UI
- Renamed ``AUTH_PROVIDER`` to ``AUTH_PROVIDERS``
- Renamed ``DATABASE_URL`` to ``DB_URL`` and made it optional conditionally. If ``DB_HOST`` is set, this is optional - otherwise it's required.
- Changed ``DOCKER_HOST`` to two new variables for hostnames (``DOCKER_HOSTNAMES``) and ports (``DOCKER_PORTS``). This allows for multiple docker integrations to be configured.
- ``AUTH_LOGOUT_REDIRECT_URL`` must now be a valid Url
- Increased minimum length of ``AUTH_OIDC_CLIENT_ID`` to 1
- Increased minimum length of ``AUTH_OIDC_CLIENT_SECRET`` to 1
- Removed ``AUTH_OIDC_TIMEOUT``
- Removed ``AUTH_LDAP_ADMIN_GROUP``, ``AUTH_LDAP_OWNER_GROUP``, ``AUTH_OIDC_ADMIN_GROUP`` and ``AUTH_OIDC_OWNER_GROUP`` -> groups are now managed without manual env binding
- ``CI`` will now also trigger an env validation skip like ``SKIP_ENV_VALIDATION``
- Mounts
- Removed ``/app/data/configs`` -> JSON configs have been removed in favour of relation databases
- Removed ``/app/public/icons`` -> Images can not be uploaded via the [media management](/docs/management/media/) in the UI
- Moved ``/data`` to ``/appdata`` and restructured contents (not backwards compatible!) -> Create a backup before migration of the entire directory to copy data to new Homarr
- Widgets:
- Calendar:
- Removed option ``Use Sonarr v4 API``
- Removed option ``Use Sonarr v5 API``
- Removed option ``Font Size``
- Removed option ``Hide week days``
- Download speed: We have removed this widget in favour of the brand new download widget which will also display this data.
- Dash. widget: We have removed this widget in favour of the System Health Widget. It will also display all data and some more than the old widget.
- Database:
- Changed schema of the database to a completely new schema. They are not backwards compatible. Please export the data from old Homarr in the UI and import it in ``1.0``
- Removed all usage of JSON files to store data. Import JSON files in Homarr ``1.0`` to import your dashboards.
- Removed ``schemaVersion`` from JSON and use the drizzle migrations to keep track of schema versions and take care of migrations.
- Web Server:
- Added an internal Nginx proxy to forward requests to two servers inside the container for technical reasons.
This could impact edge cases with iframes, cookies and custom setups.
- Dependencies
- We now depend on [Redis](https://redis.io/) as a real time messaging bus
- Development (relevant for contributors):
- Migrated to [pnpm](https://pnpm.io/)
- Migrated to a mono-repository
- All source code has been migrated to the [homarr-labs GitHub organization](https://github.com/homarr-labs)
- Platform specific breaking changes:
- TrueNAS
- The TrueCharts app chart is permanently deprecated and no longer maintained / supported. Please migrate to Electric Eel or a different Kubernetes platform.
- Unraid
- The Homarr app must be reinstalled. The existing directories on your disk are not compatible with Homarr 1.0. Export your old data in < `` 1.0`` and install using different file system locations.
- Kubernetes
- The official Helm chart has moved from https://github.com/oben01/charts to https://github.com/homarr-labs/charts
After that you'll get a list with all your boards that can be imported. You can configure how it should be imported and can select which screensizes it should import.
Once you have selected everything you can click on `Confirm import and continue` which will open the encryption key input dialog where you can input the secret key you got during the export.
This guide requires that you understand [how to access the CLI](../).
:::
If you somehow get into a situation where you have users with usernames that contain uppercase characters and you are no longer able to log in, you can use the following command to fix the usernames.
```bash
homarr fix-usernames
```
This will convert all credential usernames to lowercase and update the database accordingly.
This guide will help you set up your development environment to start contributing to homarr.
### Prerequisites
- **Node.js (lts)**: You can download it from the [official website](https://nodejs.org/).
- **pnpm (latest)**: You can install it by running `npm install -g pnpm`.
- **Git**: You can download it from the [official website](https://git-scm.com/).
- **Docker or Docker Desktop**: You can download it from the [official website](https://www.docker.com/).
- **A GitHub account**: You can create one on the [official website](https://github.com/).
### Setting up the project
1. Fork the repository by clicking on the "Fork" button on the top right corner of the repository page.

2. Clone the repository to your local machine with `git clone`. You can find the url to use by clicking on the "Code" button on the top right corner of your forks repository page.

3. Change directory to the project folder with `cd homarr`.
4. Install the dependencies with `pnpm install`.
5. Start redis by running `pnpm run docker:dev`. This will run a docker-compose file with a redis service.
6. 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='C:\\Dev\\homarr\\packages\\db\\db.sqlite'`
7. Run the migrations to create the sqlite db file with `pnpm run db:migration:sqlite:run`.
8. Start the development server with `pnpm dev` and wait for a message that says `✓ Ready in 10.7s`
9. Open your browser and navigate to [http://localhost:3000](http://localhost:3000). (Loading the page might take up to 2 minutes the first time)
### Useful npm scripts
#### During development
- `pnpm dev`: Start the development server.
- `pnpm cli`: Run cli commands.
- `pnpm db:migration:sqlite:run`: Run the migrations to create or update the sqlite db file.
- `pnpm db:migration:sqlite:generate`: Generate a new migration file for the sqlite db. Also works with `mysql`.
- `pnpm db:studio`: Open a visual studio for the database.
- `pnpm package:new`: Create a new package in the monorepo.
#### Checking code quality
- `pnpm lint`: Run the linter to check for code quality issues.
- `pnpm format:fix`: Run the formatter to fix code style issues.
- `pnpm test`: Run the tests. You can also use `pnpm test:ui` to get a UI for the tests.
- `pnpm typecheck`: Run the typescript type checker.
#### Building the project
- `pnpm build`: Build the project for production.
- `docker build -t homarr .`: Build a docker image for the project.
- `docker run -p 7575:7575 -e SECRET_ENCRYPTION_KEY='your_64_character_hex_string' homarr:latest`: Run the created docker image.
This guide provides step-by-step instructions for setting up a Kubernetes cluster using kind, configuring multiple nodes, and installing the Metrics Server.
## Prerequisites
Ensure you have the following installed:
- [Docker](https://docs.docker.com/get-started/get-docker/) (required for running `kind` clusters).
- [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/) (Kubernetes in Docker).
This will provision a cluster named `k8s-playground` with the specified node configuration.
### 2. Installing the Metrics Server
The Metrics Server is required for gathering resource utilization metrics like CPU and memory usage. It is essential for features such as Horizontal Pod Autoscaling.
#### Deploy the Metrics Server
Run the following command to install the Metrics Server:
kubectl get deployment -n kube-system metrics-server
```
Expected output:
```bash
NAME READY UP-TO-DATE AVAILABLE AGE
metrics-server 1/1 1 1 Xs
```
#### Get Node Resource Metrics
```bash
kubectl top nodes
```
This command should return CPU and memory usage statistics for each node.
## Conclusion
You have successfully set up a multi-node Kubernetes cluster using kind and installed the Metrics Server for monitoring resource usage. This setup is useful for local development and testing Kubernetes workloads before deploying them to a production environment.
Homarr offers a few environment variables, which can be used to configure the container.
:::info
With docker you can add the `_FILE` suffix to any environment variable, which will read the value from a file.
This is especially useful for [docker secrets](https://docs.docker.com/engine/swarm/secrets/#use-secrets-in-compose).
:::
## General
:::warning
Using the `PUID` and `PGID` will require you to set the correct permissions on the mounted volumes and if used the docker socket. See more in the [Running as a different user](/docs/advanced/running-as-different-user) documentation.
| ``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 some requests that need internet connection | `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` |
## Authentication
See [Single Sign-On](/docs/advanced/single-sign-on) for more informations.
## Security
The `SECRET_ENCRYPTION_KEY` is required. If none is specified before starting the container, a random key will be shown in the error message and the container will exit.
| ``DOCKER_HOSTNAMES`` | Comma-separated list of hostnames to connect to | For example `localhost,docker.example.com` | - |
| ``DOCKER_PORTS`` | Comma-separated list of ports to connect to | For example `2375,2376` | - |
| ``DOCKER_SOCKET_PATHS`` | Comma-separated list of mounted Docker-compatible socket paths. See the [Docker and Podman integration guide](/docs/integrations/docker) | For example `/var/run/docker.sock,/var/run/podman.sock` | - |
| ``DB_DRIVER`` | Database driver to use. Currently `better-sqlite3` is used for sqlite, `mysql2` for mysql and `node-postgres` for postgresql | ``better-sqlite3`` / ``mysql2`` / ``node-postgres`` | ``better-sqlite3`` |
| `REDIS_HOST` | Hostname of the Redis instance. | Any valid hostname | - |
| `REDIS_PORT` | Port of the Redis instance. | Any valid port | `6379` |
| `REDIS_USERNAME` | Username to connect to the Redis instance. | Any valid username | - |
| `REDIS_PASSWORD` | Password to connect to the Redis instance. | Any valid password | - |
| `REDIS_DATABASE_INDEX` | Select which database of your redis instance should be used. | Any numeric index | - |
| `REDIS_TLS_CA` | CA certificate for Redis TLS connections. If a certificate is specified the connection will use TLS. | Any valid CA certificate | - |
| ``HTTP_PROXY`` | HTTP proxy to use | Any valid HTTP proxy URL | - |
| ``HTTPS_PROXY`` | HTTPS proxy to use | Any valid HTTPS proxy URL | - |
| ``NO_PROXY`` | Comma separated list of hosts that should be excluded from proxying | Any valid hostnames | - |
## Advanced deployments
:::warning
The advanced deployments environment variables should only be used if you know what you are doing.
:::
| Environment Variable | Description |
| ------------------------ | ----------- |
| ``DB_MIGRATIONS_DISABLED`` | Disable db migrations. For example for helm charts |
Loaded 100 of 1782 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.