DB-Verbindung: bei Auth-Fehlern sofort abbrechen, Doku zum Volume-Wechsel.
All checks were successful
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 10s
Test Suite / lint-backend (push) Successful in 2s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 18s
Test Suite / playwright-smoke (push) Successful in 12s

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Lars 2026-07-04 22:46:12 +02:00
parent 88792eeb47
commit f8b3ae3011
4 changed files with 55 additions and 33 deletions

View File

@ -1,6 +1,9 @@
"""PostgreSQL connection helpers.""" """PostgreSQL connection helpers."""
from __future__ import annotations
import os import os
import time
import psycopg2 import psycopg2
from psycopg2.extensions import connection 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: def check_db() -> bool:
conn = get_connection() conn = get_connection()
try: try:

View File

@ -8,13 +8,12 @@ import re
import shutil import shutil
import subprocess import subprocess
import sys import sys
import time
from typing import List, Tuple from typing import List, Tuple
import psycopg2 import psycopg2
import sqlparse 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+)") _LEADING_DIGITS = re.compile(r"^(\d+)")
@ -135,21 +134,6 @@ def run_migration(conn, migration_name: str, filepath: str) -> bool:
return False 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: def migrations_directory() -> str:
docker_path = "/app/migrations" docker_path = "/app/migrations"
if os.path.isdir(docker_path): if os.path.isdir(docker_path):

View File

@ -14,7 +14,7 @@ from typing import Callable, List, Optional, Tuple
import psycopg2 import psycopg2
import sqlparse 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+)_(.+)$") _SEED_PREFIX = re.compile(r"^seed_(\d+)_(.+)$")
_DEV_MARKER = ".dev." _DEV_MARKER = ".dev."
@ -152,21 +152,6 @@ def run_python_seed(filepath: str) -> None:
run_fn() 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]: def run_seed(conn, seed_name: str, filepath: str, kind: str) -> tuple[bool, object]:
print(f"Running seed: {seed_name}") print(f"Running seed: {seed_name}")
try: try:

View File

@ -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 ## Manuelles Deploy
```bash ```bash