The e-ink firmware carries a certificate-authority bundle fixed when it was built, so it cannot validate a chain rooted in an authority created afterwards. Let's Encrypt's ISRG Root YR was issued in May 2026 and is not even in an up-to-date Ubuntu CA bundle yet; the kit's firmware predates it. The handshake fails before a request is ever sent, which is why neither Traefik nor the application saw anything at all while the device reported "API connection cannot be established". Ruled out first, with evidence: TLS 1.2 and the ECDHE-RSA-AES-GCM suites an ESP32 needs are both offered, and the intermediate is not cross-signed by an older root, so no alternate path exists in what is served. A Traefik router now serves four device paths over :80, ahead of the entrypoint-wide redirect. The administration stays on TLS. The device token travels in clear; it is used for nothing else and is revocable from the settings page, and the image URL is an unguessable content hash. DEVICE_ALLOW_HTTP existed but was never read — a setting that does nothing misrepresents what it protects. The device routes now refuse an unencrypted request unless it is set, so opening this door is a written decision rather than the silent consequence of a proxy change. DEPLOY.md records the whole diagnosis, including the commands that distinguish a TLS failure from an application one, and what to do the day the firmware learns the new roots. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012cSY9pVhZmJUKNN7wf1Myd
104 lines
5.2 KiB
Bash
104 lines
5.2 KiB
Bash
# =============================================================================
|
|
# ITA ITO — Panneau d'administration des horaires (écran e-ink TRMNL, BYOS)
|
|
# Copiez ce fichier en `.env` et renseignez les valeurs. `.env` n'est JAMAIS commité.
|
|
# =============================================================================
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Application
|
|
# -----------------------------------------------------------------------------
|
|
# Nom de domaine public de l'app sur le VPS. Un seul domaine sert à la fois
|
|
# l'écran et le navigateur : le callback OIDC, l'URL d'image envoyée au panneau
|
|
# et les liens du README en dépendent tous. Doit rester cohérent avec AUTH_URL.
|
|
APP_DOMAIN=horaires.ita-ito.com
|
|
# Port publié sur l'hôte. 3000 est déjà pris par la stack facture_ocr sur la
|
|
# machine de développement, d'où 3010 ; en production Traefik s'en charge et
|
|
# aucun port n'est publié.
|
|
APP_PORT=3010
|
|
APP_BIND=127.0.0.1
|
|
TZ=Europe/Zurich
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Base de données (PostgreSQL 16)
|
|
# -----------------------------------------------------------------------------
|
|
POSTGRES_DB=horaires
|
|
POSTGRES_USER=horaires
|
|
POSTGRES_PASSWORD=
|
|
DATABASE_URL=postgresql://horaires:CHANGEME@db:5432/horaires?schema=public
|
|
# Only needed to run the integration tests. They truncate every table, so this
|
|
# must never point at a database holding anything you want to keep.
|
|
TEST_DATABASE_URL=
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Authentification — Authentik (OIDC, Authorization Code + PKCE)
|
|
# -----------------------------------------------------------------------------
|
|
# Noms de variables imposés par Auth.js v5, identiques au projet api_llm_loxi :
|
|
# le provider est découvert automatiquement à partir d'AUTH_AUTHENTIK_*.
|
|
#
|
|
# URI de redirection à déclarer dans Authentik (correspondance stricte) :
|
|
# <AUTH_URL>/api/auth/callback/authentik
|
|
AUTH_URL=https://horaires.ita-ito.com
|
|
# Générer avec : openssl rand -base64 33
|
|
AUTH_SECRET=
|
|
|
|
AUTH_AUTHENTIK_ID=
|
|
AUTH_AUTHENTIK_SECRET=
|
|
# Doit correspondre EXACTEMENT à l'`issuer` du document de découverte, slash
|
|
# final compris :
|
|
# https://auth.loxi.ch/application/o/<SLUG>/.well-known/openid-configuration
|
|
AUTH_AUTHENTIK_ISSUER=https://auth.loxi.ch/application/o/horaires-ita-ito/
|
|
# Libellé du bouton sur la page de connexion.
|
|
AUTHENTIK_DISPLAY_NAME=Loxi
|
|
# Groupe Authentik dont les membres sont administrateurs. Tout autre utilisateur
|
|
# authentifié est en lecture seule (rôle `viewer`).
|
|
AUTHENTIK_ADMIN_GROUP=horaires-admins
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Écran e-ink — API appareil (BYOS)
|
|
# -----------------------------------------------------------------------------
|
|
# Format d'image servi à l'appareil. Le firmware Seeed référence des .bmp ;
|
|
# basculer sur `png` si l'appareil refuse le BMP.
|
|
DEVICE_IMAGE_FORMAT=bmp
|
|
# Autorise l'appareil à appeler l'API en clair (HTTP). Les routes de l'écran
|
|
# REFUSENT une requête non chiffrée tant que ce réglage vaut autre chose que
|
|
# « true » : c'est une décision explicite, pas un effet de bord d'une
|
|
# configuration de proxy.
|
|
#
|
|
# À activer quand le firmware ESP32 ne peut pas valider la chaîne TLS — le cas
|
|
# lorsque la racine Let's Encrypt est plus récente que le firmware lui-même.
|
|
# Le jeton d'appareil circule alors en clair : il ne sert à rien d'autre et se
|
|
# révoque depuis /admin/parametres. Voir DEPLOY.md.
|
|
DEVICE_ALLOW_HTTP=false
|
|
# Intervalles de réveil, en secondes. Court quand la boutique est ouverte ou sur le
|
|
# point de changer d'état, long la nuit et les jours de fermeture.
|
|
DEVICE_REFRESH_OPEN_SEC=600
|
|
DEVICE_REFRESH_CLOSED_SEC=7200
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Traduction FR -> EN — api.loxi.ch (projet api_llm_loxi)
|
|
# -----------------------------------------------------------------------------
|
|
# ATTENTION : cette API n'est ni Anthropic- ni OpenAI-compatible.
|
|
# Contrat réel : POST {TRANSLATION_API_URL}/api/generate {"model_id": <int>, "prompt": "..."}
|
|
# -> 200 {"stdout": "...", "stderr": "...", "exit_code": 0}
|
|
# Un échec du CLI renvoie quand même HTTP 200 : c'est `exit_code` qui fait foi.
|
|
TRANSLATION_API_URL=https://api.loxi.ch
|
|
# Clé API créée depuis la page « API keys » du dashboard api-llm-loxi (format llk_...).
|
|
TRANSLATION_API_KEY=
|
|
TRANSLATION_API_FLAVOR=loxi
|
|
# Nom du modèle, résolu en `model_id` au démarrage via GET /api/models.
|
|
# L'alias `haiku` pointe toujours vers la dernière version.
|
|
TRANSLATION_MODEL_NAME=haiku
|
|
# Le backend lance réellement le CLI Claude Code : prévoir large.
|
|
TRANSLATION_TIMEOUT_MS=30000
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Jours fériés — OpenHolidays (aucune clé requise)
|
|
# -----------------------------------------------------------------------------
|
|
HOLIDAYS_API_URL=https://openholidaysapi.org
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Déploiement (production, docker-compose.prod.yml)
|
|
# -----------------------------------------------------------------------------
|
|
TRAEFIK_NETWORK=web
|
|
TRAEFIK_ENTRYPOINT=websecure
|
|
TRAEFIK_CERTRESOLVER=myresolver
|