Repository navigation
Deploying.es
🌐 English · Deutsch · Français · 简体中文
deploy/ es la forma multiusuario real de Gramps Connect: un frontend
app/ + backend gramps-web-api en contenedores, con un
Postgres real (mediante el complemento SharedPostgreSQL), detrás de
Caddy para TLS, pensado para alojarse de verdad en algún sitio: secretos
reales, un dominio/certificado real y varios usuarios, cada uno con su
propio inicio de sesión. También es la única forma de ver en acción la
colaboración en vivo; la versión de escritorio independiente
es de un solo usuario por diseño, así que no hay ediciones de nadie más
que ver aparecer.
El backend es la imagen oficial y sin modificar dmstraub/gramps-webapi
(la misma que publica la CI de gramps-project/gramps-web-api en cada
versión, y sobre la que se construye el propio gramps-project/gramps-web),
no una compilación desde el código fuente mantenida por este repositorio.
Así este despliegue sigue siendo una instancia estándar y normal de
gramps-web-api con la que puede hablar cualquier cliente compatible, no
solo el frontend de Gramps Connect. Los complementos
SharedPostgreSQL/PostgreSQL/FilterRules/JSON, el soporte de varios
árboles y las traducciones compiladas ya vienen en esa imagen; lo único
que añade deploy/Dockerfile es el frontend de app/, colocado encima
como archivos estáticos. La contrapartida: esa imagen original se
construye sobre gramps-web-base (~4,3 GB: torch, sentence-transformers,
opencv, 45 paquetes de idioma de tesseract) con los extras de IA
instalados incondicionalmente, ya que no hay ninguna variante ligera
oficial que usar en su lugar.
El frontend y el backend comparten por defecto un contenedor/origen
(gramps_webapi sirve la SPA compilada mediante STATIC_PATH y atiende
/api/* en el mismo proceso), así que Gramps Connect no necesita ninguna
configuración de CORS; eso solo importa para un frontend distinto,
alojado aparte, que haga llamadas desde fuera (defina
GRAMPSWEB_CORS_ORIGINS en deploy/.env en ese caso).
Servicios: caddy (terminación TLS, el único punto de entrada
publicado), app (gunicorn, sirve el frontend + /api/*), worker
(Celery, ejecuta las tareas de importación/multimedia/reindexado de
búsqueda), postgres (datos del árbol mediante SharedPostgreSQL), redis
(intermediario de Celery).
cp deploy/.env.example deploy/.env # luego edítelo -- véanse los comentarios del archivo
docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -d --buildDespués visite https://localhost (Caddy se emite automáticamente un
certificado local autofirmado; acepte la advertencia del navegador).
Compile una vez en los runners de GitHub en lugar de en el servidor
(gh workflow run build-docker.yml, o lánzelo desde la pestaña Actions),
lo que sube ghcr.io/<owner>/gramps-connect:latest; luego, en el
servidor:
cp deploy/.env.example deploy/.env # esta vez con secretos/dominio reales
docker compose -f deploy/docker-compose.yml --env-file deploy/.env pull
docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -dApunte un dominio al servidor y edite deploy/Caddyfile (véase TLS
más abajo) para obtener un certificado real en lugar del autofirmado.
Todos los comandos siguientes suponen que está en la raíz del
repositorio. docker compose es la forma de plugin; si su instalación de
Docker solo tiene el binario independiente, use docker-compose (con las
mismas opciones en ambos casos).
# Estado de los cinco servicios
docker compose -f deploy/docker-compose.yml ps
# Registros (añada -f para seguirlos, --tail=100 para limitarlos)
docker compose -f deploy/docker-compose.yml logs app
docker compose -f deploy/docker-compose.yml logs worker
# Reiniciar un servicio (p. ej. tras un cambio de entorno en deploy/.env)
docker compose -f deploy/docker-compose.yml up -d
# ^ recrea cualquier servicio cuya configuración (imagen, entorno, volúmenes) haya cambiado;
# añada antes --build si editó deploy/Dockerfile o el código de app/backend.
# Detenerlo todo, conservando los datos (los volúmenes sobreviven)
docker compose -f deploy/docker-compose.yml down
# Detener y borrar todos los datos (cuidado -- elimina Postgres, multimedia, usuarios, etc.)
docker compose -f deploy/docker-compose.yml down -v
# Abrir una shell en un contenedor en ejecución
docker compose -f deploy/docker-compose.yml exec app shEste despliegue con Docker no tiene contraseña por defecto: usted
define GRAMPSWEB_ADMIN_USER/GRAMPSWEB_ADMIN_PASSWORD en
deploy/.env antes del primer arranque, y eso es lo que crea el punto de
entrada. Nada genera ni muestra una contraseña por usted.
Eso es distinto de gramps-connect-desktop (la versión de
demostración de un solo usuario con PyInstaller, sin relación con este
despliegue con Docker): siempre crea una cuenta fija admin/admin, lo
cual allí está bien porque es una demostración local desechable, no algo
expuesto en un servidor real.
En el primer arranque (la primera vez que los volúmenes
app-users/app-db están vacíos), el punto de entrada:
- genera y guarda una clave secreta de Flask si
GRAMPSWEB_SECRET_KEYse dejó en blanco - ejecuta las migraciones de la base de datos de usuarios
- crea un usuario administrador del sitio (sin árbol, rol 5) a partir
de
GRAMPSWEB_ADMIN_USER/GRAMPSWEB_ADMIN_PASSWORD(ambos obligatorios endeploy/.env; no hay alternativaadmin/admin)
Un administrador del sitio sin árbol puede crear/listar/eliminar árboles
mediante la API, pero, como el frontend de app/ no tiene interfaz para
elegir árbol y siempre espera que el JWT del usuario conectado ya lleve un
árbol, no puede ver los datos de un árbol hasta que se le asigne uno.
No hay ningún comando de CLI para crear árboles en modo de varios árboles,
así que cree el primer árbol mediante la API (una sola vez):
TOKEN=$(curl -sk -X POST https://localhost/api/token/ \
-H 'Content-Type: application/json' \
-d '{"username":"<admin>","password":"<admin password>"}' \
| 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":"My Family Tree"}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['id'])")
echo "$TREE_ID"Después, asigne el propio administrador del sitio a ese árbol o (recomendado: mantiene separadas la administración del sitio y la propiedad del árbol) cree un usuario dedicado, limitado al árbol:
# Opción A: asignar el administrador del sitio existente al árbol.
curl -sk -X PUT "https://localhost/api/users/<admin>/" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d "{\"tree\":\"$TREE_ID\"}"
# Opción B: crear en su lugar un usuario normal aparte, ligado al árbol.
# email y full_name son obligatorios en el esquema aunque no se usen.
curl -sk -X POST "https://localhost/api/users/<username>/" \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d "{\"email\":\"<email>\",\"full_name\":\"<full name>\",\"password\":\"<password>\",\"role\":4,\"tree\":\"$TREE_ID\"}"role es un entero (gramps_webapi.auth.const):
| Rol | Valor | Notas |
|---|---|---|
| ADMIN | 5 | Administrador del sitio; único rol que puede no tener árbol |
| OWNER | 4 | Control total de su árbol |
| EDITOR | 3 | Puede editar los datos del árbol |
| CONTRIBUTOR | 2 | Puede añadir datos |
| MEMBER | 1 | Acceso de lectura |
| GUEST | 0 | Acceso de lectura mínimo |
Luego inicie sesión en https://localhost/ con el usuario que acaba de
crear; los tokens emitidos antes de asignar un árbol no lo llevan, así que
cierre sesión y vuelva a iniciarla si reutilizó una sesión ya abierta.
TOKEN=$(curl -sk -X POST https://localhost/api/token/ \
-H 'Content-Type: application/json' \
-d '{"username":"<owner>","password":"<password>"}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")
# 1. Importar el archivo XML .gramps. IMPORTANTE: este endpoint lee el cuerpo
# bruto de la petición y lo escribe directamente en disco -- NO analiza
# multipart/form-data, así que use --data-binary, no -F/--form de curl (una
# subida multipart corrompe el archivo sin avisar con bytes de
# separadores/cabeceras, y el importador de Gramps falla entonces con un
# genérico e inútil "Import failed" sin más detalle).
curl -sk -X POST https://localhost/api/importers/gramps/file \
-H "Authorization: Bearer $TOKEN" --data-binary @example.gramps
# La respuesta es un identificador de tarea (la importación se ejecuta en el
# servicio `worker` mediante Celery) -- consúltelo hasta "state":"SUCCESS" (o "FAILURE"):
curl -sk https://localhost/api/tasks/<task_id> -H "Authorization: Bearer $TOKEN"
# 2. Multimedia: comprima en zip los archivos multimedia referenciados (sirve
# cualquier subconjunto/superconjunto -- los archivos se emparejan por suma
# de comprobación y los no referenciados se ignoran) y suba el archivo,
# de nuevo como cuerpo bruto:
curl -sk -X POST https://localhost/api/media/archive/upload/zip \
-H "Authorization: Bearer $TOKEN" --data-binary @media.zip
# 3. Verificar: recuentos de objetos del árbol del usuario conectado.
curl -sk https://localhost/api/metadata/ -H "Authorization: Bearer $TOKEN" \
| python3 -m json.toolTrampa conocida: con GRAMPSWEB_MEDIA_PREFIX_TREE=True (definido por
defecto en este archivo compose), los objetos multimedia se guardan en
MEDIA_BASE_DIR/<tree_id>/, pero nada crea automáticamente ese
subdirectorio por árbol: ni POST /api/trees/ ni la tarea de subida del
archivo multimedia. Subir multimedia para un árbol falla con
Directory /app/media/<tree_id> does not exist hasta que se crea una vez:
docker compose -f deploy/docker-compose.yml exec app mkdir -p /app/media/<tree_id>Subidas grandes de multimedia que agotan el tiempo: tanto el endpoint
de subida de un solo archivo (POST /api/media/) como el de archivo ZIP
(POST /api/media/archive/upload/zip) leen el cuerpo de la petición de
forma síncrona dentro del worker de gunicorn de app antes de pasarlo a
Celery, así que un archivo grande o una conexión lenta pueden superar el
tiempo de espera por petición de gunicorn; el navegador ve entonces un
simple Internal Server Error sin cuerpo JSON (de gunicorn/Caddy, no de
Gramps). deploy/docker-compose.yml fija GUNICORN_TIMEOUT en 600 s por
defecto; auméntelo más mediante GUNICORN_TIMEOUT en deploy/.env (véanse
los comentarios de ese archivo) si las subidas siguen fallando.
caddy es el único servicio con puertos publicados (80/443) y termina TLS
delante de app. Sin ningún dominio configurado, genera y guarda su
propia CA local y emite automáticamente a partir de ella un certificado
autofirmado (tls internal en deploy/Caddyfile); los navegadores
mostrarán una advertencia de confianza la primera vez; acéptela para
continuar (o añada la CA generada al almacén de confianza de su
sistema/navegador si quiere quitar la advertencia sin un dominio real). El
puerto 80 redirige al 443.
En cuanto un dominio real apunte a este servidor, edite
deploy/Caddyfile: sustituya el bloque :443 { tls internal ... } por
example.com { reverse_proxy app:5000 } y elimine el bloque :80; Caddy
gestiona automáticamente la emisión ACME, la renovación y la redirección
del puerto 80 para un dominio real, sin necesidad de directiva tls.
Actualice también PUBLIC_URL en deploy/.env con el dominio https://
real, y luego ejecute docker compose -f deploy/docker-compose.yml up -d
para aplicar ambos cambios.
No viene configurado por defecto (no hay un servicio grampsweb en
docker-compose.yml), pero conviene saberlo: como el backend es una
instancia normal y sin modificar de gramps-web-api,
gramps-project/gramps-web (el otro frontend oficial de Gramps) también
puede ejecutarse contra este mismo backend: los mismos datos, los mismos
árboles/usuarios, solo una interfaz distinta en otro puerto.
gramps-web publica ghcr.io/gramps-project/grampsjs:latest
precisamente para esto: nginx sirviendo solo su compilación estática, sin
backend incluido. Su configuración de nginx hace de proxy inverso de
/api hacia una variable de entorno API_HOST al arrancar el
contenedor, así que desde el punto de vista del navegador es del mismo
origen: no hace falta GRAMPSWEB_CORS_ORIGINS específicamente para esta
ruta (ese ajuste es para una instancia de gramps-web alojada en otro sitio
completamente distinto, que llama desde otro origen en lugar de a través
de este proxy).
Para añadirlo, un servicio de docker-compose.yml parecido a este:
grampsweb:
image: ghcr.io/gramps-project/grampsjs:latest
environment:
API_HOST: http://app:5000
# El resolvedor DNS integrado de Docker -- la directiva `resolver` de
# default.conf.template exige que se defina explícitamente.
NAME_SERVER: 127.0.0.11
depends_on:
- app
restart: unless-stoppedpublicado en su propio puerto (directamente, p. ej. ports: ["8081:80"],
o detrás de Caddy con un segundo bloque del estilo
:8443 { reverse_proxy grampsweb:80 } en deploy/Caddyfile para tener
TLS igual que el servicio app).
- Los datos persisten en volúmenes con nombre de Docker (
app-db,app-media,app-indexdir,app-users,app-secret,app-cache,app-tmp,postgres-data,caddy-data,caddy-config).docker compose down(sin-v) los conserva;docker compose down -vlo borra todo (incluida la CA generada: eso supone una nueva advertencia de confianza del navegador en el siguiente arranque, no solo pérdida de datos). -
redis(intermediario/almacén de resultados de Celery) es necesario para las tareas en segundo plano (indexado de búsqueda, trabajos grandes de importación/exportación); no es opcional. Tampoco lo es el servicioworker: la importación, la subida de archivos multimedia y el reindexado de búsqueda se despachan a través de Celery en cuanto se defineGRAMPSWEB_CELERY_CONFIG__*, y se quedan para siempre como una tarea sin atender si nada consume la cola.app-cache(/app/cache) debe ser un volumen compartido entreappyworkerpor el mismo motivo: el gestor de peticiones del contenedor app escribe allí el archivo subido, y luego la tarea de Celery del contenedor worker lo lee. - El servicio
workerse ejecuta con--pool=solo(sin fork), por precaución ante el pool prefork por defecto de Celery, que haría fork de un proceso que ya ha tocado estado real de PyGObject/GTK (la importación degi.repository.GLibde Gramps). Una sola instancia de worker no necesita la concurrencia a la que renuncia--pool=solo, pero conviene revisarlo si el rendimiento del worker llega a ser un cuello de botella. - La caché local del navegador de
app/(sql.js, OPFS) se identifica por un nombre de archivo fijo por vista, no por la URL del backend ni por el ID del árbol; consulte Arquitectura para ver la trampa que esto provoca y cómo borrarla. -
.github/workflows/build-docker.yml(activación manual:gh workflow run build-docker.yml, o la pestaña Actions) compiladeploy/Dockerfiley subeghcr.io/<owner>/gramps-connect:latest.docker compose -f deploy/docker-compose.yml --env-file deploy/.env pulldescarga esa imagen en lugar de compilarla localmente;up -d --buildsigue ahí para iterar localmente sobre el propio Dockerfile.
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