Administration dans un conteneur Docker (Dockerfile, exemples compose et nginx) ; arrêt propre sur SIGTERM

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mf2kATMtqVABHSYdxJMnim
This commit is contained in:
FabriceandClaude Opus 5.5 committed 2026-10-09 16:59:27 +00:00
1 parent 4852361ea6
commit 02473fafbf
6 files changed
+89 -8

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
.git
Scripts
tests
exemples
**/__pycache__
*.egg-info
build
dist
+24
View File
@@ -0,0 +1,24 @@
# Interface RTG en conteneur, derrière le nginx du site
# (README, « L'administration dans Docker »).
FROM python:3.12-slim
# ffmpeg (libx265, libx264), exiftool, fuseaux horaires
RUN apt-get update \
&& apt-get install -y --no-install-recommends ffmpeg libimage-exiftool-perl tzdata \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY pyproject.toml README.md ./
COPY rtg ./rtg
RUN pip install --no-cache-dir . && rtg verifier
# Mot de passe (acces.toml) : /config/rtg, à monter depuis le Mac.
# Le conteneur voit les disques du Mac sous /Volumes, aux mêmes chemins que
# le Mac : les sources notées dans les voyage.toml restent valables.
ENV XDG_CONFIG_HOME=/config TZ=Europe/Paris PYTHONUNBUFFERED=1 \
RTG_RACINE=/Volumes/CLEROADTRIPS/Road-Trips RTG_PREFIXE=/admin
EXPOSE 8765
# Aucun port publié : seul le nginx du site (même réseau Docker) le joint.
CMD exec rtg --racine "$RTG_RACINE" interface --proxy --prefixe "$RTG_PREFIXE" \
--ecoute 0.0.0.0 --sans-navigateur
+11
View File
@@ -181,6 +181,17 @@ Pour administrer le site depuis son adresse publique (bouton « Connexion » en
``` ```
4. Dans l'interface, Réglages → « Administration depuis le site » : adresse `/admin/`. Après « Refaire l'accueil », le bouton « Connexion » apparaît sur la page d'accueil. 4. Dans l'interface, Réglages → « Administration depuis le site » : adresse `/admin/`. Après « Refaire l'accueil », le bouton « Connexion » apparaît sur la page d'accueil.
### L'administration dans Docker
Plutôt que de laisser `rtg interface` ouvert dans un Terminal, on peut le faire tourner dans un conteneur, à côté du nginx du site : il démarre avec Docker et redémarre tout seul. Le `Dockerfile` est à la racine de RoadTripsGenerator (Python, ffmpeg, exiftool).
1. Définir le mot de passe sur le Mac, une fois : `rtg mot-de-passe`. Le conteneur lit le même fichier.
2. Ajouter le service `rtg` au `docker-compose.yml` du site : voir `exemples/docker-compose.yml`. Le conteneur voit `/Volumes` aux mêmes chemins que le Mac : la clé, le disque des rushs… Les sources notées dans les `voyage.toml` restent donc valables.
3. Dans `nginx.conf`, faire pointer `/admin/` vers ce conteneur : voir `exemples/nginx-admin.conf`. Le site reste en ligne même si le conteneur RTG est arrêté.
4. `docker compose up -d --build`.
Après une mise à jour de RTG : `docker compose up -d --build rtg`. Un disque branché après le démarrage du conteneur peut demander `docker compose restart rtg`. L'encodage utilise les processeurs attribués à Docker (réglages de Docker Desktop, « Resources ») ; l'encodeur matériel VideoToolbox du Mac n'est pas disponible dans le conteneur.
Le bouton n'est qu'un lien : la protection est celle de l'interface (mot de passe, blocage après plusieurs essais faux, cookie de session limité à `/admin/`, envoyé seulement en HTTPS). Le bouton n'est qu'un lien : la protection est celle de l'interface (mot de passe, blocage après plusieurs essais faux, cookie de session limité à `/admin/`, envoyé seulement en HTTPS).
Tout ce que fait l'interface s'écrit dans `site.toml` et les `voyage.toml`. On peut toujours utiliser les commandes ci-dessous ou modifier ces fichiers à la main. Une réécriture par l'interface conserve les valeurs mais pas les commentaires. Tout ce que fait l'interface s'écrit dans `site.toml` et les `voyage.toml`. On peut toujours utiliser les commandes ci-dessous ou modifier ces fichiers à la main. Une réécriture par l'interface conserve les valeurs mais pas les commentaires.
+29
View File
@@ -0,0 +1,29 @@
# docker-compose.yml du site, avec l'administration RTG dans son propre conteneur.
# Après une mise à jour de RTG : docker compose up -d --build rtg
services:
roadtrip:
image: nginx:alpine
restart: unless-stopped
networks:
- ouaibe_network
volumes:
- /Volumes/CLEROADTRIPS/Road-Trips:/usr/share/nginx/html:ro
- ./nginx.conf:/etc/nginx/nginx.conf:ro
labels:
- traefik.enable=true
- traefik.docker.network=ouaibe_network
- traefik.http.routers.ouaibe_roadtrip.rule=Host(`roadtrip.ouaibe.fr`) || Host(`roadtrips.ouaibe.fr`)
- traefik.http.routers.ouaibe_roadtrip.entrypoints=websecure
rtg:
build: /Volumes/CLEROADTRIPS/RoadTripsGenerator
restart: unless-stopped
networks:
- ouaibe_network
volumes:
- /Volumes:/Volumes # la clé, le LaCie… aux mêmes chemins que sur le Mac
- ${HOME}/.config/rtg:/config/rtg # le mot de passe défini avec « rtg mot-de-passe »
networks:
ouaibe_network:
external: true
+8 -6
View File
@@ -1,15 +1,17 @@
# Bloc à ajouter dans nginx.conf, à l'intérieur de « server { … } », quand le # Bloc à ajouter dans nginx.conf, à l'intérieur de « server { … } », quand le
# site est servi par un conteneur nginx derrière Traefik (Traefik → nginx → RTG). # site est servi par un conteneur nginx derrière Traefik (Traefik → nginx → RTG).
# RTG tourne sur le Mac : rtg interface --proxy --prefixe /admin
#
# Dans docker-compose.yml, sous le service nginx, ajouter aussi :
# extra_hosts:
# - "host.docker.internal:host-gateway"
# « ^~ » : sinon une règle du type « location ~* \.(js|css|png)$ » déjà # « ^~ » : sinon une règle du type « location ~* \.(js|css|png)$ » déjà
# présente dans nginx.conf prendrait les fichiers de l'interface pour ceux du site. # présente dans nginx.conf prendrait les fichiers de l'interface pour ceux du site.
location ^~ /admin/ { location ^~ /admin/ {
proxy_pass http://host.docker.internal:8765; # RTG dans le conteneur « rtg » (exemples/docker-compose.yml). Passer par
# une variable laisse nginx démarrer même si ce conteneur est arrêté.
resolver 127.0.0.11 valid=30s;
set $rtg http://rtg:8765;
proxy_pass $rtg;
# RTG lancé à la main sur le Mac à la place : remplacer les trois lignes
# ci-dessus par « proxy_pass http://host.docker.internal:8765; ».
proxy_set_header Host $host; proxy_set_header Host $host;
# Traefik a déjà noté l'adresse du visiteur et le https : on les transmet tels quels. # Traefik a déjà noté l'adresse du visiteur et le https : on les transmet tels quels.
proxy_set_header X-Forwarded-For $http_x_forwarded_for; proxy_set_header X-Forwarded-For $http_x_forwarded_for;
+9 -2
View File
@@ -17,12 +17,13 @@ Sécurité (voir aussi securite.py) :
""" """
import io import io
import json import json
import ssl
import mimetypes import mimetypes
import os import os
import platform import platform
import re import re
import shutil import shutil
import signal
import ssl
import string import string
import threading import threading
import webbrowser import webbrowser
@@ -466,7 +467,8 @@ class Application:
raccourcis = [("Dossier personnel", os.path.expanduser("~")), ("Site", self.racine)] raccourcis = [("Dossier personnel", os.path.expanduser("~")), ("Site", self.racine)]
if r.q("v"): if r.q("v"):
raccourcis.append(("Ce voyage", os.path.join(self.racine, r.q("v")))) raccourcis.append(("Ce voyage", os.path.join(self.racine, r.q("v"))))
if platform.system() == "Darwin" and os.path.isdir("/Volumes"): # /Volumes : disques du Mac, y compris vus depuis le conteneur Docker.
if os.path.isdir("/Volumes"):
raccourcis += [(d, os.path.join("/Volumes", d)) for d in sorted(os.listdir("/Volumes")) raccourcis += [(d, os.path.join("/Volumes", d)) for d in sorted(os.listdir("/Volumes"))
if not d.startswith(".")] if not d.startswith(".")]
elif platform.system() == "Windows": elif platform.system() == "Windows":
@@ -908,6 +910,11 @@ def lancer(racine, port=8765, ouvrir=True, distant=False, certificat=None, cle=N
print(" (Ctrl+C pour arrêter)") print(" (Ctrl+C pour arrêter)")
if ouvrir: if ouvrir:
threading.Timer(0.5, lambda: webbrowser.open(url)).start() threading.Timer(0.5, lambda: webbrowser.open(url)).start()
# « docker stop » envoie SIGTERM : on s'arrête aussitôt, comme sur Ctrl+C.
def sur_sigterm(*_):
raise KeyboardInterrupt
signal.signal(signal.SIGTERM, sur_sigterm)
try: try:
serveur.serve_forever() serveur.serve_forever()
except KeyboardInterrupt: except KeyboardInterrupt: