Deploying.nl - dsblank/gramps-connect GitHub Wiki

🌐 English · Deutsch · Español · Français · 简体中文

Implementeren

deploy/ is de echte vorm van Gramps Connect voor meerdere gebruikers: een gecontaineriseerde frontend app/ + backend gramps-web-api, met een echte Postgres als basis (via de add-on SharedPostgreSQL) en Caddy ervoor voor TLS, bedoeld om echt ergens te worden gehost — echte geheimen, een echt domein/certificaat en meerdere gebruikers, elk met een eigen login. Het is ook de enige manier om live samenwerken in actie te zien; de zelfstandige desktopversie is bewust voor één gebruiker, dus er zijn geen bewerkingen van anderen om te zien verschijnen.

De backend is het officiële, ongewijzigde image dmstraub/gramps-webapi — hetzelfde dat de eigen CI van gramps-project/gramps-web-api bij elke release publiceert, en waarop gramps-project/gramps-web zelf bouwt — geen build vanuit de broncode die deze repository onderhoudt. Zo blijft deze installatie een gewone, standaard gramps-web-api-instantie waarmee elke compatibele client kan praten, niet alleen de eigen frontend van Gramps Connect. De add-ons SharedPostgreSQL/PostgreSQL/FilterRules/JSON, ondersteuning voor meerdere stambomen en gecompileerde vertalingen komen allemaal al uit dat image; het enige wat deploy/Dockerfile toevoegt is de frontend van app/, als statische bestanden erbovenop gelegd. De keerzijde: dat upstream-image is gebouwd op gramps-web-base (~4,3 GB — torch, sentence-transformers, opencv, 45 taalpakketten voor tesseract), met AI-extra's die onvoorwaardelijk zijn geïnstalleerd, omdat er geen officiële slanke variant is om in plaats daarvan op te halen.

Frontend en backend delen standaard één container/origin (gramps_webapi serveert de gebouwde SPA via STATIC_PATH en handelt /api/* in hetzelfde proces af), dus Gramps Connect zelf heeft geen CORS-configuratie nodig — dat is alleen relevant voor een andere, apart gehoste frontend die aanroepen doet (stel in dat geval GRAMPSWEB_CORS_ORIGINS in deploy/.env in).

Services: caddy (TLS-afhandeling, het enige gepubliceerde toegangspunt), app (gunicorn, serveert de frontend + /api/*), worker (Celery, voert taken voor import/media/herindexering van de zoekfunctie uit), postgres (stamboomgegevens via SharedPostgreSQL), redis (Celery-broker).

Lokaal draaien

cp deploy/.env.example deploy/.env    # bewerk het daarna -- zie de opmerkingen in het bestand
docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -d --build

Ga daarna naar https://localhost (Caddy geeft zichzelf automatisch een lokaal, zelfondertekend certificaat — accepteer de waarschuwing van de browser).

Op een echte host draaien

Bouw één keer op de runners van GitHub in plaats van op de host (gh workflow run build-docker.yml, of start het vanuit het tabblad Actions), wat ghcr.io/<owner>/gramps-connect:latest pusht, en dan op de host:

cp deploy/.env.example deploy/.env    # deze keer echte geheimen/domein
docker compose -f deploy/docker-compose.yml --env-file deploy/.env pull
docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -d

Laat een domein naar de host verwijzen en bewerk deploy/Caddyfile (zie TLS hieronder) om een echt certificaat te krijgen in plaats van het zelfondertekende.

Voorbeeld: hosten op een DigitalOcean-droplet

Een stappenplan van een lege account tot een live site op een kleine cloudserver (DigitalOcean noemt die "droplets"). Elke VPS-aanbieder werkt op dezelfde manier zodra u een Linux-server met Docker hebt.

1. De droplet aanmaken

Kies in de DigitalOcean-console Create → Droplets:

  • Image: Marketplace → Docker on Ubuntu (met Docker voorgeïnstalleerd).
  • Size: Basic, 1 vCPU / 2 GB RAM (s-1vcpu-2gb) is de kleinste die goed werkt. Het backend-image is groot (~4 GB, zie hierboven), dus ga niet kleiner.
  • Region: welke het dichtst bij uw gebruikers ligt.
  • Authentication: een SSH-sleutel (veiliger dan een wachtwoord).

Daarna Networking → Firewalls → Create Firewall: sta alleen inkomend verkeer op 22, 80 en 443 toe, en pas de firewall toe op de droplet.

2. Swap toevoegen

2 GB RAM is krap tijdens imports en het opstarten, dus voeg een swapbestand van 2 GB toe (log via SSH in als root):

fallocate -l 2G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab

3. Een domein ernaar laten verwijzen

Optioneel maar aanbevolen, omdat u daarmee een echt HTTPS-certificaat krijgt. Een gratis naam van DuckDNS werkt, net als een A-record op een domein dat u bezit. Laat het hoe dan ook naar het publieke IP-adres van de droplet verwijzen.

4. Gramps Connect installeren en starten

git clone https://github.com/dsblank/gramps-connect /opt/gramps-connect
cd /opt/gramps-connect/deploy
cp .env.example .env
# bewerk .env: GRAMPSWEB_ADMIN_USER / GRAMPSWEB_ADMIN_PASSWORD,
# PUBLIC_URL=https://uw.domein en de e-mailinstellingen (zie hieronder)
docker compose pull
docker compose up -d

Dit haalt het kant-en-klare, publieke image ghcr.io/dsblank/gramps-connect:latest op, dus er wordt niets op de droplet zelf gebouwd.

5. HTTPS inschakelen

Bewerk deploy/Caddyfile zoals beschreven onder TLS: vervang het blok :443 { tls internal ... } door uw.domein { reverse_proxy app:5000 } en verwijder het blok :80, en voer daarna opnieuw docker compose up -d uit. Caddy vraagt zelf een certificaat van Let's Encrypt aan en vernieuwt het.

6. De eerste stamboom aanmaken

Volg Eerste installatie en Gegevens importeren hieronder, met https://uw.domein in plaats van https://localhost.

E-mail (wachtwoorden opnieuw instellen, uitnodigingen)

  • Nieuwe DigitalOcean-accounts blokkeren uitgaande SMTP. Gebruik een relay op poort 587 (bijv. Brevo, SendGrid, Mailgun) en open, als de e-mail nog steeds niet verstuurd wordt, een supportticket bij DigitalOcean (categorie Networking) om de blokkade te laten opheffen.
  • GRAMPSWEB_EMAIL_USE_STARTTLS moet true in kleine letters zijn. True wordt ongemerkt als tekenreeks gelezen en de verbinding mislukt.
  • De SMTP-login van Brevo is een gegenereerd adres (zoals [email protected]), niet het e-mailadres waarmee u zich hebt aangemeld.

Later bijwerken

Zie Een gehoste server bijwerken hieronder.

Een gehoste server bijwerken

Deze stappen gelden voor elke host die de deploy/-stack draait, of het nu een droplet is of niet. De commando's gaan ervan uit dat de repository in /opt/gramps-connect is gekloond en dat u in de map deploy/ daarvan staat.

1. Back-up maken

cd /opt/gramps-connect/deploy
# Stamboomgegevens (Postgres)
docker compose exec -T postgres sh -c 'pg_dumpall -U "$POSTGRES_USER"' > backup-$(date +%F).sql
# Gebruikersaccounts (sqlite) en mediabestanden
docker compose cp app:/app/users  ./backup-users-$(date +%F)
docker compose cp app:/app/media  ./backup-media-$(date +%F)

Updates kunnen bij het opstarten databasemigraties uitvoeren die niet terug te draaien zijn, dus een back-up is de enige manier om een update volledig ongedaan te maken.

2. Controleren of het nieuwe image is gebouwd

De server haalt ghcr.io/dsblank/gramps-connect:latest op, dat alleen verandert wanneer de workflow build-docker.yml draait. Controleer direct na het taggen van een release of de Docker-build klaar is (het tabblad Actions van de repository) voordat u bijwerkt, anders haalt u de vorige versie op.

3. Bijwerken

cd /opt/gramps-connect && git pull       # bijgewerkte configuratie van deploy/
cd deploy
docker compose pull app                  # het nieuwe image
docker compose up -d                     # maakt opnieuw aan wat is gewijzigd

Alleen app en worker worden opnieuw aangemaakt. postgres, redis en caddy blijven draaien, en alle gegevens blijven in Docker-volumes. Voeg nooit -v toe aan docker compose down, tenzij u echt alles wilt verwijderen (zie Opmerkingen).

Als git pull weigert vanwege lokale wijzigingen: een host met een echt domein heeft een bewerkte deploy/Caddyfile (zie TLS) die nooit wordt gecommit, dus een update die de kopie in de repository wijzigt, botst daarmee. Behoud uw wijzigingen met:

git stash && git pull && git stash pop    # los daarna eventuele conflicten in deploy/Caddyfile op

deploy/.env staat in gitignore, dus daar wordt nooit aan geraakt.

4. Controleren op nieuwe instellingen

diff .env.example .env

Kopieer nieuw toegevoegde instellingen uit .env.example naar .env, en voer daarna opnieuw docker compose up -d uit als u iets hebt gewijzigd.

5. Controleren

docker compose ps                  # app en worker onlangs aangemaakt, "Up"
docker compose logs --tail 50 app  # gunicorn-regels "Booting worker", geen tracebacks

Laad daarna de site en log in. Eén regel Gtk-CRITICAL ... gtk_icon_theme_get_for_screen per gunicorn-worker is normaal en onschuldig: de server heeft GTK geïnstalleerd maar geen beeldscherm.

Terugdraaien

Elk image wordt ook getagd met de commit waaruit het is gebouwd, ghcr.io/dsblank/gramps-connect:<commit-sha> (zie de pagina Packages van de repository voor de lijst). Om terug te gaan, verandert u de regel image: onder x-app-image bovenaan deploy/docker-compose.yml in die tag, en daarna:

docker compose up -d

Als de nieuwere versie de database heeft gemigreerd, zet dan ook de back-up uit stap 1 terug.

Uw eigen fork draaien

Een fork publiceert zijn eigen image. Voer build-docker.yml uit in uw fork (dat ghcr.io/<uw-gebruiker>/gramps-connect pusht) en laat de regel image: hierboven daarnaar verwijzen, of bouw op de server met docker compose up -d --build. Bouwen op de server vergt meer schijfruimte en geheugen dan de droplet van 2 GB hierboven, omdat het basisimage alleen al zo'n 4 GB is.

Docker-commando's

Alle commando's hieronder gaan ervan uit dat u in de hoofdmap van de repository staat. docker compose is de pluginvorm; als uw Docker-installatie alleen het losse programma heeft, gebruik dan docker-compose (in beide gevallen dezelfde opties).

# Status van alle vijf de services
docker compose -f deploy/docker-compose.yml ps

# Logs (voeg -f toe om te volgen, --tail=100 om te beperken)
docker compose -f deploy/docker-compose.yml logs app
docker compose -f deploy/docker-compose.yml logs worker

# Eén service herstarten (bijv. na een wijziging in deploy/.env)
docker compose -f deploy/docker-compose.yml up -d
# ^ maakt elke service opnieuw aan waarvan de configuratie (image, env,
#   volumes) is gewijzigd; voeg eerst --build toe als u deploy/Dockerfile
#   of de backendbroncode in app/ hebt bewerkt.

# Alles stoppen, gegevens behouden (volumes blijven bestaan)
docker compose -f deploy/docker-compose.yml down

# Stoppen en alle gegevens verwijderen (let op -- wist Postgres, media, gebruikers enz.)
docker compose -f deploy/docker-compose.yml down -v

# Een shell openen in een draaiende container
docker compose -f deploy/docker-compose.yml exec app sh

Inloggegevens: deze installatie vs. de zelfstandige versie

Deze Docker-installatie heeft geen standaardwachtwoord — u stelt GRAMPSWEB_ADMIN_USER/GRAMPSWEB_ADMIN_PASSWORD zelf in deploy/.env in vóór de eerste keer opstarten, en dat is wat het entrypoint aanmaakt. Niets genereert of toont een wachtwoord voor u.

Dat is anders dan bij gramps-connect-desktop (de PyInstaller-demoversie voor één gebruiker, los van deze Docker-installatie): die maakt altijd een vaste account admin/admin aan, wat daar prima is omdat het een lokale wegwerpdemo is, niet iets wat op een echte server bereikbaar is.

Eerste installatie: de aangemaakte beheerder

Bij de eerste keer opstarten (de eerste keer dat de volumes app-users/app-db leeg zijn) doet het entrypoint het volgende:

  • het genereert en bewaart een geheime Flask-sleutel als GRAMPSWEB_SECRET_KEY leeg is gelaten
  • het voert de migraties van de gebruikersdatabase uit
  • het maakt een sitebeheerder aan (zonder stamboom, rol 5) op basis van GRAMPSWEB_ADMIN_USER / GRAMPSWEB_ADMIN_PASSWORD (beide verplicht in deploy/.env — geen terugval op admin/admin)

Een sitebeheerder zonder stamboom kan via de API stambomen aanmaken/opsommen/verwijderen, maar — omdat de frontend van app/ geen interface heeft om een stamboom te kiezen en altijd verwacht dat de JWT van de ingelogde gebruiker al een stamboom bevat — kan de gegevens van een stamboom pas bekijken als hij eraan is toegewezen. Er is geen CLI-commando om in de modus met meerdere stambomen een stamboom aan te maken, dus maak de eerste stamboom aan via de API (eenmalig):

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"

Wijs daarna ofwel de sitebeheerder zelf aan die stamboom toe, ofwel (aanbevolen — houdt sitebeheer en eigendom van de stamboom gescheiden) maak een aparte gebruiker aan die aan de stamboom is gebonden:

# Optie A: de bestaande sitebeheerder aan de stamboom toewijzen.
curl -sk -X PUT "https://localhost/api/users/<admin>/" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d "{\"tree\":\"$TREE_ID\"}"

# Optie B: in plaats daarvan een aparte gewone gebruiker aanmaken die aan
# de stamboom is gebonden. email en full_name zijn verplicht volgens het
# schema, ook als ze niet worden gebruikt.
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 is een geheel getal (gramps_webapi.auth.const):

Rol Waarde Opmerkingen
ADMIN 5 Sitebeheerder; de enige rol die zonder stamboom mag zijn
OWNER 4 Volledige controle over de eigen stamboom
EDITOR 3 Kan stamboomgegevens bewerken
CONTRIBUTOR 2 Kan gegevens toevoegen
MEMBER 1 Leestoegang
GUEST 0 Minimale leestoegang

Log daarna in op https://localhost/ als de gebruiker die u zojuist hebt aangemaakt — bestaande tokens die vóór een toewijzing aan een stamboom zijn uitgegeven bevatten die niet, dus log uit en opnieuw in als u een sessie hergebruikt die al open was.

Gegevens importeren (bijv. example.gramps, met media)

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. Importeer het .gramps XML-bestand. BELANGRIJK: dit endpoint leest de
#    ruwe body van het verzoek en schrijft die direct naar schijf -- het
#    verwerkt GEEN multipart/form-data, dus gebruik --data-binary, niet
#    -F/--form van curl (een multipart-upload beschadigt het bestand
#    ongemerkt met boundary-/headerbytes, en de importeur van Gramps
#    mislukt dan met een algemene, nietszeggende "Import failed" zonder
#    verdere details).
curl -sk -X POST https://localhost/api/importers/gramps/file \
  -H "Authorization: Bearer $TOKEN" --data-binary @example.gramps

# Het antwoord is een taakhandle (de import draait op de service `worker`
# via Celery) -- poll die tot "state":"SUCCESS" (of "FAILURE"):
curl -sk https://localhost/api/tasks/<task_id> -H "Authorization: Bearer $TOKEN"

# 2. Media: zip de mediabestanden waarnaar wordt verwezen (elke
#    deelverzameling/superverzameling is prima -- bestanden worden op
#    controlesom gematcht, bestanden zonder verwijzing worden genegeerd)
#    en upload het archief, opnieuw als ruwe body:
curl -sk -X POST https://localhost/api/media/archive/upload/zip \
  -H "Authorization: Bearer $TOKEN" --data-binary @media.zip

# 3. Controleren: aantallen objecten in de stamboom van de ingelogde gebruiker.
curl -sk https://localhost/api/metadata/ -H "Authorization: Bearer $TOKEN" \
  | python3 -m json.tool

Bekende valkuil: met GRAMPSWEB_MEDIA_PREFIX_TREE=True (standaard ingesteld in dit compose-bestand) worden media opgeslagen onder MEDIA_BASE_DIR/<tree_id>/, maar niets maakt die submap per stamboom automatisch aan — noch POST /api/trees/, noch de taak voor het uploaden van mediaarchieven. Media uploaden voor een stamboom mislukt met Directory /app/media/<tree_id> does not exist totdat die één keer is aangemaakt:

docker compose -f deploy/docker-compose.yml exec app mkdir -p /app/media/<tree_id>

Grote media-uploads die een time-out krijgen: zowel het uploadendpoint voor losse bestanden (POST /api/media/) als dat voor ZIP-archieven (POST /api/media/archive/upload/zip) leest de body van het verzoek synchroon in de gunicorn-worker van app voordat het aan Celery wordt overgedragen, dus een groot bestand of een trage verbinding kan langer duren dan de time-out per verzoek van gunicorn — de browser ziet dan een kale Internal Server Error zonder JSON-body (van gunicorn/Caddy, niet van Gramps). deploy/docker-compose.yml zet GUNICORN_TIMEOUT standaard op 600 s; verhoog die verder via GUNICORN_TIMEOUT in deploy/.env (zie de opmerkingen in dat bestand) als uploads nog steeds mislukken.

TLS

caddy is de enige service met gepubliceerde poorten (80/443) en handelt TLS af vóór app. Zonder geconfigureerd domein genereert en bewaart hij zijn eigen lokale CA en geeft daaruit automatisch een zelfondertekend certificaat uit (tls internal in deploy/Caddyfile) — browsers tonen de eerste keer een vertrouwenswaarschuwing; accepteer die om door te gaan (of voeg de gegenereerde CA toe aan het vertrouwensarchief van uw systeem/browser als u de waarschuwing kwijt wilt zonder echt domein). Poort 80 verwijst door naar 443.

Zodra een echt domein naar deze server verwijst, bewerkt u deploy/Caddyfile: vervang het blok :443 { tls internal ... } door example.com { reverse_proxy app:5000 } en verwijder het blok :80 — Caddy regelt voor een echt domein automatisch de ACME-uitgifte, de vernieuwing en de doorverwijzing van poort 80, zonder dat een tls-directive nodig is. Werk ook PUBLIC_URL in deploy/.env bij naar het echte https://-domein, en voer daarna docker compose -f deploy/docker-compose.yml up -d uit om beide wijzigingen over te nemen.

gramps-web naast Gramps Connect draaien

Standaard niet ingesteld (er is geen service grampsweb in docker-compose.yml), maar goed om te weten: omdat de backend een gewone, ongewijzigde gramps-web-api-instantie is, kan gramps-project/gramps-web (de andere officiële frontend van Gramps) ook op dezelfde backend draaien — dezelfde gegevens, dezelfde stambomen/gebruikers, alleen een andere interface op een andere poort.

gramps-web publiceert precies hiervoor ghcr.io/gramps-project/grampsjs:latest: nginx die alleen de statische build serveert, zonder ingebouwde backend. De nginx-configuratie stuurt /api bij het starten van de container door naar een omgevingsvariabele API_HOST, dus vanuit de browser gezien is het dezelfde origin — voor dit pad is specifiek geen GRAMPSWEB_CORS_ORIGINS nodig (die instelling is voor een gramps-web-instantie die helemaal ergens anders wordt gehost en cross-origin aanroepen doet in plaats van via deze proxy).

Om die toe te voegen, een service in docker-compose.yml in deze trant:

grampsweb:
  image: ghcr.io/gramps-project/grampsjs:latest
  environment:
    API_HOST: http://app:5000
    # De ingebouwde DNS-resolver van Docker -- de directive `resolver` in
    # default.conf.template vereist dat dit expliciet wordt ingesteld.
    NAME_SERVER: 127.0.0.11
  depends_on:
    - app
  restart: unless-stopped

gepubliceerd op een eigen poort (ofwel direct, bijv. ports: ["8081:80"], ofwel achter Caddy met een tweede blok in de trant van :8443 { reverse_proxy grampsweb:80 } in deploy/Caddyfile, voor TLS zoals bij de service app).

Opmerkingen

  • Gegevens blijven bewaard in benoemde Docker-volumes (app-db, app-media, app-indexdir, app-users, app-secret, app-cache, app-tmp, postgres-data, caddy-data, caddy-config). docker compose down (zonder -v) behoudt ze; docker compose down -v verwijdert alles (inclusief de gegenereerde CA — dat betekent bij de volgende start een nieuwe vertrouwenswaarschuwing in de browser, niet alleen gegevensverlies).
  • redis (Celery-broker/resultaatbackend) is vereist voor achtergrondtaken (indexering voor de zoekfunctie, grote import-/ exporttaken) — niet optioneel. Dat geldt ook voor de service worker: importeren, uploaden van mediaarchieven en herindexeren voor de zoekfunctie lopen allemaal via Celery zodra GRAMPSWEB_CELERY_CONFIG__* is ingesteld, en blijven voor altijd als onafgehandelde taak staan als niets de wachtrij verwerkt. app-cache (/app/cache) moet om dezelfde reden een volume zijn dat app en worker delen: de verzoekafhandeling van de app-container schrijft het geüploade bestand daar, en daarna leest de Celery-taak van de worker-container het terug.
  • De service worker draait met --pool=solo (geen forking), uit voorzichtigheid omdat de standaard prefork-pool van Celery een proces forkt dat al echte PyGObject-/GTK-status heeft aangeraakt (de import van gi.repository.GLib door Gramps). Eén workerinstantie heeft de gelijktijdigheid die --pool=solo opgeeft niet nodig, maar dit is het heroverwegen waard als de doorvoer van de worker ooit een bottleneck wordt.
  • De lokale cache van app/ aan de browserzijde (sql.js, OPFS) heeft een vaste bestandsnaam per weergave als sleutel, niet de backend-URL of het stamboom-id — zie Architectuur voor de valkuil die dit veroorzaakt en hoe u die wist.
  • .github/workflows/build-docker.yml (handmatig te starten — gh workflow run build-docker.yml, of het tabblad Actions) bouwt deploy/Dockerfile en pusht ghcr.io/<owner>/gramps-connect:latest. docker compose -f deploy/docker-compose.yml --env-file deploy/.env pull haalt dat op in plaats van lokaal te bouwen; up -d --build blijft er voor lokaal itereren aan de Dockerfile zelf.
⚠️ **GitHub.com Fallback** ⚠️