-
Notifications
You must be signed in to change notification settings - Fork 1
Deploying.fr
deploy/ est la véritable forme multi-utilisateur de Gramps Connect :
un frontend app/ conteneurisé + backend
gramps-web-api, adossé à un véritable Postgres (via l'extension
SharedPostgreSQL), devant lequel se trouve Caddy pour le TLS, destiné
à être réellement hébergé quelque part — de vrais secrets, un vrai
domaine/certificat, et plusieurs utilisateurs ayant chacun leur propre
identifiant. C'est aussi le seul moyen de voir la collaboration en
direct en action ; la version autonome pour ordinateur
est conçue pour un seul utilisateur, il n'y a donc personne d'autre
dont regarder apparaître les modifications.
Le backend est l'image officielle et non modifiée dmstraub/gramps-webapi
— la même que la propre CI de gramps-project/gramps-web-api publie à
chaque version, et sur laquelle gramps-project/gramps-web lui-même
s'appuie — pas un build depuis les sources que ce dépôt maintiendrait.
Cela garde ce déploiement comme une instance gramps-web-api standard
et ordinaire, à laquelle tout client compatible peut parler, pas
seulement le propre frontend de Gramps Connect. Les extensions
SharedPostgreSQL/PostgreSQL/FilterRules/JSON, le support
multi-arbres, et les traductions compilées viennent tous déjà de cette
image ; la seule chose que deploy/Dockerfile ajoute est le frontend
de app/, superposé par-dessus comme des fichiers statiques. Le
compromis : cette image en amont est construite sur gramps-web-base
(~4,3 Go — torch, sentence-transformers, opencv, 45 paquets de langue
tesseract) avec des extras d'IA installés sans condition, puisqu'il
n'existe pas de variante allégée officielle à récupérer à la place.
Frontend et backend partagent un seul conteneur/origine par défaut
(gramps_webapi sert la SPA construite via STATIC_PATH et traite
/api/* dans le même processus), Gramps Connect lui-même n'a donc
besoin d'aucune configuration CORS — cela n'est pertinent que pour un
frontend différent, hébergé séparément, qui appelle depuis
l'extérieur (dans ce cas, définir GRAMPSWEB_CORS_ORIGINS dans
deploy/.env).
Services : caddy (terminaison TLS, le seul point d'entrée
publié), app (gunicorn, sert le frontend + /api/*), worker
(Celery, exécute les tâches d'import/média/réindexation de recherche),
postgres (données d'arbre via SharedPostgreSQL), redis (broker
Celery).
cp deploy/.env.example deploy/.env # puis le modifier -- voir les commentaires du fichier
docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -d --buildPuis visiter https://localhost (Caddy s'émet automatiquement un
certificat local auto-signé — accepter l'avertissement du navigateur).
Construire une fois sur les runners de GitHub plutôt que sur l'hôte
(gh workflow run build-docker.yml, ou le déclencher depuis l'onglet
Actions), ce qui pousse ghcr.io/<owner>/gramps-connect:latest, puis
sur l'hôte :
cp deploy/.env.example deploy/.env # cette fois avec de vrais secrets/domaine
docker compose -f deploy/docker-compose.yml --env-file deploy/.env pull
docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -dFaire pointer un domaine vers l'hôte et modifier deploy/Caddyfile
(voir TLS ci-dessous) pour obtenir un vrai certificat au lieu
de l'auto-signé.
Toutes les commandes ci-dessous supposent d'être à la racine du dépôt.
docker compose est la forme plugin ; si votre installation Docker n'a
que le binaire autonome, utiliser docker-compose à la place (mêmes
options dans les deux cas).
# État des cinq services
docker compose -f deploy/docker-compose.yml ps
# Logs (ajouter -f pour suivre, --tail=100 pour limiter)
docker compose -f deploy/docker-compose.yml logs app
docker compose -f deploy/docker-compose.yml logs worker
# Redémarrer un service (par ex. après un changement dans deploy/.env)
docker compose -f deploy/docker-compose.yml up -d
# ^ recrée tout service dont la configuration (image, environnement, volumes)
# a changé ; ajouter d'abord --build si deploy/Dockerfile ou le code
# source du backend dans app/ a été modifié.
# Tout arrêter, garder les données (les volumes survivent)
docker compose -f deploy/docker-compose.yml down
# Arrêter et supprimer toutes les données (attention -- supprime Postgres, médias, utilisateurs, etc.)
docker compose -f deploy/docker-compose.yml down -v
# Ouvrir un shell dans un conteneur en cours d'exécution
docker compose -f deploy/docker-compose.yml exec app shCe déploiement Docker n'a pas de mot de passe par défaut — vous
définissez vous-même GRAMPSWEB_ADMIN_USER/GRAMPSWEB_ADMIN_PASSWORD
dans deploy/.env avant le premier démarrage, et c'est ce que le point
d'entrée sème. Rien ne génère ou n'affiche de mot de passe pour vous.
C'est différent de gramps-connect-desktop (le build de
démonstration PyInstaller mono-utilisateur, sans rapport avec ce
déploiement Docker) : il sème toujours un compte fixe admin/admin,
ce qui est acceptable là puisque c'est une démo locale jetable, pas
quelque chose exposé sur un vrai serveur.
Au premier démarrage (la première fois que les volumes
app-users/app-db sont vides), le point d'entrée :
- génère et conserve durablement une clé secrète Flask si
GRAMPSWEB_SECRET_KEYa été laissé vide - exécute les migrations de la base de données utilisateurs
- sème un utilisateur administrateur du site (sans arbre, rôle 5) à
partir de
GRAMPSWEB_ADMIN_USER/GRAMPSWEB_ADMIN_PASSWORD(les deux requis dansdeploy/.env— pas de repliadmin/admin)
Un administrateur du site sans arbre peut créer/lister/supprimer des
arbres via l'API, mais — puisque le frontend de app/ n'a pas
d'interface de sélection d'arbre et s'attend toujours à ce que le JWT
de l'utilisateur connecté porte déjà un arbre — ne peut pas parcourir
les données d'un arbre tant qu'il n'y est pas affecté. Il n'y a pas
de commande CLI pour créer un arbre en mode multi-arbres, donc créer le
premier arbre via l'API (une seule fois) :
TOKEN=$(curl -sk -X POST https://localhost/api/token/ \
-H 'Content-Type: application/json' \
-d '{"username":"<admin>","password":"<mot de passe admin>"}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")
TREE_ID=$(curl -sk -X POST https://localhost/api/trees/ \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"Mon arbre familial"}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
echo "$TREE_ID"Ensuite, soit affecter l'administrateur du site lui-même à cet arbre, soit (recommandé — garde l'administration du site et la propriété de l'arbre séparées) créer plutôt un utilisateur dédié, rattaché à l'arbre :
# Option A : affecter l'administrateur du site existant à l'arbre.
curl -sk -X PUT "https://localhost/api/users/<admin>/" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d "{\"tree\":\"$TREE_ID\"}"
# Option B : créer à la place un utilisateur ordinaire séparé, rattaché
# à l'arbre. email et full_name sont requis par le schéma même si non
# utilisés.
curl -sk -X POST "https://localhost/api/users/<username>/" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d "{\"email\":\"<email>\",\"full_name\":\"<nom complet>\",\"password\":\"<mot de passe>\",\"role\":4,\"tree\":\"$TREE_ID\"}"role est un entier (gramps_webapi.auth.const) :
| Rôle | Valeur | Remarques |
|---|---|---|
| ADMIN | 5 | Administrateur du site ; seul rôle pouvant être sans arbre |
| OWNER | 4 | Contrôle complet de son arbre |
| EDITOR | 3 | Peut modifier les données de l'arbre |
| CONTRIBUTOR | 2 | Peut ajouter des données |
| MEMBER | 1 | Accès en lecture |
| GUEST | 0 | Accès en lecture minimal |
Ensuite, se connecter sur https://localhost/ avec l'utilisateur qui
vient d'être créé — les jetons existants émis avant une affectation à
un arbre ne le porteront pas, donc se déconnecter/reconnecter si une
session déjà ouverte a été réutilisée.
TOKEN=$(curl -sk -X POST https://localhost/api/token/ \
-H 'Content-Type: application/json' \
-d '{"username":"<owner>","password":"<mot de passe>"}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")
# 1. Importer le fichier XML .gramps. IMPORTANT : cet endpoint lit le
# corps brut de la requête et l'écrit directement sur le disque -- il
# ne parse PAS multipart/form-data, donc utiliser --data-binary, pas
# -F/--form de curl (un envoi multipart corrompt silencieusement le
# fichier avec des octets de délimiteur/en-tête, et l'importateur de
# Gramps échoue alors avec un générique et peu utile "Import failed"
# sans plus de détail).
curl -sk -X POST https://localhost/api/importers/gramps/file \
-H "Authorization: Bearer $TOKEN" --data-binary @example.gramps
# La réponse est un identifiant de tâche (l'import s'exécute sur le
# service worker via Celery) -- l'interroger jusqu'à "state":"SUCCESS"
# (ou "FAILURE") :
curl -sk https://localhost/api/tasks/<task_id> -H "Authorization: Bearer $TOKEN"
# 2. Médias : compresser en zip les fichiers médias référencés (tout
# sous-ensemble/sur-ensemble convient -- les fichiers sont associés
# par somme de contrôle, ceux non référencés sont ignorés) et
# envoyer l'archive, à nouveau comme corps brut :
curl -sk -X POST https://localhost/api/media/archive/upload/zip \
-H "Authorization: Bearer $TOKEN" --data-binary @media.zip
# 3. Vérifier : nombre d'objets pour l'arbre de l'utilisateur connecté.
curl -sk https://localhost/api/metadata/ -H "Authorization: Bearer $TOKEN" \
| python3 -m json.toolPiège connu : avec GRAMPSWEB_MEDIA_PREFIX_TREE=True (défini par
défaut dans ce fichier compose), les médias sont stockés sous
MEDIA_BASE_DIR/<tree_id>/, mais rien ne crée automatiquement ce
sous-répertoire par arbre — ni POST /api/trees/, ni la tâche
d'envoi d'archive média. Envoyer des médias pour un arbre échoue avec
Directory /app/media/<tree_id> does not exist tant qu'il n'a pas été
créé une fois :
docker compose -f deploy/docker-compose.yml exec app mkdir -p /app/media/<tree_id>Envois de médias volumineux qui expirent : les endpoints d'envoi
aussi bien du fichier unique (POST /api/media/) que de l'archive ZIP
(POST /api/media/archive/upload/zip) lisent le corps de la requête
de façon synchrone à l'intérieur du worker gunicorn de app avant de
transmettre à Celery, donc un fichier volumineux ou une connexion lente
peut dépasser le délai d'expiration par requête de gunicorn — le
navigateur voit alors une simple Internal Server Error sans corps
JSON (venant de gunicorn/Caddy, pas de Gramps). deploy/docker-compose.yml
fixe GUNICORN_TIMEOUT à 600s par défaut ; l'augmenter encore via
GUNICORN_TIMEOUT dans deploy/.env (voir les commentaires de ce
fichier) si les envois échouent toujours.
caddy est le seul service avec des ports publiés (80/443) et
termine le TLS devant app. Sans domaine configuré, il génère et
conserve sa propre AC locale et en émet automatiquement un certificat
auto-signé (tls internal de deploy/Caddyfile) — les navigateurs
afficheront un avertissement de confiance la première fois ; l'accepter
pour continuer (ou ajouter l'AC générée au magasin de confiance du
système/navigateur si vous voulez que l'avertissement disparaisse sans
vrai domaine). Le port 80 redirige vers le 443.
Une fois qu'un vrai domaine pointe vers ce serveur, modifier
deploy/Caddyfile : remplacer le bloc :443 { tls internal ... } par
example.com { reverse_proxy app:5000 } et supprimer le bloc :80 —
Caddy gère l'émission ACME, le renouvellement et la redirection du
port 80 automatiquement pour un vrai domaine, aucune directive tls
nécessaire. Mettre aussi à jour PUBLIC_URL dans deploy/.env avec le
vrai domaine https://, puis
docker compose -f deploy/docker-compose.yml up -d pour prendre en
compte les deux changements.
Non branché par défaut (pas de service grampsweb dans
docker-compose.yml), mais utile à savoir : puisque le backend est une
instance gramps-web-api ordinaire et non modifiée,
gramps-project/gramps-web (l'autre frontend officiel de Gramps) peut
aussi tourner contre ce même backend — mêmes données, mêmes
arbres/utilisateurs, juste une interface différente sur un port
différent.
gramps-web publie ghcr.io/gramps-project/grampsjs:latest exactement
pour cela : nginx servant uniquement son propre build statique, aucun
backend intégré. Sa configuration nginx redirige /api en
reverse-proxy vers une variable d'environnement API_HOST au démarrage
du conteneur, donc du point de vue du navigateur c'est same-origin —
pas besoin de GRAMPSWEB_CORS_ORIGINS pour ce cas précis (ce réglage
est pour une instance gramps-web hébergée ailleurs complètement, qui
appelle en cross-origin plutôt qu'à travers ce proxy).
Pour l'ajouter, un service docker-compose.yml dans ce genre :
grampsweb:
image: ghcr.io/gramps-project/grampsjs:latest
environment:
API_HOST: http://app:5000
# Le résolveur DNS intégré de Docker -- la directive resolver de
# default.conf.template exige que cela soit défini explicitement.
NAME_SERVER: 127.0.0.11
depends_on:
- app
restart: unless-stoppedpublié sur son propre port (soit directement, par ex.
ports: ["8081:80"], soit devant Caddy avec un second bloc dans le
style :8443 { reverse_proxy grampsweb:80 } dans deploy/Caddyfile
pour un TLS assorti au service app).
- Les données persistent dans des volumes Docker nommés (
app-db,app-media,app-indexdir,app-users,app-secret,app-cache,app-tmp,postgres-data,caddy-data,caddy-config).docker compose down(sans-v) les conserve ;docker compose down -vsupprime tout (y compris l'AC générée — c'est un nouvel avertissement de confiance du navigateur au prochain démarrage, pas seulement une perte de données). -
redis(broker/backend de résultats Celery) est requis pour les tâches en arrière-plan (indexation de recherche, gros travaux d'import/export) — pas optionnel. Il en va de même pour le serviceworker: import, envoi d'archive média et réindexation de recherche passent tous par Celery une foisGRAMPSWEB_CELERY_CONFIG__*défini, et restent pour toujours comme une tâche non satisfaite sans quelque chose qui consomme la file d'attente.app-cache(/app/cache) doit être un volume partagé entreappetworkerpour la même raison : le gestionnaire de requêtes du conteneur app y écrit le fichier envoyé, puis la tâche Celery du conteneur worker le relit. - Le service
workertourne avec--pool=solo(pas de forking), par précaution face au pool prefork par défaut de Celery qui forkerait un processus ayant déjà touché un véritable état PyGObject/GTK (l'importgi.repository.GLibde Gramps). Une seule instance de worker n'a pas besoin de la concurrence à laquelle--pool=solorenonce, mais cela vaut la peine d'être reconsidéré si le débit du worker devient un jour un goulot d'étranglement. - Le cache local côté navigateur de
app/(sql.js, OPFS) est indexé par un nom de fichier fixe par vue, pas par URL de backend ou ID d'arbre — voir Architecture pour le piège que cela cause et comment le résoudre. -
.github/workflows/build-docker.yml(déclenchement manuel —gh workflow run build-docker.yml, ou l'onglet Actions) construitdeploy/Dockerfileet pousseghcr.io/<owner>/gramps-connect:latest.docker compose -f deploy/docker-compose.yml --env-file deploy/.env pullrécupère cela au lieu de construire localement ;up -d --buildreste utile pour l'itération locale sur le Dockerfile lui-même.
Gramps Connect is part of the family of Gramps-based software.
Using the app
- Overview
- Installing
- Deploying
- Messaging
- GOQL (advanced search)
- Gramplets & Add-on Store
- Data Model & Editing
- FAQ
Building & contributing