# Déploiement Procédures d'exploitation pour `horaires.ita-ito.com`. Le [README](README.md) couvre l'installation locale et la configuration d'Authentik. ## Première installation sur le VPS Prérequis : Docker, Docker Compose, et un Traefik déjà en route possédant le réseau externe nommé par `TRAEFIK_NETWORK`. ```bash git clone /opt/horaires && cd /opt/horaires cp .env.example .env sed -i "s|^AUTH_SECRET=.*|AUTH_SECRET=$(openssl rand -base64 33)|" .env sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(openssl rand -base64 24)|" .env # Renseignez APP_DOMAIN, AUTH_URL, AUTH_AUTHENTIK_*, TRANSLATION_API_KEY. mkdir -p backups docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build --wait ``` Les migrations s'appliquent au démarrage. Si elles échouent, le conteneur s'arrête **avant** de servir quoi que ce soit, plutôt que d'exposer une base incohérente : `docker compose logs app` dira pourquoi. Ensuite : ```bash docker compose exec app node .migrator/node_modules/prisma/build/index.js migrate status curl -fsS https://horaires.ita-ito.com/api/health ``` Puis, une fois connecté : *Paramètres* pour le canton et les intervalles de réveil, *Fériés* → **Synchroniser maintenant**, *Horaires* pour la vraie semaine, et enfin l'appairage de l'écran (README). ## Mise à jour ```bash cd /opt/horaires ./scripts/backup.sh # ou attendre le dump nocturne git pull docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build --wait docker compose logs -f app # ^C une fois « démarrage de l'application » ``` `--wait` rend la main seulement quand le *healthcheck* passe. S'il ne passe pas, la mise à jour a échoué et l'ancien conteneur a déjà été remplacé : voir le retour arrière ci-dessous. **Avant une mise à jour qui touche au rendu** (polices, gabarit, logo), sachez que le nom de fichier de l'image change et que tous les écrans redessineront une fois. C'est sans gravité, seulement un cycle de batterie. ## Sauvegarde Le service `backup` dépose un dump compressé par jour dans `./backups` et purge au-delà de `BACKUP_KEEP_DAYS` (14 par défaut). Dump immédiat : ```bash docker compose exec -T db pg_dump -U horaires horaires | gzip > backups/horaires-$(date +%Y%m%d-%H%M%S).sql.gz ``` Ces fichiers contiennent les horaires, les messages et le journal d'audit. Ils ne contiennent **aucun secret** : les jetons d'appareil n'y figurent que sous forme d'empreintes, et les identifiants Authentik vivent dans `.env`, qui n'est pas dans les sauvegardes. Sauvegardez `.env` séparément, ailleurs. ## Restauration ```bash docker compose -f docker-compose.yml -f docker-compose.prod.yml stop app gunzip -c backups/horaires-20260920-030000.sql.gz \ | docker compose exec -T db psql -U horaires -d horaires docker compose -f docker-compose.yml -f docker-compose.prod.yml start app ``` Après une restauration, les écrans appairés depuis le dump ne le sont plus du point de vue de la base : ils recevront un 401 et devront être réappairés par le portail captif. Rien d'autre ne se perd. ## Retour arrière L'image porte la version du dépôt, donc revenir en arrière veut dire revenir au commit : ```bash git log --oneline -10 git checkout docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build --wait ``` **Si la mise à jour comportait une migration**, revenir au code ne suffit pas : le schéma est déjà migré. Prisma ne défait pas une migration. Restaurez alors le dump pris avant la mise à jour, puis reprenez le code. C'est la raison d'être de la sauvegarde préalable. ## Ce qu'il faut surveiller | Signe | Où | Que faire | |---|---|---| | L'écran n'a pas donné signe de vie | alerte du tableau de bord | batterie, puis Wi-Fi de la boutique | | Synchro des fériés en échec | alerte, page Fériés | le cache local tient ; relancer à la main | | Messages sans traduction | alerte, page Messages | `npm run translate:test` pour savoir pourquoi | | Conteneur *unhealthy* | `docker compose ps` | `docker compose logs app` | ## Le panneau tombe sur le certificat TLS Certains firmwares ESP32 échouent sur une chaîne de certificats que tous les navigateurs acceptent. Si l'écran n'arrive à joindre le serveur qu'en clair, `DEVICE_ALLOW_HTTP=true` existe — mais c'est le dernier recours, pas le premier. Dans l'ordre : 1. Vérifiez que l'écran atteint bien le domaine : `docker compose logs app | grep /api/setup`. 2. Vérifiez la chaîne servie : `openssl s_client -connect horaires.ita-ito.com:443 -servername horaires.ita-ito.com | head -20`. Une chaîne incomplète est le cas le plus fréquent et se corrige côté Traefik, pas côté écran. 3. En dernier recours seulement, exposez un hôte virtuel en clair réservé aux routes `/api/setup`, `/api/display`, `/api/log` et `/api/device/image/*`. Le jeton d'appareil circulerait alors en clair : il est distinct de tout autre secret et révocable depuis *Paramètres → Appareils*, ce qui rend l'arbitrage tenable — mais l'administration, elle, reste en TLS.