Files
homarr/ops/README.md
T
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

11 KiB
Raw Blame History

Fork personnel — exploitation

Ce dossier n'existe pas en amont. Il ne créera donc jamais de conflit lors d'un rebase, ce qui est exactement pourquoi les fichiers d'exploitation vivent ici et pas dans scripts/.

Déployer

ops/deploy-lxc.sh --dry-run   # montre ce qui serait écrit, sans rien toucher
ops/deploy-lxc.sh

Le script exige un arbre propre, construit, compare les migrations et l'ABI Node avant de toucher quoi que ce soit, sauvegarde /opt/homarr et la base, arrête le service, synchronise, redémarre, puis affiche les journaux. La commande de retour arrière est imprimée à la fin, horodatage compris.

Ce qu'il ne remplace jamais : /etc/homarr/homarr.env, run.sh, entrypoint.sh, nginx.conf, redis.conf — ils appartiennent à l'installation du LXC, pas au build — ni /var/lib/homarr/db.sqlite, qui est sauvegardée mais laissée en place.

Après déploiement, le contrôle qui compte n'est pas que le nouveau widget s'affiche : c'est que les intégrations préexistantes fonctionnent encore. C'est la seule preuve que SECRET_ENCRYPTION_KEY n'a pas bougé.

Le premier déploiement d'une v2 sur une base v1 est à sens unique : Homarr migre la base au démarrage, et une v1 ne sait plus la lire. Le retour arrière passe alors par la copie /root/homarr-db-<horodatage>.sqlite que fait le script, pas par la seule archive de /opt/homarr. La v2 change aussi l'échelle des tableaux (cellules fixes de 200 × 200) : les widgets paraîtront plus petits, ce qui se règle avec le nombre de colonnes, pas avec un retour en v1.

Déplacer l'état hors de /appdata

/appdata vient de l'image Docker, où c'est un point de montage. Sur une machine, il ne suit aucune convention : l'état d'un service se range sous /var/lib. La correction se fait sur l'instance installée, jamais dans le dépôt — ni le code ni le Dockerfile ne sont touchés, sans quoi chaque rebase sur l'amont rouvrirait le sujet.

sudo ops/use-var-lib-homarr.sh /opt/homarr        # ou /opt/homarr-dev

Le script réécrit /appdata en /var/lib/homarr dans le run.sh et le redis.conf de l'instance, sauvegarde chaque fichier avant de le toucher, et ajoute LOCAL_CERTIFICATE_PATH au démarrage. Cette dernière ligne n'est pas cosmétique : le chemin des certificats n'est pas lu par run.sh mais par l'application, qui retombe sur le dossier de l'image dès qu'il manque — elle recréerait donc /appdata au premier certificat chargé.

Le script ne déplace pas les données ; il imprime les commandes à passer une fois, service arrêté. Sur cette installation, /appdata ne contient qu'un fichier : le certificat racine qui valide la console UniFi locale. Le contrôle d'après-déplacement est donc tout trouvé — si le widget UniFi OS répond encore, le certificat a suivi.

À relancer après chaque assemblage : ops/assemble-standalone.sh recopie le run.sh du dépôt quand l'instance n'en a pas, et celui-là parle de /appdata.

Suivre les mises à jour d'Homarr

Aujourd'hui origin pointe sur le dépôt d'Homarr lui-même et aucune copie distante de ce travail n'existe : une panne de disque effacerait la branche. Premier geste, avant tout le reste :

git remote rename origin upstream
git remote add origin <url-de-ton-Gitea>/homarr.git
git push -u origin feat/unifi-os-integration

Le cycle de montée de version

Homarr publie souvent. Se caler sur une étiquette de version plutôt que sur dev évite de rebaser sur du code en cours :

git fetch upstream --tags
git log --oneline dev..upstream/dev | head        # ce qui est arrivé
git rebase v1.78.0 feat/unifi-os-integration      # remplace par la version visée

Puis, dans cet ordre — chaque étape a déjà attrapé quelque chose :

Depuis la v2, le dépôt se construit avec bun et exige un Node récent, aux versions fixées dans mise.toml (bun 1.4.2, Node ≥ 24.18). Sur ce poste :

export PATH="$HOME/.local/share/nvm/v24.21.0/bin:$PATH"
npm i -g bun@1.4.2                                 # une fois, dans le préfixe de ce Node
bun install                                        # le lockfile bouge presque à chaque version
bun run turbo typecheck                            # les 38 paquets
bun run test packages/integrations/ packages/widgets/ packages/definitions/
bun run test packages/widgets/src/manifest.spec.ts # les points d'enregistrement
ops/deploy-lxc.sh --dry-run

manifest.spec.ts mérite sa ligne à part : c'est le seul test qui détecte un point d'enregistrement perdu dans un conflit.

Après un passage de pnpm à bun, ou si le typecheck se plaint de deux copies d'une même dépendance, repartir d'un node_modules propre : pnpm laissait des copies imbriquées que bun ne nettoie pas.

Les échecs de tests à ne pas confondre avec une régression

Comparer avec l'étiquette amont, pas avec zéro. Sur v2.3.0, 19 tests échouent déjà sans ce fork (Unifi Controller, widgets Custom JSX, Pi-hole, et manifest.spec.ts lui-même, à cause d'une requête Immich absente de refetch-intervals.ts). Pour les lister :

git worktree add /tmp/homarr-amont v2.3.0 && (cd /tmp/homarr-amont && bun install && bun run test packages/)

Deux échecs sont propres au fork, et assumés :

  • api-key-url.spec.ts exige une page de documentation homarr.dev pour chaque intégration ; les nôtres n'en ont pas (documentationSlug: null).
  • widget-integration-map.spec.ts exige que chaque widget accepte l'intégration mock, qui ne sert qu'à la galerie de démonstration de l'amont.

Où les conflits vont tomber

Le gros du travail vit dans des dossiers neufs — packages/integrations/src/unifi-os/, unifi-site-manager/, packages/widgets/src/unifi-*/ — qui ne peuvent pas entrer en conflit. Les points de friction sont les fichiers partagés où ce fork ajoute une ligne dans une liste que l'amont fait vivre aussi :

Fichier Ce qu'on y ajoute
packages/definitions/src/integration.ts les deux genres d'intégration
packages/definitions/src/widget.ts les deux genres de widget
packages/definitions/src/widget-integration-map.ts l'association widget ↔ intégration
packages/definitions/src/docs/widget-doc-slugs.ts le slug de documentation des widgets
packages/integrations/src/base/creator.ts la construction, en import différé
packages/integrations/src/index.ts les exports
packages/integrations/src/types.ts les types réexportés côté client
packages/api/src/router/widgets/index.ts les routeurs tRPC
packages/widgets/src/registry.ts quatre entrées : import de type, module, type, composant
packages/ui/src/widget-icons.ts l'icône
packages/widgets/src/refetch-intervals.ts l'intervalle de rafraîchissement
packages/ui/src/integration-select-grid/integration-grid-shared.ts le libellé de catégorie
apps/nextjs/src/app/[locale]/manage/integrations/_components/secrets/integration-secret-icons.ts l'icône du secret
packages/translation/src/lang/en.json tous les libellés

Deux de ces lignes ne sont vérifiées par aucun compilateur, et les perdre dans un conflit passerait tous les tests :

  • integration-grid-shared.ts (dans packages/ui depuis la v2) est typé Record<string, string> : une entrée manquante affiche UNIFISITEMANAGER en majuscules collées au lieu du nom.
  • en.json n'est pas typé du tout : une clé perdue affiche son chemin brut.

Les deux se voient d'un coup d'œil sur la page « New integration » — c'est le contrôle visuel à faire après chaque montée de version.

Rejouer les résolutions de conflit

Les mêmes conflits reviendront à chaque version, au même endroit. Git sait les rejouer tout seul :

git config rerere.enabled true

À activer avant le premier rebase : Git n'enregistre que ce qu'il voit résoudre. Une fois qu'une résolution est connue, les suivantes sont automatiques — à relire quand même, l'amont peut avoir changé la forme de la liste autour.

Construire ailleurs que sur le poste

SKIP_ENV_VALIDATION=true CI=true DISABLE_REDIS_LOGS=true \
  bun run turbo run build --concurrency=1 --filter=@homarr/nextjs... --filter=@homarr/cli

SKIP_ENV_VALIDATION n'est pas optionnel : le build charge next.config.ts, qui valide les variables d'exécution — dont URL, qui n'a aucun sens à la compilation. Le Dockerfile la pose en ARG pour la même raison. Un dépôt qui construit sans elle ne le doit qu'à un .env traînant à sa racine.

Mémoire mesurée sur ce dépôt : 1,8 Go de PSS au pic, et non les 5 Go que donne la somme naïve des RSS, qui recompte la mémoire partagée. Sur une machine à 2 Go, c'est jouable service arrêté, mais sans marge.

Faire tourner une instance sous l'utilisateur homarr

L'unité homarr-dev.service déclare User=homarr. Le run.sh de l'image ne peut pas fonctionner ainsi : il écrit /etc/nginx/nginx.conf et écoute le port 80. ops/run-rootless.sh le remplace — migrations, secret d'authentification, serveur Next, et rien d'autre.

sudo install -m 755 ops/run-rootless.sh /opt/homarr-dev/run.sh
sudo chown -R homarr:homarr /opt/homarr-dev/apps/nextjs/.next

Le chown n'est pas facultatif : Next écrit son cache d'images sous .next/cache pendant l'exécution.

Trois réglages à corriger dans /etc/homarr/homarr-dev.env, chacun pour une raison distincte :

DB_URL='/var/lib/homarr-dev/db.sqlite'
LOCAL_CERTIFICATE_PATH='/var/lib/homarr-dev/trusted-certificates'

DB_URL doit quitter /var/lib/homarr, qui appartient à root : SQLite écrit aussi ses fichiers -wal et -shm, donc il lui faut le dossier en écriture, pas seulement le fichier. /var/lib/homarr-dev est créé et donné à l'utilisateur du service par le StateDirectory= de l'unité — rien à chowner.

LOCAL_CERTIFICATE_PATH doit être posée, sans quoi l'application retombe sur le dossier de l'image, tente de le recréer et échoue faute de droits. Le CA qui valide la console UniFi doit y être recopié :

sudo cp /var/lib/homarr/trusted-certificates/*.pem /var/lib/homarr-dev/trusted-certificates/
sudo chown -R homarr:homarr /var/lib/homarr-dev

Reste le proxy, qui ne peut pas tourner sous homarr : ops/homarr-dev-proxy.nginx.conf est à déposer dans /etc/nginx/sites-available/ et à activer. Il n'est pas optionnel — le client appelle ws://<hôte>:<port>/websockets sur la même origine que la page, alors que le serveur WebSocket écoute sur 3001. Sans lui, l'application se charge et perd ses mises à jour en direct, sans rien dire.

Enfin : les deux instances ne peuvent pas tourner en même temps. Le port du serveur WebSocket est écrit en dur dans apps/websocket/src/main.ts (3001), donc même en déplaçant le port HTTP, la seconde instance échouerait à se lier.