Files
NSCT---Neutral-Search-Crawl…/ADMIN-Backend.md
faligam 1aacf4aa20 Implement persistent API-key authentication
Protect the research lifecycle with X-API-Key validation backed by persistent user and key records. Store only salted scrypt hashes, support expiry and revocation, and expose a local admin CLI for create/list/revoke workflows.

Initialize only the authentication schema at startup, prevent SQL echo from exposing sensitive bound values, and keep health probes public. Add coverage for valid, missing, invalid, expired, and revoked keys.

Document deployment and key administration, update the local CLI to send NSCT_API_KEY, and record the reset handoff state.
2026-09-06 17:23:05 +02:00

35 KiB
Raw Blame History

ADMIN-Handbook — NSCT Backend (API)

Produziert vom NSCT Development Team — Stand: 2025-09-06


1. Übersicht

NSCT (Neutral Search Crawler Tool) ist ein lokal betreibbares, containerisiertes Recherche- und Analyse-System. Es durchsucht automatisch Webquellen, extrahiert Behauptungen (Claims), bewertet Evidenz multi-dimensional und generiert neutrale, quellengestützte Berichte.

Architektur-Überblick

┌─────────────────────────────────────────────────────────────────────┐
│                         Docker Compose Stack                       │
│                                                                      │
│  ┌──────────┐     ┌──────────┐     ┌──────────┐                   │
│  │ nsct-api │────▶│ postgres │────▶│ searxng  │                   │
│  │ :8080    │     │ :5432    │     │ :8888    │                   │
│  │ FastAPI  │     │ PG 16    │     │ SearXNG  │                   │
│  └──────────┘     └──────────┘     └──────────┘                   │
│                                                                      │
│  User ───▶ LLM-Provider (:8030)  (extern, z.B. Ollama/VLLM)        │
│  User ───▶ Audio-Provider (:8030) (extern, STT/TTS)                │
│  User ───▶ Vision-Provider  (:8030) (extern, Bildanalyse)           │
└─────────────────────────────────────────────────────────────────────┘

Kernprinzipien:

  • Container-first — alles läuft in Docker Compose
  • Environment-only Configuration — keine Config-Dateien, alles über .env
  • Security-first — SSRF-Schutz, non-root, read-only FS, dropped capabilities
  • Provenance-Pflicht — jeder Schritt ist nachverfolgbar und reproduzierbar

Module:

Modul Path Beschreibung
API Layer src/nsct/api/ REST-Endpunkte (FastAPI)
Models src/nsct/models/ Pydantic v2 Schemas (DTOs)
Storage src/nsct/storage/ SQLAlchemy 2.0 + asyncpg
Providers src/nsct/providers/ LLM/Vision/Audio/Search Abstraktionen
Security src/nsct/security/ SSRF-Schutz, URL-Validierung
Config src/nsct/config.py Environment-only, Pydantic BaseSettings
Logging src/nsct/logging_config.py JSON-Formatter, per-request context
Metrics src/nsct/metrics.py Prometheus-kompatible Metriken
Orchestration src/nsct/orchestration/ State-Machine, Budget-Limits
Stages src/nsct/stages/ Pipeline-Stages 513
Provenance src/nsct/provenance.py Deterministische Hashes, Audit-Trail

2. Schnellstart

2.1 Voraussetzungen

  • Docker ≥ 24.0
  • Docker Compose ≥ 2.23
  • Mindestens 2 GB freier RAM
  • Zugang zu einem OpenAI-kompatiblen LLM-Endpunkt (Port 8030)
  • Git installiert

2.2 Repository klonen

git clone https://git.frerkc.de/opencode/NSCT---Neutral-Search-Crawler-Tool.git
cd nsct

2.3 Environment-Datei erstellen und konfigurieren

cp .env.example .env

Trage die folgenden Werte in .env ein:

# ---- LLM (Hauptmodell, verpflichtend) ----
NSCT_LLM_BASE_URL=http://<LLM_HOST>:8030/openai/v1
NSCT_LLM_MODEL=Qwen3.6-35B
NSCT_LLM_MAX_CONCURRENCY=3
NSCT_LLM_API_KEY=<dein-api-key>

# ---- Vision (Bildanalyse, optional) ----
NSCT_VISION_BASE_URL=http://<LLM_HOST>:8030/openai/visual/v1
NSCT_VISION_MODEL=Qwen2.5-VL-3B
NSCT_VISION_API_KEY=<vision-api-key>

# ---- Audio (STT/TTS, optional) ----
NSCT_AUDIO_BASE_URL=http://<LLM_HOST>:8030/hermes-audio
NSCT_AUDIO_MODEL=default
NSCT_AUDIO_API_KEY=<audio-api-key>

# ---- PostgreSQL ----
POSTGRES_USER=nsct
POSTGRES_PASSWORD=<starkes-passwort>
POSTGRES_DB=nsct
NSCT_DB_URL=postgresql+asyncpg://nsct:<passwort>@postgres:5432/nsct

# ---- SearXNG (optional, interne Suche) ----
NSCT_SEARXNG_BASE_URL=http://searxng:8080

# ---- General ----
NSCT_DEBUG=false

Umgebungsvariablen-Referenz:

Variable Typ Default Beschreibung
NSCT_LLM_BASE_URL string "" OpenAI-kompatibler LLM-Endpunkt
NSCT_LLM_MODEL string "" Modell-Name
NSCT_LLM_MAX_CONCURRENCY int 3 Max parallele LLM-Requests
NSCT_LLM_API_KEY string "" API-Key für LLM-Provider
NSCT_VISION_BASE_URL string "" Vision-Endpunkt
NSCT_VISION_MODEL string "" Vision-Modell-Name
NSCT_VISION_API_KEY string "" API-Key für Vision
NSCT_AUDIO_BASE_URL string "" Audio-Endpunkt (STT/TTS)
NSCT_AUDIO_MODEL string default Audio-Modell
NSCT_AUDIO_API_KEY string "" API-Key für Audio
POSTGRES_USER string nsct PostgreSQL-User
POSTGRES_PASSWORD string nsct_secret PostgreSQL-Passwort
POSTGRES_DB string nsct PostgreSQL-Name
NSCT_DB_URL string "" DB-Connection-String
NSCT_SEARXNG_BASE_URL string "" SearXNG-Instance-URL
NSCT_DEBUG bool false Debug-Modus (Swagger aktiv)
NSCT_LOG_LEVEL string INFO Log-Level (DEBUG/INFO/WARNING/ERROR/CRITICAL)
NSCT_METRICS_ENABLED bool true /metrics Endpoint aktivieren

2.4 Dienste starten

# Vollständiger Stack mit Build
docker compose up --build -d

# Oder nur API + PostgreSQL (ohne SearXNG)
docker compose up --build -d nsct-api postgres

2.5 Health-Check prüfen

# Grundlegender Liveness-Check (immer ok, wenn Prozess lebt)
curl -s http://localhost:8080/health | jq

# Readiness-Check (prüft LLM-Erreichbarkeit)
curl -s http://localhost:8080/ready | jq

# Provider-Status (zeigt konfigurierte Models)
curl -s http://localhost:8080/providers | jq

Erwartete Antwort /health:

{
  "status": "ok",
  "version": "0.1.0"
}

Erwartete Antwort /ready (bei voller Konfiguration):

{
  "status": "ready",
  "llm": "ok",
  "database": "unknown",
  "llm_model": "Qwen3.6-35B"
}

2.6 curl-Tests

# Provider-Status anzeigen
curl -s http://localhost:8080/providers | jq

# Prometheus-Metriken (falls aktiv)
curl -s http://localhost:8080/metrics

# Swagger UI (nur bei NSCT_DEBUG=true)
# http://localhost:8080/docs
# ReDoc: http://localhost:8080/redoc

2.7 Getrenntes Frontend-Deployment

Das NSCT-Frontend darf auf einem anderen Rechner als dieser Backend-Stack laufen. Es benötigt kein gemeinsames Docker-Netzwerk: Der Frontend-Caddy proxyt /api/* an den in seinem .env gesetzten Backend-Host.

# im Frontend-Repository, ohne URL-Schema und ohne Pfad
NSCT_API_UPSTREAM=backend.example.com:8080

Erlaube am Backend-Port 8080 nur die IP bzw. das Netz des Frontend-Rechners und prüfe die Erreichbarkeit von dort mit curl http://backend.example.com:8080/health. Über nicht vertrauenswürdige Netze ist TLS, ein VPN oder ein zusätzlicher Reverse Proxy erforderlich. Browser greifen dabei nicht direkt auf das Backend zu; CORS für die Browser-Origin ist daher nicht erforderlich.


3. PostgreSQL Setup

3.1 Verbindung einrichten

Die Standard-Verbindungsdaten aus .env:

Host:     postgres (intern im Docker-Netzwerk)
Port:     5432
Database: nsct
User:     nsct
Password: <POSTGRES_PASSWORD aus .env>

Shell-Zugriff auf die DB:

# In den Container verbinden
docker compose exec postgres psql -U nsct -d nsct

# Oder von der Host-Shell (wenn Port 5432 gemappt ist)
PGPASSWORD=<passwort> psql -h localhost -U nsct -d nsct -p 5432

3.2 Backup (pg_dump)

# Vollständiges Dump (SQL-Format)
docker compose exec postgres pg_dump -U nsct -d nsct > nsct_backup_$(date +%Y%m%d).sql

# Komprimiertes Dump
docker compose exec postgres pg_dump -U nsct -d nsct | gzip > nsct_backup_$(date +%Y%m%d).sql.gz

# Nur Struktur (ohne Daten)
docker compose exec postgres pg_dump -U nsct -d nsct --schema-only > nsct_schema.sql

# Nur Daten (ohne Struktur)
docker compose exec postgres pg_dump -U nsct -d nsct --data-only > nsct_data.sql

# Backup in ein eigenes Volume (für Offline-Storage)
docker compose exec postgres pg_dump -U nsct -d nsct > /tmp/nsct_backup.sql
docker cp nsct-postgres:/tmp/nsct_backup.sql ./nsct_backup.sql

3.3 Restore (pg_restore)

# Restore aus SQL-Datei (psql-Format)
docker compose exec -T postgres psql -U nsct -d nsct < nsct_backup.sql

# Restore aus komprimierter Datei
gunzip -c nsct_backup.sql.gz | docker compose exec -T postgres psql -U nsct -d nsct

# Restore aus pg_dumpall (wenn mit pg_dumpall gesichert)
docker compose exec -T postgres psql -U nsct -d nsct < nsct_full_backup.sql

3.4 Migrationen

Die Datenbank wird aktuell manuell über SQLAlchemy-Alchemy-Reflection initialisiert. Migrationen werden über Alembic in zukünftigen Stages implementiert.

# Aktuelle Tabellenliste
docker compose exec postgres psql -U nsct -d nsct -c "\dt"

# Tabellenschema anzeigen
docker compose exec postgres psql -U nsct -d nsct -c "\d+ sources"

# Datenbankgröße
docker compose exec postgres psql -U nsct -d nsct -c "SELECT pg_database_size('nsct');"

# Tabellen-Größen (Byte)
docker compose exec postgres psql -U nsct -d nsct -c "\l+"
docker compose exec postgres psql -U nsct -d nsct -c "SELECT relname, pg_size_pretty(pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(tablename))) FROM pg_tables WHERE schemaname = 'public';"

3.5 Troubleshooting

# PostgreSQL ist nicht bereit — Connection refused
# Lösung: Warte bis health check bestanden ist
docker compose ps postgres

# Prüfe ob Container läuft
docker compose logs postgres

# Manueller health-check
docker compose exec postgres pg_isready -U nsct -d nsct

# Wenn Password-Auth fehlschlägt:
# .env prüfen: POSTGRES_PASSWORD muss mit dem Wert übereinstimmen,
# den du in NSCT_DB_URL verwendest.

# Connection-String validieren:
echo "$NSCT_DB_URL" | python3 -c "import sys; print(sys.stdin.read())"

# PostgreSQL-Fehler in Container-Logs
docker compose logs --tail=50 postgres

Häufige Fehler:

Fehler Ursache Lösung
FATAL: database "nsct" does not exist POSTGRES_DB in .env falsch gesetzt Prüfe POSTGRES_DB und NSCT_DB_URL
FATAL: password authentication failed Password mismatch .env korrigieren, docker compose down -v && up
could not connect to server: Connection refused PostgreSQL noch nicht ready Warte, prüfe docker compose ps
FATAL: too many connections Pool exhausted (default: 100) max_connections erhöhen oder Poolgröße senken
relation "xxx" does not exist Tabellen nicht initialisiert Prüfe, ob API korrekt gestartet ist

4. LLM-Endpunkte konfigurieren

Das NSCT-Backend erwartet OpenAI-kompatible Endpunkte (Standard: Ollama, vLLM, LMStudio).

4.1 Qwen3.6-35B (Hauptmodell)

Das primäre LLM für Claim-Extraktion, Synthese, Evidence-Scoring und alle LLM-gesteuerten Stages.

NSCT_LLM_BASE_URL=http://<LLM_HOST>:8030/openai/v1
NSCT_LLM_MODEL=Qwen3.6-35B
NSCT_LLM_MAX_CONCURRENCY=3
NSCT_LLM_API_KEY=<dein-api-key>

Endpoint-Validierung:

# Modellliste abfragen
curl -s http://<LLM_HOST>:8030/openai/v1/models | jq

# Erwartete Antwort:
# {
#   "data": [
#     { "id": "Qwen3.6-35B", "object": "model", "owned_by": "qwen" }
#   ]
# }

# Chat-Completion testen
curl -s http://<LLM_HOST>:8030/openai/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <dein-api-key>" \
  -d '{
    "model": "Qwen3.6-35B",
    "messages": [{"role": "user", "content": "Hello"}],
    "max_tokens": 10
  }' | jq

4.2 Qwen2.5-VL-3B (Vision)

Für Bild- und Dokumentenanalyse (Screenshots, PDFs, Charts).

NSCT_VISION_BASE_URL=http://<LLM_HOST>:8030/openai/visual/v1
NSCT_VISION_MODEL=Qwen2.5-VL-3B
NSCT_VISION_API_KEY=<vision-api-key>

Endpoint-Validierung:

# Models-Endpoint
curl -s http://<LLM_HOST>:8030/openai/visual/v1/models | jq

# Chat mit Bild (Base64) testen
curl -s http://<LLM_HOST>:8030/openai/visual/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <vision-api-key>" \
  -d '{
    "model": "Qwen2.5-VL-3B",
    "messages": [
      {
        "role": "user",
        "content": [
          {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}},
          {"type": "text", "text": "Was siehst du?"}
        ]
      }
    ]
  }' | jq

4.3 Audio-Modell (STT/TTS)

Für Audio-Transkription (Stage 11) und Text-to-Speech.

NSCT_AUDIO_BASE_URL=http://<LLM_HOST>:8030/hermes-audio
NSCT_AUDIO_MODEL=default
NSCT_AUDIO_API_KEY=<audio-api-key>

Endpoint-Validierung:

# Test-Request (STT)
curl -s http://<LLM_HOST>:8030/hermes-audio/transcribe \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <audio-api-key>" \
  -d '{
    "model": "default",
    "language": "de"
  }' | jq

4.4 Endpoint-Validierung aus der API

# Alle Provider auf einmal prüfen
curl -s http://localhost:8080/providers | jq

# Readiness-Check (LLM-Erreichbarkeit)
curl -s http://localhost:8080/ready | jq

4.5 Troubleshooting

Fehler Ursache Lösung
LLM connectivity check failed Endpunkt nicht erreichbar Prüfe Netzwerk, Firewall, .env
error:ConnectionRefused LLM-Server down Starte LLM-Server neu
error:401 API-Key falsch .env prüfen, Key regenerieren
error:404 Falscher Endpoint-Path Prüfe Base-URL (z.B. /openai/v1 vs /v1)
timeout LLM zu langsam NSCT_LLM_MAX_CONCURRENCY senken, Timeout erhöhen

LLM-Server-Status von innen prüfen:

# Vom nsct-api Container aus zum LLM-Host ping'en
docker compose run --rm nsct-api curl -s http://<LLM_HOST>:8030/openai/v1/models | jq

5. API-Key-Verwaltung

Hinweis: Research-Endpunkte unter /v1/research/* verlangen einen X-API-Key. Provider-Credentials (NSCT_LLM_API_KEY usw.) sind davon strikt getrennt und dürfen nie als Benutzer-Key verwendet werden.

5.1 API-Key administrieren

Die Anwendung legt die Tabellen beim Start an. API-Keys werden ausschließlich über den lokalen Admin-CLI erstellt; der Klartext erscheint genau einmal:

nsct-api-key create --username admin --name frontend --expires-at 2027-01-31T23:59:59Z
nsct-api-key list
nsct-api-key revoke <key_id>

Den ausgegebenen Key nur im Secret-Store bzw. im Frontend speichern. Die Datenbank enthält ausschließlich einen gesalzenen scrypt-Hash und eine öffentliche key_id, niemals den Key selbst.

5.2 Historische SQL-Struktur (nicht für neue Keys verwenden)

Die Datenbank-Struktur für User/API-Key-Management:

-- User-Tabelle (vorbereitet für Stage 22+)
CREATE TABLE IF NOT EXISTS users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    username VARCHAR(255) UNIQUE NOT NULL,
    email VARCHAR(255) UNIQUE,
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

-- API-Key-Tabelle
CREATE TABLE IF NOT EXISTS api_keys (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id) ON DELETE CASCADE,
    key_hash VARCHAR(255) NOT NULL,
    name VARCHAR(255),
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT NOW(),
    expires_at TIMESTAMP,
    last_used_at TIMESTAMP
);

-- Indexes
CREATE INDEX idx_api_keys_key_hash ON api_keys(key_hash);
CREATE INDEX idx_api_keys_user_id ON api_keys(user_id);
CREATE INDEX idx_api_keys_is_active ON api_keys(is_active);

5.2 User und API-Key anlegen

-- User erstellen
INSERT INTO users (username, email, is_active)
VALUES ('admin', 'admin@example.com', TRUE)
ON CONFLICT (username) DO NOTHING;

-- API-Key generieren (Hash speichern)
-- 1. Generiere einen sicheren Key (von der Admin-Shell)
python3 -c "import secrets, hashlib; key = secrets.token_hex(32); print('KEY:', key); print('HASH:', hashlib.sha256(key.encode()).hexdigest())"

-- 2. Hash in der DB speichern (behalte den Original-Key sicher!)
INSERT INTO api_keys (user_id, key_hash, name, is_active)
SELECT u.id, '<key-hash>', 'admin-key', TRUE
FROM users u WHERE u.username = 'admin';

5.3 API-Key-Hashes verwalten

-- Alle aktiven Keys mit User
SELECT ak.name, ak.is_active, ak.created_at, ak.expires_at, u.username, u.email
FROM api_keys ak
JOIN users u ON ak.user_id = u.id
WHERE ak.is_active = TRUE
ORDER BY ak.created_at DESC;

-- Key-Verwendung (last_used_at)
UPDATE api_keys SET last_used_at = NOW() WHERE key_hash = '<key-hash>';

-- Ablaufende Keys prüfen
SELECT ak.name, ak.expires_at, u.username
FROM api_keys ak
JOIN users u ON ak.user_id = u.id
WHERE ak.expires_at < NOW() AND ak.is_active = TRUE;

-- Inaktive Keys deaktivieren
UPDATE api_keys SET is_active = FALSE WHERE is_active = FALSE;

5.4 Debugging von Auth-Problemen

-- API-Key existiert und ist aktiv?
SELECT * FROM api_keys WHERE key_hash = '<key-hash>' AND is_active = TRUE;

-- User existiert?
SELECT * FROM users WHERE username = '<username>';

-- Hash-Korrelation prüfen
SELECT ak.key_hash, u.username, ak.is_active
FROM api_keys ak
JOIN users u ON ak.user_id = u.id
WHERE ak.key_hash = '<key-hash>';

6. Docker Compose Konfiguration

6.1 docker-compose.yml erklärt

# NSCT — docker-compose.yml
# Services: nsct-api, postgres, optional searxng

6.2 Services

Service Image Port Beschreibung
nsct-api build: . 8080:8080 FastAPI-Server
postgres postgres:16-alpine 5432:5432 PostgreSQL 16
searxng searxng/searxng:latest 8888:8080 Interne Suche (optional)

6.3 Service-Details

nsct-api

nsct-api:
  build:
    context: .
    dockerfile: Dockerfile
  container_name: nsct-api
  ports:
    - "8080:8080"
  environment:
    NSCT_DB_URL: postgresql+asyncpg://...@postgres:5432/nsct
    NSCT_LLM_MAX_CONCURRENCY: "3"
  depends_on:
    postgres:
      condition: service_healthy
    searxng:
      condition: service_started
  volumes:
    - nsct_data:/app/data
  healthcheck:
    test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:8080/health')\" || exit 1"]
    interval: 30s
    timeout: 5s
    retries: 3
    start_period: 15s
  read_only: true
  tmpfs:
    - /tmp
  cap_drop:
    - ALL

Umgebungsvariablen im Container:

  • NSCT_DB_URL — DB-URL (Host: postgres)
  • NSCT_LLM_MAX_CONCURRENCY — LLM-Concurrency (erbt aus .env)
  • Alle anderen vars aus .env (env_file: .env)

postgres

postgres:
  image: postgres:16-alpine
  container_name: nsct-postgres
  environment:
    POSTGRES_USER: ${POSTGRES_USER:-nsct}
    POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-nsct_secret}
    POSTGRES_DB: ${POSTGRES_DB:-nsct}
  ports:
    - "5432:5432"
  volumes:
    - postgres_data:/var/lib/postgresql/data
  healthcheck:
    test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-nsct} -d ${POSTGRES_DB:-nsct}"]
    interval: 10s
    timeout: 5s
    retries: 5
    start_period: 10s
  deploy:
    resources:
      limits:
        memory: 512M
        cpus: "0.5"

searxng (optional)

searxng:
  image: searxng/searxng:latest
  container_name: nsct-searxng
  ports:
    - "8888:8080"
  volumes:
    - searxng_settings:/etc/searxng
  environment:
    - SEARXNG_BASE_URL=http://localhost:8888/
    - SEARXNG_SECRET_KEY=nsct_searxng_secret_key_change_me
  read_only: true
  tmpfs:
    - /tmp
  cap_drop:
    - ALL

6.4 Volumes

Volume Pfad Beschreibung
postgres_data /var/lib/postgresql/data PostgreSQL-Datenbankdateien
nsct_data /app/data NSCT-Runtime-Daten (Cache, etc.)
searxng_settings /etc/searxng SearXNG-Konfiguration

6.5 Networks

Standard docker compose Netzwerk (bridge). Alle Services sind über Service-Namen erreichbar:

postgres     → postgres:5432
searxng      → searxng:8080
nsct-api     → nsct-api:8080

Für isolierte Netzwerke:

networks:
  nsct-internal:
    driver: bridge

6.6 Resource Limits

Service Memory Limit CPU Limit Memory Reservation
nsct-api 1G 1.0 256M
postgres 512M 0.5
searxng 512M 0.5

Anpassen: In docker-compose.yml unter deploy.resources.limits editieren.

6.7 Health Checks

Service Endpoint Interval Timeout Retries
nsct-api GET /health 30s 5s 3
postgres pg_isready 10s 5s 5

7. Health-Check, Logs, Troubleshooting

7.1 curl-Befehle

# Basic health — Liveness
curl -s http://localhost:8080/health | jq

# Readiness — prüft LLM-Connectivity
curl -s http://localhost:8080/ready | jq

# Provider-Info
curl -s http://localhost:8080/providers | jq

# Prometheus-Metriken
curl -s http://localhost:8080/metrics

# Swagger Docs (nur bei NSCT_DEBUG=true)
curl -s -I http://localhost:8080/docs

7.2 Docker Logs

# Alle Logs (live)
docker compose logs -f

# Nur API-Container
docker compose logs -f nsct-api

# Letzte 100 Zeilen
docker compose logs --tail=100 nsct-api

# Errors nur
docker compose logs -f nsct-api | grep ERROR

# PostgreSQL Logs
docker compose logs -f postgres

# Logs mit Zeitstempeln
docker compose logs -f --timestamp nsct-api

7.3 Container-Status prüfen

# Container-Status
docker compose ps

# Container-Details (JSON)
docker inspect nsct-api | jq '.[0].State'

# Container-Logs (letzte 50 Zeilen, nicht-live)
docker compose logs --tail=50 nsct-api

# Resource-Verbrauch
docker stats nsct-api nsct-postgres nsct-searxng

7.4 Restart-Prozedur

# Soft restart (alle Services)
docker compose restart

# Nur API neu starten
docker compose restart nsct-api

# Vollständiger Reload (neuer Build)
docker compose down
docker compose up --build -d

# Container komplett entfernen und neu erstellen
docker compose down -v
docker compose up --build -d

7.5 Common Errors und Lösungen

Error Lösung
Connection refused auf /health Warte 15s (start_period), prüfe docker compose ps
LLM: error:Connection refused LLM-Server prüfen, .env Base-URL validieren
LLM: error:ConnectionRefused Netzwerk-Verbindung prüfen: docker compose run --rm nsct-api curl -v <LLM_URL>
HTTP 503 bei Research-Requests Readiness prüfen: curl localhost:8080/ready
no space left on device docker system prune -f ausführen
Container OOMKilled Memory-Limit in docker-compose.yml erhöhen
relation "xxx" does not exist API neu starten (Schema wird automatisch initialisiert)
asyncpg.errors.InvalidCatalogName POSTGRES_DB in .env prüfen
CORS-Fehler im Browser ALLOWED_ORIGINS in main.py anpassen

8. SSL/TLS für externen Zugriff

8.1 Reverse-Proxy mit Caddy (empfohlen)

Caddy automatisiert TLS-Zertifikate (Let's Encrypt).

# Caddyfile — /etc/caddy/Caddyfile

# HTTPS mit automatischem TLS
nsct.example.com {
    reverse_proxy nsct-api:8080

    # Header-Sicherheit
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
    }

    # Rate-Limiting
    request_body {
        size 10MB
    }
}

Caddy in Docker Compose:

services:
  caddy:
    image: caddy:2-alpine
    container_name: nsct-caddy
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - nsct-internal
    restart: unless-stopped

8.2 Reverse-Proxy mit Nginx

# /etc/nginx/sites-available/nsct
server {
    listen 443 ssl http2;
    server_name nsct.example.com;

    ssl_certificate     /etc/letsencrypt/live/nsct.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/nsct.example.com/privkey.pem;

    # TLS-Konfiguration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # Security Headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;

    # Rate Limiting
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    limit_req zone=api burst=20 nodelay;

    location / {
        proxy_pass http://nsct-api:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Request-ID $request_id;

        # Timeouts für lange Research-Pipelines
        proxy_read_timeout 300s;
        proxy_connect_timeout 10s;
        proxy_send_timeout 300s;

        # Body-Größe
        client_max_body_size 10M;

        # Buffering deaktivieren für SSE/Streaming
        proxy_buffering off;
    }

    # Swagger UI nur whitelisted
    location /docs {
        allow 10.0.0.0/8;
        deny all;
        proxy_pass http://nsct-api:8080/docs;
    }
}

8.3 HTTPS-Zertifikate

Mit certbot (Nginx):

# Certificate anfordern
sudo certbot --nginx -d nsct.example.com

# Certificate renew test
sudo certbot renew --dry-run

# Manuell renew
sudo certbot renew

Mit Caddy (automatisch):

Caddy holt und renewt Zertifikate automatisch — keine manuelle Konfiguration nötig.

8.4 DNS-Konfiguration

# A-Record erstellen (IPv4)
# nsct.example.com. 300 IN A  <SERVER_IP>

# AAAA-Record (IPv6, optional)
# nsct.example.com. 300 IN AAAA  <SERVER_IPV6>

# Test DNS-Auflösung
dig nsct.example.com
nslookup nsct.example.com

9. Monitoring

9.1 Prometheus-Metriken

Der GET /metrics Endpunkt (Standard: aktiv) liefert Prometheus-kompatible Metriken:

curl -s http://localhost:8080/metrics

Metriken-Übersicht:

Metrik Typ Beschreibung
search_queries_total counter Anzahl Suchanfragen
sources_fetched_total counter Anzahl abgerufener Quellen
claims_extracted_total counter Extrahierte Claims gesamt
contradictions_detected_total counter Festgestellte Widersprüche
research_completed_total counter Abgeschlossene Research-Läufe
research_failed_total counter Fehlgeschlagene Research-Läufe
research_duration_seconds histogram Dauer pro Research-Lauf
llm_request_duration_seconds histogram Dauer pro LLM-Anfrage
active_research_runs gauge Laufende Research-Läufe

Steuerung über Environment:

# Metriken-Endpoint aktivieren/deaktivieren
NSCT_METRICS_ENABLED=true   # default
NSCT_METRICS_ENABLED=false  # deaktivieren

9.2 Log-Ebenen

# Log-Level konfigurieren
NSCT_LOG_LEVEL=INFO    # DEBUG | INFO | WARNING | ERROR | CRITICAL

Log-Format (JSON):

{
  "timestamp": "2025-09-06T12:00:00",
  "level": "INFO",
  "module": "nsct.api.main",
  "message": "HTTP GET /health 200 2.34ms",
  "request_id": "a1b2c3d4-...",
  "method": "GET",
  "path": "/health",
  "status_code": "200",
  "duration_ms": 2.34
}

Log-Ausgabeort:

# Logs nach stdout (default)
NSCT_LOG_FILE=

# Logs in Datei schreiben
NSCT_LOG_FILE=/app/data/nsct.log

9.3 Alerting-Empfehlungen

# Prometheus Alerting Rules (prometheus.yml)
# Beispiel: Research-Fehler-Rate alert
groups:
  - name: nsct
    rules:
      - alert: HighResearchFailureRate
        expr: rate(research_failed_total[5m]) > 0.1
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "Hohe Research-Fehler-Rate"
          description: "Mehr als 10% Research-Läufe fehlschlagen."

      - alert: LLMUnreachable
        expr: ready{status="not_ready"} == 1
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "LLM nicht erreichbar"
          description: "Readiness-Check schlägt fehl."

      - alert: HighErrorRate
        expr: rate(http_requests_total{status_code=~"5.."}[5m]) > 0.05
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "Hohe 5xx-Fehler-Rate"
          description: "Mehr als 5% aller Requests enden mit 5xx."

Prometheus-Target-Konfiguration:

# scrape_configs
- job_name: 'nsct-api'
  static_configs:
    - targets: ['nsct-api:8080']
  metrics_path: '/metrics'
  scrape_interval: 15s

9.4 Grafana-Dashboard (empfohlene Panels)

Panel Metrik Typ
Research-Rate rate(research_completed_total[5m]) Gauge
Fehlerrate rate(research_failed_total[5m]) Gauge
LLM-Latenz histogram_quantile(0.95, research_duration_seconds_bucket) Histogram
Aktive Runs active_research_runs Gauge
Quellen pro Lauf Quellen-Counter / Research-Counter Ratio
DB-Größe pg_database_size('nsct') Gauge

10. Deployment-Checkliste

10.1 Pre-Deploy-Checkliste

  • .env korrekt ausgefüllt und nicht in Git committet
  • POSTGRES_PASSWORD stark genug (mind. 16 Zeichen, Sonderzeichen)
  • NSCT_LLM_API_KEY gültig und zugreifbar
  • NSCT_LLM_BASE_URL erreichbar (curl-Test vom Host aus)
  • Resource Limits in docker-compose.yml an Hardware angepasst
  • NSCT_LOG_LEVEL auf INFO für Production gesetzt
  • NSCT_DEBUG=false (Swagger deaktiviert)
  • Firewall-Regeln: nur Port 443 (HTTPS) offen, nicht 8080
  • Backup-Policy für PostgreSQL-Volume eingerichtet
  • SSL/TLS-Zertifikat bereitgestellt (Caddy oder certbot)
  • DNS-Einträge verifiziert (dig nsct.example.com)
  • CORS-Origins an Production-URL angepasst
  • .dockerignore enthält .env, .venv, __pycache__
  • Git-Status sauber (keine uncommitted Änderungen)
  • Reverse-Proxy konfiguriert und getestet
  • Rate-Limiting aktiviert (Proxy-Ebene)

10.2 Post-Deploy-Verifikation

# 1. Container laufen?
docker compose ps
# Alle 3 Services sollten "healthy" sein

# 2. Health-Check
curl -s http://localhost:8080/health | jq
# Erwartet: {"status": "ok", "version": "0.1.0"}

# 3. Readiness-Check
curl -s http://localhost:8080/ready | jq
# Erwartet: {"status": "ready", "llm": "ok", ...}

# 4. Provider-Status
curl -s http://localhost:8080/providers | jq
# Alle konfigurierten Provider sollten available=true zeigen

# 5. Logs prüfen
docker compose logs --tail=20 nsct-api
# Keine ERROR-Level Einträge nach Startup

# 6. Research-Test (vollständig)
curl -s http://localhost:8080/v1/research \
  -H "Content-Type: application/json" \
  -d '{
    "query": "Test recherche",
    "language": "de",
    "depth": "quick"
  }' | jq

# 7. Metriken prüfen
curl -s http://localhost:8080/metrics | head -20

# 8. Reverse-Proxy Test (HTTPS)
curl -s -I https://nsct.example.com/health | head -5

# 9. DB-Verbindung
docker compose exec postgres pg_isready -U nsct -d nsct

# 10. Resource-Usage
docker stats --no-stream nsct-api nsct-postgres

10.3 Rollback-Prozedur

# --- Rollback auf vorherige Version ---

# 1. Vorheriges Image taggen (vor dem Update gemacht?)
# docker tag nsct-api nsct-api:rollback-$(date +%Y%m%d)

# 2. Alte Version deployen
git checkout <commit-hash>
docker compose down
docker compose up --build -d

# 3. Datenbank-Backup vor Rollback?
# Falls Migrationen rückgängig gemacht werden müssen:
docker compose exec postgres pg_dump -U nsct -d nsct > pre_rollback_backup.sql

# 4. Health-Check nach Rollback
sleep 20
curl -s http://localhost:8080/health | jq
curl -s http://localhost:8080/ready | jq

# 5. Logs auf Fehler prüfen
docker compose logs --tail=50 nsct-api | grep -i error

# 6. Restore aus Backup (nur falls benötigt)
# gunzip -c nsct_backup.sql.gz | docker compose exec -T postgres psql -U nsct -d nsct

10.4 Disaster Recovery

# --- Komplette Wiederherstellung ---

# 1. Neue Instanz aufsetzen (frisches System)
# Docker, Docker Compose, Git-Clone etc.

# 2. .env aus Backup wiederherstellen
cp /backup/.env ./nsct/.env

# 3. PostgreSQL-Datenbank wiederherstellen
gunzip -c /backup/nsct_backup.sql.gz | docker compose exec -T postgres psql -U nsct -d nsct

# 4. API starten
docker compose up -d nsct-api

# 5. Verifizierung
curl -s http://localhost:8080/health | jq
curl -s http://localhost:8080/ready | jq
docker compose exec postgres psql -U nsct -d nsct -c "SELECT count(*) FROM research_runs;"

10.5 Wartungsplan

# --- Regelmäßige Wartung ---

# Monatlich:
# 1. PostgreSQL-Bereinigung (alte Research-Läufe)
docker compose exec postgres psql -U nsct -d nsct -c "
SELECT relname, pg_size_pretty(pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(tablename)))
FROM pg_tables WHERE schemaname = 'public' ORDER BY pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(tablename)) DESC;"

# 2. Log-Rotation prüfen
# journalctl --vacuum-time=7d  (falls systemd-logs verwendet)

# 3. Zertifikat-Ablauf prüfen
openssl x509 -in /etc/ssl/certs/nsct.pem -noout -enddate

# 4. Docker-Prune (Platz freimachen)
docker system prune -f --filter "until=168h"  # alte Images löschen

Anhang: Schnelle Referenz

Wichtige Ports

Dienst Port Zugriff
NSCT API 8080 Intern (Docker) / 443 extern (Reverse-Proxy)
PostgreSQL 5432 Intern (Docker)
SearXNG 8888 Intern (Docker)

Wichtige Pfade

Pfad Beschreibung
/.env Environment-Konfiguration
/docker-compose.yml Container-Definition
/Dockerfile Container-Build
/src/nsct/api/ API-Router
/src/nsct/config.py Config-Module
/src/nsct/logging_config.py Logging-Setup
/src/nsct/metrics.py Prometheus-Metriken
/src/nsct/security/policy.py SSRF-Schutz

Kommando-Zusammenfassung

# Deployment
docker compose up --build -d          # Deploy
docker compose down                    # Stop
docker compose restart                 # Restart

# Debugging
docker compose logs -f nsct-api        # Logs
docker compose exec postgres psql ...  # DB-Zugriff
docker compose exec nsct-api ...       # API-Container-Shell

# Health
curl localhost:8080/health             # Liveness
curl localhost:8080/ready              # Readiness
curl localhost:8080/metrics            # Prometheus

# Backup
docker compose exec postgres pg_dump -U nsct -d nsct > backup.sql
docker compose exec postgres pg_restore ...

# Monitoring
docker stats                           # Resource Usage
docker compose ps                      # Container Status