chore(ops): add LXC deploy script and fork maintenance notes

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
zacharyandClaude Opus 5 committed 2026-09-13 16:45:53 +02:00
1 parent 7257045b8d
commit 17a2e577c0
2 files changed
+267

No files matched your search

+112
View File
@@ -0,0 +1,112 @@
# 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é.
## 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 :
```bash
pnpm install # le lockfile bouge presque à chaque version
pnpm turbo typecheck # les 37 paquets
pnpm test packages/integrations/ packages/widgets/
pnpm 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 : il compare l'icône du catalogue à
celle de la définition **par identité**, et c'est le seul test qui détecte un
point d'enregistrement perdu dans un conflit.
### 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/integration-doc-slugs.ts` | les slugs de documentation |
| `packages/definitions/src/docs/widget-doc-slugs.ts` | idem, côté widgets |
| `packages/integrations/src/base/creator.ts` | la construction des classes |
| `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/manifest.ts` | deux cartes distinctes, module et composant |
| `packages/widgets/src/index.tsx` | l'import et l'entrée |
| `packages/widgets/src/catalog.ts` | l'icône |
| `packages/widgets/src/refetch-intervals.ts` | l'intervalle de rafraîchissement |
| `apps/nextjs/src/components/integration/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` 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.
+155
View File
@@ -0,0 +1,155 @@
#!/usr/bin/env bash
#
# Déploie le build standalone de ce dépôt sur le LXC Homarr.
#
# Reproduit la disposition que le Dockerfile officiel assemble (ses COPY du
# stage `runner`), parce que l'installation du LXC en vient : /opt/homarr a la
# même forme qu'une image, sans le conteneur autour.
#
# Ce que le script NE touche PAS, volontairement :
# - /etc/homarr/homarr.env : secrets et configuration de l'instance
# - run.sh, entrypoint.sh, nginx.conf, redis.conf : modifiés par l'installateur
# du LXC, ils appartiennent à l'installation et non au build
# - /var/lib/homarr/db.sqlite : la base, sauvegardée mais jamais remplacée
#
# Usage : ops/deploy-lxc.sh [--dry-run]
#
set -euo pipefail
HOST="${HOMARR_HOST:-zachary@10.70.1.13}"
REMOTE_DIR="${HOMARR_REMOTE_DIR:-/opt/homarr}"
REMOTE_DB="${HOMARR_REMOTE_DB:-/var/lib/homarr/db.sqlite}"
SERVICE="${HOMARR_SERVICE:-homarr}"
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
DRY_RUN=""
[ "${1:-}" = "--dry-run" ] && DRY_RUN="--dry-run"
say() { printf '\n\033[1m==> %s\033[0m\n' "$1"; }
# ---------------------------------------------------------------------------
say "Contrôles préalables"
cd "$REPO"
[ -z "$(git status --porcelain)" ] || {
echo "Arbre de travail sale. Committe ou remise avant de déployer." >&2
exit 1
}
echo "Commit déployé : $(git log --oneline -1)"
# Une migration présente ici et absente en prod n'est pas bloquante — Homarr les
# applique au démarrage — mais elle rend le rollback non trivial : restaurer
# l'archive ne défait pas un schéma déjà migré. On veut le savoir AVANT.
local_migration="$(find packages/db/migrations -name '*.sql' -printf '%f\n' | sort | tail -1)"
remote_migration="$(ssh "$HOST" "find $REMOTE_DIR/db/migrations -name '*.sql' -printf '%f\n' | sort | tail -1")"
echo "Dernière migration — locale : $local_migration | prod : $remote_migration"
if [ "$local_migration" != "$remote_migration" ]; then
echo
echo "!! Les migrations divergent. Le rollback ne sera PAS une simple restauration."
echo " Sauvegarde la base avant de continuer, puis relance en connaissance de cause."
read -r -p " Continuer quand même ? [oui/NON] " answer
[ "$answer" = "oui" ] || exit 1
fi
# ---------------------------------------------------------------------------
say "Build"
pnpm turbo build --filter=@homarr/nextjs... --filter=@homarr/cli
# ---------------------------------------------------------------------------
say "Assemblage de l'arborescence à envoyer"
STAGE="$(mktemp -d)"
trap 'rm -rf "$STAGE"' EXIT
# L'ordre suit le Dockerfile : le standalone d'abord, le reste par-dessus.
cp -a apps/nextjs/.next/standalone/. "$STAGE/"
mkdir -p "$STAGE/apps/nextjs/.next"
cp -a apps/nextjs/.next/static "$STAGE/apps/nextjs/.next/static"
cp -a apps/nextjs/public "$STAGE/apps/nextjs/public"
cp -a apps/nextjs/next.config.ts apps/nextjs/package.json pnpm-workspace.yaml "$STAGE/"
mkdir -p "$STAGE/apps/cli"
cp -a packages/cli/cli.cjs "$STAGE/apps/cli/cli.cjs"
# Binaire natif : compilé ici, chargé là-bas. Même architecture et même ABI Node
# (24.x des deux côtés) sont donc des prérequis — le contrôle est plus bas.
mkdir -p "$STAGE/build"
cp -a node_modules/better-sqlite3/build/Release/better_sqlite3.node "$STAGE/build/"
mkdir -p "$STAGE/db"
cp -a packages/db/migrations "$STAGE/db/migrations"
echo "Arborescence prête : $(du -sh "$STAGE" | cut -f1)"
# ---------------------------------------------------------------------------
say "Contrôle de compatibilité du binaire natif"
local_abi="$(node -p 'process.versions.modules')"
remote_abi="$(ssh "$HOST" 'node -p "process.versions.modules"')"
local_arch="$(uname -m)"
remote_arch="$(ssh "$HOST" 'uname -m')"
echo "ABI Node — local : $local_abi | prod : $remote_abi"
echo "Arch — local : $local_arch | prod : $remote_arch"
if [ "$local_abi" != "$remote_abi" ] || [ "$local_arch" != "$remote_arch" ]; then
echo "!! better_sqlite3.node ne se chargera pas en prod. Build sur le LXC, ou aligne Node." >&2
exit 1
fi
if [ -n "$DRY_RUN" ]; then
say "Mode --dry-run : arrêt avant toute modification de la prod"
rsync -an --delete \
--exclude 'run.sh' --exclude 'entrypoint.sh' \
--exclude 'nginx.conf' --exclude 'redis.conf' \
"$STAGE/" "$HOST:$REMOTE_DIR/" | head -40
exit 0
fi
# ---------------------------------------------------------------------------
say "Sauvegarde"
stamp="$(date +%F-%H%M)"
ssh -t "$HOST" "sudo tar czf /root/homarr-backup-$stamp.tar.gz $REMOTE_DIR \
&& sudo cp $REMOTE_DB /root/homarr-db-$stamp.sqlite \
&& sudo ls -lh /root/homarr-backup-$stamp.tar.gz /root/homarr-db-$stamp.sqlite"
# ---------------------------------------------------------------------------
say "Arrêt du service"
# Jamais de rsync sur un Next.js en cours d'exécution : il lit ses chunks à la
# demande et servirait des morceaux de deux builds différents.
ssh -t "$HOST" "sudo systemctl stop $SERVICE && ! systemctl is-active --quiet $SERVICE && echo arrêté"
# ---------------------------------------------------------------------------
say "Envoi"
# --delete retire ce que le build ne produit plus ; les quatre fichiers exclus
# sont protégés de cette suppression par leur exclusion même.
rsync -a --delete --rsync-path="sudo rsync" \
--exclude 'run.sh' --exclude 'entrypoint.sh' \
--exclude 'nginx.conf' --exclude 'redis.conf' \
"$STAGE/" "$HOST:$REMOTE_DIR/"
# ---------------------------------------------------------------------------
say "Redémarrage"
ssh -t "$HOST" "sudo systemctl start $SERVICE"
sleep 10
ssh "$HOST" "systemctl is-active $SERVICE; curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1/"
say "Journaux (40 dernières lignes)"
ssh -t "$HOST" "sudo journalctl -u $SERVICE -n 40 --no-pager"
cat <<EOF
Déployé. Vérifie dans le navigateur que les intégrations PRÉEXISTANTES
fonctionnent encore — c'est ce qui prouve que SECRET_ENCRYPTION_KEY est intact.
Retour arrière :
ssh $HOST 'sudo systemctl stop $SERVICE \\
&& sudo rm -rf $REMOTE_DIR \\
&& sudo tar xzf /root/homarr-backup-$stamp.tar.gz -C / \\
&& sudo systemctl start $SERVICE'
La base n'a pas été remplacée ; sa copie est dans /root/homarr-db-$stamp.sqlite.
EOF