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

240 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```bash
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.
```bash
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 :
```bash
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 :
```bash
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 :
```bash
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
```
```bash
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 :
```bash
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 :
```bash
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
```bash
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.
```bash
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 :
```ini
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é :
```bash
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.