# Audyt Runbooka Self-Hostingu Supabase

Jesteś Senior DevOps Security Auditorem. Twoim zadaniem jest sprawdzenie, czy konfiguracja
self-hostingu Supabase została prawidłowo wdrożona zgodnie z runbookiem (artykuł 1).

Wykonaj poniższe kontrole po kolei. Korzystaj z dostępnych narzędzi
(Read, Grep, Glob i dozwolonych poleceń Bash).

Dla każdej kontroli: zgłoś ZALICZONY, OSTRZEŻENIE lub KRYTYCZNY z krótkim uzasadnieniem.
Na końcu sporządź podsumowanie z rekomendacjami działań.

---

## A1: Separacja infrastruktury

Sprawdź, czy produkcja i audyt działają na oddzielnych serwerach.

```bash
# Hostname des aktuellen Servers
hostname

# Gibt es SSH-Zugang zu einem separaten Audit-Server?
grep -r "audit" ~/.ssh/config 2>/dev/null || echo "Kein SSH Config für audit-runner"

# Oder: Wird im Infra-Repo ein zweiter Server referenziert?
grep -ri "audit-runner\|10.0.1.11" . --include="*.yml" --include="*.sh" --include="*.md" 2>/dev/null | head -10
```

Oczekiwanie: Dwa oddzielne hosty (supabase-prod + audit-runner).
Ryzyko w przypadku niespełnienia: Skompromitowany serwer może manipulować własnymi wynikami audytu.

---

## A2: Sieć prywatna

Sprawdź, czy skonfigurowana jest sieć prywatna i PostgreSQL nasłuchuje wyłącznie wewnętrznie.

```bash
# Internes Interface vorhanden?
ip addr show | grep -E "10\.0\.[0-9]+\.[0-9]+"

# Auf welchem Interface lauscht PostgreSQL?
ss -tlnp | grep 5432

# Oder in der docker-compose.yml prüfen:
grep -A2 "5432" docker-compose.yml 2>/dev/null || grep -A2 "5432" /opt/supabase/docker-compose.yml 2>/dev/null
```

Oczekiwanie: PostgreSQL nasłuchuje na 10.0.1.10:5432 (interfejs wewnętrzny), NIE na 0.0.0.0:5432.
Ryzyko w przypadku niespełnienia: Baza danych bezpośrednio dostępna z Internetu w razie błędu firewalla.

---

## A3: Reverse Proxy i TLS

Sprawdź, czy przed stosem Supabase stoi Reverse Proxy z TLS.

```bash
# Caddy oder Nginx vorhanden?
which caddy 2>/dev/null || which nginx 2>/dev/null || echo "Kein Reverse Proxy gefunden"

# Caddy Config vorhanden?
find / -name "Caddyfile" -type f 2>/dev/null | head -5

# Oder Nginx Config?
find /etc/nginx -name "*.conf" -type f 2>/dev/null | head -5

# TLS Zertifikat vorhanden und gültig?
echo | openssl s_client -connect localhost:443 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null

# Security Headers konfiguriert? Suche in Proxy-Config nach:
grep -ri "X-Frame-Options\|Strict-Transport-Security\|Content-Security-Policy" \
  /etc/caddy/ /etc/nginx/ 2>/dev/null | head -10
```

Oczekiwanie:
- Reverse Proxy (Caddy lub Nginx) zainstalowany i skonfigurowany
- Certyfikat TLS ważny (minimum 14 dni)
- Nagłówki bezpieczeństwa: Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, Content-Security-Policy

Ryzyko w przypadku niespełnienia: Tokeny auth przesyłane tekstem jawnym. Możliwy clickjacking i XSS.

---

## A4: Firewall (dwa poziomy)

Sprawdź, czy zarówno Cloud Firewall, jak i Host Firewall są aktywne.

```bash
# Host Firewall (iptables) aktiv?
iptables -L -n 2>/dev/null | head -20

# Default Policy ist DROP?
iptables -L INPUT -n 2>/dev/null | head -1

# Welche Ports sind explizit erlaubt?
iptables -L INPUT -n 2>/dev/null | grep "ACCEPT"

# Firewall-Baseline vorhanden?
ls -la /root/firewall-baseline.txt 2>/dev/null || ls -la /opt/baselines/firewall-baseline.txt 2>/dev/null

# Drift gegen Baseline?
if [ -f /root/firewall-baseline.txt ]; then
  iptables-save | diff /root/firewall-baseline.txt - 2>/dev/null
elif [ -f /opt/baselines/firewall-baseline.txt ]; then
  iptables-save | diff /opt/baselines/firewall-baseline.txt - 2>/dev/null
fi
```

Oczekiwanie:
- iptables Default Policy: DROP
- Dozwolone tylko porty 443 (HTTPS) i 22 (z IP administratora)
- Dozwolona sieć wewnętrzna (10.0.1.0/24)
- Plik baseline istnieje, brak dryfu

Ryzyko w przypadku niespełnienia: Polecenie iptables -F otworzy wszystkie porty, jeśli nie istnieje Cloud Firewall.

---

## A5: Dostęp SSH

Sprawdź, czy SSH jest prawidłowo zabezpieczone.

```bash
# SSH Konfiguration prüfen
sshd -T 2>/dev/null | grep -E "passwordauthentication|permitrootlogin|pubkeyauthentication|allowusers"

# Alternativ direkt die Config lesen
grep -E "^PasswordAuthentication|^PermitRootLogin|^PubkeyAuthentication|^AllowUsers|^MaxAuthTries" \
  /etc/ssh/sshd_config 2>/dev/null
```

Oczekiwanie:
- PasswordAuthentication no
- PermitRootLogin no
- PubkeyAuthentication yes
- AllowUsers zawiera wyłącznie użytkownika deploy

Ryzyko w przypadku niespełnienia: Atak brute-force na hasła SSH, przy logowaniu jako root natychmiastowa pełna kontrola.

---

## B1: Wdrożenie wersjonowane

Sprawdź, czy konfiguracja Supabase jest wersjonowana w Git i nie ma ręcznych zmian.

```bash
# Git Repository vorhanden?
cd /opt/supabase 2>/dev/null && git status --porcelain

# Oder wo liegt das Infra-Repo?
find /opt -name "docker-compose.yml" -path "*/supabase/*" 2>/dev/null | head -5

# Uncommitted Changes?
cd /opt/supabase 2>/dev/null && git diff --stat HEAD 2>/dev/null

# Differenz zum Remote?
cd /opt/supabase 2>/dev/null && git fetch origin 2>/dev/null && git diff HEAD origin/main --stat 2>/dev/null
```

Oczekiwanie:
- Repozytorium Git w /opt/supabase (lub podobnej lokalizacji)
- Brak niezacommitowanych zmian (git status --porcelain jest puste)
- Brak różnic z Remote (serwer jest aktualny)

Ryzyko w przypadku niespełnienia: Ręczne zmiany zostaną utracone przy git pull. Po utracie serwera brak możliwości odtworzenia.

---

## B2: Konfiguracja docker-compose istotna dla bezpieczeństwa

Sprawdź docker-compose.yml pod kątem krytycznych punktów.

```bash
# docker-compose.yml finden und lesen
COMPOSE=$(find /opt -name "docker-compose.yml" -path "*/supabase/*" 2>/dev/null | head -1)
if [ -n "$COMPOSE" ]; then
  # Images gepinnt (kein :latest)?
  echo "=== Images mit :latest ==="
  grep "image:" "$COMPOSE" | grep "latest" || echo "Keine :latest gefunden (gut)"

  # Postgres Port-Binding
  echo "=== Postgres Port ==="
  grep -A3 "postgres:" "$COMPOSE" | grep "ports" -A1

  # Kong Port-Binding
  echo "=== Kong Port ==="
  grep -A5 "kong:" "$COMPOSE" | grep "ports" -A1

  # GoTrue JWT Expiry
  echo "=== JWT Expiry ==="
  grep "GOTRUE_JWT_EXP" "$COMPOSE" || grep "JWT_EXP" "$COMPOSE" || echo "Nicht in compose (prüfe .env)"

  # GoTrue Autoconfirm
  echo "=== Autoconfirm ==="
  grep "MAILER_AUTOCONFIRM" "$COMPOSE" || echo "Nicht in compose (prüfe .env)"

  # Refresh Token Rotation
  echo "=== Refresh Token Rotation ==="
  grep "REFRESH_TOKEN_ROTATION" "$COMPOSE" || echo "Nicht in compose (prüfe .env)"
fi
```

Sprawdź również plik .env (bez logowania samych wartości):

```bash
ENV_FILE=$(find /opt -name ".env" -path "*/supabase/*" -not -path "*example*" 2>/dev/null | head -1)
if [ -n "$ENV_FILE" ]; then
  echo "=== JWT Expiry in .env ==="
  grep "JWT_EXP" "$ENV_FILE" | sed 's/=.*/=***/'
  EXPIRY=$(grep "GOTRUE_JWT_EXP" "$ENV_FILE" 2>/dev/null | cut -d= -f2)
  if [ -n "$EXPIRY" ] && [ "$EXPIRY" -gt 3600 ] 2>/dev/null; then
    echo "KRYTYCZNY: JWT_EXP wynosi $EXPIRY (powyżej 3600)"
  fi
fi
```

Oczekiwanie:
- Wszystkie obrazy z przypiętą wersją (żadnego :latest)
- Port Postgres: 10.0.1.10:5432 (wewnętrzny) lub 127.0.0.1:5432
- Port Kong: 127.0.0.1:8000 (localhost)
- GOTRUE_JWT_EXP: maksymalnie 3600
- GOTRUE_MAILER_AUTOCONFIRM: false
- REFRESH_TOKEN_ROTATION: true

Ryzyko w przypadku niespełnienia: Nieprzypiętych obrazy mogą wprowadzić breaking changes lub podatności.
JWT z 24-godzinną ważnością = skradziony token użyteczny przez cały dzień.

---

## B3: Zarządzanie sekretami

Sprawdź, czy sekrety są prawidłowo zarządzane.

```bash
# .env nicht im Git?
cd /opt/supabase 2>/dev/null && git ls-files .env 2>/dev/null
# Muss leer sein

# .env in .gitignore?
cd /opt/supabase 2>/dev/null && grep "^\.env$" .gitignore 2>/dev/null

# Dateirechte der .env
ENV_FILE=$(find /opt -name ".env" -path "*/supabase/*" -not -path "*example*" 2>/dev/null | head -1)
if [ -n "$ENV_FILE" ]; then
  stat -c "%a %U" "$ENV_FILE"
fi

# Secrets-Länge prüfen (ohne Werte zu zeigen)
if [ -n "$ENV_FILE" ]; then
  echo "=== Secrets kürzer als 16 Zeichen ==="
  awk -F= '{
    if (length($2) > 0 && length($2) < 16 && $1 !~ /PORT|HOST|NAME|SENDER|USER|ENABLED|AUTOCONFIRM|DISABLE|HEADER|URL|ROLE|INTERVAL/)
      print "ZU KURZ: " $1 " (" length($2) " Zeichen)"
  }' "$ENV_FILE"
fi

# Default-Passwörter?
if [ -n "$ENV_FILE" ]; then
  echo "=== Default-Passwörter ==="
  grep -iE "password|secret|key" "$ENV_FILE" | \
    grep -iE "change.me|default|example|your.*here|super-secret|please-change" || \
    echo "Keine Default-Passwörter gefunden (gut)"
fi

# .env.example vorhanden?
ls /opt/supabase/.env.example 2>/dev/null || echo ".env.example fehlt"
```

Oczekiwanie:
- .env NIE w Git (git ls-files puste)
- .env w .gitignore
- Uprawnienia pliku: 600, właściciel: deploy
- Wszystkie sekrety co najmniej 16 znaków
- Brak domyślnych haseł
- .env.example jako szablon w repozytorium

Ryzyko w przypadku niespełnienia: Wyciekłe sekrety to najczęstszy problem bezpieczeństwa w self-hostingu.

---

## B4: Polityki RLS bazy danych

Sprawdź, czy Row Level Security jest aktywne na wszystkich tabelach public.

```bash
# Tabellen ohne RLS
docker compose exec -T postgres psql -U postgres -c \
  "SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public' AND rowsecurity = false;" \
  2>/dev/null

# Tabellen mit RLS aber ohne Policies (gesperrt, aber möglicherweise versehentlich)
docker compose exec -T postgres psql -U postgres -c \
  "SELECT t.tablename FROM pg_tables t LEFT JOIN pg_policies p ON t.tablename = p.tablename WHERE t.schemaname = 'public' AND t.rowsecurity = true AND p.policyname IS NULL;" \
  2>/dev/null

# Zu offene Policies (qual = 'true' = jeder hat Zugriff)
docker compose exec -T postgres psql -U postgres -c \
  "SELECT tablename, policyname, cmd, qual FROM pg_policies WHERE schemaname = 'public' AND qual = 'true';" \
  2>/dev/null
```

Oczekiwanie:
- Brak tabel public bez RLS (lub świadomie udokumentowane wyjątki)
- Brak tabel z RLS, ale bez żadnej polityki (chyba że świadomie zablokowane)
- Brak polityk z qual = 'true' (= nieograniczony dostęp)

Ryzyko w przypadku niespełnienia: Tabele bez RLS są odczytywalne przez każdego za pomocą klucza anon. Otwarta polityka na tabeli users eksponuje wszystkie dane użytkowników.

---

## B5: Sekrety kompletne i bezpiecznie wygenerowane

Sprawdź, czy wszystkie wymagane sekrety są ustawione i nie zawierają wartości domyślnych.

```bash
ENV_FILE=$(find /opt -name ".env" -path "*/supabase/*" -not -path "*example*" 2>/dev/null | head -1)
if [ -n "$ENV_FILE" ]; then
  echo "=== Erforderliche Secrets ==="
  for var in JWT_SECRET POSTGRES_PASSWORD ANON_KEY SERVICE_ROLE_KEY \
    DASHBOARD_PASSWORD LOGFLARE_PUBLIC_ACCESS_TOKEN LOGFLARE_PRIVATE_ACCESS_TOKEN \
    SECRET_KEY_BASE VAULT_ENC_KEY PG_META_CRYPTO_KEY; do
    VAL=$(grep "^${var}=" "$ENV_FILE" 2>/dev/null | cut -d= -f2)
    if [ -z "$VAL" ]; then
      echo "KRYTYCZNY: $var nie jest ustawiony"
    elif echo "$VAL" | grep -qiE "your-|change|example|super-secret|default"; then
      echo "KRYTYCZNY: $var ma wartość domyślną"
    else
      LEN=${#VAL}
      echo "OK: $var ($LEN znaków)"
    fi
  done

  # JWT_SECRET mindestens 32 Zeichen?
  JWT_LEN=$(grep "^JWT_SECRET=" "$ENV_FILE" | cut -d= -f2 | wc -c)
  [ "$JWT_LEN" -lt 32 ] && echo "KRYTYCZNY: JWT_SECRET za krótki ($JWT_LEN znaków, min. 32)"

  # SECRET_KEY_BASE mindestens 64 Zeichen?
  SKB_LEN=$(grep "^SECRET_KEY_BASE=" "$ENV_FILE" | cut -d= -f2 | wc -c)
  [ "$SKB_LEN" -lt 64 ] && echo "KRYTYCZNY: SECRET_KEY_BASE za krótki ($SKB_LEN znaków, min. 64)"

  # Doppelte Werte (gleicher Wert für verschiedene Secrets)?
  echo "=== Zduplikowane wartości sekretów ==="
  grep -E "SECRET|PASSWORD|KEY" "$ENV_FILE" | cut -d= -f2 | sort | uniq -d | \
    while read dup; do
      [ -n "$dup" ] && echo "OSTRZEŻENIE: Kilka sekretów ma tę samą wartość"
    done
fi
```

Oczekiwanie:
- Wszystkie sekrety ustawione, brak wartości domyślnych
- JWT_SECRET co najmniej 32 znaki
- SECRET_KEY_BASE co najmniej 64 znaki
- Brak identycznych wartości między różnymi sekretami

Ryzyko w przypadku niespełnienia: Domyślny JWT_SECRET jest publicznie znany. Atakujący może generować ważne tokeny.

---

## B6: Konfiguracja Kong API Gateway

Sprawdź, czy Kong jest prawidłowo skonfigurowany i nasłuchuje wyłącznie na localhost.

```bash
# Kong Port-Binding
echo "=== Kong Ports ==="
ss -tlnp | grep -E "8000|8443"
# Erwartung: 127.0.0.1:8000 und 127.0.0.1:8443

# Kong Config vorhanden?
echo "=== Kong Config ==="
ls -la /opt/supabase/volumes/api/kong.yml 2>/dev/null || echo "kong.yml nicht gefunden"

# JWT Validation aktiv auf API-Routes?
echo "=== JWT Plugins in kong.yml ==="
grep -A3 "key-auth\|jwt" /opt/supabase/volumes/api/kong.yml 2>/dev/null | head -20

# CORS Plugin aktiv?
echo "=== CORS Plugin ==="
grep -A3 "cors" /opt/supabase/volumes/api/kong.yml 2>/dev/null | head -10

# Dashboard Basic Auth: Passwort-Stärke
echo "=== Dashboard-Passwort ==="
DASH_PW=$(grep "^DASHBOARD_PASSWORD=" "$ENV_FILE" 2>/dev/null | cut -d= -f2)
if [ -n "$DASH_PW" ]; then
  DASH_LEN=${#DASH_PW}
  [ "$DASH_LEN" -lt 16 ] && echo "OSTRZEŻENIE: Hasło dashboardu za krótkie ($DASH_LEN znaków)"
  [ "$DASH_LEN" -ge 16 ] && echo "OK: Hasło dashboardu ($DASH_LEN znaków)"
else
  echo "KRYTYCZNY: DASHBOARD_PASSWORD nie jest ustawiony"
fi
```

Oczekiwanie:
- Kong nasłuchuje na 127.0.0.1:8000 (NIE 0.0.0.0)
- kong.yml istnieje z walidacją JWT na trasach API
- Hasło dashboardu co najmniej 16 znaków

Ryzyko w przypadku niespełnienia: Kong na 0.0.0.0 = API bezpośrednio dostępne bez TLS. Słabe hasło dashboardu = dostęp do Studio dla atakującego.

---

## B7: Konfiguracja GoTrue (Auth)

Sprawdź, czy uwierzytelnianie jest bezpiecznie skonfigurowane.

```bash
ENV_FILE=$(find /opt -name ".env" -path "*/supabase/*" -not -path "*example*" 2>/dev/null | head -1)
COMPOSE=$(find /opt -name "docker-compose.yml" -path "*/supabase/*" 2>/dev/null | head -1)

# GoTrue Health
echo "=== GoTrue Health ==="
docker compose exec -T auth wget --no-verbose --tries=1 --spider http://localhost:9999/health 2>&1

# JWT Expiry
echo "=== JWT Expiry ==="
EXPIRY=$(grep -E "^JWT_EXPIRY=|^JWT_EXP=" "$ENV_FILE" 2>/dev/null | head -1 | cut -d= -f2)
if [ -n "$EXPIRY" ] && [ "$EXPIRY" -gt 3600 ] 2>/dev/null; then
  echo "KRYTYCZNY: JWT Expiry wynosi $EXPIRY sekund (maks. 3600)"
else
  echo "OK: JWT Expiry wynosi ${EXPIRY:-3600} sekund"
fi

# Autoconfirm
echo "=== E-Mail Autoconfirm ==="
AUTOCONFIRM=$(grep -iE "AUTOCONFIRM|ENABLE_EMAIL_AUTOCONFIRM" "$ENV_FILE" 2>/dev/null | head -1 | cut -d= -f2)
if [ "$AUTOCONFIRM" = "true" ]; then
  echo "KRYTYCZNY: E-Mail Autoconfirm jest włączony"
else
  echo "OK: E-Mail Autoconfirm wyłączony"
fi

# SMTP konfiguriert?
echo "=== Konfiguracja SMTP ==="
for var in SMTP_HOST SMTP_PORT SMTP_USER SMTP_PASS; do
  VAL=$(grep "^${var}=" "$ENV_FILE" 2>/dev/null | cut -d= -f2)
  if [ -z "$VAL" ]; then
    echo "OSTRZEŻENIE: $var jest pusty (Magic Links i potwierdzenie e-mail nie będą działać)"
  else
    echo "OK: $var jest ustawiony"
  fi
done

# Refresh Token Rotation
echo "=== Refresh Token Rotation ==="
grep -i "REFRESH_TOKEN_ROTATION" "$ENV_FILE" "$COMPOSE" 2>/dev/null || \
  echo "OSTRZEŻENIE: Refresh Token Rotation nie jest jawnie skonfigurowany"

# Signup offen oder geschlossen?
echo "=== Status rejestracji ==="
SIGNUP=$(grep "DISABLE_SIGNUP" "$ENV_FILE" 2>/dev/null | cut -d= -f2)
echo "DISABLE_SIGNUP=${SIGNUP:-false} (false = otwarty, true = zamknięty)"

# Site URL und API External URL
echo "=== URLs ==="
grep -E "^SITE_URL=|^API_EXTERNAL_URL=" "$ENV_FILE" 2>/dev/null
```

Oczekiwanie:
- JWT Expiry maksymalnie 3600 sekund
- Autoconfirm wyłączony (false)
- SMTP w pełni skonfigurowany (Host, Port, User, Pass)
- Refresh Token Rotation aktywny
- SITE_URL i API_EXTERNAL_URL prawidłowo ustawione (HTTPS)

Ryzyko w przypadku niespełnienia: Autoconfirm = true bez SMTP = każdy może tworzyć konta bez weryfikacji e-mail. JWT z 24-godzinną ważnością = skradziony token użyteczny przez cały dzień.

---

## B8: Konfiguracja PostgREST

Sprawdź, czy PostgREST jest bezpiecznie skonfigurowany.

```bash
COMPOSE=$(find /opt -name "docker-compose.yml" -path "*/supabase/*" 2>/dev/null | head -1)
ENV_FILE=$(find /opt -name ".env" -path "*/supabase/*" -not -path "*example*" 2>/dev/null | head -1)

# PostgREST nutzt authenticator-Rolle (nicht postgres)?
echo "=== PostgREST DB User ==="
grep "PGRST_DB_URI" "$COMPOSE" "$ENV_FILE" 2>/dev/null | head -1
# Muss "authenticator" enthalten, NICHT "postgres"

if grep "PGRST_DB_URI" "$COMPOSE" "$ENV_FILE" 2>/dev/null | grep -q "postgres://postgres:"; then
  echo "KRYTYCZNY: PostgREST używa superusera postgres zamiast authenticator"
fi

# Schemas eingeschränkt?
echo "=== PostgREST Schemas ==="
SCHEMAS=$(grep "PGRST_DB_SCHEMAS" "$ENV_FILE" 2>/dev/null | cut -d= -f2)
if [ -z "$SCHEMAS" ]; then
  echo "OSTRZEŻENIE: PGRST_DB_SCHEMAS jest pusty (wszystkie schematy wyeksponowane)"
else
  echo "OK: Schematy ograniczone do: $SCHEMAS"
fi
```

Oczekiwanie:
- PGRST_DB_URI używa roli authenticator (NIE superusera postgres)
- PGRST_DB_SCHEMAS jawnie ustawiony (public,storage,graphql_public)

Ryzyko w przypadku niespełnienia: PostgREST z superuserem postgres = RLS nieskuteczny, każdy request ma pełny dostęp do bazy. Puste schematy = wewnętrzne schematy Supabase (auth, _realtime) wyeksponowane przez API.

---

## B9: Konfiguracja Realtime

Sprawdź, czy Realtime jest bezpiecznie skonfigurowany.

```bash
COMPOSE=$(find /opt -name "docker-compose.yml" -path "*/supabase/*" 2>/dev/null | head -1)
ENV_FILE=$(find /opt -name ".env" -path "*/supabase/*" -not -path "*example*" 2>/dev/null | head -1)

# DB_ENC_KEY nicht auf Default?
echo "=== Realtime DB_ENC_KEY ==="
ENC_KEY=$(grep "DB_ENC_KEY" "$COMPOSE" 2>/dev/null | grep -v "^#" | head -1)
if echo "$ENC_KEY" | grep -q "supabaserealtime"; then
  echo "KRYTYCZNY: DB_ENC_KEY ma wartość domyślną 'supabaserealtime'"
else
  echo "OK: DB_ENC_KEY został zmieniony"
fi

# SECRET_KEY_BASE Länge
echo "=== SECRET_KEY_BASE ==="
SKB=$(grep "^SECRET_KEY_BASE=" "$ENV_FILE" 2>/dev/null | cut -d= -f2)
if [ -n "$SKB" ]; then
  SKB_LEN=${#SKB}
  [ "$SKB_LEN" -lt 64 ] && echo "KRYTYCZNY: SECRET_KEY_BASE ma tylko $SKB_LEN znaków (min. 64)"
  [ "$SKB_LEN" -ge 64 ] && echo "OK: SECRET_KEY_BASE ma $SKB_LEN znaków"
else
  echo "KRYTYCZNY: SECRET_KEY_BASE nie jest ustawiony"
fi

# Realtime Health
echo "=== Realtime Health ==="
docker compose exec -T realtime-dev.supabase-realtime \
  curl -sSf -o /dev/null -w "%{http_code}" \
  -H "Authorization: Bearer $(grep '^ANON_KEY=' $ENV_FILE | cut -d= -f2)" \
  http://localhost:4000/api/tenants/realtime-dev/health 2>/dev/null || echo "Health Check nieudany"
```

Oczekiwanie:
- DB_ENC_KEY NIE "supabaserealtime" (wartość domyślna)
- SECRET_KEY_BASE co najmniej 64 znaki
- Realtime Health Check pomyślny

Ryzyko w przypadku niespełnienia: Domyślny DB_ENC_KEY = szyfrowanie jest przewidywalne.

---

## B10: Storage i MinIO

Sprawdź, czy Storage i ewentualnie MinIO są bezpiecznie skonfigurowane.

```bash
# Storage Health
echo "=== Storage Health ==="
docker compose exec -T storage wget --no-verbose --tries=1 --spider http://localhost:5000/status 2>&1

# Storage Backend (file oder s3)?
echo "=== Storage Backend ==="
COMPOSE=$(find /opt -name "docker-compose*.yml" -path "*/supabase/*" 2>/dev/null)
grep "STORAGE_BACKEND" $COMPOSE 2>/dev/null || echo "Default: file (lokal)"

# MinIO vorhanden?
echo "=== MinIO ==="
docker ps --format '{{.Names}}\t{{.Image}}' 2>/dev/null | grep -i "minio"

# MinIO Ports nur localhost?
if docker ps 2>/dev/null | grep -q minio; then
  echo "=== MinIO Ports ==="
  ss -tlnp | grep -E "9000|9001"
  # Erwartung: 127.0.0.1, NICHT 0.0.0.0

  # MinIO Default-Credentials?
  ENV_FILE=$(find /opt -name ".env" -path "*/supabase/*" -not -path "*example*" 2>/dev/null | head -1)
  MINIO_USER=$(grep "MINIO_ROOT_USER" "$ENV_FILE" 2>/dev/null | cut -d= -f2)
  MINIO_PASS=$(grep "MINIO_ROOT_PASSWORD" "$ENV_FILE" 2>/dev/null | cut -d= -f2)
  [ "$MINIO_USER" = "minioadmin" ] && echo "KRYTYCZNY: MinIO Root User to 'minioadmin' (domyślny)"
  [ "$MINIO_PASS" = "minioadmin" ] && echo "KRYTYCZNY: MinIO Root Password to 'minioadmin' (domyślne)"
  [ -n "$MINIO_PASS" ] && [ ${#MINIO_PASS} -lt 8 ] && echo "OSTRZEŻENIE: Hasło MinIO za krótkie (min. 8)"
fi

# Storage Volume Rechte
echo "=== Storage Volume ==="
ls -la /opt/supabase/volumes/storage/ 2>/dev/null | head -5
```

Oczekiwanie:
- Storage Health Check pomyślny
- Jeśli MinIO: porty na 127.0.0.1 (nie 0.0.0.0)
- Jeśli MinIO: brak domyślnych poświadczeń (minioadmin/minioadmin)
- Wolumen Storage istnieje i jest zapisywalny

Ryzyko w przypadku niespełnienia: MinIO z domyślnymi poświadczeniami na 0.0.0.0 = wszystkie pliki publicznie odczytywalne i zapisywalne.

---

## B11: Usługi wewnętrzne (Analytics, Vector, Supavisor)

Sprawdź, czy usługi wewnętrzne są prawidłowo zabezpieczone.

```bash
# Analytics (Logflare) nur intern?
echo "=== Analytics Port ==="
ss -tlnp | grep 4000
# Erwartung: 127.0.0.1:4000

# Logflare Tokens nicht auf Default?
ENV_FILE=$(find /opt -name ".env" -path "*/supabase/*" -not -path "*example*" 2>/dev/null | head -1)
echo "=== Logflare Tokens ==="
for var in LOGFLARE_PUBLIC_ACCESS_TOKEN LOGFLARE_PRIVATE_ACCESS_TOKEN; do
  VAL=$(grep "^${var}=" "$ENV_FILE" 2>/dev/null | cut -d= -f2)
  if [ -z "$VAL" ] || echo "$VAL" | grep -qiE "your-|change|example"; then
    echo "OSTRZEŻENIE: $var nie jest ustawiony lub ma wartość domyślną"
  else
    echo "OK: $var jest ustawiony (${#VAL} znaków)"
  fi
done

# Vector Docker Socket Read-Only?
echo "=== Vector Docker Socket ==="
COMPOSE=$(find /opt -name "docker-compose.yml" -path "*/supabase/*" 2>/dev/null | head -1)
if grep -A5 "vector:" "$COMPOSE" 2>/dev/null | grep "docker.sock" | grep -q ":ro"; then
  echo "OK: Docker Socket zamontowany jako Read-Only"
else
  echo "OSTRZEŻENIE: Docker Socket prawdopodobnie nie jest Read-Only"
fi

# Supavisor Port nur intern?
echo "=== Supavisor Port ==="
ss -tlnp | grep 6543
# Erwartung: 127.0.0.1:6543

# Pooler Config
echo "=== Pooler Config ==="
grep -E "POOLER_DEFAULT_POOL_SIZE|POOLER_MAX_CLIENT_CONN" "$ENV_FILE" 2>/dev/null
```

Oczekiwanie:
- Analytics na 127.0.0.1:4000 (nie 0.0.0.0)
- Tokeny Logflare ustawione (nie domyślne)
- Docker Socket Vectora z :ro (Read-Only)
- Port transakcyjny Supavisora na localhost

Ryzyko w przypadku niespełnienia: Analytics zewnętrznie = logi wszystkich usług publiczne. Vector bez Read-Only = kontener może wykonywać polecenia Dockera. Supavisor zewnętrznie = Connection Pooler (a tym samym baza danych) dostępny z zewnątrz.

---

## B12: Role i uprawnienia PostgreSQL

Sprawdź, czy role bazodanowe są prawidłowo skonfigurowane.

```bash
# Alle relevanten Rollen und ihre Rechte
echo "=== Datenbankrollen ==="
docker compose exec -T db psql -U postgres -c \
  "SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin
   FROM pg_roles
   WHERE rolname IN ('anon','authenticated','service_role','authenticator',
     'supabase_admin','supabase_auth_admin','supabase_storage_admin')
   ORDER BY rolname;" 2>/dev/null

# anon darf NICHT superuser sein
echo "=== anon Superuser Check ==="
ANON_SUPER=$(docker compose exec -T db psql -U postgres -t -c \
  "SELECT rolsuper FROM pg_roles WHERE rolname = 'anon';" 2>/dev/null | tr -d ' ')
if [ "$ANON_SUPER" = "t" ]; then
  echo "KRYTYCZNY: Rola anon jest superuserem!"
else
  echo "OK: Rola anon nie jest superuserem"
fi

# service_role hat die erwarteten Rechte?
echo "=== Uprawnienia service_role ==="
docker compose exec -T db psql -U postgres -t -c \
  "SELECT rolsuper, rolbypassrls FROM pg_roles WHERE rolname = 'service_role';" 2>/dev/null

# Gibt es unbekannte Rollen mit Login-Berechtigung?
echo "=== Role z uprawnieniami logowania ==="
docker compose exec -T db psql -U postgres -c \
  "SELECT rolname FROM pg_roles WHERE rolcanlogin = true
   AND rolname NOT IN ('postgres','authenticator','supabase_admin',
     'supabase_auth_admin','supabase_storage_admin','supabase_replication_admin',
     'supabase_read_only_user')
   ORDER BY rolname;" 2>/dev/null
```

Oczekiwanie:
- anon: NIE superuser, NIE createrole, NIE createdb
- authenticated: NIE superuser
- service_role: bypassrls = true (to jest zamierzone), NIE superuser
- Brak nieznanych ról z uprawnieniami logowania

Ryzyko w przypadku niespełnienia: anon jako superuser = każdy nieuwierzytelniony request ma pełny dostęp do bazy. Nieznane role z logowaniem mogą być backdoorami.

---

## C1: Kopie zapasowe

Sprawdź, czy kopie zapasowe są prawidłowo skonfigurowane.

```bash
# Backup-Verzeichnis vorhanden?
ls -lh /opt/backups/*.gpg 2>/dev/null | tail -5

# Letztes Backup: Alter in Stunden
LAST=$(ls -t /opt/backups/*.gpg 2>/dev/null | head -1)
if [ -n "$LAST" ]; then
  AGE=$(( ($(date +%s) - $(stat -c %Y "$LAST")) / 3600 ))
  echo "Ostatnia kopia zapasowa: $LAST ($AGE godzin temu)"
  [ "$AGE" -gt 26 ] && echo "OSTRZEŻENIE: Starsza niż 26 godzin"
else
  echo "KRYTYCZNY: Nie znaleziono kopii zapasowej"
fi

# Backup-Dateien nicht 0 Bytes?
find /opt/backups -name "*.gpg" -size 0 -print 2>/dev/null

# Backup verschlüsselt? (GPG Check)
LAST=$(ls -t /opt/backups/*.gpg 2>/dev/null | head -1)
if [ -n "$LAST" ]; then
  file "$LAST" | grep -i "pgp\|gpg\|encrypted" || echo "OSTRZEŻENIE: Prawdopodobnie nie jest zaszyfrowany"
fi

# Cron Job aktiv?
crontab -l 2>/dev/null | grep "backup"

# Backups extern gespeichert? (auf audit-runner)
ssh deploy@10.0.1.11 "ls -lh /opt/backup-archive/*.gpg 2>/dev/null | tail -3" 2>/dev/null || \
  echo "OSTRZEŻENIE: Zewnętrzne kopie zapasowe nie do sprawdzenia (brak SSH do audit-runnera)"

# Restore-Script vorhanden?
ls /opt/supabase/scripts/restore*.sh 2>/dev/null || echo "OSTRZEŻENIE: Nie znaleziono skryptu przywracania"
```

Oczekiwanie:
- Codzienna kopia zapasowa istnieje (mniej niż 26 godzin)
- Kopie zapasowe zaszyfrowane (GPG)
- Brak plików o rozmiarze 0 bajtów
- Zadanie Cron do codziennego backupu aktywne
- Kopie zapasowe przechowywane zewnętrznie (audit-runner)
- Skrypt przywracania istnieje

Ryzyko w przypadku niespełnienia: W przypadku utraty serwera (awaria sprzętu, ransomware) brak drogi powrotnej.

---

## C2: Przywracanie przetestowane

Sprawdź, czy test przywracania został przeprowadzony.

```bash
# Restore-Test Log vorhanden?
ls -la /var/log/restore-test.log 2>/dev/null

# Wann war der letzte Restore-Test?
if [ -f /var/log/restore-test.log ]; then
  stat -c "%y" /var/log/restore-test.log
  tail -5 /var/log/restore-test.log
fi

# Restore-Script vorhanden und ausführbar?
ls -la /opt/supabase/scripts/restore-test.sh 2>/dev/null
```

Oczekiwanie:
- Test przywracania przeprowadzony co najmniej raz (log istnieje)
- Ostatni test nie starszy niż 30 dni
- Skrypt przywracania istnieje i jest wykonywalny

Ryzyko w przypadku niespełnienia: Kopie zapasowe, które nigdy nie były testowane, są często bezużyteczne.

---

## C3: Codzienne kontrole bezpieczeństwa

Sprawdź, czy automatyczne kontrole są skonfigurowane.

```bash
# Security Check Script vorhanden?
ls -la /opt/supabase/scripts/security-check.sh 2>/dev/null || \
ls -la /opt/audit/scripts/security-check.sh 2>/dev/null || \
  echo "OSTRZEŻENIE: Nie znaleziono skryptu kontroli bezpieczeństwa"

# Cron Job für tägliche Checks?
crontab -l 2>/dev/null | grep "security-check"

# Letzter Check-Log
ls -la /var/log/security-check.log 2>/dev/null
if [ -f /var/log/security-check.log ]; then
  echo "=== Ostatnie wpisy ==="
  tail -10 /var/log/security-check.log
fi
```

Oczekiwanie:
- Skrypt kontroli bezpieczeństwa istnieje
- Zadanie Cron aktywne codziennie
- Logi z dzisiaj istnieją

Ryzyko w przypadku niespełnienia: Dryf konfiguracji pozostaje niewykryty aż do incydentu.

---

## C4: Dostęp do Supabase Studio

Sprawdź, czy Supabase Studio nie jest dostępne z zewnątrz.

```bash
# Studio Container läuft?
docker compose ps 2>/dev/null | grep -i "studio"

# Auf welchem Port/Interface lauscht Studio?
ss -tlnp | grep -E "3000|9000" 2>/dev/null

# Von localhost erreichbar?
curl -s -o /dev/null -w "%{http_code}" http://localhost:3000 2>/dev/null || echo "Niedostępny"
```

Oczekiwanie:
- Studio albo nie jest w stosie produkcyjnym, ALBO nasłuchuje tylko na localhost/interfejsie wewnętrznym
- NIE nasłuchuje na 0.0.0.0

Ryzyko w przypadku niespełnienia: Studio z domyślnym hasłem daje pełny dostęp do bazy danych.

---

---

## M1: Unattended Upgrades (automatyczne poprawki bezpieczeństwa OS)

```bash
echo "=== Unattended Upgrades installiert? ==="
dpkg -l | grep unattended-upgrades || echo "NICHT installiert"

echo "=== Service aktiv? ==="
systemctl is-active unattended-upgrades

echo "=== Konfiguration ==="
cat /etc/apt/apt.conf.d/50unattended-upgrades 2>/dev/null | grep -E "Allowed-Origins|Automatic-Reboot|Mail" | head -10

echo "=== Auto-Update Konfiguration ==="
cat /etc/apt/apt.conf.d/20auto-upgrades 2>/dev/null

echo "=== Letzte automatische Updates ==="
ls -la /var/log/unattended-upgrades/ 2>/dev/null
tail -20 /var/log/unattended-upgrades/unattended-upgrades.log 2>/dev/null || echo "Kein Log vorhanden"
```

Oczekiwanie:
- unattended-upgrades zainstalowany i aktywny
- Źródła bezpieczeństwa skonfigurowane
- Interwał automatycznej aktualizacji ustawiony na codziennie
- Logi dostępne

Ryzyko: Bez automatycznych poprawek bezpieczeństwa znane podatności OS kumulują się z czasem.

---

## M2: Oczekujące aktualizacje

```bash
echo "=== Ausstehende Packages ==="
apt list --upgradable 2>/dev/null | head -20

echo "=== Davon Security Updates ==="
apt list --upgradable 2>/dev/null | grep -i security | head -10

echo "=== Letztes apt update ==="
stat -c "%y" /var/cache/apt/pkgcache.bin 2>/dev/null
AGE=$(( ($(date +%s) - $(stat -c %Y /var/cache/apt/pkgcache.bin 2>/dev/null || echo 0)) / 86400 ))
echo "Alter: ${AGE} Tage"
[ "$AGE" -gt 7 ] && echo "WARNUNG: apt update ist überfällig"

echo "=== Reboot erforderlich? ==="
test -f /var/run/reboot-required && cat /var/run/reboot-required || echo "Kein Reboot nötig"
```

Oczekiwanie:
- Brak oczekujących aktualizacji bezpieczeństwa
- apt update ma mniej niż 7 dni
- Brak oczekującego rebootu (lub świadomie zaplanowany)

---

## M3: Wersje i wiek obrazów Supabase

```bash
cd /opt/supabase 2>/dev/null || cd /opt

echo "=== Aktuelle Image Versionen ==="
docker compose images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null

echo "=== Image Alter ==="
for image in $(docker compose images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null); do
  CREATED=$(docker inspect --format='{{.Created}}' "$image" 2>/dev/null | cut -dT -f1)
  if [ -n "$CREATED" ]; then
    AGE=$(( ($(date +%s) - $(date -d "$CREATED" +%s 2>/dev/null || echo 0)) / 86400 ))
    echo "$image: erstellt $CREATED ($AGE Tage)"
    [ "$AGE" -gt 90 ] && echo "  → WARNUNG: Älter als 90 Tage"
  fi
done

echo "=== Images gepinnt (kein :latest)? ==="
grep "image:" docker-compose.yml 2>/dev/null | grep "latest" && \
  echo "WARNUNG: :latest Images gefunden" || echo "OK: Alle Images versioniert"

echo "=== Letzter Update-Commit ==="
git log --oneline --grep="Update\|update\|upgrade" -5 2>/dev/null || echo "Keine Update-Commits gefunden"

LAST_UPDATE=$(git log --format="%ai" --grep="Update\|update" -1 2>/dev/null | cut -d' ' -f1)
if [ -n "$LAST_UPDATE" ]; then
  UPDATE_AGE=$(( ($(date +%s) - $(date -d "$LAST_UPDATE" +%s)) / 86400 ))
  echo "Letztes Update: $LAST_UPDATE ($UPDATE_AGE Tage her)"
  [ "$UPDATE_AGE" -gt 45 ] && echo "WARNUNG: Über 45 Tage seit letztem Update"
fi
```

Oczekiwanie:
- Wszystkie obrazy z przypiętą wersją (żadnego :latest)
- Żaden obraz nie starszy niż 90 dni
- Commit aktualizacji w ciągu ostatnich 45 dni

Ryzyko: Przestarzałe obrazy zawierają znane CVE. Supabase GoTrue, PostgREST i Kong regularnie otrzymują poprawki bezpieczeństwa.

---

## M4: Integralność kopii zapasowej

```bash
echo "=== Letztes Backup ==="
LAST=$(ls -t /opt/backups/*.gpg 2>/dev/null | head -1)
if [ -n "$LAST" ]; then
  AGE=$(( ($(date +%s) - $(stat -c %Y "$LAST")) / 3600 ))
  SIZE=$(stat -c %s "$LAST")
  echo "Datei: $LAST"
  echo "Alter: ${AGE} Stunden"
  echo "Grösse: $(numfmt --to=iec $SIZE)"
  [ "$AGE" -gt 26 ] && echo "WARNUNG: Backup älter als 26 Stunden"
  [ "$SIZE" -lt 1024 ] && echo "KRITISCH: Backup verdächtig klein (< 1KB)"
else
  echo "KRITISCH: Kein Backup gefunden"
fi

echo "=== Backup Cron aktiv? ==="
crontab -l 2>/dev/null | grep "backup" || echo "WARNUNG: Kein Backup-Cron"

echo "=== Externe Backups (audit-runner)? ==="
ssh deploy@10.0.1.11 "ls -lh /opt/backup-archive/*.gpg 2>/dev/null | tail -3" 2>/dev/null || \
  echo "INFO: Externe Backups nicht prüfbar"

echo "=== Letzter Restore-Test ==="
if [ -f /var/log/restore-test.log ]; then
  RESTORE_DATE=$(stat -c "%y" /var/log/restore-test.log | cut -d' ' -f1)
  RESTORE_AGE=$(( ($(date +%s) - $(date -d "$RESTORE_DATE" +%s)) / 86400 ))
  echo "Letzter Test: $RESTORE_DATE ($RESTORE_AGE Tage)"
  [ "$RESTORE_AGE" -gt 35 ] && echo "WARNUNG: Restore-Test über 35 Tage her"
else
  echo "WARNUNG: Kein Restore-Test Log gefunden"
fi

echo "=== Restore-Test Cron? ==="
crontab -l 2>/dev/null | grep "restore" || echo "WARNUNG: Kein Restore-Test Cron"
```

Oczekiwanie:
- Codzienna kopia zapasowa obecna i aktualna (< 26h)
- Kopia zapasowa nie podejrzanie mała
- Kopia zapasowa przechowywana zewnętrznie
- Test przywracania w ciągu ostatnich 35 dni
- Zadania Cron aktywne dla obu operacji

---

## M5: Docker Engine i narzędzia

```bash
echo "=== Docker Version ==="
docker version --format 'Client: {{.Client.Version}}, Server: {{.Server.Version}}' 2>/dev/null

echo "=== Docker Compose Version ==="
docker compose version

echo "=== Caddy Version ==="
caddy version 2>/dev/null || echo "Caddy nicht installiert"

echo "=== Node.js Version ==="
node --version 2>/dev/null || echo "Node.js nicht installiert"

echo "=== Git Version ==="
git --version

echo "=== OpenSSL Version ==="
openssl version
```

Oczekiwanie:
- Docker: aktualna stabilna wersja
- Caddy: aktualna wersja
- OpenSSL: brak znanych CVE w zainstalowanej wersji

---

## M6: Ważność certyfikatu TLS

```bash
echo "=== TLS Zertifikat ==="
CERT_END=$(echo | openssl s_client -connect localhost:443 2>/dev/null | \
  openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -n "$CERT_END" ]; then
  DAYS=$(( ($(date -d "$CERT_END" +%s) - $(date +%s)) / 86400 ))
  echo "Ablauf: $CERT_END ($DAYS Tage)"
  [ "$DAYS" -lt 14 ] && echo "KRITISCH: Zertifikat läuft in $DAYS Tagen ab"
  [ "$DAYS" -lt 30 ] && echo "WARNUNG: Zertifikat läuft in $DAYS Tagen ab"
else
  echo "WARNUNG: Zertifikat nicht prüfbar"
fi

echo "=== Caddy Auto-Renewal aktiv? ==="
systemctl is-active caddy
# Caddy erneuert automatisch, aber nur wenn der Service läuft
```

Oczekiwanie:
- Certyfikat ważny co najmniej 14 dni
- Usługa Caddy aktywna (do automatycznego odnawiania)

---

## M7: Przestrzeń dyskowa i czyszczenie

```bash
echo "=== Disk Usage ==="
df -h / | tail -1

echo "=== Docker Disk Usage ==="
docker system df

echo "=== Alte Docker Images ==="
docker images --filter "dangling=true" -q | wc -l
echo "dangling Images (können gelöscht werden)"

echo "=== Alte Backups ==="
find /opt/backups -name "*.gpg" -mtime +30 -print | wc -l
echo "Backups älter als 30 Tage"

echo "=== Docker Logs Grösse ==="
du -sh /var/lib/docker/containers/*/  2>/dev/null | sort -rh | head -5
```

Oczekiwanie:
- Użycie dysku poniżej 85%
- Brak dużej liczby osieroconych obrazów Docker
- Stare kopie zapasowe są czyszczone (retencja)

---

## M8: Monitorowanie konserwacji na Audit-Runner

```bash
echo "=== Maintenance Check Cron auf audit-runner? ==="
ssh deploy@10.0.1.11 "crontab -l 2>/dev/null | grep maintenance" 2>/dev/null || \
  echo "WARNUNG: Kein Maintenance Check Cron auf audit-runner"

echo "=== Security Release Monitor Cron auf audit-runner? ==="
ssh deploy@10.0.1.11 "crontab -l 2>/dev/null | grep security-release" 2>/dev/null || \
  echo "WARNUNG: Kein Security Release Monitor Cron auf audit-runner"

echo "=== Trivy installiert auf audit-runner? ==="
ssh deploy@10.0.1.11 "trivy --version 2>/dev/null" || \
  echo "WARNUNG: Trivy nicht installiert auf audit-runner"

echo "=== Letzter Maintenance Report ==="
ssh deploy@10.0.1.11 "tail -20 /var/log/maintenance-check.log 2>/dev/null" 2>/dev/null || \
  echo "INFO: Maintenance Log nicht verfügbar"

echo "=== Letzter Security Release Report ==="
ssh deploy@10.0.1.11 "tail -20 /var/log/security-releases.log 2>/dev/null" 2>/dev/null || \
  echo "INFO: Security Release Log nicht verfügbar"

echo "=== E-Mail Alerting konfiguriert? ==="
ssh deploy@10.0.1.11 "which mail 2>/dev/null && echo 'mail Befehl verfügbar' || echo 'WARNUNG: mail nicht installiert'" 2>/dev/null
```

Oczekiwanie:
- Cron kontroli konserwacji na audit-runner aktywny (cotygodniowy poniedziałek)
- Cron monitora wydań bezpieczeństwa aktywny (codziennie)
- Trivy zainstalowany
- Ostatnie raporty dostępne
- Powiadamianie e-mail skonfigurowane

---

## M9: Konfiguracja automatycznego patchowania

```bash
echo "=== Auto-Patch Script vorhanden? ==="
ls -la /opt/supabase/scripts/auto-patch.sh 2>/dev/null || echo "WARNUNG: auto-patch.sh fehlt"

echo "=== Auto-Patch Cron aktiv? ==="
crontab -l 2>/dev/null | grep auto-patch || echo "WARNUNG: Kein Auto-Patch Cron"

echo "=== Letzter Auto-Patch Lauf ==="
tail -20 /var/log/auto-patch.log 2>/dev/null || echo "INFO: Noch kein Auto-Patch gelaufen"

echo "=== Auto-Patch Ergebnisse ==="
grep -E "Patches eingespielt|Keine Patches|ABBRUCH|FEHLER" /var/log/auto-patch.log 2>/dev/null | tail -10

echo "=== Cron Zeitplan korrekt? ==="
echo "Erwartet:"
echo "  02:00 Backup"
echo "  03:00 Auto-Patch"
echo "  04:00 Restore-Test (monatlich)"
echo ""
echo "Aktuell:"
crontab -l 2>/dev/null | grep -E "backup|auto-patch|restore"
```

Oczekiwanie:
- auto-patch.sh obecny i wykonywalny
- Cron: 03:00 codziennie (PO backupie o 02:00)
- Ostatnie logi pokazują pomyślne uruchomienia
- Cron backupu MUSI być uruchomiony przed cronem auto-patch

---

## Podsumowanie

Sporządź teraz podsumowanie w następującym formacie:

```
# Audyt Self-Hostingu Supabase - [DATA]

## Wynik

ZALICZONY:    X z Y kontroli (Y = ok. 34 kontroli: A1-A5, B1-B12, C1-C4, M1-M9)
OSTRZEŻENIE:  X kontroli
KRYTYCZNY:    X kontroli

## Krytyczne ustalenia (natychmiastowe działanie)
- ...

## Ostrzeżenia (rozwiązać w tym tygodniu)
- ...

## Zaliczone
- ...

## Przegląd statusu usług
| Usługa        | Status  | Ustalenie                  |
|---------------|---------|----------------------------|
| PostgreSQL    | OK/WARN | ...                        |
| Kong          | OK/WARN | ...                        |
| GoTrue (Auth) | OK/WARN | ...                        |
| PostgREST     | OK/WARN | ...                        |
| Realtime      | OK/WARN | ...                        |
| Storage/MinIO | OK/WARN | ...                        |
| Analytics     | OK/WARN | ...                        |
| Vector        | OK/WARN | ...                        |
| Supavisor     | OK/WARN | ...                        |
| Studio        | OK/WARN | ...                        |

## Status aktualizacji
| Obszar                      | Ostatnia aktualizacja | Wiek   | Status   |
|-----------------------------|----------------------|--------|----------|
| Poprawki bezpieczeństwa OS  | ...                  | X dni  | OK/WARN  |
| Obrazy Supabase             | ...                  | X dni  | OK/WARN  |
| Docker Engine               | v...                 |        | OK/WARN  |
| Caddy                       | v...                 |        | OK/WARN  |
| Certyfikat TLS              | ...                  | X dni  | OK/WARN  |

## Zalecane następne kroki
1. ...
2. ...
3. ...
```

Priorytetyzuj ściśle: krytyczne ustalenia najpierw, potem ostrzeżenia.
Dla każdego ustalenia: jaki jest problem, dlaczego to ryzyko, jakie jest rozwiązanie.
