From f8b3ae30119afc4b66485b0e08a2787d932ce356 Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 4 Jul 2026 22:46:12 +0200 Subject: [PATCH] DB-Verbindung: bei Auth-Fehlern sofort abbrechen, Doku zum Volume-Wechsel. Co-authored-by: Cursor --- backend/db.py | 37 +++++++++++++++++++++++++++++++++++++ backend/run_migrations.py | 18 +----------------- backend/run_seeds.py | 17 +---------------- docs/DEPLOYMENT.md | 16 ++++++++++++++++ 4 files changed, 55 insertions(+), 33 deletions(-) diff --git a/backend/db.py b/backend/db.py index d94520c..ec650a8 100644 --- a/backend/db.py +++ b/backend/db.py @@ -1,6 +1,9 @@ """PostgreSQL connection helpers.""" +from __future__ import annotations + import os +import time import psycopg2 from psycopg2.extensions import connection @@ -27,6 +30,40 @@ def get_connection() -> connection: ) +def is_retryable_db_error(exc: Exception) -> bool: + """Nur temporäre Verbindungsprobleme erneut versuchen (nicht Auth/Config).""" + msg = str(exc).lower() + if "password authentication failed" in msg: + return False + if "does not exist" in msg and any(token in msg for token in ("role", "database")): + return False + if "no pg_hba.conf entry" in msg: + return False + return True + + +def connect_with_retry(max_retries: int = 30, sleep_seconds: float = 2.0) -> connection: + p = db_params() + last_exc: Exception | None = None + for attempt in range(max_retries): + try: + conn = get_connection() + conn.autocommit = False + print(f"[OK] Connected to database: {p['dbname']}") + return conn + except psycopg2.OperationalError as exc: + last_exc = exc + if not is_retryable_db_error(exc): + raise + if attempt >= max_retries - 1: + raise + print(f"Waiting for database... ({attempt + 1}/{max_retries})") + time.sleep(sleep_seconds) + if last_exc: + raise last_exc + raise RuntimeError("database connection failed") + + def check_db() -> bool: conn = get_connection() try: diff --git a/backend/run_migrations.py b/backend/run_migrations.py index 9de7202..f74afac 100644 --- a/backend/run_migrations.py +++ b/backend/run_migrations.py @@ -8,13 +8,12 @@ import re import shutil import subprocess import sys -import time from typing import List, Tuple import psycopg2 import sqlparse -from db import db_params, get_connection +from db import check_db, connect_with_retry, db_params, get_connection _LEADING_DIGITS = re.compile(r"^(\d+)") @@ -135,21 +134,6 @@ def run_migration(conn, migration_name: str, filepath: str) -> bool: return False -def connect_with_retry(max_retries: int = 30): - p = db_params() - for attempt in range(max_retries): - try: - conn = get_connection() - conn.autocommit = False - print(f"[OK] Connected to database: {p['dbname']}") - return conn - except psycopg2.OperationalError: - if attempt >= max_retries - 1: - raise - print(f"Waiting for database... ({attempt + 1}/{max_retries})") - time.sleep(2) - - def migrations_directory() -> str: docker_path = "/app/migrations" if os.path.isdir(docker_path): diff --git a/backend/run_seeds.py b/backend/run_seeds.py index 04ae38f..24c5b94 100644 --- a/backend/run_seeds.py +++ b/backend/run_seeds.py @@ -14,7 +14,7 @@ from typing import Callable, List, Optional, Tuple import psycopg2 import sqlparse -from db import db_params, get_connection +from db import connect_with_retry, db_params, get_connection _SEED_PREFIX = re.compile(r"^seed_(\d+)_(.+)$") _DEV_MARKER = ".dev." @@ -152,21 +152,6 @@ def run_python_seed(filepath: str) -> None: run_fn() -def connect_with_retry(max_retries: int = 30): - p = db_params() - for attempt in range(max_retries): - try: - conn = get_connection() - conn.autocommit = False - print(f"[OK] Connected to database: {p['dbname']}") - return conn - except psycopg2.OperationalError: - if attempt >= max_retries - 1: - raise - print(f"Waiting for database... ({attempt + 1}/{max_retries})") - time.sleep(2) - - def run_seed(conn, seed_name: str, filepath: str, kind: str) -> tuple[bool, object]: print(f"Running seed: {seed_name}") try: diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index fcbcf95..2912438 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -75,6 +75,22 @@ Aktuell keine Medien-Speicherung. Bei Bedarf: NAS-Mount + `docker-compose.overri --- +## Dev-Datenbank wechseln + +PostgreSQL im Compose-Stack initialisiert User/Passwort **nur beim ersten Start** des Volumes (`dev-kairo-db-data`). +Wenn du `DB_NAME`, `DB_USER` oder `DB_PASSWORD` in `.env` änderst, muss das Volume neu angelegt werden: + +```bash +cd /home/lars/docker/kairo-dev +docker compose -f docker-compose.dev-env.yml down -v +docker compose -f docker-compose.dev-env.yml up -d --wait +curl -sf http://localhost:8097/api/health +``` + +Danach laufen Migrationen und Dev-Seeds automatisch (u. a. `lars@stommer.com`). + +--- + ## Manuelles Deploy ```bash