When the admin user already exists with an unknown password, the startup script now attempts to reset the password instead of skipping. This handles the case where GrampsWeb was previously set up without tracking the admin credentials. Co-Authored-By: Paperclip <noreply@paperclip.ing>
286 lines
9.2 KiB
YAML
286 lines
9.2 KiB
YAML
# Production Docker Compose Configuration
|
||
# This file is used for production deployment via GitHub Actions
|
||
# For local development, use: docker-compose -f compose.dev.yml up
|
||
#
|
||
# IMPORTANT: This configuration requires ALL environment variables to be
|
||
# provided via the production server's .env file. No fallback values are
|
||
# included for security reasons.
|
||
|
||
services:
|
||
db:
|
||
image: postgres:16-alpine
|
||
environment:
|
||
POSTGRES_DB: ${POSTGRES_DB}
|
||
POSTGRES_USER: ${POSTGRES_USER}
|
||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||
volumes:
|
||
- dbdata:/var/lib/postgresql/data
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 5
|
||
|
||
redis:
|
||
image: redis:7-alpine
|
||
|
||
web:
|
||
build:
|
||
context: ./app
|
||
args:
|
||
APP_VERSION: ${APP_VERSION:-unknown}
|
||
depends_on:
|
||
db:
|
||
condition: service_healthy
|
||
redis:
|
||
condition: service_started
|
||
environment:
|
||
- POSTGRES_DB=${POSTGRES_DB}
|
||
- POSTGRES_USER=${POSTGRES_USER}
|
||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||
- DB_HOST=${DB_HOST}
|
||
- DB_PORT=${DB_PORT}
|
||
- DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}
|
||
- DJANGO_DEBUG=${DJANGO_DEBUG}
|
||
- DJANGO_ALLOWED_HOSTS=${DJANGO_ALLOWED_HOSTS}
|
||
- LANGUAGE_CODE=${LANGUAGE_CODE}
|
||
- TIME_ZONE=${TIME_ZONE}
|
||
- REDIS_URL=${REDIS_URL}
|
||
- SESSION_COOKIE_NAME=${SESSION_COOKIE_NAME}
|
||
- CSRF_COOKIE_NAME=${CSRF_COOKIE_NAME}
|
||
- GRAMPS_URL=${GRAMPS_URL}
|
||
- GRAMPS_USERNAME=${GRAMPS_USERNAME}
|
||
- GRAMPS_PASSWORD=${GRAMPS_PASSWORD}
|
||
- GRAMPS_API_TOKEN=${GRAMPS_API_TOKEN}
|
||
- IMAP_HOST=${IMAP_HOST}
|
||
- IMAP_PORT=${IMAP_PORT}
|
||
- IMAP_USER=${IMAP_USER}
|
||
- IMAP_PASSWORD=${IMAP_PASSWORD}
|
||
- IMAP_FOLDER=${IMAP_FOLDER}
|
||
- IMAP_USE_SSL=${IMAP_USE_SSL}
|
||
ports:
|
||
- "8081:8000"
|
||
volumes:
|
||
- ./app:/app
|
||
command: ["gunicorn", "core.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "3"]
|
||
|
||
worker:
|
||
build:
|
||
context: ./app
|
||
args:
|
||
APP_VERSION: ${APP_VERSION:-unknown}
|
||
environment:
|
||
- POSTGRES_DB=${POSTGRES_DB}
|
||
- POSTGRES_USER=${POSTGRES_USER}
|
||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||
- DB_HOST=${DB_HOST}
|
||
- DB_PORT=${DB_PORT}
|
||
- DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}
|
||
- DJANGO_DEBUG=${DJANGO_DEBUG}
|
||
- REDIS_URL=${REDIS_URL}
|
||
- GRAMPS_URL=${GRAMPS_URL}
|
||
- GRAMPS_USERNAME=${GRAMPS_USERNAME}
|
||
- GRAMPS_PASSWORD=${GRAMPS_PASSWORD}
|
||
- GRAMPS_API_TOKEN=${GRAMPS_API_TOKEN}
|
||
- IMAP_HOST=${IMAP_HOST}
|
||
- IMAP_PORT=${IMAP_PORT}
|
||
- IMAP_USER=${IMAP_USER}
|
||
- IMAP_PASSWORD=${IMAP_PASSWORD}
|
||
- IMAP_FOLDER=${IMAP_FOLDER}
|
||
- IMAP_USE_SSL=${IMAP_USE_SSL}
|
||
depends_on:
|
||
- redis
|
||
- db
|
||
command: ["celery", "-A", "core", "worker", "-l", "info"]
|
||
|
||
beat:
|
||
build:
|
||
context: ./app
|
||
args:
|
||
APP_VERSION: ${APP_VERSION:-unknown}
|
||
environment:
|
||
- POSTGRES_DB=${POSTGRES_DB}
|
||
- POSTGRES_USER=${POSTGRES_USER}
|
||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||
- DB_HOST=${DB_HOST}
|
||
- DB_PORT=${DB_PORT}
|
||
- DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}
|
||
- DJANGO_DEBUG=${DJANGO_DEBUG}
|
||
- REDIS_URL=${REDIS_URL}
|
||
- GRAMPS_URL=${GRAMPS_URL}
|
||
- GRAMPS_USERNAME=${GRAMPS_USERNAME}
|
||
- GRAMPS_PASSWORD=${GRAMPS_PASSWORD}
|
||
- GRAMPS_API_TOKEN=${GRAMPS_API_TOKEN}
|
||
- IMAP_HOST=${IMAP_HOST}
|
||
- IMAP_PORT=${IMAP_PORT}
|
||
- IMAP_USER=${IMAP_USER}
|
||
- IMAP_PASSWORD=${IMAP_PASSWORD}
|
||
- IMAP_FOLDER=${IMAP_FOLDER}
|
||
- IMAP_USE_SSL=${IMAP_USE_SSL}
|
||
depends_on:
|
||
- redis
|
||
- db
|
||
command: ["celery", "-A", "core", "beat", "-l", "info"]
|
||
|
||
mcp:
|
||
build:
|
||
context: ./app
|
||
args:
|
||
APP_VERSION: ${APP_VERSION:-unknown}
|
||
depends_on:
|
||
db:
|
||
condition: service_healthy
|
||
environment:
|
||
- POSTGRES_DB=${POSTGRES_DB}
|
||
- POSTGRES_USER=${POSTGRES_USER}
|
||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||
- DB_HOST=${DB_HOST}
|
||
- DB_PORT=${DB_PORT}
|
||
- DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}
|
||
- DJANGO_DEBUG=0
|
||
- DJANGO_ALLOWED_HOSTS=localhost
|
||
- LANGUAGE_CODE=${LANGUAGE_CODE}
|
||
- TIME_ZONE=${TIME_ZONE}
|
||
- MCP_TOKEN_READONLY=${MCP_TOKEN_READONLY}
|
||
- MCP_TOKEN_EDITOR=${MCP_TOKEN_EDITOR}
|
||
- MCP_TOKEN_ADMIN=${MCP_TOKEN_ADMIN}
|
||
# Kein Port-Mapping – nur internes Netz
|
||
# Start via: docker compose run --rm -e MCP_AUTH_TOKEN=<token> mcp
|
||
stdin_open: true
|
||
command: ["python", "-m", "mcp_server"]
|
||
|
||
gramps-mcp:
|
||
build:
|
||
context: ./app
|
||
args:
|
||
APP_VERSION: ${APP_VERSION:-unknown}
|
||
depends_on:
|
||
- grampsweb
|
||
environment:
|
||
- GRAMPS_URL=http://grampsweb:5000
|
||
- GRAMPS_USERNAME=${GRAMPS_USERNAME}
|
||
- GRAMPS_PASSWORD=${GRAMPS_PASSWORD}
|
||
# Kein Port-Mapping – nur internes Netz
|
||
# Start via: docker compose run --rm gramps-mcp
|
||
stdin_open: true
|
||
command: ["python", "-m", "gramps_mcp_server"]
|
||
|
||
ollama:
|
||
image: ollama/ollama:latest
|
||
# Kein externes Port-Mapping — nur über internes Docker-Netzwerk erreichbar
|
||
# Django-App: http://ollama:11434
|
||
environment:
|
||
- OLLAMA_MAX_LOADED_MODELS=1
|
||
- OLLAMA_NUM_PARALLEL=1
|
||
- OLLAMA_DEFAULT_MODEL=${OLLAMA_DEFAULT_MODEL:-qwen2.5:3b}
|
||
volumes:
|
||
- ollama_data:/root/.ollama
|
||
restart: unless-stopped
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "curl -sf http://localhost:11434/api/tags || exit 1"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 5
|
||
start_period: 60s
|
||
# Beim ersten Start: Ollama starten, dann Modell laden (falls nicht vorhanden)
|
||
entrypoint: >
|
||
sh -c "
|
||
ollama serve &
|
||
OLLAMA_PID=$$!
|
||
echo '[ollama] Warte auf API...'
|
||
RETRIES=0
|
||
until curl -sf http://localhost:11434/api/tags > /dev/null 2>&1; do
|
||
RETRIES=$$((RETRIES + 1))
|
||
[ $$RETRIES -ge 60 ] && echo '[ollama] FEHLER: API nicht bereit.' && exit 1
|
||
sleep 1
|
||
done
|
||
MODEL=$${OLLAMA_DEFAULT_MODEL:-qwen2.5:3b}
|
||
if ollama list | grep -q \"$$MODEL\"; then
|
||
echo \"[ollama] Modell '$$MODEL' bereits vorhanden.\"
|
||
else
|
||
echo \"[ollama] Lade Modell '$$MODEL'...\"
|
||
ollama pull \"$$MODEL\"
|
||
fi
|
||
wait $$OLLAMA_PID
|
||
"
|
||
|
||
grampsweb: &grampsweb
|
||
image: ghcr.io/gramps-project/grampsweb:latest
|
||
restart: unless-stopped
|
||
ports:
|
||
- "8090:5000"
|
||
environment:
|
||
GRAMPSWEB_TREE: ${GRAMPSWEB_TREE:-Stiftung}
|
||
GRAMPSWEB_CELERY_CONFIG__broker_url: "redis://redis:6379/0"
|
||
GRAMPSWEB_CELERY_CONFIG__result_backend: "redis://redis:6379/0"
|
||
GRAMPSWEB_RATELIMIT_STORAGE_URI: "redis://redis:6379/1"
|
||
GRAMPSWEB_BASE_URL: ${GRAMPSWEB_BASE_URL:-https://ahnenforschung.vhtv-stiftung.de}
|
||
GRAMPSWEB_ADMIN_EMAIL: ${GRAMPSWEB_ADMIN_EMAIL:-admin@vhtv-stiftung.de}
|
||
GRAMPSWEB_ADMIN_PASSWORD: ${GRAMPSWEB_ADMIN_PASSWORD:-nHcPMjEKORwqGxEO}
|
||
command:
|
||
- sh
|
||
- -c
|
||
- |
|
||
echo "[grampsweb] Ensuring admin user exists ..."
|
||
python3 << 'PYEOF' 2>&1 | grep -v Gtk
|
||
from gramps_webapi.app import create_app
|
||
from gramps_webapi.auth import add_user, get_number_users, ROLE_OWNER
|
||
import os, sqlite3, hashlib
|
||
email = os.environ.get('GRAMPSWEB_ADMIN_EMAIL', '')
|
||
pw = os.environ.get('GRAMPSWEB_ADMIN_PASSWORD', '')
|
||
if email and pw:
|
||
app = create_app()
|
||
with app.app_context():
|
||
if get_number_users() == 0:
|
||
add_user(name='Admin', email=email, password=pw, role=ROLE_OWNER)
|
||
print('[grampsweb] Admin user created')
|
||
else:
|
||
try:
|
||
add_user(name='Admin', email=email, password=pw, role=ROLE_OWNER)
|
||
print('[grampsweb] Admin user created')
|
||
except Exception:
|
||
from gramps_webapi.auth import get_user_details, modify_user
|
||
try:
|
||
user = get_user_details(name='Admin')
|
||
modify_user(name='Admin', password=pw, role=ROLE_OWNER)
|
||
print('[grampsweb] Admin user password reset')
|
||
except Exception as e:
|
||
print(f'[grampsweb] Could not update admin user: {e}')
|
||
else:
|
||
print('[grampsweb] No admin credentials configured, skipping')
|
||
PYEOF
|
||
exec gunicorn -w $${GUNICORN_NUM_WORKERS:-4} -b 0.0.0.0:5000 \
|
||
gramps_webapi.wsgi:app --timeout $${GUNICORN_TIMEOUT:-120} \
|
||
--limit-request-line 8190
|
||
volumes:
|
||
- gramps_users:/app/users
|
||
- gramps_index:/app/indexdir
|
||
- gramps_thumb_cache:/app/thumbnail_cache
|
||
- gramps_cache:/app/cache
|
||
- gramps_secret:/app/secret
|
||
- gramps_db:/root/.gramps/grampsdb
|
||
- gramps_media:/app/media
|
||
- gramps_tmp:/tmp
|
||
depends_on:
|
||
- redis
|
||
|
||
grampsweb_celery:
|
||
<<: *grampsweb
|
||
ports: []
|
||
depends_on:
|
||
- grampsweb
|
||
- redis
|
||
command: celery -A gramps_webapi.celery worker --loglevel=INFO --concurrency=2
|
||
|
||
volumes:
|
||
dbdata:
|
||
gramps_users:
|
||
gramps_index:
|
||
gramps_thumb_cache:
|
||
gramps_cache:
|
||
gramps_secret:
|
||
gramps_db:
|
||
gramps_media:
|
||
gramps_tmp:
|
||
ollama_data:
|