Compare commits

...
430 Commits
Author SHA1 Message Date
zacharyandClaude Opus 5.5 95df5af7cf chore(ops): build with bun and ship v2's .output/standalone
v2 replaces pnpm with bun and moves the standalone build to
apps/nextjs/.output/standalone, which already carries static assets,
public and next.config.ts. Next still writes an incomplete
.next/standalone, so the assembler now refuses to run without the v2
output rather than silently shipping that one.

The notes now list v2's registration points, the tests that already
fail on the upstream tag, and the one-way v1 to v2 database migration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 00:49:44 +02:00
zacharyandClaude Opus 5.5 dd5a3c83a8 fix(definitions): declare how UniFi requests authenticate, as v2 requires
v2 makes httpAuth mandatory on every integration definition: it is the
server-owned authentication the Stats widget and arbitrary requests reuse.
Both APIs take the key in an X-API-KEY header; Site Manager reads it from
its own secret kind.

Also split the inline type imports v2's stricter oxlint rejects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 00:46:25 +02:00
zacharyandClaude Opus 5 9c3e33510d chore(ops): give the unprivileged instance its own proxy unit
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:57 +02:00
zacharyandClaude Opus 5 79eb42b578 fix(ops): bind the app to loopback, not to the machine name
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:57 +02:00
zacharyandClaude Opus 5 b963a26446 chore(ops): start Homarr as an unprivileged user under systemd
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:57 +02:00
zacharyandClaude Opus 5 977bfece6e chore(ops): build without depending on a stray .env
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:57 +02:00
zacharyandClaude Opus 5 83d9db6447 chore(ops): move instance state to /var/lib/homarr without touching the image
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:57 +02:00
zacharyandClaude Opus 5 5867b14836 chore(ops): create only the state directories the instance uses
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:51 +02:00
zacharyandClaude Opus 5 04ef6e2a4a chore(ops): write the deploy plan before truncating it
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:40 +02:00
zacharyandClaude Opus 5 a2a5af4078 chore(ops): itemize the dry-run so it actually shows the plan
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:40 +02:00
zacharyandClaude Opus 5 7c16836c84 chore(ops): fail early on a stale Node and compare migrations per dialect
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:40 +02:00
zacharyandClaude Opus 5 373b9a8107 chore(ops): add LXC deploy script and fork maintenance notes
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:40 +02:00
zacharyandClaude Opus 5 35095276fa refactor(widgets): let the UniFi OS chart fill the tile
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:40 +02:00
zacharyandClaude Opus 5 fa945e947e fix(integrations): end the health window on the last measured hour
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:40 +02:00
zacharyandClaude Opus 5 13b14c50af feat(widgets): show WAN uptime in the UniFi site table
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:40 +02:00
zacharyandClaude Opus 5 b32cc745c4 feat(integrations): fill the recent hours from the 5-minute ISP metrics
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:40 +02:00
zacharyandClaude Opus 5 30b581ee52 fix(widgets): make the UniFi site table sortable and fill the tile
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:40 +02:00
zachary 1fb30bd2b3 Revert "fix(integrations): label sites with the console name Site Manager shows"
This reverts commit 12db2c0677.
2026-10-08 00:24:40 +02:00
zacharyandClaude Opus 5 7b73744e2c fix(integrations): label sites with the console name Site Manager shows
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:40 +02:00
zacharyandClaude Opus 5 bd9c86dca9 docs(integrations): say what a null SD-WAN status does not tell us
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:40 +02:00
zacharyandClaude Opus 5 1c317987ff fix(integrations): treat an unstated console update state as unknown
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:40 +02:00
zacharyandClaude Opus 5 ca3e798a30 fix(widgets): hide the decorative health bar from assistive technology
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:40 +02:00
zacharyandClaude Opus 5 145dc17d8d fix(widgets): report every health state in the UniFi site summary
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:40 +02:00
zacharyandClaude Opus 5 2c0f0bbdd3 fix(widgets): omit packet loss when the cloud did not measure it
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:40 +02:00
zacharyandClaude Opus 5 00f022956c feat(widgets): add UniFi Site Manager overview widget
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:40 +02:00
zacharyandClaude Opus 5 78f63d5d75 fix(widgets): remove out-of-scope UniFi Site Manager widget stub
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:28 +02:00
zacharyandClaude Opus 5 3fb2f2dc1f feat(api): expose unifiSiteManager overview query
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:24 +02:00
zacharyandClaude Opus 5 bfbba5782a fix(integrations): type the Site Manager transport boundary and guard it
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:21 +02:00
zacharyandClaude Opus 5 cc4641afd7 docs: plan — annotate test fixture arrays and require typecheck per task
Le fixture déclarait warnings: [], inféré never[], alors que le test lui
assigne un tableau de chaînes. tsc refuse ; vitest ne le voit pas.

Ajouté à la vérification de chaque tâche : pnpm turbo typecheck, pas
seulement pnpm test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:21 +02:00
zacharyandClaude Opus 5 787e36d8ed feat(integrations): add UnifiSiteManagerIntegration
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:21 +02:00
zacharyandClaude Opus 5 87cf30fe76 docs: plan — the SD-WAN dash means unknown, not 'no configuration'
Constat de revue : un échec total des appels de statut produit le même
tableau vide qu'un compte sans configuration. Le rendu reste honnête —
le tiret n'affirme rien — mais le commentaire, lui, tranchait à tort.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:15 +02:00
zacharyandClaude Opus 5 49d4c3ae28 feat(integrations): add UniFi Site Manager cloud client
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:15 +02:00
zacharyandClaude Opus 5 87056f998d feat(integrations): add UniFi Site Manager schemas and pure mappers
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:15 +02:00
zacharyandClaude Opus 5 3baf9066f8 feat(definitions): declare unifiSiteManager integration and widget kinds
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:15 +02:00
zacharyandClaude Opus 5 0239383009 docs: plan — ESM-safe guard test and the .spec.ts inclusion constraint
Scan pré-vol. Le test de garde du bundle client lisait son source via
__dirname, qui n'existe pas en ESM et n'est utilisé par aucun autre spec
du dépôt. Remplacé par import.meta.url, avec la directive d'environnement
node puisqu'il lit le disque et que le défaut est jsdom.

Ajouté aux contraintes globales : vitest n'inclut que **/*.spec.ts, donc
un fichier .spec.tsx ne serait jamais exécuté et son absence passerait
pour un succès.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:05 +02:00
zacharyandClaude Opus 5 b5e4109740 docs: plan — add per-site SD-WAN tunnel status
L'état des tunnels entre dans quatre des sept tâches : schémas et index
par site (tâche 2), deux méthodes de client bornées (tâche 3),
orchestration en enrichissement non fatal (tâche 4), colonne et
traductions (tâche 6), et une étape de vérification dédiée (tâche 7).

Deux points de justesse portés par des tests :
- `ping: 0` devient `pingMs: null`. Relevé sur un tunnel connecté entre
  deux sites distants, où une latence nulle est physiquement
  impossible : le champ veut dire « non mesuré ».
- le nombre de configurations interrogées est borné à cinq. Le statut
  étant indexé par configuration, un compte à dix produirait onze
  requêtes par rafraîchissement.

La topologie reste hors périmètre : une liste de quinze réseaux n'a pas
sa place dans une ligne de tableau.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:05 +02:00
zacharyandClaude Opus 5 5dd35e48f1 docs: spec — add per-site SD-WAN tunnel status
Ajout demandé après validation : l'état des tunnels, indexé par siteId,
se greffe sur les lignes existantes. La topologie reste hors v1 — une
liste de quinze réseaux n'a pas sa place dans une ligne de tableau.

Relevé au passage : ping vaut 0 sur un tunnel connecté entre deux sites
distants, où une latence nulle est physiquement impossible. Le champ
signifie « non mesuré » et le contrat le traduit en null, plutôt que de
propager un zéro que l'interface afficherait comme une performance.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:05 +02:00
zacharyandClaude Opus 5 050b96f1e3 docs: spec — health states are a status palette, with a mandatory text summary
Le validateur de palette ne couvre que les palettes catégorielles. Sain,
dégradé et coupé sont des statuts : palette réservée et fixe, assortie de
la règle qu'ils ne voyagent jamais sans icône ni libellé.

Une icône par segment étant impossible sur vingt-quatre segments, la
bande est doublée d'un résumé textuel. Mesuré : un vert/orange/rouge
classique donne un ΔE de 2,9 entre orange et vert en vision protanope.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:05 +02:00
zacharyandClaude Opus 5 29390a0cc8 docs: spec — back to a table, health bar as a column
L'utilisateur revient sur la forme liste et retient la proposition
initiale : tableau HomarrDataTable avec la bande de santé en colonne.

La bande reste dans la v1 (D4 inchangée) — c'est la forme qui change,
pas le périmètre. Le tableau rend le tri et la visibilité responsive,
absorbe la croissance du nombre de sites, et correspond à ce que fait
l'interface Ubiquiti.

Contrepartie consignée : dans une colonne la bande est étroite, ce qui
fait de l'infobulle par segment le moyen principal de lire une tranche
plutôt qu'un confort.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:05 +02:00
zacharyandClaude Opus 5 5736f9ac4e docs: spec — merge ISP metrics into the site health list
Proposition de l'utilisateur, retenue : fusionner les métriques ISP avec
la barre « Internet Health » pour une liste d'état rapide des sites.

Elle inverse la décision D4. La barre avait été écartée au motif que
`internetIssues` est un tableau creux dont il faudrait reconstituer les
périodes saines — vrai de cette source, mais `isp-metrics` fournit la
même information déjà dense, une entrée par site, vérifié. La barre
coûte alors une boucle de coloration, pas un moteur de graphe.

D3 change aussi : liste d'état plutôt que tableau, la bande au centre
plutôt qu'en colonne. HomarrDataTable n'a donc plus lieu d'être, et le
tri disparaît. Limite assumée et consignée : au-delà de cinq ou six
sites la forme tableau redeviendrait préférable.

Point de justesse ajouté au contrat : une tranche horaire sans mesure
vaut "unknown" et jamais "healthy". Un site enregistré depuis douze
heures n'a que douze tranches sur vingt-quatre ; les peindre en vert
affirmerait un bon fonctionnement jamais mesuré.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:05 +02:00
zacharyandClaude Opus 5 92c878befd docs: design for UniFi Site Manager integration
Nouvelle intégration cloud-only, distincte de unifiOs, remontant une vue
multi-sites. Premier des trois projets identifiés à partir des captures
du Site Manager ; il construit aussi le socle des deux suivants.

Constat notable : il n'existe pas d'API cachée. /ea/* est un alias
complet de /v1/*, et une trentaine d'autres chemins plausibles
renvoient 404. La surface documentée est toute la surface.

La séparation types / parse est imposée dès la conception par le défaut
rencontré sur l'intégration précédente, où un import serveur dans un
module réexporté côté client empêchait l'application de démarrer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:05 +02:00
zacharyandClaude Opus 5 950a925fd6 feat(widgets): 5s refresh, download first, chart from the first reading
Trois demandes utilisateur, plus une adaptation rendue nécessaire par la
première.

- Ordre inversé : descendant avant montant.
- Rafraîchissement porté de 60 s à 5 s. Il fallait le faire sur les DEUX
  couches locales : l'intervalle client seul aurait relu la même entrée
  de cache serveur douze fois. Le cache cloud reste à 5 min, ses
  métriques n'évoluant que par tranches de 5 min.
- Le graphe est rendu dès la première mesure : vide plutôt qu'absent,
  ce qui évite un saut de mise en page et n'affirme rien de faux.

Adaptation : la console ne rafraîchit ses statistiques que toutes les
30 s (mesuré — `lastHeartbeatAt` avance par pas de 30 s exacts, valeurs
identiques entre deux battements). Interroger toutes les 5 s renvoie
donc six fois la même mesure. Sans filtrage, le graphe deviendrait un
escalier et 60 points ne couvriraient plus que 5 minutes. Une lecture
identique à la précédente n'est donc pas enregistrée.

Seuil de péremption locale recalibré de 150 s à 30 s : l'ancien était
dimensionné pour 60 s + 60 s et ne signalait plus rien d'utile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:24:05 +02:00
zacharyandClaude Opus 5 0a623b7434 feat(widgets): chart WAN throughput instead of static figures
L'API UniFi ne fournit que l'instantané et Homarr ne stocke aucun
historique : la série est accumulée côté client, comme le fait déjà le
widget systemResources. Elle repart donc vide à chaque rechargement.

Deux décisions d'honnêteté, dans la ligne du reste du widget :
- une lecture en échec est ignorée, jamais enregistrée comme un zéro,
  qui dessinerait une chute à la ligne de base n'ayant pas eu lieu ;
- sous deux points, on affiche les chiffres seuls : une ligne plate
  tracée sur un point unique se lit comme un réseau au repos alors
  qu'on n'a rien mesuré.

Couleurs validées par le validateur de palette (bande de luminosité,
chroma, séparation daltonisme et vision normale, contraste) en thème
clair ET sombre. La paire orange.5/yellow.5 du widget systemResources
n'a délibérément pas été reprise : son ΔE en vision normale est de
12,1, sous le plancher de lisibilité de 15.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:23:55 +02:00
zacharyandClaude Opus 5 0e950ce5e3 fix(nextjs): register the unifiOs category translation key
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:23:55 +02:00
zacharyandClaude Opus 5 3125bcf400 fix(integrations): keep unifi-os-types client-safe
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:23:44 +02:00
zacharyandClaude Opus 5 277fbb841e fix: address final review — cloud TLS, MAC guard, honest ISP metrics
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:23:44 +02:00
zacharyandClaude Opus 5 1b312b3103 docs: spec — make ISP metrics nullable, they are absent during a WAN outage
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:23:44 +02:00
zacharyandClaude Opus 5 a793e3c325 fix(widgets): stop asserting a gateway that does not exist, label the ISP gap
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:23:44 +02:00
zacharyandClaude Opus 5 5958c6df65 feat(widgets): add UniFi OS widget
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:23:44 +02:00
zacharyandClaude Opus 5 e8c8343b9f fix(widgets): remove out-of-scope UniFi OS widget stub from task 6
The widget stub (packages/widgets/src/unifi-os/) was incorrectly added in
Task 6 and belongs to Task 7. Its removal confirms the type system does not
require it, and prevents inconsistencies: the stub declares IconWifi while
Task 7 specifies IconRouter, and it is not registered in manifest.ts,
catalog.ts or refetch-intervals.ts.

- Delete packages/widgets/src/unifi-os/ directory entirely
- Revert packages/widgets/src/index.tsx to commit 57286d83d

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:22:24 +02:00
zacharyandClaude Opus 5 604c6d0855 feat(api): expose unifiOs widget summary query through tRPC router and request handler
Create unifiOs request handler with 60s cache and stale-on-error fallback, and tRPC router exposing clientApi.widget.unifiOs.summary query. Include minimal widget stub for type system.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:22:19 +02:00
zacharyandClaude Opus 5 11dd6f44ea fix(integrations): make client/device counts nullable and test real degradation
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:22:04 +02:00
zachary 7758e6537b docs: spec — drop unused VPN servers call, make client/device counts nullable 2026-10-08 00:22:04 +02:00
zacharyandClaude Opus 5 0abde13101 feat(integrations): add UnifiOsIntegration with optional cloud enrichment
Introduces UnifiOsIntegration, orchestrating UnifiOsApi (local) and
UnifiSmApi (optional cloud) to build the UnifiOsSummary. Local endpoint
calls degrade independently via Promise.allSettled; the cloud client is
never instantiated without a configured siteManagerApiKey secret.
Registers the unifiOs kind in the integration creator, index, and types
barrels, resolving the previously-failing typecheck.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:22:04 +02:00
zacharyandClaude Opus 5 2de3063dc1 fix(integrations): select newest ISP metric period explicitly
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:21:50 +02:00
zacharyandClaude Opus 5 2d601279e3 fix(integrations): type unifi-os test fetch mocks for tsc
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:21:50 +02:00
zacharyandClaude Opus 5 ca1cc54623 feat(integrations): add optional UniFi Site Manager client with 5min cache
Task 4 of the UniFi OS integration: an optional cloud client for
Ubiquiti's Site Manager API that enriches the local console with ISP
latency data. Instantiated only when a cloud API key is configured
(Task 5). Every failure path (bad key, non-ok status, parse error,
unmatched gateway MAC) returns null rather than throwing, since the
cloud is a pure enrichment. Metrics are cached 5 minutes, keyed by
gateway MAC, to respect the ~27 min publication lag and avoid rate
limiting; the query window is 3 hours because a 15-minute window
returns an empty array on the real API.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:21:50 +02:00
zacharyandClaude Opus 5 c17b863e3f feat(integrations): add UniFi OS local API client
Implements HTTP client for UniFi OS console integration API with support for
fetching site information, client counts, device statistics, and VPN server
configs. Includes pagination handling with filter-based fallback when client
count exceeds API limit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:21:50 +02:00
zacharyandClaude Opus 5 65d1266e63 fix(integrations): make unifi-os test invocation-independent with vi.hoisted()
Guarded environment validation with SKIP_ENV_VALIDATION and SECRET_ENCRYPTION_KEY
set in vi.hoisted() so that tests pass both with 'pnpm test' (NODE_ENV=development)
and bare 'pnpm vitest run' (no env override).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:21:50 +02:00
zachary 2692b6ea17 docs: fix test command in plan (pnpm test, not pnpm vitest run) 2026-10-08 00:21:50 +02:00
zacharyandClaude Opus 5 9339e6308b feat(integrations): add UniFi OS schemas and pure mappers
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:21:50 +02:00
zacharyandClaude Opus 5 13d27fb308 feat(definitions): declare unifiOs integration kind, category and widget kind
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:21:50 +02:00
zacharyandClaude Opus 5 f3d0c2f527 docs: implementation plan for UniFi OS integration
Plan en 9 tâches avec cycle TDD par étape, du vocabulaire dans
@homarr/definitions jusqu'au déploiement sur le LXC.

Corrige aussi la spec sur deux points relevés en écrivant le plan :
- catalog.ts (widgetCatalogIcons) est un cinquième point d'enregistrement,
  vérifié par manifest.spec.ts et absent du §5.2
- le §9 décrivait un test d'agrégation multi-site devenu sans objet depuis
  la décision de ne pas agréger

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:21:14 +02:00
zacharyandClaude Opus 5 ac9a0b0f42 docs: design for UniFi OS integration
Conception validée pour une intégration UniFi OS en complément de
unifiController, basée sur l'API d'intégration officielle (clé X-API-KEY)
plutôt que sur l'ancienne API du Network Controller.

Toutes les capacités et limites de l'API ont été mesurées sur une console
réelle en 10.6.102, pas déduites de la documentation publiée (qui s'arrête
à 10.4.57 et est déjà en retard sur les consoles).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-08 00:21:14 +02:00
Releases Homarr e8d67b9b02 chore(release): 2.3.0 [skip ci] 2026-10-07 18:21:46 +00:00
homarr-releases[bot] 95d10d32c2 chore(release): automatic release v2.3.0 2026-10-07 18:21:01 +00:00
ajnart 01d142455b fix(release): invoke Bun explicitly for lockfile updates 2026-10-07 19:40:53 +02:00
ajnart d31f8568dc fix(rss-feed): update description length test fixture 2026-10-07 18:01:22 +02:00
homarr-releases[bot] 6c355897d2 chore(release): automatic release v2.3.0 2026-10-07 15:45:15 +00:00
Ben Zikriandajnart 68238fa69b feat(rss-feed): add configurable description max length (#7051)
Co-authored-by: ajnart <thomas@ajnart.dev>
2026-10-07 17:42:59 +02:00
Thomas a7fac57d62 fix(docs): simplify integration secrets styling (#7076) 2026-10-07 17:03:12 +02:00
Thomas 2ea05f50cc fix(widgets): restore readable summary text and icons (#7068)
* fix(widgets): restore readable summary text and icons

* fix: size Bazarr columns using displayed dimensions

* Revert "fix: size Bazarr columns using displayed dimensions"

This reverts commit 02e28a964faa191a91f840b7b45d79ea937dac5d.
2026-10-07 17:02:38 +02:00
Thomas 8d78d0ae54 fix(board): separate nested container headers (#7060)
* fix(board): separate nested container headers

* fix(board): bound nested header inset
2026-10-07 16:39:37 +02:00
Thomas 229bd4b3ed chore: use Bun package management and Turbo remote caching (#6978)
* feat: migrate tooling and runtimes to bun

* fix: serialize act installs and adapt backup checks

* fix: finish bun scripts and rootless act setup

* fix: support bun images in runtime benchmarks

* fix: keep a single bun toolchain configuration

* fix: include docs mascot sources in workshop build

* docs: use bun in remaining tool commands

* fix: complete bun sqlite development tooling

* docs: explain sqlite development adapter

* fix: align remaining commands and local ci validation

* fix: use renovate regex fallback with bun

* fix: finalize bun sqlite connections during cleanup

* fix: preserve sqlite foreign key enforcement

* fix: align docker build frontends

* fix: prepare release versions with bun

* Add Bun benchmarks and preserve SQLite connection defaults

* Benchmark Bun installs, conflicts and production workloads

* Force Bun workspace tools and HTTP/1.1 upstream streaming

* Exclude benchmark evidence from Docker contexts

* Expose branch benchmarks through CI

* Verify hydrated widget data in production benchmarks

* Measure image archives and document benchmark protocol

* Audit dependency sharing across every workspace

* Pin pnpm for nested baseline benchmark scripts

* Preserve nested manager pinning on benchmark resume

* Include application and runtime image footprints

* Fail benchmark gates on candidate command errors

* Report complete build overhead and sampled memory bounds

* Validate migration comparisons across pinned revisions

* Record configurable runtime memory budgets

* Retain out-of-memory kills in runtime budget probes

* Reject OOM events consistently across runtime metrics

* Record complete Bun migration benchmarks

* Record memory diagnosis and correct Turbo caching

* Use Bun package management while preserving Node runtimes

* Keep native build fallbacks and exclude development artifacts

* Fix Node ESM metadata and isolate local CI configuration

* Measure development performance and isolate act environment

* Support isolated environment files in rootless act

* Validate the full migration range with act

* Preserve native SQLite binding for bundled migrations

* Use documented dev defaults and report benchmark scope

* Validate dev defaults and isolate restart measurements

* Record package-manager and development benchmarks

* Keep benchmark measurements and link full command artifacts

* Clean up temporary act environment files

* Validate Node runtimes in package-manager comparisons

* Validate every workspace in manual CI runs

* Retain patched nested brace expansion versions

* Complete package-manager and Node performance report

* Normalize archived benchmark logs

* Record final hosted validation

* Cache both Next builds with platform isolation

* Prime cache with write-only mode

* Split Next artifacts below Cloudflare upload limit

* Assemble coherent standalone cache outputs

* Preserve traced middleware entrypoints

* Assemble traced directory links before files

* Separate cached dependencies from assembled output

* Record signed Turbo cache benchmarks

* Remove archived benchmark captures

Full benchmark evidence is preserved at 8f7d3bcd14109bfb13fa664d31710b88d820ad66.

* Remove migration benchmark harness

* fix: correct migration launch and local Act prerequisites

* Consolidate Turbo caches and production artifacts

* Exclude generated files from Docker cache inputs

* fix: address Bun migration review findings

* fix: check restored compiler bundle manifests
2026-10-07 16:38:14 +02:00
Thomas dc5f382875 fix: avoid stretching Beszel charts while rows overflow (#7074) 2026-10-07 15:39:44 +02:00
1bc614b367 feat(api): expose existing dashboard controls (#6546)
* feat(api): expand REST coverage for full dashboard automation

Closes #6435.

Boards could only be created empty through the API. The single endpoint for
adding a widget took no geometry, so the size came from the widget default and
the position was always the next free cell. Everything else a setup needs was
reachable from the interface only.

Board composition
- POST /api/boards/items accepts an optional placement: width, height, xOffset,
  yOffset and sectionId, or a layouts array for per breakpoint control. Without
  it the previous automatic placement is unchanged.
- Adds read, update and delete for items, CRUD for sections, read and replace
  for layouts, and GET /api/boards/{id} for the whole board.
- GET /api/boards/{id}/export and POST /api/boards/import make a board
  reproducible from version control. onConflict decides what happens when the
  name is taken, so a pipeline can be re-run.
- Board access is manageable through GET/PUT /api/boards/{id}/permissions.

Remaining services
- Search engines, integrations, groups, api keys and server settings are
  exposed over OpenAPI and registered as MCP tools.
- GET /api/config/export and POST /api/config/import cover the configuration of
  a whole instance. Ids are kept so references inside opaque widget options stay
  intact, and entities are matched by natural key when the id is unknown, which
  is what makes a document from another instance applicable.
- The backup endpoints accept an ApiKey header, so a recovery job no longer
  needs a browser session.

Headless bootstrap
- homarr api-keys create prints a key without opening the web interface, which
  together with the existing recreate-admin removes the last manual step.

Notes
- No database migration, the schema already stores position and size.
- Grid coordinates and sizes in a document are bounded, and the placement scan
  no longer walks every column, so a large grid cannot occupy the server.
- Imports collect their statements first and apply them in one transaction, so a
  rejected document never leaves a board half written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: harden dashboard automation APIs

* fix: validate global home board settings

* fix: validate merged import settings

* docs(api): describe the bounded placement scan accurately

The comment still described the earlier version that walked every row up to the
last occupied one. Both axes now iterate the edges of the occupied areas, so the
cost follows the amount of content rather than the dimensions of the grid.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(api): keep the search engine type narrow and record the new MCP tools

The `.output()` added to `searchEngine.getPaginated` widened `type` back to a
string through the plain select schema, which broke the management page that
switches on it. It now reuses the narrowed union the detail endpoint already has.

`mcp.spec` pins the reviewed tool inventory; the 33 tools this branch exposes are
added to it. The configuration round trip asserted the first layout of an item,
which is the mobile one half of the time and holds the same item clamped, so it
addresses the base layout by id instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(api): complete Homarr automation endpoints

* refactor(api): consolidate preferences and task docs

* refactor(api): narrow REST to dashboard automation

* refactor(api): expose existing dashboard controls only

* fix(docs): drop removed API key CLI path

---------

Co-authored-by: rfdrew <20803599+rfdrew@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Thomas Camlong <thomas@ajnart.fr>
Co-authored-by: ajnart <thomas@ajnart.dev>
2026-10-07 15:02:59 +02:00
homarr-renovate[bot] 6b1a956ce3 chore(deps): update dependency shell-quote@<1.8.5 to v1.11.0 [security] (#7071)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-10-07 11:54:42 +00:00
Thomas a932cb85a7 fix(boards): reduce phone outer gutters (#7070)
Related to #7069. Remove duplicated shell gutters on phone boards while retaining widget insets and wider layout spacing.
2026-10-07 13:22:11 +02:00
Thomas eb1d449dca fix(widgets): allow narrow Docker and downloads tables to scroll (#7067)
Related to #7058. Preserve readable table column widths and enable horizontal scrolling in narrow Docker and Downloads widgets.
2026-10-07 12:16:44 +02:00
Thomas 29a422e4d8 fix: collapse Beszel controls on touch screens (#7065)
Related to #7022. Keep Beszel touch controls accessible through a compact dismissible menu.
2026-10-07 12:12:58 +02:00
Thomas ff22c577ac fix: keep dashboard CSS out of board settings (#7066)
Related to #7062. Keep dashboard custom CSS out of dashboard settings.
2026-10-07 12:12:43 +02:00
Thomas fb435eff41 fix(db): explain unsupported MySQL upgrade options (#7063) 2026-10-07 11:14:45 +02:00
Thomas 3f02da7b59 fix(dns-hole-summary): restore colors and fit compact cards (#7045)
Related to #7020 and #7018. Restore DNS summary colors, fit compact cards, and match preview text scaling.
2026-10-07 10:00:24 +02:00
Thomas d1c88b6821 fix: return board header to current dashboard (#7059)
Related to #7056. Keep the current board destination when returning from dashboard settings.
2026-10-07 09:59:43 +02:00
homarr-releases[bot] 8c977dfd1f chore: update bug report template (#7044)
Add Homarr 2.2.0 to the bug report version list.
2026-10-07 09:58:42 +02:00
Thomas 8ae66b2c5b fix: allow unlimited sidebar content while editing (#7061) 2026-10-07 09:56:31 +02:00
Thomas f420df5d03 Merge pull request #7046 from homarr-labs/docs/update-helm-docs
docs(helm): update chart installation page
2026-10-07 07:46:19 +02:00
homarr-crowdin[bot] 892015065c chore(lang): updated translations from crowdin (#7049) 2026-10-07 07:45:55 +02:00
Thomas b7b803816c fix: use public Pi-hole version discovery (#7032) 2026-10-06 19:27:33 +02:00
Thomas 3bd8101b36 fix(lang): restore English UK labels corrupted by Crowdin (#7050) 2026-10-06 11:53:17 +02:00
github-actions[bot] c757c33bf3 docs(helm): update chart installation page 2026-10-05 18:10:41 +00:00
Releases Homarr 4df4ee0599 chore(release): 2.2.0 [skip ci] 2026-10-05 12:59:28 +00:00
homarr-releases[bot] 1980046b45 chore(release): automatic release v2.2.0 2026-10-05 12:58:15 +00:00
Thomas f39bb15d79 fix: report GitHub icon indexing failures clearly (#7008) 2026-10-05 14:56:02 +02:00
homarr-releases[bot] 837b382227 chore: update bug report template (#7028) 2026-10-05 14:53:48 +02:00
SmilingJoeandajnart df6410d56c feat(widgets): add GPU metrics to Beszel system stats (#6645)
Co-authored-by: ajnart <thomas@ajnart.dev>
2026-10-05 14:53:23 +02:00
Thomas 177fa97b76 feat: support Unraid storage pools (#7038) 2026-10-05 14:51:48 +02:00
homarr-crowdin[bot] 08412d5f1d chore(lang): updated translations from crowdin (#6986) 2026-10-05 14:51:20 +02:00
Sobe Silvertree 67196975f3 feat(sabnzbd): add archived history options to the Downloads widget (#6515) 2026-10-05 14:51:03 +02:00
Thomas 4dbe4343e5 fix(demo): stop seeded widget background polling (#6980)
* fix(demo): stop seeded widget background polling

* docs(demo): clarify single-replica requirements

* fix: abort startup when auth secret generation fails

* docs: remove demo deployment changes from PR

* fix: scope demo polling changes
2026-10-05 09:51:35 +02:00
Thomas a17845aa1c feat: add create-only root certificate API (#7033)
* feat: add create-only root certificate API [skip ci]

* fix: harden root certificate API handling
2026-10-05 09:48:06 +02:00
Thomas 67cff1fbc0 Merge pull request #7039 from homarr-labs/docs/update-helm-docs
docs(helm): update chart installation page
2026-10-05 08:42:57 +02:00
github-actions[bot] 3a0a25f04b docs(helm): update chart installation page 2026-10-04 15:25:58 +00:00
Releases Homarr 473b6cb7c4 chore(release): 2.1.2 [skip ci] 2026-10-04 07:28:32 +00:00
homarr-releases[bot] 60c602072d chore(release): automatic release v2.1.2 2026-10-04 07:27:19 +00:00
Thomas 6ce517df92 fix(ci): synchronize release branches from a clean worktree (#7036) 2026-10-04 09:21:39 +02:00
Bergasha e089d690ee fix(iframe): scale iframe content with the board (#7034) 2026-10-04 09:16:16 +02:00
Releases Homarr d56b20e0f6 chore(release): 2.1.1 [skip ci] 2026-10-03 23:21:49 +00:00
homarr-releases[bot] 8c3985824f chore(release): automatic release v2.1.1 2026-10-03 23:19:24 +00:00
Thomas 917d92c9f3 fix: improve compact app label readability (#7029)
* fix: improve compact app label readability

* fix: align truncated app titles
2026-10-04 01:14:08 +02:00
Releases Homarr 33c3b1ea70 chore(release): 2.1.0 [skip ci] 2026-10-03 22:35:09 +00:00
homarr-releases[bot] 6cdce9bdc3 chore(release): automatic release v2.1.0 2026-10-03 22:34:03 +00:00
Thomas f50d4ed134 fix(integrations): authenticate Frigate stats and HTTP requests (#7019)
* fix(integrations): authenticate Frigate stats and HTTP requests [skip ci]

* fix(integrations): reject HTTP Frigate URLs before authentication
2026-10-03 23:33:23 +02:00
Thomas 5a2f6cfa1b docs: require agent docs and tests only when user-critical (#7021) 2026-10-03 23:31:11 +02:00
Thomas d15987c9fb fix(docker): resolve Homepage Dashboard Icons keys (#7004) 2026-10-03 23:14:54 +02:00
Thomas c1dd2b3bca fix(unifi): count active remote VPN sessions (#7013) 2026-10-03 23:14:37 +02:00
Thomas 83ae98847a fix(onboarding): return to welcome before administrator creation (#7012) 2026-10-03 22:34:05 +02:00
Thomas b40f000dd5 fix(board): make container headers fully clickable (#7006) 2026-10-03 22:33:57 +02:00
Thomas 6f93be1227 fix: include Jellyfin releases across mixed libraries (#6998) 2026-10-03 22:33:49 +02:00
Thomas af4209886d fix(board): persist setup checklist dismissal (#6992) 2026-10-03 22:33:41 +02:00
Thomas 27a7f5f42e docs(migration): explain backups and board scaling (#6991)
* docs(migration): explain backups and board scaling

* docs: qualify migration layout restoration
2026-10-03 22:33:33 +02:00
Thomas 121a50cb19 fix(integrations): accept persisted IDs when updating (#6985) 2026-10-03 22:33:30 +02:00
Thomas e048ae9cd7 fix(board): restore layouts after column changes (#6979)
* fix: preserve geometry across layout resizing

* style: format layout migration snapshots
2026-10-03 22:32:49 +02:00
Thomas eec910aabb fix: classify Plex music releases correctly (#6977)
* fix: classify Plex music releases correctly

* test(plex): cover music release classification
2026-10-03 22:32:45 +02:00
Thomas 55b203ad93 feat(api): test connections using stored integration credentials (#7014) 2026-10-03 21:51:34 +02:00
Thomas a9820e99a4 chore: trim supporting files from Docker DNS fix (#7011) [skip ci] 2026-10-03 20:32:45 +02:00
Thomas 67d76f034d fix: use native DNS resolution by default for Docker services (#6997)
* fix: use native DNS resolution by default for Docker services

* test: verify Docker service DNS through production Compose

* test: isolate Docker DNS smoke resources per invocation [skip ci]
2026-10-03 20:19:28 +02:00
Thomas 907b166f46 docs(readme): fix installation links and sync integrations (#6996) 2026-10-03 11:25:41 +02:00
homarr-renovate[bot] 25120cd2be chore(deps): update dependency brace-expansion@>=1.0.0 <1.1.18 to v1.1.21 [security] (#6966)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-10-03 04:17:29 +00:00
homarr-renovate[bot] 3adb63850d chore(deps): update dependency next to v16.3.6 [security] (#6960)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-10-02 23:54:34 +00:00
homarr-renovate[bot] 819337afc6 chore(deps): update dependency brace-expansion@>=2.0.0 <2.1.4 to v2.1.7 [security] (#6967)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-10-02 23:52:12 +00:00
homarr-releases[bot] d096dbafc6 chore: update bug report template (#6957) 2026-10-03 01:45:39 +02:00
Thomas 65dcd5f3aa Merge pull request #6970 from homarr-labs/docs/update-helm-docs
docs(helm): update chart installation page
2026-10-03 01:45:22 +02:00
Thomas 97474ebc87 fix: restore app icon sizing on scaled boards (#6972) 2026-10-03 01:08:42 +02:00
github-actions[bot] fb1537a248 docs(helm): update chart installation page 2026-10-02 20:15:37 +00:00
homarr-renovate[bot] f46dbb630a chore(deps): update dependency @grpc/grpc-js@<1.12.7 to v1.13.6 [security] (#6961)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-10-02 20:12:09 +00:00
homarr-renovate[bot] 2778a1cb0f chore(deps): update dependency ip-address@<10.3.1 to v10.7.1 [security] (#6964)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-10-02 20:10:43 +00:00
homarr-renovate[bot] 8a177c5d0f chore(deps): update dependency axios@>=1.0.0 <1.18.1 to v1.20.0 [security] (#6962)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-10-02 20:08:55 +00:00
homarr-renovate[bot] 35baf9938e chore(deps): update dependency fast-uri@>=3.0.0 <3.1.6 to v3.1.8 [security] (#6959)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-10-02 20:04:23 +00:00
homarr-renovate[bot] ce29c8627f fix(deps): update dependency mysql2 to v3.23.1 [security] (#6965)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-10-02 20:03:27 +00:00
homarr-renovate[bot] 5c2b6b37d8 chore(deps): update dependency brace-expansion@>=4.0.0 <5.0.9 to v5.0.12 [security] (#6958)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-10-02 19:56:52 +00:00
homarr-update-contributors[bot]andThomas ddbc275058 chore: update contributors (#6911)
* chore: update contributors

* style: auto-fix lint and format

---------

Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-10-02 19:20:47 +00:00
Releases Homarr d80a53f52e chore(release): 2.0.0 [skip ci] 2026-10-02 19:07:46 +00:00
Thomas 4b5e04a6ec Merge pull request #6956 from homarr-labs/dev
chore(release): automatic release v2.0.0
2026-10-02 21:06:36 +02:00
+12 fcd7c979df feat!: release v2 (#6545)
Co-authored-by: Artur <145776561+arturict@users.noreply.github.com>
Co-authored-by: arturict <arturict@users.noreply.github.com>
Co-authored-by: Thomas <thomas@ajnart.fr>
Co-authored-by: Afonso Jorge Ramos <afonsojorgeramos@gmail.com>
Co-authored-by: Danura30082 <danura30082+github@gmail.com>
Co-authored-by: Kushie <122986913+kushiemoon-dev@users.noreply.github.com>
Co-authored-by: ajnart <thomas@ajnart.fr>
Co-authored-by: ajnart <thomas@ajnart.dev>
Co-authored-by: chenjun <chenjun@foxmail.com>
Co-authored-by: Wikibear <wikibear@users.noreply.github.com>
Co-authored-by: Bergasha <bergasha@gmail.com>
Co-authored-by: leafyelin <43306196+leafyelin@users.noreply.github.com>
Co-authored-by: Lucas Araujo <lucas.lra@gmail.com>
Co-authored-by: GreenMonito <86085570+GreenMonito@users.noreply.github.com>
Co-authored-by: Nerdy-Technician <roffo@nerdy-technician.social>
Co-authored-by: Jun <34545896+jcstrong@users.noreply.github.com>
Co-authored-by: Edmond Abraham <54119952+gitedmond@users.noreply.github.com>
Co-authored-by: thrawnx <tight.bulb9384@fastmail.com>
Co-authored-by: Chris <58918656+nordada@users.noreply.github.com>
Co-authored-by: yi111 <153097222+Yi-111-a@users.noreply.github.com>
Co-authored-by: Yanis <naamaneyanis@gmail.com>
Co-authored-by: Wikibear <me@sej.de>
Co-authored-by: alessiottavo <93441886+alessiottavo@users.noreply.github.com>
2026-10-02 21:00:14 +02:00
homarr-crowdin[bot] 3a53bb77aa chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-10-01 03:09:11 +00:00
Thomas e5ee468fa5 chore: pause automatic CODEOWNERS review requests 2026-09-29 20:59:18 +02:00
Thomas 661eb20d13 chore: pause automatic CODEOWNERS review requests 2026-09-29 20:59:13 +02:00
homarr-crowdin[bot] 5eae701450 chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-09-29 03:18:26 +00:00
homarr-crowdin[bot] 4febfc8387 chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-09-25 02:29:24 +00:00
homarr-crowdin[bot] 22c2d0cc1e chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-09-24 02:13:37 +00:00
homarr-crowdin[bot] 0887bddb86 chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-09-19 02:14:07 +00:00
Releases Homarr 18dafb1276 chore(release): 1.77.2 [skip ci] 2026-09-18 19:18:42 +00:00
homarr-releases[bot] 286abf392a chore(release): automatic release v1.77.2 2026-09-18 19:17:27 +00:00
homarr-update-contributors[bot] 88692b86be chore: update contributors (#6825)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-09-18 15:54:13 +00:00
homarr-renovate[bot] 9c40f10384 chore(deps): update dependency fastify to v5.12.1 [security] (#6844)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-09-13 18:15:24 +00:00
homarr-renovate[bot] dbda477c77 chore(deps): update dependency next-auth to v5.0.0-beta.32 [security] (#6840)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-09-13 14:25:25 +00:00
homarr-renovate[bot] a5c92b114e chore(deps): update dependency hono@<4.6.5 to >=4.13.7 [security] (#6842)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-09-13 03:53:52 +00:00
homarr-renovate[bot] 58ac28d6b5 chore(deps): update dependency @auth/core to v0.41.3 [security] (#6839)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-09-13 00:05:05 +00:00
Kushie 43a57a2dad fix(firewall widget): allow content to shrink and scroll (#6818) 2026-09-12 10:49:01 +02:00
Releases Homarr 7859faaa1d chore(release): 1.77.1 [skip ci] 2026-09-11 19:22:36 +00:00
homarr-releases[bot] bff6ec735e chore(release): automatic release v1.77.1 2026-09-11 19:21:19 +00:00
Thomas 1fbe844598 fix(settings): move board access control out of the nested form (#6662) 2026-09-10 18:13:56 +02:00
Thomas 365ef7f02b Revert "feat: announce Homarr v2 beta" (#6813) 2026-09-10 16:36:54 +02:00
homarr-releases[bot] 2b1a03849d chore: update bug report template (#6798) 2026-09-06 22:31:26 +02:00
Releases Homarr 4c243bc374 chore(release): 1.77.0 [skip ci] 2026-09-06 19:31:37 +00:00
homarr-releases[bot] ecd02f73c0 chore(release): automatic release v1.77.0 2026-09-06 19:30:29 +00:00
Thomas cd23b36f3c fix(ci): use pinned pnpm for releases (#6796) 2026-09-06 21:18:30 +02:00
homarr-releases[bot] f7f29eade9 chore(release): automatic release v1.77.0 2026-09-06 18:47:01 +00:00
Thomas 32a51c1e60 feat: announce Homarr v2 beta (#6793)
* feat: announce Homarr v2 beta

* chore: refine v2 beta announcement

* feat: expand v2 beta guidance

* chore: refine v2 beta resources

* chore: simplify v2 beta modal

* chore: improve v2 beta feedback links

* fix: keep v2 modal above header

* chore: sharpen v2 beta message

* fix: clear header for v2 modal
2026-09-06 20:36:04 +02:00
homarr-crowdin[bot] da32acb4f0 chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-09-06 01:52:04 +00:00
homarr-update-contributors[bot] 1da13d9bb3 chore: update contributors (#6767)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-09-04 15:58:09 +00:00
homarr-crowdin[bot] cb0fec088f chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-09-03 02:05:21 +00:00
homarr-crowdin[bot] a2fe8dd461 chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-09-01 02:36:36 +00:00
homarr-update-contributors[bot] b6dfcd69ba chore: update contributors (#6735)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-08-28 21:54:00 +00:00
homarr-crowdin[bot]andThomas a38b0ef71d chore(lang): updated translations from crowdin (#6732)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-08-28 14:07:42 +02:00
Thomas 2cf3027eeb Merge pull request #6720 from homarr-labs/docs/update-helm-docs
docs(helm): update chart installation page
2026-08-28 08:32:40 +02:00
homarr-releases[bot] fa18f6fc71 chore: update bug report template (#6724) 2026-08-28 08:32:03 +02:00
Releases Homarr 331925c197 chore(release): 1.76.2 [skip ci] 2026-08-27 20:09:22 +00:00
Thomas 0e51f745d3 Merge pull request #6723 from homarr-labs/dev
chore(release): automatic release v1.76.2
2026-08-27 22:08:05 +02:00
Thomas d6f720d0e6 Revert "feat(http): set custom User-Agent on outbound integration requests (#6606)" (#6722)
This reverts commit ddbf05524b.
2026-08-27 22:00:23 +02:00
Thomas 5324bf12d4 fix(startup): use stable internal service addresses (#6721) 2026-08-27 21:52:54 +02:00
github-actions[bot] 2922a02ecf docs(helm): update chart installation page 2026-08-27 17:09:27 +00:00
Thomas 87cdcf28e0 fix(mcp): return valid string for void tool results (#6703) 2026-08-27 11:10:37 +02:00
homarr-releases[bot] 441b181e79 chore: update bug report template (#6716)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-08-27 11:08:43 +02:00
Releases Homarr eba639b59c chore(release): 1.76.1 [skip ci] 2026-08-26 16:43:52 +00:00
Thomas Camlong da36283ad1 chore(release): retrigger release pipeline 2026-08-26 18:30:12 +02:00
Thomas Camlong c050368782 chore(release): trigger release pipeline 2026-08-26 18:16:26 +02:00
homarr-releases[bot] 82b50f0fd7 chore(release): automatic release v1.76.1 2026-08-26 15:50:55 +00:00
Thomas a0149d5894 Merge pull request #6673 from homarr-labs/investigate-6672-immich-401 2026-08-22 19:44:03 +02:00
ajnart 8579fcbfc2 test(http,immich): address CodeRabbit review
- guarantee server teardown via try/finally in both wire-level tests
- add tuple-array HeadersInit coverage to the helper suite
2026-08-22 17:21:38 +02:00
ajnart b6f3aba23e test(http,immich): prove User-Agent fix at the wire level
Add two regression tests that fail on v1.76.0 and pass with the fix:
- wire-level test: a Headers instance (the shape @immich/sdk hands over)
  must reach the remote server with all headers intact
- real-SDK e2e: getServerStatistics through fetchWithTrustedCertificatesAsync
  against a live local server must deliver x-api-key
2026-08-22 17:16:31 +02:00
ajnart 31e218295d fix(http): preserve SDK headers when injecting User-Agent
The v1.76.0 User-Agent injection object-spread `fetchOptions.headers`, which
silently drops all headers when the value is a Headers instance. The Immich
SDK passes a Headers object with the merged x-api-key header, so requests
lost their API key and returned 401 (issue #6672). Build a real Headers
instance instead, keeping case-insensitive user-agent handling.
2026-08-22 17:12:43 +02:00
Thomas aca3b5d861 fix: correct Unraid CPU, link context menus, and board resizing (#6510)
Fix Unraid logical CPU averaging, preserve native context menus for app and bookmark widgets in view mode, and prevent scrollbar-width GridStack resize oscillation.
2026-08-22 10:53:26 +02:00
homarr-crowdin[bot] a7a2f0223a chore(lang): updated translations from crowdin (#6596)
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-08-22 08:49:51 +02:00
homarr-releases[bot] 7773fdc10b chore: update bug report template (#6655) 2026-08-21 22:14:45 +02:00
Thomas 7019a1d0a4 Merge pull request #6656 from homarr-labs/docs/update-helm-docs 2026-08-21 22:14:26 +02:00
github-actions[bot] 2d535caeac docs(helm): update chart installation page 2026-08-21 17:41:09 +00:00
Releases Homarr 2013fbe80d chore(release): 1.76.0 [skip ci] 2026-08-21 17:20:50 +00:00
homarr-releases[bot] 219ef58926 chore(release): automatic release v1.76.0 2026-08-21 17:19:40 +00:00
homarr-update-contributors[bot] 97195bc463 chore: update contributors (#6649)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-08-21 12:53:40 +00:00
Danura30082 73a742154e docs: Proxmox certificate obtention instructions (#6613) 2026-08-20 18:51:58 +02:00
Afonso Jorge Ramos 993a267e0c fix(truenas): keep reporting pools without usable dataset space (#6635) 2026-08-20 15:37:10 +02:00
Thomas f1c49d1fbf fix(startup): only chown runtime paths for custom PUID/PGID (#6598) 2026-08-19 08:29:17 +02:00
Thomas 95c66c61eb fix(widgets): stop Health Monitoring system tab firing NOT_FOUND when Proxmox is mixed in (#6623) 2026-08-18 19:56:47 +02:00
Thomas ec2f8c6c2c fix(widgets): match OMV disk temperature via normalized partition names (#6615) (#6619)
OMV reports filesystem devices (sda1) without the /dev/ prefix, so the
partition-suffix normalization never applied and filesystem-to-SMART
matching returned no temperature. Strip partition suffixes from
unprefixed sd/vd, nvme and mmcblk names, and use the shared normalized
matcher in both the health monitoring and system disks widgets.
2026-08-18 19:39:27 +02:00
Thomas 05ba57c2fa chore: migrate cursor rules to portable agent skills + mantine MCP guidance
* chore: migrate cursor rules to portable agent skills and add mantine MCP guidance

Replace Cursor-only .cursor/rules/*.mdc with Agent Skills open-standard
SKILL.md files in .agents/skills/ (canonical), mirrored to .cursor/skills/
and .claude/skills/ so they work across Claude Code, Cursor, Codex, and
OpenCode. Add mantine MCP server guidance to AGENTS.md and un-ignore
.agents/skills/ in .gitignore.

* refactor: single-source agent skills at .agents/skills

Cursor, Codex, and OpenCode all read .agents/skills/ natively, so the
.cursor/skills/ mirror was redundant. Claude Code reads .claude/skills/,
which is now a symlink back to .agents/skills/ so the content lives once.
Deletes .cursor/skills/ and converts .claude/skills/ to a symlink.

* chore: ship mantine MCP server via project-scoped .mcp.json

Declares the keyless @mantine/mcp-server in .mcp.json (Claude Code
project-scope standard, committable) and documents MCP server setup and
the mantine entry in AGENTS.md.

* chore: address coderabbit security-review and markdown lint findings

Add explicit security-review guidance before exposing MCP tools and label
plain-text code fences to satisfy markdownlint MD040.
2026-08-16 16:27:51 +02:00
Thomas ddbf05524b feat(http): set custom User-Agent on outbound integration requests (#6606)
* feat(http): set custom User-Agent on outbound integration requests

Set a default 'User-Agent: Homarr/<version> (+https://homarr.dev)' header
on all outbound HTTP requests made via fetchWithTrustedCertificatesAsync,
so Homarr's integration traffic is distinguishable in reverse-proxy,
WAF, and security-monitoring logs instead of appearing as generic undici.

Closes #6604

* refactor(version): centralize Homarr version in @homarr/version package

Replace fragile deep-relative 'package.json' imports across the monorepo
with a single source of truth: the new @homarr/version leaf package reads
the version once from the root package.json and exports it as VERSION.

Consumers now import by package name incl. the new User-Agent header in
fetchWithTrustedCertificatesAsync (packages/core):
  - core, api (info router), analytics, request-handler (update checker)
This also removes all manually-maintained relative version imports.

* fix(http): normalize User-Agent injection with Headers

Object-spreading fetchOptions.headers breaks when callers pass a Headers
instance (spread yields no entries) or a tuple array (numeric keys), and is
case-sensitive. Build a Headers instance and set the default User-Agent only
when no case-insensitive User-Agent entry exists, via a shared helper in both
fetch paths.

Adds unit tests covering record, Headers, tuple-array, and casing variants.

* revert(version): undo version refactor and tests, keep single-file change

Keep the User-Agent change as a single edit to
packages/core/src/infrastructure/http/request.ts. Remove the @homarr/version
package, the consumer migrations, and the unit tests.
2026-08-16 16:09:15 +02:00
Thomas df438b4cd2 Merge pull request #6571 from homarr-labs/feat/fix-home-assistant-actions 2026-08-16 00:23:14 +02:00
Thomas a1f91eb385 Merge pull request #6567 from homarr-labs/feat/fix-truenas-free-space 2026-08-15 20:20:35 +02:00
Thomas 8d5ffb2dd9 feat(paseo): add worktree install script (#6599)
* feat(paseo): add worktree install script

* feat(paseo): inline worktree setup in paseo.json

* feat(paseo): add scripts and teardown to paseo.json

* feat(paseo): expand workspace scripts catalog

* feat(paseo): reset encryption key in worktree env
2026-08-15 12:29:52 +02:00
Thomas Camlong 56e6b23af6 test(e2e): remove flaky credentials onboarding test 2026-08-15 11:05:06 +02:00
Thomas Camlong 5488bf4d18 fix(truenas): use lowercase in operator for dataset query 2026-08-15 01:33:20 +02:00
homarr-crowdin[bot] 621ea07549 chore(lang): updated translations from crowdin (#6553) 2026-08-14 20:39:02 +02:00
homarr-update-contributors[bot] f64c01456b chore: update contributors (#6588) 2026-08-14 20:38:42 +02:00
homarr-releases[bot] 56198c3f02 chore: update bug report template (#6591) 2026-08-14 20:38:26 +02:00
Thomas ca1bb6f341 Merge pull request #6592 from homarr-labs/docs/update-helm-docs
docs(helm): update chart installation page
2026-08-14 20:38:10 +02:00
github-actions[bot] bf6b6663cd docs(helm): update chart installation page 2026-08-14 18:25:32 +00:00
Releases Homarr 5c2d86767f chore(release): 1.75.0 [skip ci] 2026-08-14 17:45:24 +00:00
homarr-releases[bot] 7c75642d4d chore(release): automatic release v1.75.0 2026-08-14 17:43:56 +00:00
Thomas Camlong 7652eb8c1c preserve smart home toggle response 2026-08-13 17:04:00 +02:00
Thomas Camlong e761c10f32 fix Home Assistant widget actions 2026-08-13 16:21:48 +02:00
Thomas 2513e8454f fix: open user menu instantly (#6568) 2026-08-13 11:44:10 +02:00
ajnart f6137b2dcc fix(truenas): guard empty dataset percentage 2026-08-12 18:24:33 +02:00
ajnart 425dd90871 fix(truenas): report usable dataset space 2026-08-12 18:19:07 +02:00
Thomas 527e911eef fix(beszel): let Recharts own tooltip lifecycle (#6560) 2026-08-11 15:35:17 +02:00
Thomas ff2cdecc86 feat: rewrite image picker (#6556)
* feat: rewrite image picker

* style: auto-fix lint and format

* fix: address image picker review

* fix: preserve unicode icon searches

* test: stabilize custom widget login

* fix: suggest image searches only on create
2026-08-11 14:49:52 +02:00
Sven 742bde1d27 fix(synology): support vol_info in storage_v2 responses (#6554) 2026-08-11 08:59:28 +00:00
homarr-update-contributors[bot] 64febbce12 chore: update integrations list readme (#6551)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-08-10 11:44:39 +00:00
TimonandThomas 98bb1aecf8 feat(integrations): add What's Up Docker (WUD) integration and widget (#6523)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-08-10 08:42:23 +00:00
homarr-crowdin[bot]andThomas 06c489bd48 chore(lang): updated translations from crowdin (#6548)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-08-10 09:06:56 +02:00
Thomas 464d98e124 feat(settings): unify manage and board settings into a single save form (#6541) 2026-08-08 15:33:38 +02:00
Thomas 987c9ade18 fix: make internal nginx proxy tolerate disabled IPv6 (#4596) (#6539)
* fix: keep nginx IPv6 listen only when IPv6 is available (#4596)

Commit 2c48b1896 (1.45.2) made the internal nginx proxy listen on the IPv6
wildcard ('listen [::]:7575;'), which aborts nginx startup on hosts/VMs with
IPv6 disabled:

    nginx: [emerg] socket() [::]:7575 failed (97: Address family not supported by protocol)

A plain revert would reintroduce #2697 and break IPv6-only/dual-stack
environments where this listener is required. Instead, only emit the IPv6
listen directive when the host actually has IPv6 configured.

Reuse the envsubst step run.sh already performs: compute NGINX_LISTEN_IPV6
from /proc/net/if_inet6 and substitute it into the template. proc files always
report size 0, so presence is detected by grepping the file (not 'test -s').

* fix: honor HOMARR_DISABLE_IPV6 override in nginx config generation

Review feedback on #6539: give users an explicit opt-out from the IPv6
listener even when the host reports IPv6. The issue reporter (#4596)
requested such an env var, and the pattern matches the existing
ENABLE_DNS_CACHING IPv6 toggle. Auto-detection behavior is unchanged when
the var is unset. Validated in containers: override wins, dual-stack
preserved by default, IPv4-only fallback on IPv6-less hosts.
2026-08-08 11:16:49 +02:00
Sven Gottwald 6141d505e4 fix(docker): Expose TCP port in Dockerfile (#6538) 2026-08-08 08:53:50 +02:00
Thomas 6178843198 Merge pull request #6536 from homarr-labs/docs/update-helm-docs 2026-08-07 23:53:22 +02:00
github-actions[bot] a7006d746e docs(helm): update chart installation page 2026-08-07 19:37:35 +00:00
Thomas 06724cfeb2 ci: checkout repo in bot auto-merge workflows (#6535)
* ci: drop --delete-branch from reconcile auto-merge call

The reconcile job runs without a git checkout, so gh pr merge --delete-branch
fails with 'not a git repository' before enabling auto-merge. Auto-merge
itself needs no local git state; branch cleanup is already handled by the
PR-creating workflows.

* ci: checkout repo in bot auto-merge workflows

gh pr merge fails with 'not a git repository' when no checkout is present,
which prevented the reconcile job from enabling auto-merge. Restore the
checkout the previous approval workflow relied on.
2026-08-07 20:31:56 +02:00
homarr-update-contributors[bot] 131a5558b5 chore: update contributors (#6527) 2026-08-07 20:26:34 +02:00
Thomas b32ad82850 perf: cut cold route compilation and unblock preview images (#6441) 2026-08-07 20:24:26 +02:00
Thomas 622e1dcfa5 ci: drop --delete-branch from reconcile auto-merge call (#6534)
The reconcile job runs without a git checkout, so gh pr merge --delete-branch
fails with 'not a git repository' before enabling auto-merge. Auto-merge
itself needs no local git state; branch cleanup is already handled by the
PR-creating workflows.
2026-08-07 20:12:37 +02:00
homarr-releases[bot] 9674bde758 chore: update bug report template (#6533)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-08-07 18:08:24 +00:00
Thomas b479ada14f ci: auto-approve and auto-merge trusted bot pull requests (#6531)
* ci: auto-approve and auto-merge trusted bot pull requests

Replace the approve-only automatic-approval workflow with a combined
approve + enable-auto-merge workflow that also re-runs on every push,
plus a scheduled reconcile job that re-enables auto-merge for prepared
bot PRs if GitHub ever disables it. Bot PRs are gated to the known bot
actors, same-repo head, and base branch dev.

* ci: match bot authors by app slug in reconcile filter

gh pr list reports pull request authors of GitHub Apps as app/<slug>;
adjust the allowlist so the reconcile job actually matches bot PRs.
2026-08-07 20:05:46 +02:00
Releases Homarr bff3383ac9 chore(release): 1.74.0 [skip ci] 2026-08-07 17:41:07 +00:00
homarr-releases[bot] d865733838 chore(release): automatic release v1.74.0 2026-08-07 17:39:49 +00:00
homarr-crowdin[bot] 85fcea3837 chore(lang): updated translations from crowdin (#6500) 2026-08-07 18:22:43 +02:00
Thomas f76a0f240a feat(apps): allow custom URI schemes in application links (#6521) 2026-08-06 21:52:56 +02:00
Thomas 637f03e107 docs(settings): update analytics docs to PostHog (#6525) 2026-08-06 21:47:04 +02:00
Thomas 55214ccbc5 fix(integrations): throw on total item-info failure to preserve stale-cache fallback (#6522) 2026-08-06 21:42:46 +02:00
Thomas 778533a62e fix: revert regressions from recent integration/releases/app-href changes (#6514) 2026-08-03 17:37:53 +02:00
Thomas b4fba47a23 fix(releases): add success flag to successful response for correct error detection (#6512) 2026-08-03 17:10:35 +02:00
Thomas 76ddfceb13 fix: restore auto-refresh for docker and notifications widgets (#6511) 2026-08-03 17:07:46 +02:00
Thomas 2f6b96e5ba feat: restrict integrations management to integration-full-all permission (#6509) 2026-08-03 17:04:35 +02:00
Thomas f1b7a5146a fix: allow [homarr_base] template variables in app href validation (#6508) 2026-08-03 16:47:40 +02:00
Thomas Camlong 05292ae1f5 fix: map legacy media server options 2026-08-03 15:24:30 +02:00
homarr-renovate[bot] 48fc8db2b4 chore(deps): update semantic-release monorepo (major) (#6486) 2026-08-03 14:39:28 +02:00
Thomas 018f84f80d feat(dev): add Docker development CLI (#6495) 2026-08-03 14:38:56 +02:00
Thomas d58a47b776 fix: restrict system health monitoring integrations (#6505) 2026-08-02 10:17:05 +02:00
Thomas 797bdd001f fix: keep direct icon URLs visible (#6504) 2026-08-01 23:28:18 +02:00
Bergasha 49314533bf feat(Media player widget): expands on Direct Play and Transcoding badges (#6489) 2026-08-01 23:21:13 +02:00
Thomas 27ca058088 feat(widgets): persist Beszel and Docker table layouts (#6490) 2026-08-01 23:20:09 +02:00
homarr-releases[bot] f4449f9581 chore: update bug report template (#6498) 2026-08-01 23:08:14 +02:00
Thomas b52bc03c8d Merge pull request #6499 from homarr-labs/docs/update-helm-docs
docs(helm): update chart installation page
2026-08-01 23:07:58 +02:00
Thomas edb56f3fcd fix: accept direct icon URLs
Use valid direct HTTP(S) icon URLs without repository search.
2026-08-01 23:06:26 +02:00
homarr-crowdin[bot] fcc5130068 chore(lang): update translations from crowdin (#6457) 2026-07-31 23:50:10 +02:00
homarr-update-contributors[bot] c7751e293d chore: update contributors (#6496) 2026-07-31 23:49:37 +02:00
github-actions[bot] bd669a1a8f docs(helm): update chart installation page 2026-07-31 19:35:37 +00:00
Releases Homarr 9b5b79525f chore(release): 1.73.0 [skip ci] 2026-07-31 18:16:44 +00:00
homarr-releases[bot] 426929556f chore(release): automatic release v1.73.0 2026-07-31 18:15:13 +00:00
Thomas ade640dcea test(navidrome): complete cleanup review (#6494)
* test(navidrome): hoist album fixture helper

* test(navidrome): address coderabbit cleanup feedback
2026-07-31 14:39:11 +02:00
Thomas 61128ddcc8 fix(navidrome): clean up audio stats integration (#6493) 2026-07-31 14:20:51 +02:00
Thomas 6a3cee8107 fix(navidrome): complete media server session data (#6492) 2026-07-31 13:53:06 +02:00
Matt Van Horn 06d9c07d22 feat: expose Navidrome as a media-server (now-playing) integration (#6070) 2026-07-31 13:30:44 +02:00
Thomas 49d0f1a141 docs: explain Podman socket setup (#6487)
* docs: explain Podman socket setup

* docs: clarify Podman runtime requirements

* docs: complete Podman compose examples
2026-07-29 16:53:29 +02:00
Clarkar00 ac73b9d891 fix(widgets): refetch after render errors (#6399)
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.
2026-07-28 23:12:03 +02:00
Thomas 489980814c Merge pull request #6465 from homarr-labs/docs/update-helm-docs
docs(helm): update chart installation page
2026-07-28 22:59:08 +02:00
Thomas 14654acced chore: gate Renovate updates behind dashboard approval (#6485) 2026-07-28 22:55:32 +02:00
Thomas 2885f7a297 fix(widgets): preserve data on integration failures (#6484) 2026-07-28 22:18:08 +02:00
Thomas 906c9f11fa feat(widgets): rework downloads widget with mantine-datatable (#6430) 2026-07-28 21:50:09 +02:00
Thomas 3419805a49 fix(beszel): restore balanced grid layout for Beszel System Grid widget (#6389) 2026-07-28 21:49:04 +02:00
homarr-releases[bot] 51a6cbe6d4 chore: update bug report template (#6464) 2026-07-28 14:56:56 +02:00
homarr-update-contributors[bot] d08eb2afa6 chore: update contributors (#6461) 2026-07-28 14:56:42 +02:00
Tim MooreandTim Moore 91a6b19ba1 fix(beszel): expire stored sessions so the integration recovers from token expiry (#6476)
Co-authored-by: Tim Moore <tim.moore@ingenuity.com.au>
2026-07-28 14:55:33 +02:00
Thomas e66979bab6 feat(auth): link credentials users with OIDC (#6445) 2026-07-28 14:49:51 +02:00
homarr-renovate[bot] 4ef7641cb2 chore(deps): update pnpm to v11.15.1 (#6466)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-25 02:43:47 +00:00
github-actions[bot] f33fc838cf docs(helm): update chart installation page 2026-07-24 21:17:32 +00:00
Releases Homarr f0019fa213 chore(release): 1.72.0 [skip ci] 2026-07-24 18:11:58 +00:00
homarr-releases[bot] ba8536b36d chore(release): automatic release v1.72.0 2026-07-24 18:10:37 +00:00
homarr-renovate[bot] 965aec5155 chore(deps): update pnpm to v11.15.0 (#6455)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-23 16:29:37 +00:00
homarr-crowdin[bot] 387db030cd chore(lang): updated translations from crowdin (#6283) 2026-07-23 09:14:14 +02:00
homarr-renovate[bot] 8e557c9924 chore(deps): update pnpm to v11.14.0 (#6449)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-23 05:58:19 +00:00
Thomas f26e60edf0 fix(old-import): map new clock weather options (#6452) 2026-07-23 02:08:50 +02:00
Thomas 7bbfd74454 Merge pull request #6384 from homarr-labs/docs/update-helm-docs
docs(helm): update chart installation page
2026-07-23 02:04:11 +02:00
homarr-releases[bot] 018b1ea4da chore: update bug report template (#6382) 2026-07-23 02:03:43 +02:00
Thomas 14c3fa2e76 fix(beszel): prevent live subscription memory growth (#6448)
* fix(beszel): bound live stats subscriptions

* fix(beszel): avoid blocking subscription teardown

* fix(import): map new clock options
2026-07-23 02:03:08 +02:00
Thomas 9168f8114b fix(manage): hide sidebar nav items from unprivileged users (#6236) 2026-07-23 02:01:10 +02:00
Thomas 9cabb42edb fix(widgets): add 350ms hover delay before opening calendar day popover (#6431) 2026-07-23 01:54:23 +02:00
Thomas bfba3c9215 fix(releases): avoid unnecessary GitHub pagination for LinuxServer updates (#6444) 2026-07-23 00:20:54 +02:00
Bergasha 7184bc09c1 feat(clock widget): Adds animated weather and temp (#6372) 2026-07-23 00:04:32 +02:00
Thomas 9bb0c0f811 fix(media-requests): cache statistics responses (#6443) 2026-07-22 17:38:28 +02:00
Thomas cf0e469465 feat: restrict apps management to board modifiers (#6425) 2026-07-21 16:56:33 +02:00
Thomas 8e7fa782b3 feat(weather): add humidity display (#6429) 2026-07-21 16:36:03 +02:00
Thomas 97652802f0 feat(immich): show random photos without album (#6424) 2026-07-21 16:10:45 +02:00
Thomas 1706289124 fix(media-server): guard against undefined playback from stale cached data (#6433) 2026-07-21 16:01:37 +02:00
homarr-renovate[bot] dd23de7aaa chore(deps): update pnpm to v11.13.1 (#6422)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-21 05:54:52 +00:00
Thomas f1602f67d4 fix: stop auto-seeding ArchiveTeam Warrior integration and widget on default dashboards (#6413) 2026-07-20 17:01:07 +02:00
Thomas c03cd97d3a fix(test): use Home Assistant long-lived token JWT
* fix(test): use Home Assistant long-lived token

* fix(test): revert to correct Home Assistant long-lived token JWT
2026-07-20 15:27:50 +02:00
Thomas a8e9a074d3 Merge pull request #6411 from homarr-labs/mysql-migration-test-timeout
fix(db): increase mysql migration test timeout to 120s
2026-07-20 15:19:12 +02:00
Thomas Camlong 1e9d3e0656 fix(db): increase mysql migration test timeout to 120s 2026-07-20 15:18:50 +02:00
Thomas 01e0f3612d docs(immich): list Asset View permission (#6408) 2026-07-20 14:57:07 +02:00
Thomas 0015a6d88d fix(immich): use thumbnail endpoint instead of original for album carousel (#6396)
* 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
2026-07-20 14:53:27 +02:00
Thomas 77dc317da2 chore(ci): remove memory regression tests (#6395) 2026-07-18 22:55:38 +02:00
homarr-update-contributors[bot] 6aeda3faaf chore: update contributors (#6378) 2026-07-18 22:43:01 +02:00
github-actions[bot] ac6a0c9254 docs(helm): update chart installation page 2026-07-17 19:24:43 +00:00
Releases Homarr ca358d9f8b chore(release): 1.71.0 [skip ci] 2026-07-17 17:58:17 +00:00
homarr-releases[bot] 03e0683892 chore(release): automatic release v1.71.0 2026-07-17 17:57:04 +00:00
Junker der Provinz 6ac4c6a7b9 fix(search): let public dashboards use generic custom search engines (#6375) 2026-07-17 14:53:52 +02:00
Junker der Provinz a0e0bf65ff fix(releases): let LinuxServer.io version filters find older releases via GitHub (#6376) 2026-07-17 14:53:01 +02:00
Junker der Provinz bc018e1fe0 fix(docker): disable the broken full-screen toggle on the container table (#6374) 2026-07-17 14:43:37 +02:00
Thomas 5e39c8813a feat(preferences): add enableRightClickOnWidgets user preference (#6298) 2026-07-17 14:42:19 +02:00
Thomas 06719cdef0 fix(ci): remove coverage report, scope format to changed files, fix oxlint config (#6359) 2026-07-16 10:21:07 +02:00
Bergasha 5144e14da4 fix(media-server): populate playback progress, transcode status, and network location for streams (#6315) 2026-07-15 16:27:58 +02:00
Thomas 8528d5068b feat(media-requests): filter requests list by status and time window (#6320) 2026-07-15 16:27:24 +02:00
Thomas c6c4428f7d Revert "fix(docker): report current CPU usage instead of the lifetime average" (#6357) 2026-07-15 14:55:18 +02:00
Thomas 7e4cb0201b fix(widgets): add refetchInterval to WidgetDefinition for per-widget polling (#6352) 2026-07-15 11:52:35 +02:00
Thomas 7887ce12c0 fix(integrations): resolve intermittent Seerr/Overseerr crashes and add query status to context menu (#6347) 2026-07-15 11:41:18 +02:00
Thomas 2d450239ac fix(releases): add required octokit throttling rate limit handlers (#6338) 2026-07-15 11:27:32 +02:00
Thomas bf4ccd8cb3 fix(ci): remove persist-credentials: false to fix git-auto-commit-action push (#6346) 2026-07-15 10:33:32 +02:00
homarr-renovate[bot] c473e80243 chore(deps): update pnpm to v11.11.0 (#6342)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-15 02:31:21 +00:00
homarr-renovate[bot] bf55bcce92 fix(deps): update dependency swiper to ^14.0.5 (#6341)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-14 23:55:02 +00:00
homarr-renovate[bot] 49248cedaf chore(deps): update dependency posthog-js to ^1.399.1 (#6340)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-14 22:09:52 +00:00
homarr-renovate[bot] a0034b2cfc chore(deps): update dependency @immich/sdk to ^3.0.2 (#6339)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-14 21:01:58 +00:00
homarr-renovate[bot] 4a297180fd chore(deps): update dependency lucide-react to ^1.24.0 (#6334)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-14 19:23:26 +00:00
Thomas 5fa3a3313a Merge pull request #6337 from homarr-labs/fix/ts7-nextjs-build
fix(docker): pin TypeScript to v6 to fix Next.js 16.2.10 build in CI
2026-07-14 18:11:38 +02:00
Thomas Camlong 29ee87c4aa fix(docker): pin typescript to v6 via alias to fix Next.js 16.2.10 build 2026-07-14 18:11:15 +02:00
Thomas 94a247e24a fix(docker): remove --offline flag from pnpm install step (#6335) 2026-07-14 17:19:51 +02:00
homarr-renovate[bot] 6d37710fec chore(deps): update dependency vite@>=5.0.0 <=5.4.18 to >=8.1.4 (#6333)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-14 11:26:55 +00:00
homarr-renovate[bot] c016beb2e5 chore(deps): update dependency vite to ^8.1.4 (#6332)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-14 09:06:17 +00:00
homarr-renovate[bot] f9e1b85c6a chore(deps): update dependency posthog-node to ^5.40.0 (#6314)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-14 06:14:41 +00:00
homarr-renovate[bot] 38ba19e665 chore(deps): update dependency oxlint to ^1.73.0 (#6306)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-14 03:22:16 +00:00
homarr-renovate[bot] f55c99d2d6 chore(deps): update dependency posthog-js to ^1.399.0 (#6331)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-14 00:00:38 +00:00
homarr-renovate[bot] c5e8b83d8a chore(deps): update vitest monorepo to ^4.1.10 (#6286)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-13 23:01:00 +00:00
homarr-renovate[bot] d52d9cfed2 chore(deps): update dependency posthog-js to ^1.398.7 (#6319)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-13 21:58:26 +00:00
Thomas 93325642aa Merge pull request #6324 from homarr-labs/renovate/major-github-artifact-actions 2026-07-13 22:12:32 +02:00
Thomas 165e94abbc Merge pull request #6328 from homarr-labs/fix-pnpm-fetch-build 2026-07-13 22:12:11 +02:00
homarr-renovate[bot] 2e3127e766 chore(deps): update docker/dockerfile docker tag to v1.25 (#6321) 2026-07-13 22:10:35 +02:00
Thomas d37326d905 Merge pull request #6322 from homarr-labs/renovate/actions-cache-6-x 2026-07-13 22:09:57 +02:00
homarr-renovate[bot] e51e39ff38 chore(deps): update dependency typescript to v7 (#6325) 2026-07-13 22:09:30 +02:00
homarr-renovate[bot] 6c075b6d74 chore(deps): update dependency mysql2 to v3.22.6 (#6310)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-13 18:45:08 +00:00
ajnart b2f63c6e0f fix(docker): drop --frozen-lockfile from install step per pnpm fetch docs
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
2026-07-13 20:24:59 +02:00
ajnart 8c2d73e8af fix(docker): resolve pnpm fetch crash with patchedDependencies
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
2026-07-13 20:06:28 +02:00
homarr-renovate[bot] 1c0ff8930e chore(deps): update actions/upload-artifact action to v7 2026-07-13 16:39:27 +00:00
homarr-renovate[bot] e2b8355639 chore(deps): update actions/cache action to v6 2026-07-13 16:39:10 +00:00
Thomas ba7718bde5 feat: rework the ci (#6296) 2026-07-13 18:32:48 +02:00
Thomas 06f1877ae1 Merge pull request #6282 from homarr-labs/docs/update-helm-docs
docs(helm): update chart installation page
2026-07-13 18:11:10 +02:00
homarr-renovate[bot] 1d9e8aabf3 chore(deps): update turbo monorepo to ^2.10.4 (#6303)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-13 13:36:45 +00:00
homarr-renovate[bot] b111e5285b chore(deps): update dependency @scalar/api-reference-react to ^0.9.54 (#6317)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-13 10:38:14 +00:00
homarr-renovate[bot] f4c491b570 chore(deps): update dependency @types/node to ^24.13.3 (#6318)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-13 10:37:23 +00:00
homarr-renovate[bot] 29bd4fd70b chore(deps): update dependency posthog-js to ^1.398.1 (#6311)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-12 18:09:44 +00:00
homarr-renovate[bot] 269dadee8d fix(deps): update dependency swiper to ^14.0.2 (#6313)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-12 18:09:11 +00:00
Thomas 03f68a0421 fix(qbittorrent): sort torrents by progress so active items appear before limit cutoff (#6297) 2026-07-12 14:56:24 +02:00
Bergasha 807e5eb2a9 feat(docker-widget): replace footer timestamp text with compact refresh button (#6307) 2026-07-12 14:51:56 +02:00
Thomas 8e8c29979f fix(system-disks): format absolute storage values with shared byte helper (#6299) 2026-07-12 14:50:58 +02:00
Manuel 7d859c1af0 docs: update pr template (#6147) 2026-07-12 14:49:56 +02:00
homarr-renovate[bot] e9543dc30d chore(deps): update tiptap monorepo to v3.27.3 (#6308)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-12 09:12:55 +00:00
Nerdy Technician 9f313440ac fix(integrations): update PatchMon API key settings link (#6302) 2026-07-12 08:59:52 +02:00
homarr-renovate[bot] c2508dffea chore(deps): update dependency posthog-js to ^1.396.7 (#6285)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-12 06:18:17 +00:00
Artur d44bc06a93 feat(notebook): support text direction controls (#6293) 2026-07-12 07:58:02 +02:00
homarr-renovate[bot] c56b91570b chore(deps): update tiptap monorepo to v3.27.2 (#6292)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-12 03:01:23 +00:00
homarr-renovate[bot] d53c4e6513 chore(deps): update dependency hono@<4.6.5 to >=4.12.28 (#6291)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-12 02:58:51 +00:00
homarr-renovate[bot] 0426d600cc chore(deps): update dependency oxfmt to ^0.58.0 (#6305)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-12 00:06:59 +00:00
Thomas 576856d634 fix(beszel): add reliable live stats and filesystem UI (#6289) 2026-07-11 17:55:04 +02:00
Thomas def3ede3bc fix(db): restore widget_secret migration journal entries accidentally removed by #6206 (#6288) 2026-07-11 15:40:06 +02:00
github-actions[bot] 34bd574354 docs(helm): Update Helm chart documentation 2026-07-10 20:32:11 +00:00
homarr-releases[bot] d9c503d01b chore: update bug report template (#6281)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-07-10 20:17:43 +00:00
Releases Homarr 82aa905214 chore(release): 1.70.0 [skip ci] 2026-07-10 20:03:12 +00:00
Thomas 4b351bb209 chore(release): automatic release v1.70.0 2026-07-10 22:01:50 +02:00
Thomas 13c9e5e181 fix(release): repair v1.70.0 quality failures (#6279)
* fix(release): repair v1.70.0 quality failures

* style: auto-fix lint and format
2026-07-10 21:59:57 +02:00
homarr-releases[bot] 6b047a9c8e chore(release): automatic release v1.70.0 2026-07-10 18:16:39 +00:00
Thomas 7a768517ef fix(request-handler): restore media organizer build (#6277) 2026-07-10 18:09:05 +02:00
Thomas 4ae8d527a8 fix(releases): token not applied due to broken allowedRepoIds check (#6276) 2026-07-10 17:55:18 +02:00
homarr-update-contributors[bot] 05a48e2930 chore: update integrations list readme (#6275) 2026-07-10 17:50:02 +02:00
Josh PrestonandThomas 33395ae226 feat(integrations): add PatchMon integration and widget (#6119)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-07-10 17:48:43 +02:00
Thomas 174c5f98f8 fix(beszel): disable request handler cache (#6274) 2026-07-10 17:40:54 +02:00
a2cf2d6cff Update Helm chart documentation (#6261)
Co-authored-by: Thomas <thomas@ajnart.fr>
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-10 17:33:35 +02:00
Anil K TiwariandThomas 9fb2e37605 feat(widgets): add missing & queued media widget for Radarr and Sonarr (#6078)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-07-10 17:31:08 +02:00
homarr-crowdin[bot] 713fcd6f3f chore(lang): updated translations from crowdin (#6240) 2026-07-10 17:30:10 +02:00
homarr-update-contributors[bot] 6b10cd6874 chore: update integrations list readme (#6258) 2026-07-10 17:29:47 +02:00
homarr-update-contributors[bot] cabc66cc69 chore: update contributors (#6272) 2026-07-10 17:29:30 +02:00
Felix-AyushandThomas 2c5368dd4e feat: add Bazarr integration and subtitle status widget (#6156)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-07-10 17:29:10 +02:00
Nam NguyenandThomas 7d93df04dd fix: add utf8 email validation support (#6239)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-07-10 17:28:22 +02:00
Thomas 7c152cf4fe feat(api): expose board automation endpoints (#6213)
* feat(api): expose board automation endpoints

* style: auto-fix lint and format

* test(api): cover board settings updates

* Delete .opencode/changelog/2026-07-06.md
2026-07-10 17:27:42 +02:00
Thomas 718f095cb7 fix: prevent stale persisted query cache from blocking widget data (#6273) 2026-07-10 17:19:24 +02:00
Thomas 24c5d04bfa fix(beszel): disable request handler cache for stats to make live mode work (#6244) 2026-07-10 17:16:23 +02:00
Thomas 0e513fffd5 feat(widgets): add per-filesystem storage chart to Beszel system stats (#6209) 2026-07-10 17:08:22 +02:00
Thomas 2ada1aa249 fix(qbittorrent): handle v5 stopped torrent states for pause functionality (#6211) 2026-07-10 16:55:20 +02:00
Junker der Provinz af89423f66 fix(docker): report current CPU usage instead of the lifetime average (#6249) 2026-07-10 16:54:52 +02:00
Nerdy TechnicianandThomas 9864fed775 feat: Add Traefik Integration with dedicated widget and Add optional Icons for Gotify/Notifcation Widget (ONLY GOTIFY) (#6206)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-07-10 16:49:24 +02:00
Thomas bcd36b1b27 fix(integrations): sort media requests by last modified instead of creation date (#6264) 2026-07-10 16:46:33 +02:00
homarr-renovate[bot] a2f7b35c5c chore(deps): update pnpm to v11.10.0 (#6265)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-09 21:36:24 +00:00
homarr-renovate[bot] 616a444e64 chore(deps): update dependency @immich/sdk to ^3.0.1 (#6251)
* chore(deps): update dependency @immich/sdk to ^3.0.1

* style: auto-fix lint and format

---------

Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-09 19:51:55 +00:00
Josh Preston 65ba866239 feat: add Synology integration support for health/system monitoring (#6121) 2026-07-09 18:23:21 +02:00
Thomasandajnart 260b167134 fix: context menu refresh stays open with loading spinner, cache age reacts to refetch
* 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>
2026-07-07 18:39:34 +02:00
Thomas VervestandThomas 7a4de03c0e fix(integrations): accept gluetun-latest VPN settings payload (#6149)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-07-07 17:24:50 +02:00
homarr-renovate[bot] 68f8e8146b chore(deps): update dependency @scalar/api-reference-react to ^0.9.51 (#6243)
* chore(deps): update dependency @scalar/api-reference-react to ^0.9.51

* style: auto-fix lint and format

---------

Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-07 13:23:56 +00:00
Thomas 1297e6c2ac fix(query-cache): guard against undefined from superjson.parse on legacy cache entries (#6228) 2026-07-07 11:28:13 +02:00
Thomas Camlong 31288dcbfe fix(docs): update Algolia crawler config with exclusion patterns and ignoreCanonicalTo 2026-07-07 10:08:24 +02:00
Thomas fc2c251557 feat(demo): allow mutations in demo mode via DEMO_READ_ONLY flag (#6224) 2026-07-07 10:01:36 +02:00
Thomas Vervest d067261171 ci: skip lint-format-autofix on fork PRs (#6237) 2026-07-07 10:00:24 +02:00
Thomas Camlong ae2f05c834 fix(docs): sitemap priorities, exclude category redirects, remove trailingSlash 2026-07-07 09:57:32 +02:00
homarr-renovate[bot] c7fa7cdb50 chore(deps): update dependency vite@>=5.0.0 <=5.4.18 to >=8.1.3 (#6242)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-07 07:49:42 +00:00
homarr-renovate[bot] 0c28224e82 chore(deps): update nextjs monorepo to v16.2.10 (#6238)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-06 21:56:19 +00:00
homarr-renovate[bot] 4e6272dbe2 chore(deps): update dependency posthog-js to ^1.396.4 (#6232)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-06 17:56:28 +00:00
homarr-renovate[bot] f5bc090d36 chore(deps): update dependency lucide-react to ^1.23.0 (#6225)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-06 17:53:57 +00:00
homarr-renovate[bot] f23e06dff1 chore(deps): update dependency @semantic-release/github to ^12.0.9 (#6231)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-06 17:53:39 +00:00
homarr-renovate[bot] 24188ce51f chore(deps): update dependency posthog-node to ^5.39.2 (#6226)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-06 14:37:51 +00:00
homarr-renovate[bot] e31f60fdec chore(deps): update dependency cookie to v2.0.1 (#6223)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-06 14:36:14 +00:00
homarr-renovate[bot] 6bb547f3c5 chore(deps): update dependency conventional-changelog-conventionalcommits to ^10.2.0 (#6222)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-06 10:32:35 +00:00
Thomas b07cc14d0e Merge pull request #6221 from homarr-labs/feat/fix-build
fix(deps): revert typescript from 7.0.1-rc to ^6.0.3
2026-07-06 12:16:11 +02:00
Thomas Camlong 0dc93c1a02 fix(deps): revert typescript from 7.0.1-rc to ^6.0.3
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.
2026-07-06 12:15:38 +02:00
Thomas 00661c9b3d fix: fix pnpm lockfile (#6220) 2026-07-06 10:52:58 +02:00
Thomas 2e8fddd29f fix(docs): fix docs build — MDX acorn error and CSS @import ordering (#6217) 2026-07-06 10:14:36 +02:00
homarr-releases[bot] 302d5a0d04 chore: update bug report template (#6181) 2026-07-06 10:14:15 +02:00
ThomasandReleases Homarr a9ed95856a fix(layout): raise AppShellHeader z-index above Indicator dots (#6218)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-07-06 10:13:54 +02:00
Thomasandhomarr-renovate[bot] 4d42eb65e4 chore(deps): mega dependency update (#6212)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-06 10:05:51 +02:00
Releases Homarr 57e18f6ce6 chore(release): 1.69.2 [skip ci] 2026-07-06 08:05:24 +00:00
Thomas 29369e23c1 Merge pull request #6208 from homarr-labs/proxmox-status-zindex
fix(layout): raise AppShellHeader z-index above Proxmox status dots
2026-07-06 10:04:04 +02:00
Thomas 82858ecd75 fix(forms): break AppForm infinite render loop on edit page (#6198) (#6215) 2026-07-06 10:03:22 +02:00
homarr-crowdin[bot] 0e365ed53c chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2026-07-06 02:04:20 +00:00
Thomas a55dc98487 Remove memory regression job from code quality workflow
Removed memory regression testing steps from the workflow.
2026-07-06 02:22:05 +02:00
ajnart 687edb9226 fix(layout): raise AppShellHeader z-index above Indicator dots
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
2026-07-06 01:50:13 +02:00
3556 changed files with 498088 additions and 222162 deletions

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
---
name: codebase-context
description: Navigate Homarr's monorepo architecture and reuse shared packages. Use when locating code, choosing a package boundary, understanding runtime services, tRPC, databases, widgets, integrations, cron jobs, Custom Widgets, Workshop, or checking @homarr/common before creating a utility.
---
# Homarr Codebase Context
Orient from the current checkout before editing. Treat package manifests, exports, and source as authoritative when they differ from this snapshot.
## Workflow
1. Confirm the branch and inspect the nearest `package.json`, existing sibling modules, and package exports.
2. Search `@homarr/common` before creating a general-purpose helper. Read [references/common.md](references/common.md) for every public entrypoint and export.
3. Read [references/architecture.md](references/architecture.md) when the task crosses packages, runtimes, routing, data, widgets, integrations, cron jobs, Custom Widgets, or Workshop.
4. Keep the change in the deepest package that owns the behavior. Reuse public entrypoints rather than deep-importing implementation files.
5. Re-run targeted searches before documenting counts, consumers, or dependency edges; these change faster than the architectural seams.
## Current toolchain
- Use Node `24.18.0` for application runtimes and Bun `1.4.2` for package management, pinned in `mise.toml` and `package.json`.
- Use Bun workspaces with Turborepo and a shared hoisted install. Workspace packages use the `@homarr/` scope and catalog-managed dependencies.
- Use Next.js App Router, TypeScript, tRPC, Drizzle, Mantine v9, Tabler icons, Jotai, TanStack Query, next-intl, Vitest, and Playwright.
- Use oxlint and oxfmt. Mantine is the application UI system; Tailwind is limited to the docs app.
- `bun run dev` starts only `@homarr/nextjs`. Start a standalone runtime explicitly when the task needs it.
## High-value seams
- Server/RSC tRPC: `@homarr/api/server`
- Client tRPC hooks: `@homarr/api/client`
- Main router: `packages/api/src/root.ts`
- MCP eager router: `packages/api/src/mcp.ts`
- Database schemas: `packages/db/schema/{sqlite,postgresql}.ts`
- Widget registry: `packages/widgets/src/registry.ts`
- Widget loading manifest: `packages/widgets/src/manifest.ts`
- Integration definitions: `packages/definitions/src/integration.ts`
- Integration factory: `packages/integrations/src/base/creator.ts`
- Cron jobs: `packages/cron-jobs/src/jobs/`
- App routes: `apps/nextjs/src/app/[locale]/`
Invoke `documentation-sync` only when users need new or corrected information to set up, use, or troubleshoot Homarr; follow the Documentation Sync criteria in `AGENTS.md`. When exposing tRPC through MCP, invoke `mcp-integration`.
@@ -0,0 +1,4 @@
interface:
display_name: "Homarr Codebase Context"
short_description: "Navigate Homarr architecture and shared APIs"
default_prompt: "Use $codebase-context to locate the right Homarr package and reuse existing shared APIs."
@@ -0,0 +1,110 @@
# Homarr architecture
## Contents
- [Applications and runtimes](#applications-and-runtimes)
- [Package map](#package-map)
- [Dependency direction](#dependency-direction)
- [Core patterns](#core-patterns)
- [Commands and conventions](#commands-and-conventions)
## Applications and runtimes
| Path | Role |
| ---------------- | ---------------------------------------------------------------------------------------- |
| `apps/nextjs` | Main Next.js application and HTTP APIs on port 3000 |
| `apps/docs` | Next.js and Fumadocs documentation site on port 3003 |
| `apps/tasks` | Cron-job initialization and scheduling runtime; it does not expose the old port-3002 API |
| `apps/websocket` | tRPC WebSocket subscriptions on port 3001 |
| `apps/workshop` | Go/PocketBase Workshop service and provider integration |
`bun run dev` runs only `@homarr/nextjs`. Use `bun run --filter @homarr/tasks dev`, `bun run --filter @homarr/websocket dev`, or `bun run dev:docs` when a task needs those runtimes.
In production, `apps/nextjs/src/instrumentation-node.ts` starts the tasks and WebSocket runtimes inside the Bun process. Nginx listens on port 7575, proxies `/websockets` to port 3001, and sends other traffic to port 3000. Redis can run internally or externally.
## Package map
### Foundation and domain
- `core`: environment validation, database and Redis factories, logging, infrastructure.
- `definitions`: domain enums, integration definitions, widget kinds, permissions.
- `common`: shared utilities with environment-specific entrypoints. Search it before adding helpers.
- `validation`: shared Zod schemas.
- `server-settings`: typed server-setting keys and access.
### Data, identity, and transport
- `db`: Drizzle schemas, queries, migrations, and SQLite and PostgreSQL drivers.
- `auth`: Auth.js/NextAuth configuration, sessions, providers, and API keys.
- `api`: tRPC routers, contexts, MCP metadata extraction, OpenAPI, and client/server entrypoints.
- `redis`: Redis clients, pub/sub channels, and caches.
- `cron-jobs-core`, `cron-jobs`, `cron-job-status`: scheduling primitives, job implementations, and status transport.
### Backends and external systems
- `integrations`: typed clients for external applications.
- `request-handler`: shared server request flows.
- `docker`, `ping`, `image-proxy`, `icons`, `analytics`: bounded infrastructure capabilities.
- `custom-widgets`: Custom JSX v2 schema, validation, runtime, and security contracts.
- `workshop`: TypeScript Workshop client, backend contracts, and schemas used by Homarr.
### UI and composed features
- `ui`: shared Mantine components, theme, and hooks. Search here before creating a primitive.
- `form`, `forms-collection`: Zod-backed form setup and reusable forms.
- `modals`, `modals-collection`: modal primitives and feature modals.
- `boards`: board state, edit mode, and cache coordination.
- `settings`, `spotlight`, `notifications`, `onboarding`: cross-feature UI flows.
- `widgets`: widget definitions, dynamic component loaders, and shared widget UI.
- `translation`: next-intl configuration and locale catalogs.
- `cli`: Bun operations CLI. `tools/homarr-dev` is the separate Go developer CLI.
## Dependency direction
Prefer these directions; verify the exact dependency in package manifests before changing a boundary.
1. Put infrastructure in `core` and stable domain names in `definitions`.
2. Put reusable, dependency-light helpers in `common`; use its public environment-specific entrypoints.
3. Build persistence in `db`, identity in `auth`, transport in `redis`, and external clients in `integrations`.
4. Compose server behavior in `api`, `cron-jobs`, and request-handler packages.
5. Build primitives in `ui`, `form`, and `modals`; compose product surfaces in widgets and collection packages.
6. Keep app entrypoints in `apps/*` thin. Avoid moving reusable behavior into `apps/nextjs` when a package already owns it.
## Core patterns
### tRPC and MCP
`packages/api/src/root.ts` lazily composes the application router. Server components use `@homarr/api/server`; client components use `@homarr/api/client`. MCP extraction requires the eager router in `packages/api/src/mcp.ts`, so MCP-enabled subrouters must be imported and registered there directly. Use the `mcp-integration` skill for exposure and security rules.
### Databases
Drizzle schemas live in `packages/db/schema/`. `DB_DRIVER` selects `better-sqlite3` or `node-postgres`; MySQL and MariaDB are no longer runtime drivers in v2. Existing MySQL installations must use the documented converter before upgrading. Migrations live under `packages/db/migrations/sqlite/` and `packages/db/migrations/postgresql/`.
### Widgets
`packages/widgets/src/registry.ts` owns the explicit widget-module and component-loader registries. `packages/widgets/src/manifest.ts` caches those loaders and exposes definitions, components, and combined resources. Widget modules use `createWidgetDefinition` and typed option builders. Data commonly comes from tRPC queries or Redis-backed cron results. Use existing shared widget components before creating local variants.
### Integrations
`packages/definitions/src/integration.ts` owns `integrationDefs` and `IntegrationKind`. `packages/integrations/src/base/creator.ts` owns `createIntegrationAsync`; concrete integrations implement the shared base contract. Keep credentials and permission checks on server-owned paths.
### Cron jobs
Jobs live in `packages/cron-jobs/src/jobs/` and use `createCronJob`. `apps/tasks` initializes and starts the job group. Job results and status can flow through Redis and tRPC; the retired port-3002 management-API model is no longer part of the architecture.
### Routing and localization
User routes live below `apps/nextjs/src/app/[locale]/`; the locale segment does not add a URL prefix. Route groups organize home and board surfaces. Administrative routes live under `manage`, Custom Widget configuration has a token route, and Workshop has a management surface.
### Custom Widgets and Workshop
`packages/custom-widgets` owns the Custom JSX v2 contract. `packages/workshop` is the Homarr-side client/contract package; `apps/workshop` is the separate Go/PocketBase service. Use the `homarr-custom-widget` skill for authoring and security constraints.
## Commands and conventions
- Read current scripts from the root or package `package.json`; avoid caching commands that do not exist.
- Use `bun run dev` for Next.js, `bun run dev:docs` for docs, and `bun run dev:cli -- <args>` for the Go developer CLI.
- Use `bun run docker:dev:up` for the Redis development dependency.
- Follow the Testing section in `AGENTS.md` for test creation and validation scope.
- Use `~/*` only inside the Next.js app. Use public `@homarr/*` entrypoints across package boundaries.
- Keep Mantine application styles separate from the Tailwind-based docs app.
@@ -0,0 +1,85 @@
# `@homarr/common` public API
## Contents
- [Reuse workflow](#reuse-workflow)
- [Entrypoints](#entrypoints)
- [Root exports](#root-exports)
- [Types exports](#types-exports)
- [Server exports](#server-exports)
- [Client and environment exports](#client-and-environment-exports)
## Reuse workflow
Before writing a utility, search both the public name and the behavior:
```bash
rg -n "export (const|function|class|type|interface)|export \*" packages/common
rg -n "@homarr/common" apps packages
```
Import only from an exported package entrypoint. Do not deep-import `@homarr/common/src/*`. Check `packages/common/package.json` and the source again when adding or renaming an export; this inventory describes the current repository tree.
## Entrypoints
| Import | Source | Environment |
| ----------------------- | ---------------------------- | ----------------------------------------------------------------------------------- |
| `@homarr/common` | `index.ts` -> `src/index.ts` | Shared root; includes React hooks and some Next/Mantine-aware helpers |
| `@homarr/common/types` | `src/types.ts` | Type-only utilities |
| `@homarr/common/server` | `src/server.ts` | Node/server-only security, encryption, request, error, and bounded-response helpers |
| `@homarr/common/client` | `src/client.ts` | Client-facing Next server action export |
| `@homarr/common/env` | `env.ts` | Validated package environment |
## Root exports
| 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.
## Types exports
`@homarr/common/types` exports:
- `MaybePromise<T>`
- `AtLeastOneOf<T>`
- `Modify<T, R>`
- `RemoveReadonly<T>`
- `MaybeArray<T>`
- `Inverse<T>`
- `inferSearchParamsFromSchema<TSchema>`
## Server exports
`@homarr/common/server` exports:
- Security: `generateSecureRandomToken`.
- Encryption: `encryptSecret`, `decryptSecret`, `decryptSecretWithKey`, `createKeyedFingerprint`, `verifyKeyedFingerprint`.
- Requests: `userAgent`, `DeviceType`, `ipAddressFromHeaders`.
- Bounded bodies: `readBoundedJsonResponseAsync`.
- Parse errors: `ParseError`, `ParseErrorHandler`, `ZodParseErrorHandler`, `JsonParseErrorHandler`.
- HTTP handlers: `HttpErrorHandler`, `FetchHttpErrorHandler`, `matchErrorCode`, `OFetchHttpErrorHandler`, `AxiosHttpErrorHandler`, `TsdavHttpErrorHandler`, `OctokitHttpErrorHandler`.
- HTTP errors: `RequestError`, `ResponseError`, `requestErrorMap`.
- HTTP error types: `AnyRequestError`, `AnyRequestErrorInput`, `RequestErrorInput`, `RequestErrorType`, `RequestErrorReason`, `AnyRequestErrorReason`, `RequestErrorCode`.
`NodeFetchHttpErrorHandler` exists as an implementation file but is not exported by the public server entrypoint. Add an intentional public export before using it across packages.
## Client and environment exports
- `@homarr/common/client` exports `revalidatePathActionAsync`, a Next server action for page revalidation.
- `@homarr/common/env` exports `env` with validated `NODE_ENV`, `SECRET_ENCRYPTION_KEY`, and `NO_EXTERNAL_CONNECTION` values.
@@ -0,0 +1,57 @@
---
name: documentation-sync
description: Write Homarr docs only when users need information to set up, use, or troubleshoot the app. Skip routine, obvious, or self-explanatory changes.
---
# Documentation Sync
Apply the audience and scope rules in `AGENTS.md` first. Documentation exists only for information users need to set up, use, or troubleshoot Homarr. A code, UI, or API change alone does not warrant documentation; if a user does not need to know it, do not add it.
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
These are destinations for warranted updates, not triggers to add documentation:
| Code change | Documentation target |
| ----------------------------------- | -------------------------------------------------------------------- |
| New integration | `apps/docs/docs/integrations/<slug>/index.mdx` and `index.ts` |
| New widget | `apps/docs/docs/widgets/<slug>/index.mdx` and `index.ts` |
| Changed public or management API | `apps/docs/docs/management/api/index.mdx` |
| New or changed environment variable | Relevant page under `apps/docs/docs/advanced/` or installation docs |
| New or changed CLI command | `apps/docs/docs/advanced/command-line/` |
| Authentication/provider change | Relevant SSO or authentication page under `apps/docs/docs/advanced/` |
| UI or workflow change | Relevant getting-started, management, widget, or integration page |
| Cron-job behavior | `apps/docs/docs/management/tasks.mdx` |
| Permission behavior | `apps/docs/docs/management/users.mdx` and the affected feature page |
| Custom Widget or Workshop behavior | Relevant Custom Widget, Assistant, or Workshop documentation |
If no existing row fits, search by the user-visible term and update the page where a user would look for the behavior:
```bash
rg -n "<term>" apps/docs/docs apps/docs/src
```
## Follow existing typed patterns
For an integration:
- Add or update `apps/docs/docs/integrations/<slug>/index.ts` with the local `IntegrationDefinition` pattern.
- Add or update `index.mdx` with the established `IntegrationHeader`, `IntegrationCapabilites`, and `IntegrationSecrets` components as applicable.
- Reuse metadata from `@homarr/definitions` when the docs app already exposes it.
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.
- Explain only non-obvious option semantics, integration prerequisites, permission constraints, or behavior that the interface does not make clear.
## Completion criteria
1. Every passage adds information a user needs and 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 `bun run turbo build --filter=@homarr/docs` when links, MDX, generated definitions, or navigation can fail; otherwise run the docs package formatter on touched files.
5. Treat broken links and anchors as failures; run `bun run --filter @homarr/docs validate:links` after the static build.
@@ -0,0 +1,4 @@
interface:
display_name: "Homarr Documentation Sync"
short_description: "Document only what users need to know"
default_prompt: "Use $documentation-sync only when users need new or corrected information to set up, use, or troubleshoot Homarr."
@@ -0,0 +1,58 @@
---
name: homarr-custom-widget
description: Author, validate, preview, test, install, or configure API-backed Homarr Custom JSX v2 widgets.
---
# Homarr Custom Widget
Author requested widgets with release context; validate, test, persist, and return artifacts.
- Read primary API docs when missing/changed. Samples and successful previews are binding; load only needed schema, runtime,
security, or component context.
- Batch unknown component searches/details. `contextAlreadyLoaded` reuses context; `phaseComplete` advances. Stop only for
genuine provider/model, lifecycle-service, or workbench-closure failure.
- Community widgets use `customWidget_workshopSearch`, `customWidget_workshopGet`, and
`customWidget_workshopInstall`; configure and persist.
Return one fenced `json` block with the complete definition; keep evidence prose outside it. The definition has keyed
`sources`, `requests`, `template`, and optional `options`; actions are requests with `kind: "action"`.
- `sources.default` is required. HTTP has `baseUrl`, `networkScope`, and credential-free `auth`; localhost/loopback requires
`networkScope: "loopback"`; never widen an explicit scope. Saved sources use `type: "integration"`/`integrationKind`;
Homarr holds credentials.
- Saved integrations: discover kinds/full-access entries with `integration_getKinds`/`integration_all`, bind `integrationId`
before preview, omit URL/auth, and keep non-GET requests as actions.
- Paths are slash-prefixed: strings use `{option:name}`/`{param:name}`; query/body uses `{"$option":"name"}`/`{"$param":"name"}`.
Loads use `trigger: "load"`; manual helpers use `trigger: "manual"`.
- Actions stay manual; preserve `confirmation`, `permission`, and `invalidates`; DELETE requires full permission/confirmation.
`$param` is manual-only; `$option` may drive loads.
- Load data.requestId/status.requestId with RefreshButton; status.requestId?.ok === false is error. Manual SubFetch never
populates data/status; its child receives (result, metadata) and renders its fields.
- Options have `label`, `control`, `default`; installation config is `options.name`, never `inputs`. Request-bound TextInput,
Select, NumberInput, Pagination use literal `bind` + default and manual `SubFetch params` map `inputs.<name>` to `$param`.
Dependent pagination uses `defaultValue={1}`/`resetKey={inputs.query}`. Remove controls without an option/request/helper; guard arrays/nested with `??`;
preserve documented timezone values; use UTC only when the contract says UTC.
- Templates are one expression: no imports, hooks, refs, raw HTML/events, browser requests, eval, recursion, IIFEs,
statement blocks, or arbitrary functions. Use registered component names; `Icon` may alias `TablerIcon`. Keep hierarchy,
theme tokens, useful states, and narrow/wide layouts purposeful.
## Bounded lifecycle
1. Build a credential-free definition from request, verified context, and sample. Preserve a migration's API path, method,
body, options, and behavior; omit unknown requests rather than guessing.
2. Send the coherent complete definition directly to `customWidget_previewCreate`; it validates both manifest and JSX.
Use `customWidget_validateTemplate` only for isolated JSX diagnostics, never as a preview prerequisite. Use
`customWidget_previewReviseTemplate` for JSX-only corrections after a preview exists. In the Assistant wrapper, multiline
JSX uses `templateLines` and preview creation receives the complete definition.
3. Test every returned query/simulated action once, batching independent queries. On a concrete schema/preview error, fix only
that field and retry `customWidget_previewCreate` with the corrected definition; use `customWidget_previewReviseTemplate`
only for JSX errors after a preview exists. Stop only for genuine provider/model, lifecycle-service, or
workbench-closure failure.
4. If `previewCreate` used `definitionId`, persist with `customWidget_updateFromPreview`; otherwise use
`customWidget_createFromPreview`. Follow create `nextAction` once. Configure credentials in Homarr; never repeat plaintext
secrets.
## Delivery
Report actual lifecycle results. If unavailable, add one post-artifact `Unverified:` line naming missing validation, preview,
renderer, or persistence. Never claim rendering/persistence from schema checks.
@@ -0,0 +1,4 @@
interface:
display_name: "Homarr Custom Widget"
short_description: "Build and deploy safe Custom JSX widgets"
default_prompt: "Use $homarr-custom-widget to author, validate, preview, and configure a safe Custom JSX v2 widget."
@@ -0,0 +1,37 @@
# Runtime
Load-query templates read `data.requestId` and `status.requestId`, plus `options.name` and temporary `inputs.name`. Manual `SubFetch` results stay local to that instance and never populate `data`/`status`; render them in its child callback. Status is `{ loading, ok, status, statusText, error }`. Render load queries directly from `data` and `status` with `RefreshButton`; never wrap them in `SubFetch`.
`bind` is temporary; manual request values go in `params` and map to `$param`:
```jsx
<TextInput bind="search" label="Search" />
<NumberInput bind="page" label="Page" defaultValue={1} resetKey={inputs.search} min={1} />
<SubFetch requestId="search" trigger="manual" params={{ query: inputs.search ?? "", page: inputs.page ?? 1 }}>
{(result) => <Stack>{(result.results ?? []).map(item => <Text key={item.id}>{item.name}</Text>)}</Stack>}
</SubFetch>
```
Manual queries require `trigger: "manual"` on request and `SubFetch`; otherwise they run automatically. `triggerContent` with `triggerAriaLabel` makes custom content the launcher. `SubFetch` owns loading/error/retry; its child receives success plus `{ ok, status, statusText, loading: false }`. Never author `onClick` or fetch callbacks.
`SubFetch`, `ActionButton`, and `ToggleSwitch` need literal `requestId`; validation rejects missing/computed IDs.
Inside a successful manual result, `<RefreshButton requestId="search" label="Run again" />` reruns the same parameters.
When a manual SubFetch ID, params, or definition changes, Homarr hides the prior result and returns to the trigger; new params fetch only after triggering again.
Exact paths: load request ID `q` with raw preview body `B` -> `data.q === B`. `q=events` body`{"events":[...]}` -> `data.events.events`; do not flatten repeated keys. Manual `SubFetch` receives `B` as result -> `result.events`. Before persistence inspect core paths against preview; revise JSX and retest if mismatched
Format timestamps with safe static helpers; never use `new Date`. Never invent a formatter component. Use `Date.toLocaleString(value, "en-US", documentedTimezone)` and label the documented timezone; if no timezone is documented, preserve the source value or omit any timezone label; use UTC only when the response contract says UTC. Also available: `Date.toISOString`, `Date.toLocaleDateString`, and `Date.toLocaleTimeString`.
For compact numeric enums, index a literal label array with a fallback:
```jsx
<Text>{["Unknown", "Pending", "Ready"][(item.status ?? 1) - 1] ?? "Unknown"}</Text>
```
Request-bound controls use literal `bind` plus a default (`defaultChecked` for Switch/Checkbox); pass `inputs.<name>` through manual `SubFetch params` to matching `$param`. Options are installation config via `options.name`, never `inputs`; dependent pagination uses `defaultValue={1}`/`resetKey={inputs.query}`. Remove dead controls.
Callback parameters must not shadow the reserved roots `data`, `status`, `options`, or `inputs`. Use registered component names returned by discovery; `Icon` is an accepted alias for canonical `TablerIcon`. Never invent components such as `<IconFoo />`.
Use expression callbacks for supported collections and trusted slots. No callback blocks, IIFEs, authored recursion, or raw events. Regex is limited to safe string operations.
@@ -0,0 +1,53 @@
# Schema
```ts
interface HomarrCustomWidgetV2 {
$schema: "homarr-custom-widget-v2";
name: string;
description?: string;
iconUrl?: string;
sources: Record<string, CustomWidgetSource>;
requests: Record<string, CustomWidgetRequest>;
options?: Record<string, CustomWidgetOption>;
template: string;
}
```
Key `default` is the required source ID, not a source property. Fields: `name?`, `baseUrl`, `networkScope`, `auth?`; localhost/loopback URLs require `networkScope: "loopback"`; never widen explicit scope:
```json
{
"sources": {
"default": {
"name": "Service",
"baseUrl": "http://service.local:5055/api/v1",
"networkScope": "private",
"auth": { "type": "apiKeyHeader", "name": "X-Api-Key" }
}
},
"requests": {
"summary": { "path": "/summary" },
"search": { "trigger": "manual", "path": "/search", "query": { "q": { "$param": "query" } } },
"create": {
"kind": "action",
"method": "POST",
"path": "/items",
"body": { "id": { "$param": "id" } },
"confirmation": "Create this item?",
"invalidates": ["search"]
}
}
}
```
Saved sources use `{"type":"integration","integrationKind":"sonarr","integrationId":"saved-id"}`. Discover and bind a full-access entry before preview. Omit URL/auth; paths append to its URL, non-GET requests are actions, and exports omit `integrationId`.
Auth is `none`, `bearer`, `basic`, `apiKeyHeader`, or `apiKeyQuery`. Requests default to source `default`, query/GET/load, inherited auth, and view permission. Use `load` for initial/current display and `manual` only for explicit interactions or invocation params. Actions are manual/modify; DELETE requires full permission and confirmation.
JSON responses become their decoded value. Responses with `application/x-ndjson` become an array with one decoded object per non-empty line.
Use real public API URLs and clear self-hosted placeholders. Homarr collects URL, scope, and credentials outside the manifest.
Paths use `{option:name}`/`{param:name}`; query/body objects use `{"$option":"name"}`/`{"$param":"name"}`. `$param` is manual-only; `$option` may drive loads. Constants stay primitive.
Every option has `label`, `control`, and `default`. Optional fields are `description`, `choices`, `choicesFrom`, `min`, `max`, `step`, `advanced`, and `group`.
@@ -0,0 +1,7 @@
# Security
All requests use Homarr's protected server executor. Source origin, network scope, DNS, redirects, SSRF, rate limits, permissions, size limits, timeouts, and encrypted credential injection remain enforced.
The JSX interpreter blocks imports, hooks, refs, raw event callbacks, browser requests, eval, arbitrary functions, prototype access, unsafe URLs, global CSS escape, arbitrary portals, bigint, statement blocks, IIFEs, and recursion. Regex literals must be bounded and reject backreferences, lookbehind, nested quantifiers, excessive length, and unsupported flags.
Credentials are stored separately and never exported or returned to an agent. A published self-hosted source URL is only a suggestion: installers must confirm or replace private and loopback URLs for their own Homarr deployment. Source origins cannot be controlled through widget options.
+98
View File
@@ -0,0 +1,98 @@
---
name: mcp-integration
description: Expose Homarr tRPC procedures safely through MCP. Use when adding or changing tRPC procedures under packages/api, adding MCP metadata, registering an eager MCP router, shaping tool inputs and descriptions, or reviewing MCP authorization, sensitive data, and destructive actions.
---
# MCP Integration
Expose a procedure only when an AI client should call it. Preserve the procedure's existing authorization and add MCP-specific safeguards for sensitive or destructive behavior.
## Assess exposure
Prefer:
- Data queries: list, search, get, health, stats, and summaries.
- User-requested actions with explicit permission checks and bounded inputs.
- Serializable results that do not reveal credentials or internal-only state.
Skip subscriptions, session/onboarding internals, file streams, blobs, credential values, and procedures whose authorization or audit behavior is unclear. Ask for a security review when a mutation is destructive, tenant boundaries are ambiguous, or returned data is sensitive.
## Add metadata
Place `.meta()` before `.input()` and `.query()` or `.mutation()`:
```typescript
getAll: protectedProcedure
.meta({
mcp: {
enabled: true,
description: "List resources the current user can access, including stable IDs used by resource_get",
},
})
.input(z.object({ limit: z.number().int().min(1).max(100).default(50) }))
.query(async ({ ctx, input }) => {
// Preserve normal procedure authorization.
});
```
No-input procedures can omit `.input()`; the MCP extractor accepts an empty object for them.
For mutations, state the effect and required permission in the description:
```typescript
remove: permissionRequiredProcedure
.meta({
mcp: {
enabled: true,
description: "Delete one resource by ID. Requires full access; get the ID from resource_all",
},
})
.input(z.object({ id: z.string() }))
.mutation(async ({ ctx, input }) => {
// Perform the permission-scoped action.
});
```
## Write discoverable descriptions
Include every item that applies:
- What the tool returns or changes.
- Supported integrations or services.
- Required IDs and the tool that returns them.
- Permission requirements and the meaning of permission fields.
- Important bounds, confirmation semantics, or irreversible effects.
Use precise domain language. A description such as `Get calendar events` is insufficient because it omits supported services, required IDs, and result scope.
## Shape inputs for clients
- Use a top-level `z.object({...})` for parameterized tools.
- Bound strings, arrays, numeric ranges, pagination, and payload sizes.
- Use defaults where a safe, unsurprising value exists.
- Prefer explicit fields over a top-level union or discriminated union.
- Keep identifiers stable and describe how to obtain them.
- Keep secrets out of inputs unless the procedure is explicitly a credential-configuration flow with server-owned encryption and non-return guarantees.
## Register the eager router
The application router in `packages/api/src/root.ts` is lazy. MCP tool extraction is synchronous, so add an eager import and registration in `packages/api/src/mcp.ts`:
```typescript
import { resourceRouter } from "./router/resource";
export const mcpRouter = createTRPCRouter({
resource: resourceRouter,
});
```
Register the smallest router that owns the enabled procedures. If a parent router uses `lazy()`, import the needed subrouter directly. Metadata on a procedure is not enough; a procedure absent from `mcpRouter` is not exposed.
## Validate the tool surface
1. Confirm the procedure still enforces its ordinary auth and permission boundary.
2. Confirm `packages/api/src/mcp.ts` eagerly includes the procedure.
3. Confirm the generated tool name, description, and JSON schema are unambiguous.
4. When validating tool-list or extraction changes, use the focused API MCP spec: `bun run test packages/api/src/test/mcp.spec.ts`.
5. When validating protocol or transport changes, use the focused route spec: `bun run test apps/nextjs/src/app/api/mcp/[transport]/route.spec.ts`.
6. If manually probing `/api/mcp/<transport>`, use a scoped API key and avoid printing credentials or secret-bearing results.
@@ -0,0 +1,4 @@
interface:
display_name: "Homarr MCP Integration"
short_description: "Expose tRPC procedures safely through MCP"
default_prompt: "Use $mcp-integration to expose this Homarr tRPC procedure as a safe, discoverable MCP tool."
+1
View File
@@ -0,0 +1 @@
../.agents/skills
-206
View File
@@ -1,206 +0,0 @@
---
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`
- **Auth**: NextAuth v5 (database sessions, Credentials/LDAP/OIDC providers)
- **UI**: Mantine v9 (not Tailwind) + Tabler icons
- **State**: Jotai (atoms), TanStack Query (server state via tRPC)
- **Realtime**: WebSocket server (`ws`) for tRPC subscriptions, backed by Redis pub/sub
- **Cron**: `node-cron` in a standalone Fastify service (`apps/tasks`)
- **i18n**: next-intl with `[locale]` dynamic segment (prefix mode: `"never"`, locale from cookie)
- **Testing**: Vitest + jsdom, Playwright for E2E
- **Lint/Format**: oxlint + oxfmt (not ESLint/Prettier)
- **Docs**: Docusaurus 3 in `apps/docs/` (`@homarr/docs`)
- **Package manager**: pnpm 10.34.1, Node >= 24.16.0
## Repository Structure
```
homarr/
├── apps/
│ ├── nextjs/ # Main Next.js application (port 3000)
│ ├── docs/ # Docusaurus 3 documentation site (@homarr/docs)
│ ├── tasks/ # Cron job runner + Fastify tRPC API (port 3002)
│ └── websocket/ # Standalone tRPC WebSocket server (port 3001)
├── packages/
│ ├── api/ # tRPC appRouter, procedures, OpenAPI
│ ├── auth/ # NextAuth config, providers, session, API keys
│ ├── db/ # Drizzle schema (3 DB drivers), migrations, queries
│ ├── core/ # Env validation, DB/Redis driver factories, logging
│ ├── definitions/ # Domain enums: WidgetKind, IntegrationKind, permissions
│ ├── widgets/ # All 39 dashboard widgets (definitions + components)
│ ├── integrations/ # Integration classes (HTTP clients to external apps)
│ ├── redis/ # Redis pub/sub channels, caching abstractions
│ ├── translation/ # next-intl setup, locale configs, lang JSON files
│ ├── ui/ # Shared Mantine components, theme, hooks
│ ├── validation/ # Shared zod schemas for API/forms
│ ├── common/ # Shared utilities, IDs, errors
│ ├── cron-jobs/ # Cron job implementations (25+ jobs)
│ ├── cron-jobs-core/ # Cron scheduling primitives
│ ├── cron-job-api/ # tRPC router for job management (start/stop/trigger)
│ ├── cron-job-status/ # Cron status via Redis
│ ├── boards/ # Board context, edit mode, cache updater
│ ├── modals/ # Modal primitives on Mantine
│ ├── modals-collection/ # Feature modals (apps, boards, docker, etc.)
│ ├── form/ # useZodForm (Mantine + zod resolver)
│ ├── forms-collection/# Reusable form UIs (new app, icon picker, upload)
│ ├── spotlight/ # Command palette / search with multiple modes
│ ├── request-handler/ # Server request handlers (feeds, integrations)
│ ├── notifications/ # Mantine notifications wrapper
│ ├── docker/ # Dockerode-based Docker access
│ ├── icons/ # Icon DB/repo integration
│ ├── image-proxy/ # Image proxy + caching
│ ├── ping/ # Reachability / ping utilities
│ ├── analytics/ # Server-side analytics (Umami)
│ ├── server-settings/ # Server setting keys/types
│ ├── settings/ # User-facing settings UI context
│ ├── old-import/ # Legacy Homarr import
│ ├── old-schema/ # Legacy Homarr zod schemas
│ └── cli/ # Node CLI for ops (brocli)
├── tooling/
│ ├── typescript/ # Base tsconfig
│ └── github/ # CI setup action
├── development/ # Dev docker-compose (Redis, MySQL, PostgreSQL)
├── e2e/ # E2E test specs
└── Dockerfile # Multi-stage production build
```
## Three Runtime Services
| Service | Port | Role |
|---------|------|------|
| Next.js | 3000 | Main web app (SSR + API routes) |
| WebSocket | 3001 | tRPC subscriptions via `ws` |
| Tasks | 3002 | Cron job runner + management API via Fastify |
In Docker, nginx listens on **7575** and proxies to all three. The tasks service authenticates via `CRON_JOB_API_KEY` header.
## Package Dependency Layers
1. **Foundation**: `core` (env, DB drivers, Redis client, logging) → no `@homarr` deps
2. **Domain**: `definitions` → `common` → `core`
3. **Data**: `db` → `core`, `definitions`, `common`
4. **Auth**: `auth` → `db`, `definitions`, `core`, `validation`
5. **Cross-cutting**: `translation`, `validation`, `redis`, `server-settings`
6. **Feature backends**: `integrations`, `request-handler`, `cron-jobs`, `docker`, `ping`
7. **API surface**: `api` → pulls in auth, db, redis, integrations, etc.
8. **UI stack**: `ui`, `modals`, `form`, `notifications`, `boards`, `spotlight`
9. **Composed features**: `widgets`, `modals-collection`, `forms-collection`
## Key Patterns
### tRPC
- **Server caller** (RSC): `import { api } from "@homarr/api/server"` → cached caller with auth context
- **Client hooks**: `import { clientApi } from "@homarr/api/client"` → React Query hooks
- **Router structure**: `packages/api/src/root.ts` defines top-level namespaces (`user`, `board`, `widget`, `integration`, `docker`, `kubernetes`, etc.)
- **Procedures**: `publicProcedure`, `protectedProcedure`, `permissionRequiredProcedure`, `onboardingProcedure`
- **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
- Route groups: `(home)`, `(content)`, `(board)` — don't affect URLs
- Key routes: `boards/[name]`, `manage/...` (admin), `auth/login`, `init` (onboarding), `widgets/[kind]`
- Locale driven by cookie (no URL prefix)
### Auth Providers
Configured via `AUTH_PROVIDERS` env. Supports: `credentials` (local), `ldap`, `oidc`. Database sessions with httpOnly cookies. API key auth for programmatic access.
## Environment Variables
Key env vars (from `.env.example`):
- `DB_DRIVER`: `better-sqlite3` | `mysql2` | `node-postgres`
- `DB_URL`: Database connection string
- `AUTH_SECRET`: NextAuth secret
- `SECRET_ENCRYPTION_KEY`: AES-256-CBC for integration secrets
- `AUTH_PROVIDERS`: Comma-separated (`credentials`, `ldap`, `oidc`)
- `CRON_JOB_API_KEY`: Shared key between Next.js and tasks service
- `REDIS_*`: Redis connection (host, port, password)
- `ENABLE_KUBERNETES`: Optional Kubernetes integration
## Development Commands
- `pnpm dev` — runs all three services in parallel via Turborepo
- `pnpm docker:dev` — starts Redis + MySQL + PostgreSQL containers
- `pnpm db:push` — push schema to SQLite (dev)
- `pnpm db:studio` — Drizzle Studio
- `pnpm test` — Vitest unit tests
- `pnpm test:e2e` — Playwright E2E
- `pnpm lint` / `pnpm format` — oxlint + oxfmt
- `pnpm dev:docs` — Docusaurus docs site (port 3003)
- `pnpm typecheck` — tsc --noEmit across all packages
## Conventions
- Workspace packages use `@homarr/` scope
- All versions managed via pnpm `catalog:` in workspace
- Path alias `~/*` → `./src/*` in Next.js app
- Mantine for all UI (no Tailwind) — primary color is red, `autoContrast: true`
- Icons from `@tabler/icons-react`
- Drag-and-drop via `@dnd-kit/*`
- `typescript.ignoreBuildErrors: true` in next.config (types checked separately via `typecheck`)
- `serverExternalPackages`: `dockerode`, `isomorphic-dompurify`, `jsdom`
## Documentation (`apps/docs`)
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/`
- Integration docs: `apps/docs/docs/integrations/<slug>/index.mdx` + `index.ts`
- Widget docs: `apps/docs/docs/widgets/<slug>/index.mdx` + `index.ts`
- Strict link checking: broken links/anchors cause build failure
-50
View File
@@ -1,50 +0,0 @@
---
description: Keep documentation in sync with code changes
globs:
- packages/definitions/src/**
- packages/widgets/src/**
- packages/integrations/src/**
- packages/api/src/**
- apps/nextjs/src/**
- apps/docs/**
---
# Documentation Sync Rule
When modifying code that affects user-facing behavior, you MUST also update the documentation in `apps/docs/`.
## What triggers a doc update
| Code change | Doc location |
|---|---|
| New integration | `apps/docs/docs/integrations/<slug>/index.mdx` + `index.ts` |
| New widget | `apps/docs/docs/widgets/<slug>/index.mdx` + `index.ts` |
| Changed API surface | `apps/docs/docs/management/api.mdx` |
| New/changed env vars | `apps/docs/docs/advanced/` |
| New CLI commands | `apps/docs/docs/advanced/command-line/` |
| Changed auth providers | `apps/docs/docs/advanced/` SSO pages |
| UI/UX changes | Relevant getting-started or management pages |
| New cron job | `apps/docs/docs/management/tasks.mdx` |
| Changed permissions | `apps/docs/docs/management/users.mdx` |
## Integration doc pattern
Each integration has a folder under `apps/docs/docs/integrations/<slug>/`:
- `index.ts` — exports a typed `IntegrationDefinition` with name, description, iconUrl, path
- `index.mdx` — uses shared components: `IntegrationHeader`, `IntegrationCapabilites`, `IntegrationSecrets`
- Import metadata from `@homarr/definitions` where possible
## Widget doc pattern
Each widget has a folder under `apps/docs/docs/widgets/<slug>/`:
- `index.ts` — exports a typed `WidgetDefinition` with icon, name, description, path, configuration
- `index.mdx` — uses shared components: `WidgetHeader`, `WidgetConfig`, `WidgetAdding`
## Development
- Run docs locally: `pnpm dev:docs` from root
- Build docs: `pnpm turbo build --filter=@homarr/docs`
- The docs app can import from `@homarr/definitions` for type-safe integration/widget metadata
- Docusaurus strict mode throws on broken links — fix any broken references before committing
-103
View File
@@ -1,103 +0,0 @@
---
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" } })
.input(z.object({ limit: z.number().default(50) }))
.query(async ({ ctx, input }) => { ... })
// Write tool (mutation)
myAction: protectedProcedure
.meta({ mcp: { enabled: true, description: "Delete an item by ID. Requires interact permission on the integration" } })
.input(z.object({ id: z.string() }))
.mutation(async ({ ctx, input }) => { ... })
// No-input procedure (z.void) — works fine, no .input() needed
getAll: protectedProcedure
.meta({ mcp: { enabled: true, description: "List all resources" } })
.query(async ({ ctx }) => { ... })
```
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)
- Internal procedures (session management, onboarding steps)
- File upload/download procedures
- Procedures that return non-serializable data (streams, blobs)
## Input schema rules
- Use `z.object({...})` for inputs — this maps cleanly to MCP tool parameters
- `z.void()` (no `.input()`) works — the MCP handler patches these to accept empty objects
- Avoid `z.union()` or `z.discriminatedUnion()` at the top level — AI clients struggle with these
- Use `.default()` on optional fields so the AI doesn't need to guess values
## Checklist for new features
1. Add `.meta({ mcp: { enabled: true, description: "..." } })` to relevant procedures
2. Import and register the router in `packages/api/src/mcp.ts` (eager, no lazy)
3. Write descriptions that explain what the tool does, what it needs, and what it returns
4. If the tool requires an ID from another tool, say which one in the description
5. Test with `curl` against `/api/mcp/mcp` to verify the tool appears in `tools/list`
-8
View File
@@ -1,8 +0,0 @@
version = 1
[[analyzers]]
name = "javascript"
[analyzers.meta]
plugins = ["react"]
environment = ["nodejs"]
+23 -1
View File
@@ -5,13 +5,35 @@ node_modules
npm-debug.log
README.md
.next
**/.output
.turbo
**/.turbo
**/next-env.d.ts
packages/db/migrations/*/migrate.cjs
packages/cli/cli.cjs
apps/tasks/tasks.cjs
apps/tasks/tasks.css
apps/websocket/wssServer.cjs
apps/websocket/wssServer.css
.bun-cache
**/.bun-cache
coverage
**/coverage
**/.docusaurus
**/build
.git
.github
dev
.build
e2e
apps/docs
apps/docs/*
!apps/docs/package.json
apps/nextjs/.next
.env
.env.*
!.env.example
benchmark-results
scripts/benchmarks/results
**/__pycache__
**/*.tsbuildinfo
+42 -20
View File
@@ -1,34 +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'
# Those are the two ways to use the mysql2 driver:
# 1. Using the URL format:
# DB_DRIVER='mysql2'
# DB_URL='mysql://user:password@host:port/database'
# 2. Using the connection options format:
# DB_DRIVER='mysql2'
# DB_HOST='localhost'
# DB_PORT='3306'
# DB_USER='username'
# DB_PASSWORD='password'
# DB_NAME='name-of-database'
# 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=
# The following is an example on how to use the node-postgres driver:
# DB_DRIVER='node-postgres'
@@ -59,4 +50,35 @@ TURBO_TELEMETRY_DISABLED=1
# ENABLE_KUBERNETES=true
# Enable mock integration
UNSAFE_ENABLE_MOCK_INTEGRATION=true
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.
# WORKSHOP_API_URL=https://homarr.dev
# Public Workshop website used by links in Homarr, Custom Widgets, onboarding,
# and assistant context. Defaults to HOMARR_WEBSITE_URL/workshop.
# WORKSHOP_WEB_URL=https://homarr.dev/workshop
# Documentation and PocketBase deployment settings.
# WORKSHOP_PUBLIC_ORIGIN=https://homarr.dev
# PB_ALLOWED_ORIGINS=*
# Local Workshop stack:
# HOMARR_WEBSITE_URL=http://127.0.0.1:3003
# WORKSHOP_API_URL=http://127.0.0.1:8090
# WORKSHOP_WEB_URL=http://127.0.0.1:3003/workshop
# WORKSHOP_PUBLIC_ORIGIN=http://127.0.0.1:3003
# Optional: GITHUB_CLIENT_ID=
# Optional: GITHUB_CLIENT_SECRET=
# Enable demo mode (seeds a demo user "demo"/"demo", a sample board and a mock integration)
# DEMO_MODE=true
# Whether demo mode is read-only. Defaults to true; Assistant conversation history remains writable for canned chats.
# Set to false for writable preview deployments so the demo user can modify the dashboard.
# DEMO_READ_ONLY=false
+26
View File
@@ -25,6 +25,18 @@ body:
attributes:
label: Additional information
description: Add any other information related to the bug here, screenshots if applicable.
- type: input
id: exactBuild
attributes:
label: Exact build or image
description: For preview or development builds, include the commit SHA or image digest. A moving tag such as v2 is not enough to identify the build.
placeholder: "Commit SHA, image digest, or release version"
- type: input
id: lastWorkingVersion
attributes:
label: Last working version
description: If this worked before, include the last version or build where you tried the same action. Leave blank if unknown.
placeholder: "Version or commit SHA"
- type: dropdown
id: version
attributes:
@@ -33,6 +45,20 @@ body:
options:
# The below comment is used to insert a new version with on-release.yml
#NEXT_VERSION#
- 2.2.0
- 2.1.2
- 2.0.0
- 1.77.0
- 1.76.2
- 1.76.1
- 1.76.0
- 1.75.0
- 1.74.0
- 1.73.0
- 1.72.0
- 1.71.0
- 1.70.0
- 1.69.2
- 1.69.0
- 1.68.0
- 1.67.0
@@ -0,0 +1,48 @@
name: 🐞 Custom Widget bug
description: Report a problem with a Custom JSX widget, its requests, preview, or editor
title: "bug: custom widget "
labels: ["needs triage"]
body:
- type: markdown
attributes:
value: |
Share a small reproduction so we can distinguish widget code, upstream API, and Homarr problems.
Remove API keys, passwords, tokens, cookies, authorization headers, private URLs, and personal data from everything you share, including exports and screenshots. Replace private hosts with `https://example.invalid` while preserving relevant paths and response structure.
- type: textarea
id: behavior
attributes:
label: Actual and expected behavior
description: Describe what happens and what you expected. Include the exact error text and a sanitized screenshot if useful.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to reproduce
description: Include the affected request or control, relevant widget options, and whether this occurs in Test, preview, or on the dashboard. For layout issues, include the widget size and desktop or mobile viewport.
validations:
required: true
- type: textarea
id: definition
attributes:
label: Sanitized widget definition
description: Paste or attach the exported JSON with sources, requests, options, and JSX template. Prefer the smallest definition that reproduces the problem. If export fails, include the relevant JSX and request configuration. Check for private URLs and sensitive data before sharing.
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: Include the exact Homarr release, commit SHA, or image digest; installation method; browser/version; and desktop or mobile OS. For request issues, include the source type (HTTP or integration), integration kind, and upstream service version. Do not include server URLs or credentials.
placeholder: |
Homarr version / commit / image digest:
Installation method:
Browser and OS:
Source type, integration kind, and service version:
validations:
required: true
- type: textarea
id: diagnostics
attributes:
label: Sanitized API response and logs
description: For request or data issues, include the request ID, method, relative API path, HTTP status, and a small response sample preserving field names and types. Add relevant Homarr or browser console errors. Remove sensitive values and private URLs; leave blank for purely visual issues.
+2
View File
@@ -0,0 +1,2 @@
.secrets
!.secrets.example
+4
View File
@@ -0,0 +1,4 @@
TURBO_API=
TURBO_TEAM=
TURBO_TOKEN=
TURBO_REMOTE_CACHE_SIGNATURE_KEY=
+38
View File
@@ -0,0 +1,38 @@
# Running CI locally
Install [Act](https://nektosact.com/installation/index.html) and ensure Docker is running, then use:
```bash
bun run ci:act:fast
bun run ci:act:docker
bun run ci:act:all
bun run ci:act:cleanup
```
`fast` runs the database checks and quality gate and requires an `origin/dev` tracking ref. `docker` and `all` use the manual validation event to run those checks, build the container once, and run the converter boot/restart smoke 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.
For rootless Docker, set `ACT_TESTCONTAINERS_HOST` to a host address reachable from the runner when loopback callbacks are unavailable.
Remote caching is optional locally. Copy `.github/act/.secrets.example` to `.github/act/.secrets`, populate it with rotated credentials, and run with:
```bash
ACT_SECRET_FILE=.github/act/.secrets bun run ci:act:all
```
Act never publishes or deletes GHCR images. The secret file is ignored by Git.
## PR preview images
Successful non-draft pull requests from this repository publish an amd64 image:
```bash
docker pull ghcr.io/homarr-labs/homarr-test:pr-<number>
```
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.
@@ -19,11 +19,6 @@ inputs:
runs:
using: "composite"
steps:
- name: Prebuilt debian dependencies
uses: homarr-labs/homarr/.github/actions/prebuilt-debian@dev
id: prebuilt-debian
with:
architecture: ${{ inputs.architecture }}
- name: Start docker container for ${{ inputs.architecture }}
run: |
docker run --name homarr \
@@ -43,8 +38,6 @@ runs:
shell: bash
- name: Extract source from ${{ inputs.architecture }} container (debian)
run: |
docker cp ${{ steps.prebuilt-debian.outputs.path }}/. homarr:/app/build && \
docker cp ${{ steps.prebuilt-debian.outputs.path }}/. homarr:/app/node_modules/better-sqlite3/build/Release && \
docker exec homarr tar -czf extraction-debian.tar.gz -C /app . && \
docker cp homarr:/app/extraction-debian.tar.gz ${{ runner.temp }}/extraction/${{ inputs.architecture }}/build-debian-${{ inputs.architecture }}.tar.gz
shell: bash
@@ -11,9 +11,6 @@ outputs:
runs:
using: "composite"
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
+13 -5
View File
@@ -4,10 +4,18 @@
<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/).
- [ ] No shorthand variable names are used (for example, `x`, `y`, `i`, or abbreviations)
- [ ] Documentation under `apps/docs` is updated only when users need new or corrected information to use the app
- [ ] 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:**
- [ ] I confirmed manually that the integration or widget is working with a real system and performed basic smoke
tests (e.g. what happens if it is offline)
- [ ] Optional but recommended: I confirm that I am willing to be added to the `CODEOWNERS` of this feature and will
provide support and maintain the integration (e.g. in case of breaking or failure)
+3 -15
View File
@@ -1,16 +1,11 @@
{
$schema: "https://docs.renovatebot.com/renovate-schema.json",
extends: ["config:recommended"],
extends: ["config:recommended", ":dependencyDashboardApproval"],
packageRules: [
{
matchPackageNames: ["^@homarr/"],
enabled: false,
},
// Reenable once https://github.com/privatenumber/tsx/issues/737 is fixed
{
matchPackageNames: ["tsx"],
enabled: false,
},
// Reenable once bug with undici v8 is fixed (https://github.com/homarr-labs/homarr/issues/5467)
{
matchPackageNames: ["undici"],
@@ -22,11 +17,10 @@
automerge: true,
},
{
// disallow automerge for allowBuilds dependencies (native builds need manual verification)
// disallow automerge for trustedDependencies (native builds need manual verification)
matchPackageNames: [
"@tree-sitter-grammars/tree-sitter-yaml",
"bcrypt",
"better-sqlite3",
"cpu-features",
"esbuild",
"sharp",
@@ -37,17 +31,11 @@
automerge: false,
},
],
// Updates lockfile for prebuilt-debian after if package.json is updated, otherwise only root lockfile is updated
postUpgradeTasks: {
commands: ["cd deployments/prebuilt-debian && pnpm install --ignore-workspace --lockfile-only"],
fileFilters: ["deployments/prebuilt-debian/pnpm-lock.yaml"],
executionMode: "update",
},
updateInternalDeps: true,
rangeStrategy: "bump",
automerge: false,
baseBranchPatterns: ["dev"],
dependencyDashboard: false,
dependencyDashboard: true,
// prevent special characters in branch names (otherwise git errors occur on windows)
branchNameStrict: true,
}
+35
View File
@@ -0,0 +1,35 @@
name: Formatting
on:
pull_request:
types: [opened, synchronize, reopened]
branches: ["**"]
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
format:
name: Check formatting
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
- name: Setup
uses: ./tooling/github/setup
- name: Check changed files
env:
BASE_REF: ${{ github.base_ref }}
run: |
git diff --name-only --diff-filter=ACMR -z "origin/${BASE_REF}...HEAD" -- \
'*.css' '*.js' '*.json' '*.jsonc' '*.jsx' '*.md' '*.mdx' '*.scss' '*.ts' '*.tsx' '*.yaml' '*.yml' \
| xargs -0 -r bun x oxfmt --check --no-error-on-unmatched-pattern --
-35
View File
@@ -1,35 +0,0 @@
name: "[Dependency Updates] Auto Approve"
on:
pull_request:
types: [opened, synchronize]
permissions: {}
jobs:
approve-automatic-prs:
runs-on: ubuntu-latest
timeout-minutes: 2
# 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>
if: github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 || github.actor_id == 175486441
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Obtain token
id: obtainToken
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
permission-pull-requests: write # required to approve pull request
- name: Install GitHub CLI
run: sudo apt-get install -y gh
- name: Approve automatic PRs
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
run: |
gh pr review ${{github.event.pull_request.number}} --approve --body "Automatically approved by GitHub Action"
@@ -0,0 +1,43 @@
name: "[Bots] Reconcile Auto-merge"
on:
schedule:
- cron: "*/30 * * * *"
workflow_dispatch:
permissions: {}
jobs:
reconcile:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Obtain token
id: obtainToken
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
client-id: ${{ vars.RENOVATE_APPROVE_CLIENT_ID }}
permission-pull-requests: write # required to enable auto-merge
- name: Install GitHub CLI
run: sudo apt-get install -y gh
- name: Re-enable auto-merge for prepared bot PRs
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
run: |
# 158783068: homarr-renovate[bot]
# 190541745: homarr-crowdin[bot]
# 210161987: homarr-update-contributors[bot]
# 175441: homarr-releases[bot]
gh pr list --repo "${{ github.repository }}" --base dev --state open --limit 100 \
--json number,author,reviewDecision,mergeStateStatus,autoMergeRequest \
--jq '.[] | select(.author.login as $login | ["app/homarr-renovate", "app/homarr-crowdin", "app/homarr-update-contributors", "app/homarr-releases"] | index($login)) | select(.reviewDecision == "APPROVED") | select(.mergeStateStatus == "CLEAN") | select(.autoMergeRequest == null) | .number' \
| while read -r pr; do
echo "Re-enabling auto-merge on PR #$pr"
gh pr merge "$pr" --auto --squash
done
+47
View File
@@ -0,0 +1,47 @@
name: "[Bots] Approve and Auto-merge"
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
permissions: {}
jobs:
approve-and-automerge:
runs-on: ubuntu-latest
timeout-minutes: 5
# 158783068: homarr-renovate[bot]
# 190541745: homarr-crowdin[bot]
# 210161987: homarr-update-contributors[bot]
# 175441: homarr-releases[bot]
# Extracted from https://api.github.com/users/<username>
if: |
(github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 || github.actor_id == 175441) &&
github.event.pull_request.head.repo.full_name == github.repository &&
github.event.pull_request.base.ref == 'dev'
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Obtain token
id: obtainToken
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
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
run: sudo apt-get install -y gh
- name: Approve and enable auto-merge
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
run: |
PR_NUMBER="${{ github.event.pull_request.number }}"
if [ "$(gh pr view "$PR_NUMBER" --json reviewDecision --jq .reviewDecision)" != "APPROVED" ]; then
gh pr review "$PR_NUMBER" --approve --body "Automatically approved by Homarr bot policy"
fi
if [ "$(gh pr view "$PR_NUMBER" --json autoMergeRequest --jq '.autoMergeRequest != null')" != "true" ]; then
gh pr merge "$PR_NUMBER" --auto --squash
fi
+463
View File
@@ -0,0 +1,463 @@
name: CI
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches: ["**"]
pull_request_target:
types: [closed]
branches: ["**"]
push:
branches: [main, dev, beta, next, v2, release/v2]
merge_group:
release:
types: [published]
workflow_dispatch:
inputs:
operation:
description: Operation to run
required: true
default: validate
type: choice
options:
- validate
- publish
- cleanup
pr_number:
description: PR number for publish or cleanup
required: false
type: number
ref:
description: Git ref to validate or publish
required: false
type: string
permissions:
contents: read
concurrency:
# Every event for a PR shares one group so a close event cancels stale image
# publication and a newer synchronize run cannot be overwritten by an older run.
group: ${{ github.workflow }}-${{ github.event.pull_request.number || inputs.pr_number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' || github.event_name == 'workflow_dispatch' || github.ref != 'refs/heads/main' }}
env:
FORCE_COLOR: 3
SKIP_ENV_VALIDATION: true
TURBO_TELEMETRY_DISABLED: 1
REGISTRY: ghcr.io
PREVIEW_IMAGE: ghcr.io/homarr-labs/homarr-test
jobs:
database-validation:
name: Database validation
if: github.event_name != 'pull_request_target' && (github.event_name != 'release' || startsWith(github.event.release.tag_name, 'v2.')) && (github.event_name != 'workflow_dispatch' || inputs.operation != 'cleanup')
uses: ./.github/workflows/database.yml
fast-gate:
name: Fast gate
needs: database-validation
if: ${{ !cancelled() && github.event_name != 'pull_request_target' && (github.event_name != 'release' || startsWith(github.event.release.tag_name, 'v2.')) && (github.event_name != 'workflow_dispatch' || inputs.operation != 'cleanup') }}
runs-on: ubuntu-latest
timeout-minutes: 20
env:
TURBO_API: ${{ secrets.TURBO_API }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before || 'HEAD~1' }}
TURBO_SCM_HEAD: ${{ github.sha }}
steps:
- name: Require database validation
env:
DATABASE_RESULT: ${{ needs.database-validation.result }}
run: test "$DATABASE_RESULT" = success
- uses: actions/checkout@v7
if: "!env.ACT"
with:
fetch-depth: 0
persist-credentials: false
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
- name: Validate manual inputs
if: github.event_name == 'workflow_dispatch' && inputs.operation == 'publish'
env:
PR_NUMBER: ${{ inputs.pr_number }}
BUILD_REF: ${{ inputs.ref }}
run: |
test -n "$PR_NUMBER" || { echo "pr_number is required for publish"; exit 1; }
test -n "$BUILD_REF" || { echo "ref is required for publish"; exit 1; }
- name: Setup
uses: ./tooling/github/setup
with:
next-cache: "all"
turbo-cache: "true"
- name: Copy environment defaults
run: cp .env.example .env
- name: Lint, typecheck, and build workspaces
run: |
scope=(--affected)
if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then
scope=()
fi
bun run turbo run lint typecheck build "${scope[@]}" --continue=dependencies-successful --concurrency=2
- name: Validate workspace configuration
run: bun run lint:ws
- name: Validate Custom Widget architecture
run: bun run check:custom-widgets
- name: Validate checked-in OpenAPI schema
run: bun run check:openapi
- name: Validate Custom Widget bundle budget
run: |
bun run turbo run build --filter=@homarr/nextjs
bun run check:custom-widget-bundle
container:
name: Container and migration smoke
# Legacy converter verification is opt-in, not a release publication gate.
needs: [fast-gate, preview-amd64]
if: |
!cancelled() && needs.fast-gate.result == 'success' &&
github.event_name == 'workflow_dispatch' && inputs.operation == 'validate'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
packages: read
steps:
- uses: actions/checkout@v7
if: "!env.ACT"
with:
persist-credentials: false
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
- name: Setup
uses: ./tooling/github/setup
- 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-e2e
- name: Set up Docker Buildx
if: needs.preview-amd64.outputs.digest == '' && !env.ACT
uses: docker/setup-buildx-action@v4
- name: Build container once
if: needs.preview-amd64.outputs.digest == '' && !env.ACT
uses: docker/build-push-action@v7
with:
context: .
network: host
platforms: linux/amd64
load: true
push: false
tags: |
homarr-ci
homarr-e2e
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.ref.name=pr-${{ github.event.pull_request.number || inputs.pr_number || 'none' }}
cache-from: type=gha,scope=homarr-e2e
cache-to: type=gha,scope=homarr-e2e,mode=max
secrets: |
TURBO_API=${{ secrets.TURBO_API }}
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
- name: Build container once with Act
if: env.ACT
env:
TURBO_API: ${{ secrets.TURBO_API }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
run: |
bash tooling/github/docker-build.sh --platform linux/amd64 \
--tag homarr-ci --tag homarr-e2e .
- name: Cache Playwright browsers
if: "!env.ACT"
id: playwright-cache
uses: actions/cache@v6
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('bun.lock') }}
- name: Install Playwright Chromium
if: steps.playwright-cache.outputs.cache-hit != 'true' || env.ACT
run: bun x playwright install --with-deps chromium
- name: Install Playwright system deps
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: bun x playwright install-deps chromium
- name: Install standalone converter test dependencies
run: bun install --cwd tools/mysql-to-sqlite --frozen-lockfile
- name: Verify converted database boots and survives restart
env:
NODE_ENV: development
CI: "true"
run: bun x vitest run --project e2e e2e/mysql-conversion.spec.ts --maxWorkers 1
- name: Upload E2E artifacts
if: failure() && !env.ACT
uses: actions/upload-artifact@v7
with:
name: playwright-report
path: e2e/test-results/
retention-days: 7
preview-amd64:
name: Build amd64 preview image
needs: fast-gate
if: |
(
(github.event_name == 'pull_request' && github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository) ||
(github.event_name == 'push' && github.ref == 'refs/heads/release/v2') ||
(github.event_name == 'workflow_dispatch' && inputs.operation == 'publish')
)
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
packages: write
outputs:
digest: ${{ steps.preview-build.outputs.digest }}
steps:
- uses: actions/checkout@v7
if: "!env.ACT"
with:
persist-credentials: false
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
- name: Log in to GHCR
if: "!env.ACT"
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
if: "!env.ACT"
uses: docker/setup-buildx-action@v4
- name: Build and push amd64 preview image by digest
id: preview-build
if: "!env.ACT"
uses: docker/build-push-action@v7
with:
context: .
network: host
platforms: linux/amd64
outputs: type=image,"name=${{ env.PREVIEW_IMAGE }}",push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=homarr-linux-amd64
cache-to: type=gha,scope=homarr-linux-amd64,mode=max
secrets: |
TURBO_API=${{ secrets.TURBO_API }}
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
preview-arm64:
name: Build arm64 preview image
needs: fast-gate
if: |
(
(github.event_name == 'pull_request' && github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository) ||
(github.event_name == 'push' && github.ref == 'refs/heads/release/v2') ||
(github.event_name == 'workflow_dispatch' && inputs.operation == 'publish')
)
runs-on: ubuntu-24.04-arm
timeout-minutes: 20
permissions:
contents: read
packages: write
outputs:
digest: ${{ steps.preview-build.outputs.digest }}
steps:
- uses: actions/checkout@v7
if: "!env.ACT"
with:
persist-credentials: false
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
- name: Log in to GHCR
if: "!env.ACT"
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
if: "!env.ACT"
uses: docker/setup-buildx-action@v4
- name: Build and push arm64 preview image by digest
id: preview-build
if: "!env.ACT"
uses: docker/build-push-action@v7
with:
context: .
network: host
platforms: linux/arm64
outputs: type=image,"name=${{ env.PREVIEW_IMAGE }}",push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=homarr-linux-arm64
cache-to: type=gha,scope=homarr-linux-arm64,mode=max
secrets: |
TURBO_API=${{ secrets.TURBO_API }}
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
publish-preview:
name: Publish multi-platform preview image
needs: [fast-gate, preview-amd64, preview-arm64]
if: |
!cancelled() &&
needs.fast-gate.result == 'success' &&
needs.preview-amd64.result == 'success' &&
needs.preview-arm64.result == 'success' &&
(
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
(github.event_name == 'push' && github.ref == 'refs/heads/release/v2') ||
(github.event_name == 'workflow_dispatch' && inputs.operation == 'publish')
)
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
packages: write
pull-requests: read
env:
PREVIEW_TAG: ${{ github.event_name == 'push' && 'v2' || format('pr-{0}', github.event.pull_request.number || inputs.pr_number) }}
steps:
- name: Log in to GHCR
if: "!env.ACT"
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
if: "!env.ACT"
uses: docker/setup-buildx-action@v4
- name: Verify PR head before publication
if: github.event_name == 'pull_request'
uses: actions/github-script@v8
env:
EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
with:
script: |
const { data: pull } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
});
if (pull.state !== "open") {
core.setFailed(`PR #${pull.number} is ${pull.state}; refusing to publish its preview.`);
return;
}
if (pull.head.sha !== process.env.EXPECTED_HEAD_SHA) {
core.setFailed(`PR #${pull.number} moved from ${process.env.EXPECTED_HEAD_SHA} to ${pull.head.sha}.`);
}
- name: Publish preview manifest
if: "!env.ACT"
env:
AMD64_DIGEST: ${{ needs.preview-amd64.outputs.digest }}
ARM64_DIGEST: ${{ needs.preview-arm64.outputs.digest }}
run: |
tags=(-t "${PREVIEW_IMAGE}:${PREVIEW_TAG}")
docker buildx imagetools create "${tags[@]}" \
"${PREVIEW_IMAGE}@${AMD64_DIGEST}" \
"${PREVIEW_IMAGE}@${ARM64_DIGEST}"
- name: Verify preview manifest platforms
if: "!env.ACT"
run: |
tags=("${PREVIEW_TAG}")
for tag in "${tags[@]}"; do
image="${PREVIEW_IMAGE}:${tag}"
manifest="$(docker buildx imagetools inspect "$image" --raw)"
for architecture in amd64 arm64; do
jq --exit-status --arg architecture "$architecture" \
'any(.manifests[]; .platform.os == "linux" and .platform.architecture == $architecture)' \
<<<"$manifest" >/dev/null
done
docker buildx imagetools inspect "$image"
done
cleanup:
name: Clean up preview image
if: github.event_name == 'pull_request_target' || (github.event_name == 'workflow_dispatch' && inputs.operation == 'cleanup')
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
packages: write
env:
IMAGE_TAG: pr-${{ github.event.pull_request.number || inputs.pr_number }}
steps:
- name: Validate cleanup input
if: github.event_name == 'workflow_dispatch'
env:
PR_NUMBER: ${{ inputs.pr_number }}
run: test -n "$PR_NUMBER" || { echo "pr_number is required for cleanup"; exit 1; }
- name: Delete GHCR package version
if: "!env.ACT"
uses: actions/github-script@v8
with:
script: |
const packageName = "homarr-test";
const org = context.repo.owner;
const tag = process.env.IMAGE_TAG;
const versions = await github.paginate(
github.rest.packages.getAllPackageVersionsForPackageOwnedByOrg,
{ package_type: "container", package_name: packageName, org, per_page: 100 },
);
const target = versions.find((version) =>
version.metadata?.container?.tags?.includes(tag),
);
if (!target) {
core.info(`No package version found with tag "${tag}"`);
return;
}
const targetTags = target.metadata?.container?.tags ?? [];
const extraTags = targetTags.filter((targetTag) => targetTag !== tag);
if (extraTags.length > 0) {
core.warning(
`Package version ${target.id} also carries tag(s) ${extraTags.map((targetTag) => `"${targetTag}"`).join(", ")}; refusing cleanup.`,
);
return;
}
await github.rest.packages.deletePackageVersionForOrg({
package_type: "container",
package_name: packageName,
org,
package_version_id: target.id,
});
core.info(`Deleted package version ${target.id} (${tag})`);
- name: Show Act cleanup result
if: env.ACT
run: echo "Act validated cleanup for ${IMAGE_TAG}; external GHCR writes are disabled locally."
-227
View File
@@ -1,227 +0,0 @@
name: "[Quality] Code Analysis"
permissions:
contents: read
on:
pull_request:
branches: ["*"]
push:
branches: ["main"]
merge_group:
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
env:
FORCE_COLOR: 3
SKIP_ENV_VALIDATION: true
TURBO_API: ${{ secrets.TURBO_API }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha || github.event.before || 'HEAD~1' }}
TURBO_SCM_HEAD: ${{ github.sha }}
jobs:
lint-format-autofix:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.head_ref }}
- name: Setup
uses: ./tooling/github/setup
- name: Get changed files
id: changed
run: |
git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }}...HEAD -- '*.ts' '*.tsx' '*.js' '*.jsx' > /tmp/changed-files
echo "count=$(wc -l < /tmp/changed-files | tr -d '[:space:]')" >> $GITHUB_OUTPUT
- name: Auto-fix lint + format
if: steps.changed.outputs.count != '0'
run: |
xargs -r -a /tmp/changed-files pnpm exec oxlint --fix || true
xargs -r -a /tmp/changed-files pnpm exec oxfmt --write
- name: Commit fixes
uses: stefanzweifel/git-auto-commit-action@v7
with:
commit_message: "style: auto-fix lint and format"
lint:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup
uses: ./tooling/github/setup
- name: Copy env
shell: bash
run: cp .env.example .env
- name: Lint
run: pnpm turbo lint --affected && pnpm lint:ws
format:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup
uses: ./tooling/github/setup
- name: Format
run: pnpm turbo format --affected --continue
typecheck:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup
uses: ./tooling/github/setup
- name: Typecheck
run: pnpm turbo typecheck --affected
test:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- name: Setup
uses: ./tooling/github/setup
- name: Test
run: pnpm test
- name: "Report Coverage"
# Set if: always() to also generate the report if tests are failing
# Only works if you set `reportOnFailure: true` in your vite config as specified above
if: always()
uses: davelosert/vitest-coverage-report-action@v2
e2e:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- name: Setup
uses: ./tooling/github/setup
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Build docker image
id: build-docker-image
uses: docker/build-push-action@v7
with:
platforms: linux/amd64
context: .
push: false
load: true
tags: homarr-e2e
network: host
cache-from: type=gha
cache-to: type=gha,mode=max
env:
SKIP_ENV_VALIDATION: true
- name: Install playwright browsers
run: pnpm exec playwright install chromium
- name: Run E2E Tests
shell: bash
run: pnpm test:e2e
build:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup
uses: ./tooling/github/setup
- name: Copy env
shell: bash
run: cp .env.example .env
- name: Build
run: pnpm turbo build --affected
memory-regression:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Build Docker image
uses: docker/build-push-action@v7
with:
platforms: linux/amd64
context: .
push: false
load: true
tags: homarr-memory-test
network: host
cache-from: type=gha
cache-to: type=gha,mode=max
env:
SKIP_ENV_VALIDATION: true
- name: Start container and measure memory
run: |
docker run -d --name homarr-mem-test -p 17575:7575 homarr-memory-test
# Wait for container to be healthy (up to 60s)
healthy=false
for i in $(seq 1 30); do
if curl -sf http://localhost:17575/api/health 2>/dev/null; then
echo "Container healthy after $((i * 2))s"
healthy=true
break
fi
sleep 2
done
if [ "$healthy" != "true" ]; then
echo "ERROR: Container never became healthy"
docker logs homarr-mem-test --tail 50
exit 1
fi
# Warm up: hit the dashboard page a few times
for i in $(seq 1 5); do
curl -sf http://localhost:17575/ > /dev/null 2>&1 || true
sleep 2
done
# Measure RSS after warmup
MEM_KB=$(docker stats --no-stream --format '{{.MemUsage}}' homarr-mem-test | sed 's| /.*||')
echo "Raw memory: $MEM_KB"
# Convert to numeric MB (handles GiB and MiB suffixes)
MEM_NUM=$(echo "$MEM_KB" | awk '{
if ($0 ~ /GiB/) { gsub(/GiB/,""); print $1 * 1024 }
else if ($0 ~ /MiB/) { gsub(/MiB/,""); print $1 }
else if ($0 ~ /KiB/) { gsub(/KiB/,""); print $1 / 1024 }
else { print $1 / 1048576 }
}')
echo "Memory usage: ${MEM_NUM} MB"
MAX_MB=700
if awk "BEGIN {exit !($MEM_NUM > $MAX_MB)}"; then
echo "ERROR: Memory usage ${MEM_NUM} MB exceeds ${MAX_MB} MB threshold"
docker logs homarr-mem-test --tail 50
exit 1
fi
echo "Memory check passed: ${MEM_NUM} MB <= ${MAX_MB} MB"
@@ -15,14 +15,14 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Obtain token
id: obtainToken
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
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Upload Crowdin translations
uses: crowdin/github-action@v2
+103
View File
@@ -0,0 +1,103 @@
name: Database migrations
on:
workflow_call:
workflow_dispatch:
permissions:
contents: read
env:
NODE_ENV: development
CI: "true"
SKIP_ENV_VALIDATION: "true"
TURBO_TELEMETRY_DISABLED: "1"
jobs:
changes:
runs-on: ubuntu-latest
outputs:
migrations: ${{ steps.select.outputs.migrations }}
steps:
- uses: actions/checkout@v7
if: "!env.ACT"
with:
fetch-depth: 0
persist-credentials: false
- name: Select database checks
id: select
env:
EVENT_NAME: ${{ github.event_name }}
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before }}
shell: bash
run: |
migrations=false
if [[ "$EVENT_NAME" != pull_request && "$EVENT_NAME" != merge_group && "$EVENT_NAME" != push ]] ||
[[ -z "$BASE_SHA" || "$BASE_SHA" =~ ^0+$ ]] ||
! git cat-file -e "$BASE_SHA^{commit}" 2>/dev/null; then
migrations=true
else
changed_files=$(mktemp)
trap 'rm -f "$changed_files"' EXIT
comparison="$BASE_SHA...HEAD"
if [[ "$EVENT_NAME" = push ]]; then comparison="$BASE_SHA..HEAD"; fi
git diff --name-only -z "$comparison" > "$changed_files"
while IFS= read -r -d '' file; do
case "$file" in
packages/db/*|packages/core/src/infrastructure/db/*|packages/core/src/infrastructure/certificates/*|packages/auth/*|packages/api/src/router/onboard*|packages/api/src/router/test/onboard*|packages/onboarding/*|apps/nextjs/src/app/api/backup/*|apps/nextjs/src/app/*/init/*|Dockerfile|scripts/run.sh|scripts/entrypoint.sh|deployments/*|package.json|packages/core/package.json|packages/common/package.json|packages/definitions/package.json|packages/server-settings/package.json|packages/validation/package.json|mise.toml|bun.lock|bunfig.toml|patches/*|vitest*|tooling/github/*|.github/workflows/database.yml|.github/workflows/ci.yml)
migrations=true
;;
esac
done < "$changed_files"
fi
echo "migrations=$migrations" >> "$GITHUB_OUTPUT"
journals:
name: Migration journals
needs: changes
if: needs.changes.outputs.migrations == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
if: "!env.ACT"
with:
persist-credentials: false
- uses: ./tooling/github/setup
- run: bun x vitest run --project db-node packages/db/test/migration-journal.spec.ts
migrations:
name: SQLite and PostgreSQL migrations
needs: changes
if: needs.changes.outputs.migrations == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
if: "!env.ACT"
with:
persist-credentials: false
- uses: ./tooling/github/setup
- name: Reject retired database configuration
run: bun x vitest run --project dom packages/core/src/infrastructure/db/env.spec.ts
- run: bun x vitest run --project db-node --project integration packages/db/test/sqlite-migration.spec.ts packages/db/test/postgresql-migration.spec.ts packages/db/proxy-reader.integration.spec.ts --maxWorkers 1
database-gate:
name: Database gate
needs: [changes, journals, migrations]
if: always()
runs-on: ubuntu-latest
steps:
- name: Require selected database checks
env:
CHANGES: ${{ needs.changes.result }}
JOURNALS: ${{ needs.journals.result }}
MIGRATIONS: ${{ needs.migrations.result }}
RUN_MIGRATIONS: ${{ needs.changes.outputs.migrations }}
run: |
test "$CHANGES" = success
test "$RUN_MIGRATIONS" = true || test "$RUN_MIGRATIONS" = false
if [ "$RUN_MIGRATIONS" = true ]; then
test "$JOURNALS" = success
test "$MIGRATIONS" = success
fi
+22 -11
View File
@@ -16,17 +16,17 @@ 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
with:
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@v6
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Get Next Version
@@ -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
+91 -28
View File
@@ -13,7 +13,7 @@ on:
type: boolean
required: false
default: true
description: Send notifications
description: Send notifications when redeploying an existing release
permissions:
contents: write # Required to update package.json version
@@ -38,10 +38,11 @@ jobs:
SKIP_RELEASE: ${{ github.event_name == 'workflow_dispatch' || github.ref_name == 'dev' || github.ref_name == 'next' }}
outputs:
version: ${{ steps.read-semver.outputs.version || steps.version-fallback.outputs.version }}
git_ref: ${{ steps.read-git-ref.outputs.ref || github.ref }}
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 "Skipping release for workflow_dispatch event"
- run: echo "Manual dispatch redeploys the latest existing release without creating a new one"
if: env.SKIP_RELEASE == 'true'
# The below generated version fallback represents a normalized branch name, for example "feature/branch-name" -> "feature-branch-name"
- run: echo "version="$(echo ${{github.ref_name}} | sed 's/[^a-zA-Z0-9\-]/-/g') >> "$GITHUB_OUTPUT"
@@ -54,26 +55,41 @@ 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@v6
if: env.SKIP_RELEASE == 'false'
- uses: actions/checkout@v7
if: env.SKIP_RELEASE == 'false' || github.ref_name == 'main' || github.ref_name == 'beta'
with:
fetch-depth: 0
persist-credentials: false
- uses: pnpm/action-setup@v6
if: env.SKIP_RELEASE == 'false'
- uses: actions/setup-node@v6
- uses: oven-sh/setup-bun@v2
if: env.SKIP_RELEASE == 'false'
with:
node-version: 24.18.0
package-manager-cache: false
- run: npm i -g pnpm
if: env.SKIP_RELEASE == 'false'
- name: Install dependencies
bun-version: 1.4.2
- name: Verify major release intent
if: env.SKIP_RELEASE == 'false'
run: |
pnpm install
latest_tag="$(git describe --tags --abbrev=0)"
latest_major="${latest_tag#v}"
latest_major="${latest_major%%.*}"
package_version="$(jq --raw-output '.version' package.json)"
package_major="${package_version%%.*}"
if (( package_major <= latest_major )); then
exit 0
fi
if git log --format='%s%n%b' "${latest_tag}..HEAD" \
| grep --extended-regexp --quiet '(^[[:alnum:]-]+(\([^)]*\))?!:)|(^BREAKING[ -]CHANGE:)'; then
exit 0
fi
echo "::error::package.json declares ${package_version}, but commits since ${latest_tag} contain no breaking-change signal. Use a conventional ! commit or BREAKING CHANGE footer so Semantic Release creates the intended major version."
exit 1
- name: Install dependencies
if: env.SKIP_RELEASE == 'false'
run: bun install --frozen-lockfile
- name: Run Semantic Release
if: env.SKIP_RELEASE == 'false'
env:
@@ -83,7 +99,7 @@ jobs:
GIT_COMMITTER_NAME: "Releases Homarr"
GIT_COMMITTER_EMAIL: "175486441+homarr-releases[bot]@users.noreply.github.com"
run: |
pnpm release
bun run release
- name: Read semver output
# We read the last tag either from the created release or from the current branch, this is to rerun the deployment job for the currently released version when it failed
if: env.SKIP_RELEASE == 'false' || github.ref_name == 'main' || github.ref_name == 'beta'
@@ -97,20 +113,26 @@ 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' }}
RELEASE_REF: ${{ steps.read-git-ref.outputs.ref }}
run: |
git config user.name "Releases Homarr"
git config user.email "175486441+homarr-releases[bot]@users.noreply.github.com"
git remote set-url origin https://x-access-token:${{ steps.obtainToken.outputs.token }}@github.com/${{ github.repository }}.git
git fetch origin ${{ env.UPSTREAM_BRANCH }}
git checkout ${{ env.UPSTREAM_BRANCH }}
git pull origin ${{ env.UPSTREAM_BRANCH }}
git rebase ${{ github.ref_name }}
git push origin ${{ env.UPSTREAM_BRANCH }}
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
git fetch origin "$UPSTREAM_BRANCH"
# Dependency installation can modify generated files in the release checkout.
sync_dir="$(mktemp -d "${RUNNER_TEMP}/homarr-release-sync.XXXXXX")"
trap 'git worktree remove --force "$sync_dir"' EXIT
git worktree add --detach "$sync_dir" FETCH_HEAD
git -C "$sync_dir" merge --no-edit "$RELEASE_REF"
git -C "$sync_dir" push origin "HEAD:${UPSTREAM_BRANCH}"
build-amd64:
name: Build docker image for amd64
needs: release
@@ -119,7 +141,7 @@ jobs:
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
ref: ${{ needs.release.outputs.git_ref }}
@@ -148,6 +170,11 @@ jobs:
platforms: linux/amd64
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true
secrets: |
TURBO_API=${{ secrets.TURBO_API }}
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
env:
SKIP_ENV_VALIDATION: true
build-arm64:
@@ -158,7 +185,7 @@ jobs:
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
ref: ${{ needs.release.outputs.git_ref }}
@@ -187,6 +214,11 @@ jobs:
platforms: linux/arm64
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true
secrets: |
TURBO_API=${{ secrets.TURBO_API }}
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
env:
SKIP_ENV_VALIDATION: true
extract-asset-amd64:
@@ -195,9 +227,14 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v7
if: needs.release.outputs.skipped == 'false'
with:
ref: ${{ needs.release.outputs.git_ref }}
persist-credentials: false
- name: Extract amd64
if: needs.release.outputs.skipped == 'false'
uses: homarr-labs/homarr/.github/actions/extract-build-artifact@dev
uses: ./.github/actions/extract-build-artifact
with:
digest: "${{ env.GHCR_REPO }}@${{ needs.build-amd64.outputs.digest }}"
architecture: amd64
@@ -211,9 +248,14 @@ jobs:
runs-on: ubuntu-24.04-arm
timeout-minutes: 5
steps:
- uses: actions/checkout@v7
if: needs.release.outputs.skipped == 'false'
with:
ref: ${{ needs.release.outputs.git_ref }}
persist-credentials: false
- name: Extract arm64
if: needs.release.outputs.skipped == 'false'
uses: homarr-labs/homarr/.github/actions/extract-build-artifact@dev
uses: ./.github/actions/extract-build-artifact
with:
digest: "${{ env.GHCR_REPO }}@${{ needs.build-arm64.outputs.digest }}"
architecture: arm64
@@ -236,7 +278,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
@@ -273,9 +315,30 @@ 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:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
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 }})"
+18 -4
View File
@@ -5,7 +5,13 @@ on:
branches: ["dev"]
paths:
- "apps/docs/**"
- "packages/definitions/src/docs/**"
- "packages/**"
- "tooling/**"
- "tools/motion-reel/three/**"
- "package.json"
- "bun.lock"
- "bunfig.toml"
- "turbo.json"
- ".github/workflows/deployment-docs.yml"
workflow_dispatch:
@@ -30,19 +36,27 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Setup
uses: ./tooling/github/setup
with:
next-cache: "docs"
turbo-cache: "true"
- name: Build docs
run: pnpm turbo build --filter=@homarr/docs
run: bun run turbo run build --filter=@homarr/docs
env:
CI: "true"
- name: Validate exported links and search
run: |
bun run --filter @homarr/docs validate:links
bun run --filter @homarr/docs verify:search
bun run --filter @homarr/docs verify:seo
- name: Setup Pages
uses: actions/configure-pages@v6
- name: Upload pages artifact
uses: actions/upload-pages-artifact@v5
with:
path: "apps/docs/build"
path: "apps/docs/out"
deploy:
runs-on: ubuntu-latest
+32 -12
View File
@@ -19,17 +19,17 @@ 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
with:
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,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
@@ -1,58 +0,0 @@
name: "[Docs] Algolia Recrawl"
on:
workflow_run:
workflows: ["[Deployment] Documentation"]
types: [completed]
branches: [dev]
workflow_dispatch:
permissions:
contents: read
jobs:
recrawl:
if: >-
${{
(github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event != 'pull_request')
|| github.event_name == 'workflow_dispatch'
}}
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- name: Setup
uses: ./tooling/github/setup
- name: Check Algolia crawler secrets
run: |
missing=0
for secret in ALGOLIA_CRAWLER_ID ALGOLIA_CRAWLER_USER_ID ALGOLIA_CRAWLER_API_KEY; do
if [ -z "${!secret}" ]; then
echo "::warning::Missing repository secret: $secret"
missing=1
fi
done
if [ "$missing" -eq 1 ]; then
echo "Add crawler secrets to enable automatic recrawls."
exit 1
fi
env:
ALGOLIA_CRAWLER_ID: ${{ secrets.ALGOLIA_CRAWLER_ID }}
ALGOLIA_CRAWLER_USER_ID: ${{ secrets.ALGOLIA_CRAWLER_USER_ID }}
ALGOLIA_CRAWLER_API_KEY: ${{ secrets.ALGOLIA_CRAWLER_API_KEY }}
- name: Wait for GitHub Pages to serve the new deployment
run: sleep 60
- name: Trigger Algolia DocSearch recrawl
uses: cssnr/algolia-crawler-action@v2
with:
crawler_id: ${{ secrets.ALGOLIA_CRAWLER_ID }}
crawler_user_id: ${{ secrets.ALGOLIA_CRAWLER_USER_ID }}
crawler_api_key: ${{ secrets.ALGOLIA_CRAWLER_API_KEY }}
- name: Verify search index
working-directory: apps/docs
run: pnpm verify:search
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
ref: dev
@@ -28,12 +28,12 @@ jobs:
- name: Dump contributors
working-directory: apps/docs
run: pnpm api-dump-contributors
run: bun run api-dump-contributors
- name: Commit updated contributors
uses: EndBug/add-and-commit@v10
with:
add: "apps/docs/static"
add: "apps/docs/public"
default_author: github_actor
message: "chore(docs): update contributors"
push: true
+4 -32
View File
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
ref: dev
@@ -26,41 +26,13 @@ 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: bun run scripts:update-openapi
- name: Commit updated spec
uses: EndBug/add-and-commit@v10
with:
add: "apps/docs/static/api/"
add: "apps/docs/public/api/"
default_author: github_actor
message: "chore(docs): update OpenAPI spec"
push: true
@@ -5,7 +5,7 @@ permissions:
on:
pull_request:
branches: ["*"]
branches: ["**"]
paths: [".github/actions/prebuilt-debian/**", "deployments/prebuilt-debian/**"]
jobs:
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Validate prebuilt dependencies for amd64
id: validate-amd64
uses: ./.github/actions/prebuilt-debian
@@ -30,7 +30,7 @@ jobs:
runs-on: ubuntu-24.04-arm
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Validate prebuilt dependencies for arm64
id: validate-arm64
uses: ./.github/actions/prebuilt-debian
+10 -3
View File
@@ -5,15 +5,22 @@ permissions:
on:
pull_request:
branches: ["*"]
branches: ["**"]
paths: [".github/renovate.json5"]
jobs:
renovate-validate:
runs-on: ubuntu-latest
timeout-minutes: 2
env:
# The optional RE2 addon uses V8 APIs; validation can use JavaScript regexes.
RENOVATE_X_IGNORE_RE2: "true"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
if: "!env.ACT"
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.4.2
- run: |
npx --yes --package renovate -- \
bun x --package renovate \
renovate-config-validator --strict .github/renovate.json5
+3 -36
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,17 +15,17 @@ 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
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
token: ${{ steps.obtainToken.outputs.token }}
ref: dev
- name: Setup
uses: ./tooling/github/setup
- run: pnpm run scripts:update-bug-report-template
- run: bun run scripts:update-bug-report-template
env:
NEXT_VERSION: ${{ github.event.release.tag_name }}
@@ -1,5 +1,6 @@
permissions:
statuses: write
contents: read
on:
pull_request:
@@ -16,12 +17,20 @@ jobs:
env:
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: |
gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
/repos/${{github.repository}}/statuses/${{github.event.pull_request.head.sha}} \
-f 'state=success' \
-f 'target_url=https://docs.renovatebot.com/configuration-options#minimumreleaseage' \
-f 'description=Skipped for non renovate branches' \
-f 'context=renovate/stability-days'
for attempt in 1 2 3 4 5; do
if gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
/repos/${{github.repository}}/statuses/${{github.event.pull_request.head.sha}} \
-f 'state=success' \
-f 'target_url=https://docs.renovatebot.com/configuration-options#minimumreleaseage' \
-f 'description=Skipped for non renovate branches' \
-f 'context=renovate/stability-days' \
--silent; then
exit 0
fi
echo "status POST failed (attempt $attempt/5), retrying in 5s"
sleep 5
done
exit 1
+5 -12
View File
@@ -15,33 +15,26 @@ jobs:
update-contributors:
runs-on: ubuntu-latest
timeout-minutes: 2
strategy:
matrix:
node-version: [22]
steps:
- name: Obtain token
id: obtainToken
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 repository
uses: actions/checkout@v6
uses: actions/checkout@v7
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
- uses: pnpm/action-setup@v6
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
- uses: oven-sh/setup-bun@v2
with:
node-version: ${{ matrix.node-version }}
cache: "pnpm"
bun-version: 1.4.2
- name: Run update script
run: node ./scripts/update-contributors.mjs
run: bun ./scripts/update-contributors.mjs
- name: Commit changes
env:
@@ -24,16 +24,16 @@ 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
uses: actions/checkout@v6
uses: actions/checkout@v7
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
- name: Setup
uses: ./tooling/github/setup
- run: pnpm run scripts:update-readme-integrations
- run: bun run scripts:update-readme-integrations
- name: Commit changes
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
+65
View File
@@ -0,0 +1,65 @@
name: Validate documentation
on:
pull_request:
paths:
- "apps/docs/**"
- "apps/nextjs/src/app/api/**"
- "packages/definitions/src/**"
- "packages/widgets/src/**"
- "packages/custom-widgets/src/**"
- "packages/ui/src/**"
- "packages/workshop/src/**"
- "packages/api/src/**"
- "packages/auth/src/**"
- "packages/integrations/src/**"
- "tools/motion-reel/three/**"
- "tooling/**"
- "bun.lock"
- "package.json"
- "bunfig.toml"
- "turbo.json"
- ".github/workflows/deployment-docs.yml"
- ".github/workflows/validate-docs.yml"
permissions:
contents: read
concurrency:
group: validate-docs-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
docs:
name: Build and validate docs
runs-on: ubuntu-latest
timeout-minutes: 15
env:
SKIP_ENV_VALIDATION: true
TURBO_API: ${{ secrets.TURBO_API }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
steps:
- uses: actions/checkout@v7
if: "!env.ACT"
with:
persist-credentials: false
- name: Setup
uses: ./tooling/github/setup
with:
next-cache: "docs"
turbo-cache: "true"
- name: Validate the published API schema against the application
run: bun run check:openapi
- name: Validate browser access and API schema rendering
run: bun x vitest run 'apps/nextjs/src/app/api/[...trpc]/route.spec.ts' apps/docs/src/lib/openapi.spec.ts
- name: Build docs
run: bun run turbo run build --filter=@homarr/docs
env:
CI: "true"
- name: Validate exported links, search, and metadata
run: |
bun run --filter @homarr/docs validate:links
bun run --filter @homarr/docs verify:search
bun run --filter @homarr/docs verify:seo
+247
View File
@@ -0,0 +1,247 @@
name: Workshop
on:
pull_request:
paths:
- ".github/workflows/workshop.yml"
- "tooling/github/**"
- "apps/workshop/**"
- "apps/docs/**"
- "packages/common/**"
- "packages/core/**"
- "packages/custom-widgets/**"
- "packages/definitions/**"
- "packages/db/**"
- "packages/server-settings/**"
- "packages/settings/**"
- "packages/translation/**"
- "packages/ui/**"
- "packages/validation/**"
- "packages/workshop/**"
- "tooling/typescript/**"
- "patches/**"
- "package.json"
- "bun.lock"
- "bunfig.toml"
- "turbo.json"
push:
branches: [dev, v2, release/v2, main]
paths:
- ".github/workflows/workshop.yml"
- "tooling/github/**"
- "apps/workshop/**"
- "apps/docs/**"
- "packages/common/**"
- "packages/core/**"
- "packages/custom-widgets/**"
- "packages/definitions/**"
- "packages/db/**"
- "packages/server-settings/**"
- "packages/settings/**"
- "packages/translation/**"
- "packages/ui/**"
- "packages/validation/**"
- "packages/workshop/**"
- "tooling/typescript/**"
- "patches/**"
- "package.json"
- "bun.lock"
- "bunfig.toml"
- "turbo.json"
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ (github.ref_name == 'v2' || github.ref_name == 'release/v2') && 'v2-preview' || github.ref }}
cancel-in-progress: true
jobs:
production-smoke:
if: github.event_name == 'pull_request'
name: Production image smoke (${{ matrix.arch }})
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ubuntu-24.04
- arch: arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
if: "!env.ACT"
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version: 24.18.0
- name: Set up Docker Buildx
if: "!env.ACT"
uses: docker/setup-buildx-action@v4
- name: Build the production image
if: "!env.ACT"
uses: docker/build-push-action@v7
with:
context: .
file: apps/workshop/Dockerfile
target: production
platforms: linux/${{ matrix.arch }}
load: true
tags: homarr-workshop:smoke
cache-from: type=gha,scope=workshop-linux-${{ matrix.arch }}
cache-to: type=gha,scope=workshop-linux-${{ matrix.arch }},mode=max
secrets: |
TURBO_API=${{ secrets.TURBO_API }}
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
- name: Exercise the production image
if: "!env.ACT"
env:
WORKSHOP_IMAGE_TEST_IMAGE: homarr-workshop:smoke
run: sh apps/workshop/test-image.sh
- name: Build and exercise the production image with Act
if: env.ACT
env:
TURBO_API: ${{ secrets.TURBO_API }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
run: sh apps/workshop/test-image.sh
validate:
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: actions/checkout@v7
if: "!env.ACT"
- run: docker compose --env-file apps/workshop/.env.staging.example -f apps/workshop/docker-compose.staging.yml config --quiet
publish-amd64:
if: github.event_name == 'push' && (github.ref_name == 'v2' || github.ref_name == 'release/v2' || github.ref_name == 'main')
needs: validate
runs-on: ubuntu-24.04
timeout-minutes: 20
permissions:
contents: read
packages: write
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v7
if: "!env.ACT"
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v7
id: build
with:
context: .
file: apps/workshop/Dockerfile
target: production
platforms: linux/amd64
outputs: type=image,"name=ghcr.io/homarr-labs/workshop",push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=workshop-linux-amd64
cache-to: type=gha,scope=workshop-linux-amd64,mode=max
secrets: |
TURBO_API=${{ secrets.TURBO_API }}
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
publish-arm64:
if: github.event_name == 'push' && (github.ref_name == 'v2' || github.ref_name == 'release/v2' || github.ref_name == 'main')
needs: validate
runs-on: ubuntu-24.04-arm
timeout-minutes: 20
permissions:
contents: read
packages: write
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v7
if: "!env.ACT"
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v7
id: build
with:
context: .
file: apps/workshop/Dockerfile
target: production
platforms: linux/arm64
outputs: type=image,"name=ghcr.io/homarr-labs/workshop",push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=workshop-linux-arm64
cache-to: type=gha,scope=workshop-linux-arm64,mode=max
secrets: |
TURBO_API=${{ secrets.TURBO_API }}
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
publish:
if: github.event_name == 'push' && (github.ref_name == 'v2' || github.ref_name == 'release/v2' || github.ref_name == 'main')
needs: [publish-amd64, publish-arm64]
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
packages: write
env:
WORKSHOP_IMAGE: ghcr.io/homarr-labs/workshop
steps:
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Verify branch head before publication
uses: actions/github-script@v8
with:
script: |
const branchName = context.ref.replace("refs/heads/", "");
const { data: branch } = await github.rest.repos.getBranch({
owner: context.repo.owner,
repo: context.repo.repo,
branch: branchName,
});
if (branch.commit.sha !== context.sha) {
core.setFailed(`Branch ${branchName} moved from ${context.sha} to ${branch.commit.sha}.`);
}
- name: Publish multi-platform image
env:
AMD64_DIGEST: ${{ needs.publish-amd64.outputs.digest }}
ARM64_DIGEST: ${{ needs.publish-arm64.outputs.digest }}
run: |
tags=(-t "${WORKSHOP_IMAGE}:sha-${GITHUB_SHA}")
if [[ "${GITHUB_REF_NAME}" == "v2" || "${GITHUB_REF_NAME}" == "release/v2" ]]; then
tags+=(-t "${WORKSHOP_IMAGE}:v2")
fi
if [[ "${GITHUB_REF_NAME}" == "main" ]]; then
tags+=(-t "${WORKSHOP_IMAGE}:latest")
fi
docker buildx imagetools create "${tags[@]}" \
"${WORKSHOP_IMAGE}@${AMD64_DIGEST}" \
"${WORKSHOP_IMAGE}@${ARM64_DIGEST}"
- name: Verify multi-platform image
run: |
image="${WORKSHOP_IMAGE}:sha-${GITHUB_SHA}"
manifest="$(docker buildx imagetools inspect "$image" --raw)"
for architecture in amd64 arm64; do
jq --exit-status --arg architecture "$architecture" \
'any(.manifests[]; .platform.os == "linux" and .platform.architecture == $architecture)' \
<<<"$manifest" >/dev/null
done
docker buildx imagetools inspect "$image"
+20 -4
View File
@@ -2,7 +2,7 @@
# dependencies
node_modules
.pnpm-store/
.bun-cache/
.pnp
.pnp.js
.idea/
@@ -16,6 +16,7 @@ out/
next-env.d.ts
# artifacts
.ai-evaluations/
packages/db/migrations/*/migrate.cjs
# nitro
@@ -24,6 +25,8 @@ packages/db/migrations/*/migrate.cjs
# production
build
tools/homarr-dev/homarr
tools/homarr-dev/homarr-dev
# misc
.DS_Store
@@ -33,7 +36,7 @@ build
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
bun-debug.log*
# local env files
.env
@@ -71,10 +74,23 @@ apps/nextjs/public/images/background.png
# personal agent config
.opencode
.agents/
.agents/*
!.agents/skills/
skills-lock.json
# docusaurus
apps/docs/.docusaurus
apps/docs/build
.pnpm-store
.bun-cache
benchmark-results
# local performance investigation (never ship benchmark data or restored backups)
.widget-performance/
.bench/
homarr-backup*.zip
*.sqlite-wal
*.sqlite-shm
/scripts/benchmarks/
# PR description screenshots belong on the pull request, not in the repository
.screenshots/
+8
View File
@@ -0,0 +1,8 @@
{
"mcpServers": {
"mantine": {
"command": "bun",
"args": ["x", "@mantine/mcp-server@9.6.0"]
}
}
}
-2
View File
@@ -1,2 +0,0 @@
[tools]
node = "24.18.0"
-2
View File
@@ -1,2 +0,0 @@
# pnpm v11: non-auth settings moved to pnpm-workspace.yaml
# Keep this file for any future registry/auth configuration
+16 -1
View File
@@ -3,5 +3,20 @@
"printWidth": 120,
"endOfLine": "lf",
"sortPackageJson": true,
"ignorePatterns": ["**/*.cjs", "**/lang/**", "static-data/**", "CHANGELOG.md", "apps/docs/.docusaurus/**", "apps/docs/build/**", "apps/docs/blog/**"]
"ignorePatterns": [
"**/*.cjs",
"**/lang/**",
"static-data/**",
"CHANGELOG.md",
"apps/docs/.next/**",
"apps/docs/out/**",
"apps/docs/blog/2022/**",
"apps/docs/blog/2023/**",
"apps/docs/blog/2024/**",
"apps/docs/blog/2025/**",
"apps/docs/blog/authors.yml",
"apps/docs/public/api/open-api-schema.json",
"packages/custom-widgets/src/core/component-catalog.generated.json",
"apps/docs/public/custom-widgets/component-catalog-v1.json"
]
}
+29 -1
View File
@@ -30,7 +30,35 @@
"options": {
"reportUnusedDisableDirectives": "off"
},
"ignorePatterns": ["**/*.config.js", "**/*.config.mjs", "**/*.cjs", "**/lang/*.d.json.ts", ".next/**", "apps/docs/.docusaurus/**", "apps/docs/build/**"],
"overrides": [
{
"files": ["apps/nextjs/src/**/*.ts", "apps/nextjs/src/**/*.tsx"],
"excludeFiles": ["**/*.spec.ts", "**/*.spec.tsx"],
"rules": {
"eslint/no-restricted-imports": [
"error",
{
"paths": [
{
"name": "@homarr/widgets",
"allowTypeImports": true,
"message": "Import a narrow @homarr/widgets export to preserve widget lazy loading."
}
]
}
]
}
}
],
"ignorePatterns": [
"**/*.config.js",
"**/*.config.mjs",
"**/*.cjs",
"**/lang/*.d.json.ts",
".next/**",
"apps/docs/.docusaurus/**",
"apps/docs/build/**"
],
"settings": {
"react": { "version": "19" }
}
+2 -7
View File
@@ -26,16 +26,11 @@
"changelogFile": "CHANGELOG.md"
}
],
[
"@semantic-release/npm",
{
"npmPublish": false
}
],
"./scripts/release-version.mjs",
[
"@semantic-release/git",
{
"assets": ["package.json", "CHANGELOG.md"],
"assets": ["package.json", "bun.lock", "CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version} [skip ci]"
}
],
-13
View File
@@ -1,13 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="db:migration:mysql:generate" type="js.build_tools.npm">
<package-json value="$PROJECT_DIR$/package.json" />
<command value="run" />
<scripts>
<script value="db:migration:mysql:generate" />
</scripts>
<node-interpreter value="project" />
<package-manager value="pnpm" />
<envs />
<method v="2" />
</configuration>
</component>
@@ -6,7 +6,7 @@
<script value="db:migration:postgresql:generate" />
</scripts>
<node-interpreter value="project" />
<package-manager value="pnpm" />
<package-manager value="bun" />
<envs />
<method v="2" />
</configuration>
+1 -1
View File
@@ -6,7 +6,7 @@
<script value="db:migration:sqlite:generate" />
</scripts>
<node-interpreter value="project" />
<package-manager value="pnpm" />
<package-manager value="bun" />
<envs />
<method v="2" />
</configuration>
+1 -1
View File
@@ -6,7 +6,7 @@
<script value="db:push" />
</scripts>
<node-interpreter value="project" />
<package-manager value="pnpm" />
<package-manager value="bun" />
<envs />
<method v="2" />
</configuration>
+1 -1
View File
@@ -6,7 +6,7 @@
<script value="db:studio" />
</scripts>
<node-interpreter value="project" />
<package-manager value="pnpm" />
<package-manager value="bun" />
<envs />
<method v="2" />
</configuration>
+1 -1
View File
@@ -6,7 +6,7 @@
<script value="dev" />
</scripts>
<node-interpreter value="project" />
<package-manager value="pnpm" />
<package-manager value="bun" />
<envs />
<method v="2" />
</configuration>
+1 -1
View File
@@ -6,7 +6,7 @@
<script value="docker:dev" />
</scripts>
<node-interpreter value="project" />
<package-manager value="pnpm" />
<package-manager value="bun" />
<envs />
<method v="2" />
</configuration>
+1 -1
View File
@@ -6,7 +6,7 @@
<script value="format" />
</scripts>
<node-interpreter value="project" />
<package-manager value="pnpm" />
<package-manager value="bun" />
<envs />
<method v="2" />
</configuration>
+1 -1
View File
@@ -6,7 +6,7 @@
<script value="format:fix" />
</scripts>
<node-interpreter value="project" />
<package-manager value="pnpm" />
<package-manager value="bun" />
<envs />
<method v="2" />
</configuration>
+1 -1
View File
@@ -6,7 +6,7 @@
<script value="test" />
</scripts>
<node-interpreter value="project" />
<package-manager value="pnpm" />
<package-manager value="bun" />
<envs />
<method v="2" />
</configuration>
+1 -1
View File
@@ -6,7 +6,7 @@
<script value="test:ui" />
</scripts>
<node-interpreter value="project" />
<package-manager value="pnpm" />
<package-manager value="bun" />
<envs />
<method v="2" />
</configuration>
+1 -1
View File
@@ -6,7 +6,7 @@
<script value="typecheck" />
</scripts>
<node-interpreter value="project" />
<package-manager value="pnpm" />
<package-manager value="bun" />
<envs />
<method v="2" />
</configuration>
+9 -9
View File
@@ -5,7 +5,7 @@
"name": "dev",
"type": "node-terminal",
"request": "launch",
"command": "pnpm dev",
"command": "bun run dev",
"cwd": "${workspaceFolder}",
"skipFiles": ["<node_internals>/**"]
},
@@ -13,7 +13,7 @@
"name": "docker dev",
"type": "node-terminal",
"request": "launch",
"command": "pnpm docker:dev",
"command": "bun run docker:dev",
"cwd": "${workspaceFolder}",
"skipFiles": ["<node_internals>/**"]
},
@@ -21,7 +21,7 @@
"name": "lint fix",
"type": "node-terminal",
"request": "launch",
"command": "pnpm lint:fix",
"command": "bun run lint:fix",
"cwd": "${workspaceFolder}",
"skipFiles": ["<node_internals>/**"]
},
@@ -29,7 +29,7 @@
"name": "format fix",
"type": "node-terminal",
"request": "launch",
"command": "pnpm format:fix",
"command": "bun run format:fix",
"cwd": "${workspaceFolder}",
"skipFiles": ["<node_internals>/**"]
},
@@ -37,7 +37,7 @@
"name": "db push",
"type": "node-terminal",
"request": "launch",
"command": "pnpm db:push",
"command": "bun run db:push",
"cwd": "${workspaceFolder}",
"skipFiles": ["<node_internals>/**"]
},
@@ -45,7 +45,7 @@
"name": "db studio",
"type": "node-terminal",
"request": "launch",
"command": "pnpm db:studio",
"command": "bun run db:studio",
"cwd": "${workspaceFolder}",
"skipFiles": ["<node_internals>/**"]
},
@@ -53,7 +53,7 @@
"name": "db migration run",
"type": "node-terminal",
"request": "launch",
"command": "pnpm db:migration:run",
"command": "bun run db:migration:sqlite:run",
"cwd": "${workspaceFolder}",
"skipFiles": ["<node_internals>/**"]
},
@@ -61,7 +61,7 @@
"name": "test",
"type": "node-terminal",
"request": "launch",
"command": "pnpm test",
"command": "bun run test",
"cwd": "${workspaceFolder}",
"skipFiles": ["<node_internals>/**"]
},
@@ -69,7 +69,7 @@
"name": "test ui",
"type": "node-terminal",
"request": "launch",
"command": "pnpm test:ui",
"command": "bun run test:ui",
"cwd": "${workspaceFolder}",
"skipFiles": ["<node_internals>/**"]
}
+59 -18
View File
@@ -6,23 +6,24 @@
homarr/
├── apps/
│ ├── nextjs/ # Main Next.js application (port 3000)
│ ├── docs/ # Docusaurus 3 documentation site (@homarr/docs)
│ ├── tasks/ # Cron job runner + Fastify tRPC API (port 3002)
│ └── websocket/ # Standalone tRPC WebSocket server (port 3001)
│ ├── docs/ # Next.js + Fumadocs documentation site (@homarr/docs)
│ ├── tasks/ # Cron-job initialization and scheduling runtime
│ ├── websocket/ # Standalone tRPC WebSocket server (port 3001)
│ └── workshop/ # Go/PocketBase Workshop service
├── packages/
│ ├── api/ # tRPC appRouter, procedures, OpenAPI
│ ├── auth/ # NextAuth config, providers, session, API keys
│ ├── db/ # Drizzle schema (3 DB drivers), migrations, queries
│ ├── db/ # SQLite and PostgreSQL schemas, migrations, queries
│ ├── core/ # Env validation, DB/Redis driver factories, logging
│ ├── definitions/ # Domain enums: WidgetKind, IntegrationKind, permissions
│ ├── widgets/ # All 39 dashboard widgets (definitions + components)
│ ├── widgets/ # Dashboard widget definitions and components
│ ├── integrations/ # Integration classes (HTTP clients to external apps)
│ ├── redis/ # Redis pub/sub channels, caching abstractions
│ ├── translation/ # next-intl setup, locale configs, lang JSON files
│ ├── ui/ # Shared Mantine components, theme, hooks
│ ├── validation/ # Shared zod schemas for API/forms
│ ├── common/ # Shared utilities, IDs, errors
│ ├── cron-jobs/ # Cron job implementations (25+ jobs)
│ ├── cron-jobs/ # Cron job implementations
│ ├── cron-jobs-core/ # Cron scheduling primitives
│ ├── cron-job-status/ # Cron status via Redis
│ ├── boards/ # Board context, edit mode, cache updater
@@ -40,26 +41,31 @@ homarr/
│ ├── analytics/ # Server-side analytics (Umami)
│ ├── server-settings/ # Server setting keys/types
│ ├── settings/ # User-facing settings UI context
│ ├── old-import/ # Legacy Homarr import
│ ├── old-schema/ # Legacy Homarr zod schemas
│ ├── custom-widgets/ # Custom JSX v2 schema, validation, and runtime
│ ├── onboarding/ # Onboarding studio and setup flow
│ ├── workshop/ # Homarr-side Workshop client and contracts
│ └── cli/ # Node CLI for ops (brocli)
├── tooling/
│ ├── typescript/ # Base tsconfig
│ └── github/ # CI setup action
├── tools/
│ └── homarr-dev/ # Go CLI for local and PR Docker images
├── development/ # Dev docker-compose (Redis, MySQL, PostgreSQL)
├── e2e/ # E2E test specs
└── Dockerfile # Multi-stage production build
```
## Documentation Sync
## Documentation
The documentation site lives at `apps/docs/` (Docusaurus 3, `@homarr/docs`).
Add documentation only when users need the information to set up, use, or troubleshoot Homarr and cannot reasonably discover it from the UI or generated API schema. If a user does not need to know it, do not document it. A code, UI, or API change alone is never a reason to add docs.
When modifying user-facing code, you MUST also update the corresponding documentation:
Keep warranted documentation concise. Omit UI walkthroughs, visible control descriptions, internal implementation details, and anything already clear from the interface or schema.
Only after this user-need test passes, 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`
- Changed API → `apps/docs/docs/management/api.mdx`
- Changed API → `apps/docs/docs/management/api/index.mdx`
- New/changed env vars → `apps/docs/docs/advanced/`
- New CLI commands → `apps/docs/docs/advanced/command-line/`
- Auth changes → `apps/docs/docs/advanced/` SSO pages
@@ -67,17 +73,52 @@ When modifying user-facing code, you MUST also update the corresponding document
## Monorepo Commands
- `pnpm dev` — Next.js app only
- `pnpm dev:docs` — Docusaurus docs site only
- `pnpm turbo build` — build all packages
- `pnpm turbo build --filter=@homarr/docs` — build docs only
- `pnpm turbo typecheck` — typecheck all packages
- `pnpm lint` / `pnpm format` — oxlint / oxfmt
- `bun run dev` — Next.js app only
- `bun run dev:cli -- dev` — run the developer CLI without installing a global binary
- `bun run db:seed` — seed default database data explicitly
- `bun run docker:dev:up` — start the Redis development service in the background
- `bun run dev:docs` — Fumadocs site only
- `bun run turbo build` — build all packages
- `bun run turbo build --filter=@homarr/docs` — build docs only
- `bun run turbo typecheck` — typecheck all packages
- `bun run lint` / `bun run format` — oxlint / oxfmt
## Code Style
- Lint: oxlint (not ESLint)
- Format: oxfmt (not Prettier)
- UI: Mantine (not Tailwind) — Tailwind is only used in docs app
- Mantine: use the `mantine` MCP server in `.mcp.json` for current v9 APIs before writing component code. Prefer built-in primitives (`Combobox`/`useCombobox`, the polymorphic `component` prop, `@mantine/hooks`) and check `packages/ui/` for existing conventions first.
- Icons: @tabler/icons-react
- Docs app can import from `@homarr/definitions` for shared types
- Run `bun run dev:cli -- dev` to browse local `homarr:*` images and remote PR images.
- Run `bun run dev:cli -- build <name>` from a Homarr checkout to build `homarr:<name>` with rebuild provenance.
- Run `bun run dev:cli -- build --pr <number>` to build a PR locally from a temporary checkout.
## Testing
Add tests only when requested, and only when the test is extremely relevant and will plausibly prevent a future regression in user-visible behavior or a security boundary. Do not add tests that restate implementation details or duplicate stronger coverage. For focused changes, run only the relevant existing or newly requested checks when validation is needed. Do not run broad test, Docker, or E2E suites by default.
## MCP servers
`.mcp.json` declares project-scoped MCP servers. Keep only keyless definitions there; credentials belong in local dotfiles. See [Mantine LLM tooling](https://mantine.dev/guides/llms/) for client-specific setup.
## Agent Skills
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` — user-critical documentation only
- `mcp-integration` — safe tRPC-to-MCP exposure
- `homarr-custom-widget` — safe Custom JSX v2 authoring
<!-- BEGIN:turborepo-agent-rules -->
# This is NOT the Turborepo you know
Turborepo configuration, task behavior, and CLI commands can vary between installed versions and may differ from your training data. Resolve the `turbo` package from this file's directory or relevant workspace; in monorepos, it may not be visible from the repository root. For example, run `node -p "require.resolve('turbo/package.json')"` from a workspace that depends on `turbo`.
Read `docs/README.md` inside that installed package first, then read the relevant pages from its `docs/` directory before changing Turborepo configuration or commands. Heed deprecation notices. These bundled docs match the installed package version and are available without network access.
This block is written and re-added by `turbo` before repository-scoped commands when an AI agent is detected. In the Turborepo source repository, its template is defined in `crates/turborepo-cli/src/cli/agent_guidance.rs`. Removing the managed block while updates are enabled means a later qualifying invocation will add it again. Set `"agentGuidance": false` in the root `turbo.json` or `turbo.jsonc` to opt out; this does not remove an existing block. When agent guidance is enabled, keep the block committed to avoid an uncommitted change on the next agent invocation. This repository disables agent guidance in `turbo.json`.
<!-- END:turborepo-agent-rules -->
+40
View File
@@ -1,3 +1,43 @@
## [2.3.0](https://github.com/homarr-labs/homarr/compare/v2.2.0...v2.3.0) (2026-10-07)
## [2.2.0](https://github.com/homarr-labs/homarr/compare/v2.1.2...v2.2.0) (2026-10-05)
## [2.1.2](https://github.com/homarr-labs/homarr/compare/v2.1.1...v2.1.2) (2026-10-04)
## [2.1.1](https://github.com/homarr-labs/homarr/compare/v2.1.0...v2.1.1) (2026-10-03)
## [2.1.0](https://github.com/homarr-labs/homarr/compare/v2.0.0...v2.1.0) (2026-10-03)
## [2.0.0](https://github.com/homarr-labs/homarr/compare/v1.77.2...v2.0.0) (2026-10-02)
## [1.77.1](https://github.com/homarr-labs/homarr/compare/v1.77.0...v1.77.1) (2026-09-11)
## [1.77.0](https://github.com/homarr-labs/homarr/compare/v1.76.2...v1.77.0) (2026-09-06)
## [1.76.2](https://github.com/homarr-labs/homarr/compare/v1.76.1...v1.76.2) (2026-08-27)
## [1.76.1](https://github.com/homarr-labs/homarr/compare/v1.76.0...v1.76.1) (2026-08-26)
## [1.76.0](https://github.com/homarr-labs/homarr/compare/v1.75.0...v1.76.0) (2026-08-21)
## [1.75.0](https://github.com/homarr-labs/homarr/compare/v1.74.0...v1.75.0) (2026-08-14)
## [1.74.0](https://github.com/homarr-labs/homarr/compare/v1.73.0...v1.74.0) (2026-08-07)
## [1.73.0](https://github.com/homarr-labs/homarr/compare/v1.72.0...v1.73.0) (2026-07-31)
## [1.72.0](https://github.com/homarr-labs/homarr/compare/v1.71.0...v1.72.0) (2026-07-24)
## [1.71.0](https://github.com/homarr-labs/homarr/compare/v1.70.0...v1.71.0) (2026-07-17)
## [1.70.0](https://github.com/homarr-labs/homarr/compare/v1.69.2...v1.70.0) (2026-07-10)
## [1.69.2](https://github.com/homarr-labs/homarr/compare/v1.69.1...v1.69.2) (2026-07-06)
### Bug Fixes
* **layout:** raise AppShellHeader z-index above Indicator dots ([687edb9](https://github.com/homarr-labs/homarr/commit/687edb9226307cb83986097abb24fe7f672e1778)), closes [#6194](https://github.com/homarr-labs/homarr/issues/6194)
## [1.69.1](https://github.com/homarr-labs/homarr/compare/v1.69.0...v1.69.1) (2026-07-04)
### Bug Fixes
-96
View File
@@ -1,96 +0,0 @@
# Changes
## Goal
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`.
### Database Migration
- Added `packages/db/migrations/custom/0003_remove_widget_only_integrations.ts`.
- The migration moves old integration-linked widget config into widget options, then deletes the old widget-only integration rows.
- Updated the earlier release widget migration to use the new `provider` field instead of `providerIntegrationId`.
### Docs
- Updated Releases widget docs to describe providers instead of integrations.
- Updated Timetable docs to remove Search.ch integration setup.
- Removed obsolete integration docs pages for the deleted integration kinds.
- Updated docs sitemap and integration docs mappings.
### Translations
- Added English labels for the new widget options:
- `widget.timetable.option.baseUrl.label`
- `widget.releases.option.repositories.providerUrl.label`
## How It Works Now
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.
- Full `pnpm turbo typecheck` passed.
- Full `pnpm lint` passed with existing warnings.
- Focused release provider normalization tests passed.
- Widget translation test passed.
- 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`.
-199
View File
@@ -1,199 +0,0 @@
# ── CODEOWNERS ───────────────────────────────────────
* @homarr-labs/maintainers
# Specific features / integrations
# These owners come from the PR that introduced each integration implementation.
# Specific rules override the global fallback because CODEOWNERS uses the last
# matching pattern; do not include @homarr-labs/maintainers here unless they
# should also be requested for that integration.
# AdGuard Home - introduced in https://github.com/homarr-labs/homarr/pull/929 by @hillaliy
apps/docs/docs/integrations/adguard-home @hillaliy
packages/integrations/src/adguard-home @hillaliy
# Anchor - introduced in https://github.com/homarr-labs/homarr/pull/5040 by @ohmzi
apps/docs/docs/integrations/anchor @ohmzi
packages/integrations/src/anchor @ohmzi
# ArchiveTeam Warrior - introduced in https://github.com/homarr-labs/homarr/pull/5784 by @darbyjack
apps/docs/docs/integrations/archive-team-warrior @darbyjack
packages/integrations/src/archive-team-warrior @darbyjack
# Audiobookshelf - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
apps/docs/docs/integrations/audiobookshelf @ajnart
packages/integrations/src/audiobookshelf @ajnart
# Beszel - introduced in https://github.com/homarr-labs/homarr/pull/5900 by @ajnart
apps/docs/docs/integrations/beszel @ajnart
packages/integrations/src/beszel @ajnart
# Codeberg - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
apps/docs/docs/integrations/codeberg @Aandree5
packages/integrations/src/codeberg @Aandree5
# Coolify - introduced in https://github.com/homarr-labs/homarr/pull/4789 by @Rhiz3K
apps/docs/docs/integrations/coolify @Rhiz3K
packages/integrations/src/coolify @Rhiz3K
# Dash. - introduced in https://github.com/homarr-labs/homarr/pull/1541 by @manuel-rw
apps/docs/docs/integrations/dash-dot @manuel-rw
packages/integrations/src/dashdot @manuel-rw
# Docker Hub - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
apps/docs/docs/integrations/docker-hub @Aandree5
packages/integrations/src/docker-hub @Aandree5
# Emby - introduced in https://github.com/homarr-labs/homarr/pull/2466 by @Meierschlumpf
apps/docs/docs/integrations/emby @Meierschlumpf
packages/integrations/src/emby @Meierschlumpf
# GitHub - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
apps/docs/docs/integrations/github @Aandree5
packages/integrations/src/github @Aandree5
# GitHub Container Registry - introduced in https://github.com/homarr-labs/homarr/pull/3727 by @Aandree5
apps/docs/docs/integrations/github-containerregistry @Aandree5
packages/integrations/src/github-container-registry @Aandree5
# GitLab - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
apps/docs/docs/integrations/gitlab @Aandree5
packages/integrations/src/gitlab @Aandree5
# Glances - introduced in https://github.com/homarr-labs/homarr/pull/5102 by @manuel-rw
apps/docs/docs/integrations/glances @manuel-rw
packages/integrations/src/glances @manuel-rw
# Gluetun - introduced in https://github.com/homarr-labs/homarr/pull/5812 by @Lok3sh-kumar
apps/docs/docs/integrations/gluetun @Lok3sh-kumar
packages/integrations/src/gluetun @Lok3sh-kumar
# Gotify - introduced in https://github.com/homarr-labs/homarr/pull/5847 by @Nerdy-Technician
apps/docs/docs/integrations/gotify @Nerdy-Technician
packages/integrations/src/gotify @Nerdy-Technician
# Home Assistant - introduced in https://github.com/homarr-labs/homarr/pull/578 by @manuel-rw
apps/docs/docs/integrations/home-assistant @manuel-rw
packages/integrations/src/homeassistant @manuel-rw
# ICal - introduced in https://github.com/homarr-labs/homarr/pull/3980 by @NicolasNewman
apps/docs/docs/integrations/ical @NicolasNewman
packages/integrations/src/ical @NicolasNewman
# Immich - introduced in https://github.com/homarr-labs/homarr/pull/5165 by @manuel-rw
apps/docs/docs/integrations/immich @manuel-rw
packages/integrations/src/immich @manuel-rw
# Jellyfin - introduced in https://github.com/homarr-labs/homarr/pull/672 by @manuel-rw
apps/docs/docs/integrations/jellyfin @manuel-rw
packages/integrations/src/jellyfin @manuel-rw
# Jellyseerr - introduced in https://github.com/homarr-labs/homarr/pull/774 by @manuel-rw
apps/docs/docs/integrations/jellyseerr @manuel-rw
packages/integrations/src/jellyseerr @manuel-rw
# LinuxServer.io - introduced in https://github.com/homarr-labs/homarr/pull/3607 by @Aandree5
apps/docs/docs/integrations/linux-server-io @Aandree5
packages/integrations/src/linuxserverio @Aandree5
# Navidrome - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
apps/docs/docs/integrations/navidrome @ajnart
packages/integrations/src/navidrome @ajnart
# Nextcloud - introduced in https://github.com/homarr-labs/homarr/pull/2501 by @manuel-rw
apps/docs/docs/integrations/nextcloud @manuel-rw
packages/integrations/src/nextcloud @manuel-rw
# npm - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
apps/docs/docs/integrations/npm @Aandree5
packages/integrations/src/npm @Aandree5
# ntfy - introduced in https://github.com/homarr-labs/homarr/pull/2900 by @itzTheMeow
apps/docs/docs/integrations/ntfy @ajnart @manuel-rw
packages/integrations/src/ntfy @ajnart @manuel-rw
# OpenMediaVault - introduced in https://github.com/homarr-labs/homarr/pull/1142 by @hillaliy
apps/docs/docs/integrations/open-media-vault @hillaliy
packages/integrations/src/openmediavault @hillaliy
# OPNsense - introduced in https://github.com/homarr-labs/homarr/pull/3424 by @Oupsman
apps/docs/docs/integrations/opnsense @Oupsman
packages/integrations/src/opnsense @Oupsman
# Overseerr - introduced in https://github.com/homarr-labs/homarr/pull/774 by @manuel-rw
apps/docs/docs/integrations/overseerr @manuel-rw
packages/integrations/src/overseerr @manuel-rw
# Paperless-ngx - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
apps/docs/docs/integrations/paperless-ngx @ajnart
packages/integrations/src/paperless-ngx @ajnart
# PeaNUT - introduced in https://github.com/homarr-labs/homarr/pull/5862 by @afonsojramos
apps/docs/docs/integrations/peanut @afonsojramos
packages/integrations/src/peanut @afonsojramos
# Pi-hole - introduced in https://github.com/homarr-labs/homarr/pull/521 by @Meierschlumpf
apps/docs/docs/integrations/pi-hole @Meierschlumpf
packages/integrations/src/pi-hole @Meierschlumpf
# Plex - introduced in https://github.com/homarr-labs/homarr/pull/1342 by @hillaliy
apps/docs/docs/integrations/plex @hillaliy
packages/integrations/src/plex @hillaliy
# Prowlarr - introduced in https://github.com/homarr-labs/homarr/pull/965 by @hillaliy
apps/docs/docs/integrations/prowlarr @hillaliy
packages/integrations/src/prowlarr @hillaliy
# Proxmox - introduced in https://github.com/homarr-labs/homarr/pull/1969 by @Meierschlumpf
apps/docs/docs/integrations/proxmox @Meierschlumpf
packages/integrations/src/proxmox @Meierschlumpf
# Quay - introduced in https://github.com/homarr-labs/homarr/pull/3607 by @Aandree5
apps/docs/docs/integrations/quay @Aandree5
packages/integrations/src/quay @Aandree5
# search.ch - introduced in https://github.com/homarr-labs/homarr/pull/5180 by @Meierschlumpf
apps/docs/docs/integrations/search-ch @Meierschlumpf
packages/integrations/src/search-ch @Meierschlumpf
# Speedtest Tracker - introduced in https://github.com/homarr-labs/homarr/pull/5363 by @kylegrieder
apps/docs/docs/integrations/speedtest-tracker @kylegrieder
packages/integrations/src/speedtest-tracker @kylegrieder
# Technitium DNS - introduced in https://github.com/homarr-labs/homarr/pull/5832 by @lopsch
apps/docs/docs/integrations/technitium-dns @lopsch
packages/integrations/src/technitium @lopsch
# Tracearr - introduced in https://github.com/homarr-labs/homarr/pull/5060 by @StrandedTurtle
apps/docs/docs/integrations/tracearr @StrandedTurtle
packages/integrations/src/tracearr @StrandedTurtle
# TrueNAS - introduced in https://github.com/homarr-labs/homarr/pull/3745 by @manuel-rw
apps/docs/docs/integrations/truenas @manuel-rw @afonsojramos
packages/integrations/src/truenas @manuel-rw @afonsojramos
# Umami - introduced in https://github.com/homarr-labs/homarr/pull/5362 by @febrile42
apps/docs/docs/integrations/umami @febrile42
packages/integrations/src/umami @febrile42
# UniFi Controller - introduced in https://github.com/homarr-labs/homarr/pull/2236 by @pitschi
apps/docs/docs/integrations/unifi-controller @pitschi
packages/integrations/src/unifi-controller @pitschi
# Unraid - introduced in https://github.com/homarr-labs/homarr/pull/4439 by @manuel-rw
apps/docs/docs/integrations/unraid @manuel-rw
packages/integrations/src/unraid @manuel-rw
# Uptime Kuma - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
apps/docs/docs/integrations/uptime-kuma @ajnart
packages/integrations/src/uptime-kuma @ajnart
# Exempt Renovate-managed files (no owners)
package.json
**/package.json
package-lock.json
**/package-lock.json
pnpm-lock.yaml
**/pnpm-lock.yaml
pnpm-workspace.yaml
Dockerfile
docker-compose.yml
+37 -45
View File
@@ -1,70 +1,62 @@
# syntax=docker/dockerfile:1.25
FROM oven/bun:1.4.2-alpine AS bun-tool
FROM node:24.18.0-alpine AS base
FROM base AS builder
RUN apk add --no-cache libc6-compat
RUN apk update
# Set working directory
ARG TARGETPLATFORM
WORKDIR /app
RUN apk add --no-cache libc6-compat curl bash
RUN apk update
COPY --from=bun-tool /usr/local/bin/bun /usr/local/bin/bun
# Compiler tools stay in the builder for optional native dependency fallbacks.
RUN apk add --no-cache libstdc++ python3 make g++
COPY bun.lock bunfig.toml package.json ./
COPY patches ./patches
COPY --parents ./apps/*/package.json ./packages/*/package.json ./tooling/*/package.json ./
RUN --mount=type=cache,id=homarr-bun-cache,target=/root/.bun/install/cache,sharing=locked \
npm_config_nodedir=/usr/local bun install --frozen-lockfile --concurrent-scripts=1
COPY . .
RUN corepack enable pnpm && pnpm install --recursive --frozen-lockfile
# Copy static data as it is not part of the build
COPY static-data ./static-data
ARG SKIP_ENV_VALIDATION='true'
ARG CI='true'
ARG DISABLE_REDIS_LOGS='true'
ARG TARGETPLATFORM
RUN --mount=type=secret,id=TURBO_API,env=TURBO_API \
--mount=type=secret,id=TURBO_TEAM,env=TURBO_TEAM \
--mount=type=secret,id=TURBO_TOKEN,env=TURBO_TOKEN \
--mount=type=secret,id=TURBO_REMOTE_CACHE_SIGNATURE_KEY,env=TURBO_REMOTE_CACHE_SIGNATURE_KEY \
--mount=type=cache,id=homarr-next-build-${TARGETPLATFORM},target=/app/apps/nextjs/.next/cache,sharing=locked \
--mount=type=cache,id=homarr-turbo-${TARGETPLATFORM},target=/app/.turbo,sharing=locked \
TURBO_PLATFORM="${TARGETPLATFORM:-linux/amd64}/musl/node-24.18.0" \
bun run turbo run build --filter=@homarr/nextjs... --filter=@homarr/cli
RUN corepack enable pnpm && pnpm build
FROM base AS runner
FROM alpine:3.24.1 AS runner
WORKDIR /app
# gettext is required for envsubst, openssl for generating AUTH_SECRET, su-exec for running application as non-root
RUN apk add --no-cache redis nginx bash gettext su-exec openssl
RUN mkdir /appdata
COPY --from=base /usr/local/bin/node /usr/local/bin/node
# envsubst, privilege drop and AUTH_SECRET generation are used by the entrypoint.
RUN apk add --no-cache libstdc++ ca-certificates redis nginx bash gettext su-exec openssl && \
mkdir -p /appdata /var/cache/nginx /var/log/nginx /var/lib/nginx \
/run/nginx /etc/nginx/templates /etc/nginx/ssl/certs && \
touch /run/nginx/nginx.pid
VOLUME /appdata
# Enable homarr cli
COPY --from=builder /app/packages/cli/cli.cjs /app/apps/cli/cli.cjs
RUN echo $'#!/bin/bash\ncd /app/apps/cli && node ./cli.cjs "$@"' > /usr/bin/homarr
RUN chmod +x /usr/bin/homarr
# Don't run production as root
RUN mkdir -p /var/cache/nginx && \
mkdir -p /var/log/nginx && \
mkdir -p /var/lib/nginx && \
touch /run/nginx/nginx.pid && \
mkdir -p /etc/nginx/templates /etc/nginx/ssl/certs
COPY --from=builder /app/apps/nextjs/next.config.ts .
COPY --from=builder /app/apps/nextjs/package.json .
COPY --from=builder /app/pnpm-workspace.yaml ./pnpm-workspace.yaml
# Bundled CLI/migrations resolve the native binding from the application root.
COPY --from=builder /app/node_modules/better-sqlite3/build/Release/better_sqlite3.node /app/build/better_sqlite3.node
RUN printf '#!/bin/sh\ncd /app/apps/cli && exec node ./cli.cjs "$@"\n' > /usr/bin/homarr && \
chmod +x /usr/bin/homarr
COPY --from=builder /app/packages/db/migrations ./db/migrations
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder /app/apps/nextjs/.next/standalone ./
COPY --from=builder /app/apps/nextjs/.next/static ./apps/nextjs/.next/static
COPY --from=builder /app/apps/nextjs/public ./apps/nextjs/public
# Ship only Next's traced production dependencies and application assets.
COPY --from=builder /app/apps/nextjs/.output/standalone ./
COPY scripts/run.sh ./run.sh
COPY --chmod=755 scripts/entrypoint.sh ./entrypoint.sh
COPY packages/redis/redis.conf /app/redis.conf
COPY nginx.conf /etc/nginx/templates/nginx.conf
ENV DB_URL='/appdata/db/db.sqlite'
ENV DB_DIALECT='sqlite'
ENV DB_DRIVER='better-sqlite3'
ENV AUTH_PROVIDERS='credentials'
ENV REDIS_IS_EXTERNAL='false'
ENV NODE_ENV='production'
ENTRYPOINT [ "/app/entrypoint.sh" ]
EXPOSE 7575
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["sh", "run.sh"]
+33
View File
@@ -0,0 +1,33 @@
# Product
## Register
brand
## Users
Self-hosted Homarr users who need to evaluate the project, install it, connect services, find extensions, and reference its APIs. They move between the homepage, documentation, Workshop, API reference, and project updates as one continuous experience.
## Product Purpose
The site explains what Homarr does and gives users reliable paths to install, configure, extend, and operate it. Success means users can identify where they are, move between major surfaces without relearning the navigation, and read technical content comfortably.
## Brand Personality
Practical, technical, welcoming.
## Anti-references
Avoid disconnected microsites, marketing-heavy copy, ornamental dashboards, hidden navigation, and dense long-form pages without visible structure.
## Design Principles
- Keep one recognizable Homarr identity across every surface.
- Let navigation topology match the task while keeping labels and behavior consistent.
- Prioritize readable line lengths, strong heading hierarchy, and visible document structure.
- Make primary paths obvious without adding decorative UI.
- Preserve useful functionality on mobile, keyboard navigation, and reduced-motion settings.
## Accessibility & Inclusion
Target WCAG 2.2 AA. Maintain keyboard access, visible focus states, sufficient contrast, semantic landmarks, touch-friendly controls, responsive layouts, and reduced-motion behavior.
+3 -2
View File
@@ -5,7 +5,7 @@
/node_modules
/.pnp
.pnp.js
.pnpm-store/
.bun-cache/
# testing
/coverage
@@ -13,6 +13,7 @@
# next.js
/.next/
/out/
/.source/
# production
/build
@@ -55,4 +56,4 @@ static/data/configs
# CI Stuff
screenshots
test-results
test-results
-3
View File
@@ -23,9 +23,6 @@
"react-hooks/exhaustive-deps": "warn",
"eslint/no-unused-expressions": "warn"
},
"options": {
"reportUnusedDisableDirectives": "off"
},
"ignorePatterns": ["**/*.config.js", "**/*.config.mjs", "**/*.cjs", ".docusaurus/**", "build/**"],
"settings": {
"react": { "version": "19" }
+2 -9
View File
@@ -1,10 +1,3 @@
{
"cSpell.words": [
"Homarr",
"Lidarr",
"Nextcloud",
"Radarr",
"Readarr",
"Sonarr"
]
}
"cSpell.words": ["Homarr", "Lidarr", "Nextcloud", "Radarr", "Readarr", "Sonarr"]
}
+18
View File
@@ -0,0 +1,18 @@
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
<!-- END:nextjs-agent-rules -->
## Fumadocs workflow
- Start with `README.md`, `src/lib/source.ts`, `source.config.ts`, and the matching route under `src/app/`.
- Read current [Fumadocs documentation](https://fumadocs.dev/docs) with Context7 before changing framework APIs. Check the installed version and types; latest examples may use a different search adapter.
- Preserve static export: search uses `staticGET` with the matching static client. Do not introduce a request-time search server into this app.
- Keep integration/widget metadata in the existing typed definitions. Check both rendered HTML and processed Markdown when changing MDX components; JSX alone does not guarantee readable search or AI output.
- Run the README validation commands after content or navigation changes. Search results and table-of-contents links must resolve to rendered anchors.
- Kapa is an independent hosted assistant. Keep its keyboard shortcut disabled so it does not override docs search; follow the README for its build configuration and crawler setup.
+37
View File
@@ -0,0 +1,37 @@
# Homarr Documentation
## Register
product
## Users
People who run Homarr, administrators configuring self-hosted infrastructure, and contributors extending Homarr. They arrive with a concrete task such as installing Homarr, connecting a service, configuring a widget, managing users, or building a custom widget, and need an answer they can scan and apply quickly.
## Product Purpose
The documentation is the complete, authoritative operating guide for Homarr. It keeps setup steps, configuration contracts, screenshots, generated integration and widget metadata, API references, and Workshop tooling next to the application code. Success means a reader can find the right page quickly, understand the next action, copy reliable instructions, and verify the result without searching elsewhere.
## Brand Personality
Focused, dependable, and approachable. The writing is direct and practical. The interface should feel calm and familiar during long technical sessions while retaining Homarr's red identity.
## Anti-references
- Generic marketing layouts that replace Homarr's product identity with unrelated slogans or decorative conversion copy.
- Decorative dashboard aesthetics that compete with reading, including glass effects, large radii, card grids, and gratuitous motion.
- Generic generated-docs styling that obscures Homarr's identity or makes navigation feel detached from the main product.
- Dense reference portals that expose structure without giving readers a clear path through a task.
## Design Principles
1. Put the reader's task first: search, navigation, page outline, and the answer must remain easy to reach.
2. Preserve one source of truth: typed Homarr metadata and existing custom components stay connected to the monorepo.
3. Prefer familiar documentation patterns: the interface should disappear while a reader scans, copies, and follows instructions.
4. Make complete content feel manageable through strong hierarchy, progressive disclosure, and precise cross-links.
5. Treat machine readers as users through canonical Markdown, structured metadata, and standard LLM discovery endpoints.
6. Keep the root landing page recognizably Homarr: preserve its self-hosted dashboard promise and real product evidence while documentation routes remain task-first.
## Accessibility & Inclusion
Meet WCAG 2.2 AA for color contrast, keyboard operation, focus visibility, landmarks, headings, and interactive controls. Respect reduced-motion and system color-scheme preferences. Diagrams, screenshots, icons, and emoji must have meaningful text alternatives when they carry information.
+141 -78
View File
@@ -1,105 +1,168 @@
# Homarr Documentation (`@homarr/docs`)
# Homarr documentation (`@homarr/docs`)
The official [homarr.dev](https://homarr.dev) documentation site, built with [Docusaurus 3](https://docusaurus.io/) and living inside the Homarr monorepo.
The source for [homarr.dev](https://homarr.dev), built with Next.js and Fumadocs UI inside the Homarr monorepo.
## Why it's in the monorepo
## Develop
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
## Development
From the **monorepo root**:
From the repository root:
```bash
# Start the docs dev server (port 3003)
pnpm dev:docs
# Or from within this directory
pnpm dev
bun run dev:docs
```
## Build
The site runs on `http://127.0.0.1:3003`. To use a local Workshop API:
```bash
# Build docs only
pnpm turbo build --filter=@homarr/docs
# Build everything (docs + main app + tasks + websocket)
pnpm build
WORKSHOP_API_URL=http://127.0.0.1:8090 bun run dev:docs
```
## Lint & Format
Uses oxlint and oxfmt (same as the rest of the monorepo):
## Validate
```bash
pnpm lint # oxlint
pnpm format # oxfmt (check)
pnpm format -- --write # oxfmt (fix)
bun run --filter @homarr/docs typecheck
bun run --filter @homarr/docs build
bun run --filter @homarr/docs validate:links
bun run --filter @homarr/docs verify:search
bun run --filter @homarr/docs verify:seo
```
## Typecheck
The static export is written to `out/`. The build validates docs pages and typed metadata for integrations and widgets
that declare a documentation slug. Set the slug to `null` when a page would add no useful information for advanced users.
CI enforces coverage alongside schema drift and export validation. The link check validates internal Markdown and MDX
links and rendered anchors against the export.
The search check exercises the exported index with title, heading, and body queries and verifies result destinations.
The SEO check validates canonical URLs, descriptions, social metadata, heading structure, and sitemap coverage in the
exported HTML. These tags are generated at build time; set `HOMARR_WEBSITE_URL` before building for a different origin.
## Preview the static export with PocketBase
After building the docs, run from the repository root:
```bash
pnpm typecheck
sh apps/workshop/preview-docs.sh
```
## Content Structure
Open `http://127.0.0.1:8093/docs/`. This starts an isolated PocketBase container with temporary local data and serves
the export directly, including search, Markdown, and the custom 404 page. Workshop browser requests use the configured
backend; a trailing `/api/` is removed because the PocketBase SDK appends its API paths. The preview does not copy or
migrate another database. Set `WORKSHOP_API_URL` to a remote PocketBase origin when testing against one;
`WORKSHOP_REMOTE_API_URL` can override the server-side metadata source independently. Direct item links then use that
source for titles and social metadata. Missing remote items return 404; an unavailable backend returns 503.
```
docs/
├── getting-started/ # Installation guides, glossary, prerequisites
├── management/ # Boards, apps, users, integrations, settings, tasks, API
├── integrations/ # 55 integration guides (one folder each)
├── widgets/ # 42 widget guides (one folder each)
├── advanced/ # SSO, env vars, proxy, styling, CLI, development
└── community/ # FAQ, donate, license, translations, get-in-touch
```
Each integration/widget doc follows a consistent pattern:
- `index.ts` — typed metadata (`IntegrationDefinition` / `WidgetDefinition`)
- `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.
Crawler reference config: [`docsearch.config.js`](docsearch.config.js).
**Verify search health:**
Verify public item pages without changing the remote database:
```bash
pnpm verify:search
REMOTE_WORKSHOP_URL=https://workshop.example.com
WORKSHOP_TEST_URL=http://127.0.0.1:8093 WORKSHOP_REMOTE_API_URL="$REMOTE_WORKSHOP_URL" \
bun apps/workshop/tests/remote-workshop.integration.mjs
```
## Tech Stack
Stop it with `docker stop homarr-docs-static-preview`. Rebuild and restart the preview after changing source files;
startup recreates the runtime configuration. `DOCS_PREVIEW_PORT`, `DOCS_PREVIEW_HOST`, and `DOCS_PREVIEW_NAME` override
the defaults. Set the host to this machine's Tailscale address to access it from your other devices.
| Layer | Technology |
|---|---|
| Site generator | Docusaurus 3 (`@docusaurus/preset-classic`) |
| Styling | Tailwind CSS 3 + Docusaurus Infima |
| Icons | `@tabler/icons-react` |
| Diagrams | Mermaid (`@docusaurus/theme-mermaid`) |
| Search | Algolia DocSearch |
| Analytics | PostHog |
| Charts | @nivo/line |
## Write content
## Contributing
Documentation lives in `docs/`; project posts live in `blog/`. Navigation is controlled by the nearest `meta.json`.
Integration and widget folders pair typed `index.ts` metadata with `index.mdx` content.
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.
Use standard Markdown and MDX. Shared components live in `src/components/`. Mermaid fences, tabs, callouts, zoomable
images, and syntax-highlighted code are supported by the Fumadocs pipeline.
Update docs in the same pull request only when a smart, advanced user cannot infer the changed behavior from the UI or
generated API schema. Routine or self-explanatory changes need no docs update. See the
[documentation-sync skill](../../.agents/skills/documentation-sync/SKILL.md) for target paths.
## Published formats
- `/docs` — searchable documentation with typed navigation
- `/api-reference` — interactive reference generated from the OpenAPI schema
- `/llms.txt` — compact documentation and blog index for agents
- `/llms-full.txt` — complete processed Markdown corpus
- `/llms.mdx/docs/.../content.md` — raw Markdown for each docs page
- `/llms.mdx/blog/.../content.md` — raw Markdown for each blog post
- `/llms.mdx/api-reference/.../content.md` — operation parameters, bodies, responses, and authentication
- `/blog/rss.xml` — RSS feed for project posts
- `/search?q=...` — opens documentation search with the supplied query, including existing Homarr search-engine links
Every docs page and blog post includes copy-Markdown and view-source actions. Search is generated at build time and
runs locally in the browser; it does not depend on an external crawler. Markdown exports and search resolve
integration credentials and widget defaults from the same typed metadata used by the pages.
The search dialog and its client-side engine load when search is first opened. Fumadocs caches the downloaded index
for subsequent queries. The docs index, individual Markdown pages, and full corpus share the `llms()` renderer.
API operations are indexed alongside docs and exported as Markdown from the same OpenAPI schema. The reference uses
Fumadocs with Scalar: select an instance URL and API key, then explicitly send a request. Authentication is not
persisted in local storage. Browser access requires the documented REST CORS configuration.
The Custom JSX guide includes a lazily loaded, editable example using the application renderer and real component
registry. Its bindings work locally; configured widget queries and actions do not run.
The getting-started overview and complete Custom JSX component catalog are also included, with property types,
binding contracts, and blocked capabilities taken from the published catalog JSON.
Published Docusaurus `/docs/category/...` addresses remain available as static compatibility pages. They forward to
the corresponding overview or developer setup guide, preserving query strings and anchors, and provide a direct
link when JavaScript is disabled. Their canonical metadata points to the destination; compatibility pages are
excluded from search and the sitemap.
## Fumadocs upgrade notes
The September 2026 update moves Core/UI from 16.15.4 to 16.15.12, MDX from 15.4.0 to 15.4.3, and OpenAPI from
11.3.5 to 12.0.1. Review the upstream [Core](https://github.com/fuma-nama/fumadocs/blob/dev/packages/core/CHANGELOG.md),
[UI](https://github.com/fuma-nama/fumadocs/blob/dev/packages/radix-ui/CHANGELOG.md),
[MDX](https://github.com/fuma-nama/fumadocs/blob/dev/packages/mdx/CHANGELOG.md), and
[OpenAPI](https://github.com/fuma-nama/fumadocs/blob/dev/packages/openapi/CHANGELOG.md) changelogs when upgrading.
- Core/UI and MDX now declare side effects for better tree shaking. UI also fixes tabs reverting to the URL hash.
- Core's `llms({ renderPage })` API provides the shared `index()`, `page()`, and `full()` exports.
- OpenAPI 12 replaces several custom renderer options and hooks. This site uses `createOpenAPIPage()` and
`getOpenAPIPageProps()` without those removed overrides; the built-in renderer retains all request languages.
The Scalar adapter receives an equivalent nonrecursive union for unconstrained JSON values to avoid recursive
schema expansion during rendering. The generated public OpenAPI document remains unchanged.
- MDX 15.4.3 fixes duplicate compilation output when its experimental build cache is enabled. We leave that cache
disabled: its current key checks source content without accounting for all compiler configuration changes.
- The existing macro collections, processed-Markdown component adapters, and static search remain supported.
Typed version roots and server-side MCP tools are available upstream but are not needed by this static site.
## Kapa AI
The compact Homarr mascot in the bottom-right opens **Ask AI**. It slowly spins when idle and gives a subtle claw movement and wink on hover. Animation respects reduced motion.
The launcher uses Homarr's existing public Kapa Website ID. The root layout loads the widget once across
client-side navigation and follows the docs light or dark theme. **Search** and `Ctrl+K` / `Cmd+K` always use Fumadocs search, including when Kapa is unavailable.
Set `KAPA_WEBSITE_ID` at build time to use a different Kapa Website Widget integration. An explicitly empty value
disables the widget, for example `KAPA_WEBSITE_ID= bun run --filter @homarr/docs build`. This is a public integration ID
included in exported pages, not a Kapa API key. Rebuild the static export after changing it.
In the Kapa dashboard, confirm the integration is live and enable the production and preview domains. Restoring the
script does not verify that the historical integration is still active. Follow the
[Kapa widget setup](https://docs.kapa.ai/integrations/website-widget/quickstart) for domain restrictions and CSP settings.
After changing the documentation structure, review the Website Crawl source in Kapa: use the deployed `/docs` URL as
the start URL and preview `main` as the content selector. Replace any old Docusaurus-specific selector and check that
titles, headings, code samples, and integration details appear in the extracted Markdown. Docs are exported as HTML;
the site also publishes `/sitemap.xml` and permits crawling in `/robots.txt`. The `/llms.txt` exports do not configure
Kapa ingestion automatically. See [Kapa source setup](https://docs.kapa.ai/getting-started/index-your-first-source).
Before publishing, check that Ask AI opens after navigating between pages, ask a documentation question, and verify
its citations point to the current docs. In a separate check, block `widget.kapa.ai` and confirm search still works.
## Release verification
The production image compiles the docs itself; it does not depend on a host `out/` directory.
Build-time `HOMARR_WEBSITE_URL` controls canonical metadata. Runtime URL overrides configure Workshop connections
but do not rewrite already-exported canonical URLs.
PostHog records SPA pageviews, installation clicks, searches with no results (including the search query), and Workshop
content downloads and submission starts/completions through `hog.homarr.dev`. Downloads include JSON files and
successful JSON/CSS clipboard copies. All events carry `site` and `source_path`. Autocapture, page-leave events, and
session replay are disabled; tracked URL query strings and fragments are removed. Localhost and `?analytics_test`
traffic carries `verification=true`; exclude it from production reports.
Carbon loads one visible placement after the desktop TOC, below API examples, or below content on other layouts.
The homepage is excluded at every viewport size. Navigation reloads the ad script; resizing changes placement only when crossing its breakpoint. Ad blockers
or no-fill responses must not prevent content, navigation, or search from working.
To preview Carbon locally, start docs with `NEXT_PUBLIC_ENABLE_CARBON_ADS=true bun run dev:docs`. Production builds
enable ads automatically. When the TOC collapses into a toggle, the ad appears below the content before “Edit this page”.
@@ -1,6 +1,7 @@
---
slug: documentation-migration
title: Migration of Documentation
date: 2022-06-22
authors:
- ajnart
- manuel-rw
@@ -12,7 +13,7 @@ The documentation has been re-written partly and includes now many animations, g
We are still figuring things out and highly depend on your feedback.
<!-- truncate -->
{/* Historical excerpt boundary. */}
## Why we migrated
@@ -32,4 +33,4 @@ After we've reviewed your changes, we'll merge your PR and this documentation wi
## 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.
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.
@@ -1,6 +1,7 @@
---
slug: translations
title: Help us make Homarr multilingual
date: 2022-08-28
authors:
- ajnart
- manuel-rw
@@ -13,8 +14,8 @@ We've been working hard to offer Homarr in many languages. The next update of Ho
![](img/homarr-we-need-you.webp) <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 -->
{/* Historical excerpt boundary. */}
### 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 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! 😎
@@ -1,5 +1,6 @@
---
title: "We present: Homarr 0.11 - the most complex Homarr version yet!"
date: 2023-01-11
authors:
- ajnart
- manuel-rw
@@ -20,7 +21,7 @@ It contains many breaking changes and huge improvements to your favourite dashbo
- Huge design and usability improvements to older components to Homarr
- Better handeling of exceptions and unexpected issues
<!-- truncate -->
{/* Historical excerpt boundary. */}
## Complete implementation of highly customizable drag and drop library "Gridstack"
@@ -1,5 +1,6 @@
---
title: "We present: Homarr 0.12 - The fastest homarr version ever!"
date: 2023-04-16
authors:
- ajnart
- manuel-rw
@@ -15,14 +16,12 @@ This applies to all widgets, but in the case of the IFrame widget, it enables yo
- Integrate monitoring tools like Grafana, Splunk or Elasticsearch
- Integrate your services (for example Sonarr, Radarr and Overseerr) directly into Homarr
![](https://media.discordapp.net/attachments/988335098493542410/1094665745226539168/update-teaser.png?width=1046&height=666)
## Edit mode password
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 -->
{/* Historical excerpt boundary. */}
## 😎 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* !!!
@@ -72,4 +71,4 @@ Now, you can use the following environment variables to configure this:
- ``DOCKER_HOST``
- ``DOCKER_PORT``
**Full Changelog**: https://github.com/ajnart/homarr/compare/v0.11.5...v0.12.0
**Full Changelog**: https://github.com/ajnart/homarr/compare/v0.11.5...v0.12.0
@@ -1,5 +1,6 @@
---
title: "Authentication in Homarr - 0.14 is here!"
date: 2023-11-10
authors:
- ajnart
- manuel-rw
@@ -23,7 +24,7 @@ provide you a viable solution today.
- 📝 Overhauled notepad widget with a complex graphical editor that enables easy editing right from your browser.
- :globe_with_meridians: Live Translation on Crowdin that enable you to directly translate using images to see what exactly you're translating
<!-- truncate -->
{/* Historical excerpt boundary. */}
## Breaking changes
@@ -83,4 +84,4 @@ We are aware that this update has many breaking changes and may confuse some use
For that reason, we updated our documentation and will enhance it further within the next few weeks.
We've also added this small button at the bottom of the page were you can ask **your questions** to an AI assistant:
![image](https://github.com/ajnart/homarr/assets/30572287/a56f721a-d022-42ad-813a-d85c89462a8c)
![image](https://github.com/ajnart/homarr/assets/30572287/a56f721a-d022-42ad-813a-d85c89462a8c)
@@ -1,5 +1,6 @@
---
title: "Updated documentation with better organization & design"
date: 2023-12-22
authors:
- manuel-rw
tags: [homarr, documentation, design, docs]
@@ -8,7 +9,7 @@ tags: [homarr, documentation, design, docs]
We'd like to let you know that we've updated our documentation and reorganized the pages.
This means, that some links may no longer be valid - we apologise if this causes any inconvenience for you.
<!-- truncate -->
{/* Historical excerpt boundary. */}
A few highlights include:
@@ -18,4 +19,4 @@ A few highlights include:
- Bug fixes for image zoom
- Added documentation for management pages and [new CLI tool](/docs/advanced/command-line/password-recovery/)
Please [let us know](/docs/community/get-in-touch) is something is not working as expected.
Please [let us know](/docs/community/get-in-touch) is something is not working as expected.
@@ -1,9 +1,10 @@
---
title: Homarr 1.0 - complete rewrite of all systems
date: 2024-09-23
authors:
- manuel-rw
---
# Homarr 1.0 - complete rewrite of all systems
We proudly present to you - Homarr ``1.0``.
We have started work on this new version back in 2023 and have worked tirelessly for months on this brand-new dashboard.
@@ -13,8 +14,6 @@ This will be a one time occurrence and version > ``1.0`` will have less breaking
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.
@@ -30,14 +29,14 @@ 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 -->
{/* Historical excerpt boundary. */}
## Breaking changes
:::warning
<Alert type="warning">
Please consult the breaking changes list & upgrade guide if you used third-party installation methods.
:::
</Alert>
- Docker Image
- Removed support for ``arm/v7`` -> upgrade to newer architectures as an alternative
@@ -1,16 +1,16 @@
---
title: Open Beta 1.0
date: 2024-12-17
authors:
- meierschlumpf
---
# Open Beta 1.0
We are proud to announce the open beta of Homarr 1.0. This is a major milestone for us and we are excited to share it with you.
The open beta allows us to polish the last rough edges and gather feedback from you.
We are looking forward to your feedback and hope you enjoy the new features.
Let us know throgh discord or a [github issues](https://github.com/homarr-labs/homarr/issues/new/choose) if you encounter any issues.
<!-- truncate -->
{/* Historical excerpt boundary. */}
## How to use the beta with docker
@@ -36,4 +36,4 @@ services:
Then, run `docker compose up -d` in the same directory. This will start the Homarr container in the background.
Please note that the mounts have changed and it's not backwards compatible with versions before 1.0.0-beta.1.
You can find a list of all breaking changes [here](/blog/2024/09/23/version-1.0)
You can find a list of all breaking changes [here](/blog/2024/09/23/version-1.0)
@@ -1,21 +1,20 @@
---
title: Migrate from 0.15.10+ to 1.0.0
date: 2025-01-19
authors:
- meierschlumpf
---
# Migrate from 0.15.10+ to 1.0.0
In this guide, we will show you how to migrate from Homarr 0.15.10+ to 1.0.0.
If you are still on a version before 0.15.10, please update to 0.15.10 where we introduced the migration export page.
<!-- truncate -->
{/* Historical excerpt boundary. */}
## Export your data
## Preserve your old installation
As 1.0 is a complete rewrite of Homarr and therefor not compatible with the old docker mounts, we need to export your data first.
For this you can navigate to the `Migrate to 1.0` page which you can find under `Management` > `Tools` > `Migrate to 1.0`.
Here you can select which things you want to export and download the zip file.
It will also show you a secret key which you will need to import the secret data like user password hashes, salts and integration secrets.
Homarr 1.0 is a complete rewrite and cannot reuse the old Docker mounts. Keep the 0.15 installation and its data intact while you build the replacement. The historical `Management` > `Tools` > `Migrate to 1.0` export can still be kept as a record, but current Homarr releases cannot import that ZIP or its secret key.
Plan to recreate your apps, integrations, and boards manually in the new installation. A SQLite database backup can be restored from the onboarding welcome screen only when it was created by a compatible current Homarr release; it is not a conversion path for a 0.15 database.
![Export page](./img/old-homarr-export-data.png)
@@ -23,33 +22,20 @@ It will also show you a secret key which you will need to import the secret data
We highly recommend to create a parallel installation of Homarr 1.0.0.
So if something with for example the import does not quite work, the data in the old installation is still available.
To do this you can follow the appropriate [installation guide](https://homarr.dev/docs/category/installation-1) for your system.
To do this you can follow the appropriate [installation guide](/docs/getting-started) for your system.
Please note that if you are using docker you need to specify a diffrent external port for the new installation as the old one had the same port.
So in your compose file the ports would be `7576:7575` instead of `7575:7575`.
## Import your data
Once the installation is completed you can access the new installation. During the onboarding you'll need to select `Import from Homarr before 1.0`.
![Onboarding initial step](./img/onboarding-step-1.png)
<Alert type="warning" title="Legacy guide">
After that you can upload the zip file you downloaded in the first step.
The pre-1.0 ZIP importer is no longer included in current Homarr releases. Keep the old installation available until you have manually verified the replacement. Compatible SQLite backups from current releases can still be restored from the onboarding welcome screen.
![Onboarding import dropzone](./img/old-homarr-import-dropzone.png)
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.
![Onboarding import list](./img/old-homarr-import-choose-imported-data.png)
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.
![Onboarding import key input](./img/old-homarr-import-decryption-token.png)
After that you can continue the onboarding and you should see all your boards and data in the new installation.
See [After the installation](https://homarr.dev/docs/getting-started/after-the-installation) for more information.
</Alert>
## Remove the old installation
After you are happy with the new installation and everything is working as expected you can remove the old installation.
Please make sure to backup the data before you remove the old installation.
Also once the port 7575 is free again you can change the port mapping of the new installation to `7575:7575` if you want to.
Also once the port 7575 is free again you can change the port mapping of the new installation to `7575:7575` if you want to.
@@ -1,14 +1,14 @@
---
title: How to use Argus with Homarr
date: 2025-08-02
authors:
- meierschlumpf
---
# How to use Argus with Homarr
In `v1.32.0` we added the endpoint `/api/info` which returns the current version of Homarr and can therefore be used in Argus to watch for updates.
This guide will show you how to set up a user with apiKey to access the endpoint without providing admin access through it.
<!-- truncate -->
{/* Historical excerpt boundary. */}
![Argus](./img/argus.png)
@@ -64,4 +64,4 @@ Now you can configure Argus to use the user and apiKey we created.
![Argus configuration](./img/argus-configuration.png)
Replace the `<secret>` with the apiKey you created and the deployed version url with the url of your Homarr instance.
Replace the `<secret>` with the apiKey you created and the deployed version url with the url of your Homarr instance.
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: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Loaded 100 of 3556 files, more files were not shown because too many files have changed in this diff. Show more