""" Datenbankzugriff (SQLite, WAL-Modus) + einfacher, idempotenter Migration-Runner. Bewusst ohne schweres ORM gehalten: alle Queries sind strikt parametrisiert (nie String-Concat mit Nutzereingaben), siehe Security-Konzept 6.6. """ from __future__ import annotations import logging from pathlib import Path import aiosqlite from app.config import settings logger = logging.getLogger("jumphost.db") MIGRATIONS_DIR = Path(__file__).parent / "db" / "migrations" _connection: aiosqlite.Connection | None = None async def init_db() -> None: """Legt das Datenverzeichnis an, oeffnet die DB und wendet Migrationen an.""" settings.data_dir.mkdir(parents=True, exist_ok=True, mode=0o700) settings.recordings_dir.mkdir(parents=True, exist_ok=True, mode=0o700) global _connection _connection = await aiosqlite.connect(settings.db_path, isolation_level=None) await _connection.execute("PRAGMA journal_mode = WAL;") await _connection.execute("PRAGMA foreign_keys = ON;") await _connection.execute("PRAGMA busy_timeout = 5000;") await _apply_migrations(_connection) try: settings.db_path.chmod(0o600) except OSError: logger.warning("Konnte Dateirechte der DB nicht setzen (%s)", settings.db_path) async def _apply_migrations(conn: aiosqlite.Connection) -> None: await conn.execute( "CREATE TABLE IF NOT EXISTS schema_migrations " "(filename TEXT PRIMARY KEY, applied_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')))" ) applied = {row[0] async for row in await conn.execute("SELECT filename FROM schema_migrations")} for migration_file in sorted(MIGRATIONS_DIR.glob("*.sql")): if migration_file.name in applied: continue logger.info("Wende Migration an: %s", migration_file.name) sql = migration_file.read_text(encoding="utf-8") await conn.executescript(sql) await conn.execute( "INSERT INTO schema_migrations (filename) VALUES (?)", (migration_file.name,) ) async def close_db() -> None: global _connection if _connection is not None: await _connection.close() _connection = None def get_db() -> aiosqlite.Connection: if _connection is None: raise RuntimeError("Datenbank ist nicht initialisiert (init_db() aufrufen).") return _connection