Playbook: Zarządzanie Incydentami — p24-infra

Scope: Pinbox24 W3/W4, MongoDB rs0, n8n, WAHA, monitoring, AI workers
Ostatnia aktualizacja: 2026-07-01


1. Poziomy severności

PoziomDefinicjaPrzykładySLA odpowiedzi
P0Produkcja całkowicie niedostępna lub ryzyko utraty danychMongoDB rs0 down, bms-1 unreachable, nginx-proxy crashed, wyciek credentials w logachNatychmiast — przerywa wszystko
P1Kluczowa funkcja niesprawna, wszyscy lub większość użytkowników dotkniętaZepsuty upload plików, Excel import 500, auth 504, GPS sync zatrzymany, s3 microservice dead≤15 min
P2Pojedynczy użytkownik/biuro lub degradacja wydajności, alert GrafanaJeden endpoint zwraca 422, Redis reconnect loop, alert EndpointDown bez potwierdzenia globalnej awarii≤2h
P3Brak wpływu na użytkowników, kosmetykaStary ghost entry w nginx-proxy, nieaktualna dokumentacja, alert false-positiveNajbliższa sesja robocza

Trigger examples

P0: MongoDB rs0 PRIMARY down → wszystkie zapisy odrzucone
P0: v42-prod nie może połączyć z RabbitMQ przy starcie → wszystkie endpointy zawieszają się (ETIMEDOUT)
P0: PM2 loguje pełne MongoDB URI → potencjalny wyciek credentials (issue #2397)

P1: s3-v2-v42-prod zwraca OP_QUERY error → upload/Excel broken (Mongoose 4.x vs MongoDB 6.x)
P1: v42-prod oczekuje body.result[0] ale microservice zwraca bare array → 500 na wszystkich uploadach
P1: nginx-proxy nie widzi kontenera (błędna sieć Docker) → 502 na wszystkich API calls

P2: Redis OVH DBaaS zamknął idle connection → sporadyczne timeouty
P2: alert EndpointDown w Grafanie dla jednego URL-a
P2: iptables DROP przed UFW blokuje MongoDB RS heartbeat → SECONDARY lag

2. Jak zgłosić incydent

Kto zgłasza

  • Claude agent (jeśli wykryje anomalię w odpowiedzi HTTP lub logach)
  • Grafana Alertmanager (webhook → Discord)
  • Użytkownik (przez Discord lub bezpośrednio)
  • GitHub Action (failed deployment)

Gdzie zgłosić — OBOWIĄZKOWE przed workaroundem

ZAWSZE utwórz GH issue PRZED zastosowaniem workaroundu. Nie pomijaj zgłoszenia w milczeniu.

gh issue create \
  --repo radieu/p24-infra \
  --label "bug" \
  --title "[Incident] <komponent> — <symptom>" \
  --body "$(cat <<'EOF'
## Symptom
<dokładny komunikat błędu, HTTP status>
 
## Blast radius
<ilu użytkowników dotyczy, jakie funkcje>
 
## Severność
P0 / P1 / P2
 
## Pierwsze kroki dochodzenia
<co już sprawdzono>
EOF
)"

Format tytułu GH issue

[Incident] v42-prod — RabbitMQ ETIMEDOUT, wszystkie endpointy zawieszają się
[Incident] nginx-proxy — 502 na api.w4.pinbox24.com (błędna sieć Docker)
[Incident] MongoDB rs0 — PRIMARY bms-2 niedostępny
[Incident] s3-v2-v42-prod — OP_QUERY error na Mongoose 4.x / MongoDB 6.x

Discord notification

MSG="[P1] v42-prod — upload broken, s3 microservice returns 500\nPrzyczynia: Mongoose 4.x OP_QUERY\nGH: https://github.com/radieu/p24-infra/issues/NNN"
curl -s -X POST "$P24_DISCORD_INFRA_SCRIPTS_ERRORS_WEBHOOK_URL" \
  -H "Content-Type: application/json" \
  -d "{\"embeds\":[{\"title\":\"🔴 INCIDENT P1 — p24-infra\",\"color\":15158332,\"description\":\"$MSG\"}]}"

3. Dochodzenie

Pierwsze kroki — w tej kolejności

KrokNarzędzieCo sprawdzić
1Mezmo (centralne logi)Logi z ostatnich 15 min dla affected container / serwisu. To jest PIERWSZY krok.
2curl endpointHTTP status — 502 (nginx nie widzi kontenera) vs 504 (timeout) vs 4xx/5xx (app)
3docker ps na bms-1Czy kontener działa? Uptime? Status?
4PM2 logsBłędy aplikacji — Mongoose disconnect, RabbitMQ ETIMEDOUT, OP_QUERY
5nginx-proxy configGhost entries, błędna sieć Docker
6MongoDB rs.status()Stan replica set, PRIMARY/SECONDARY health

Komendy diagnostyczne

# --- bms-1 (94.23.26.113) ---
 
# 1. Status kontenerów
ssh root@94.23.26.113 'docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "v4[0-9]|v3[0-9]|nginx"'
 
# 2. PM2 logs (v42-prod)
ssh root@94.23.26.113 'docker exec v42-prod tail -50 /var/log/v42-prod/pm2/pm2_v42-prod_production_err.log'
ssh root@94.23.26.113 'docker exec v42-prod tail -50 /var/log/v42-prod/pm2/pm2_v42-prod_production_out.log'
 
# 3. PM2 logs (v32-prod)
ssh root@94.23.26.113 'docker exec v32-prod tail -50 /var/log/v32-prod/pm2/pm2_v32-prod_production_err.log'
 
# 4. nginx-proxy — czy widzi kontenery?
ssh root@94.23.26.113 'docker exec nginx-proxy nginx -T 2>&1 | grep -E "server_name|upstream|error" | head -40'
 
# 5. Sprawdź sieci Docker kontenera
ssh root@94.23.26.113 'docker inspect v42-prod --format "{{range \$k,\$v := .NetworkSettings.Networks}}{{println \$k}}{{end}}"'
 
# --- bms-4 (54.36.123.110) — RabbitMQ ---
ssh root@54.36.123.110 'docker ps | grep rabbit; ss -tlnp | grep 5672'
 
# --- MongoDB rs0 (z bms-2) ---
# Hasło z secrets/bms-servers.env.sops (klucz: mongodb_rs0_admin_password)
# NIGDY nie drukuj hasła w logu — używaj bezpiecznego wzorca ekstrakcji
ssh root@145.239.133.104 'mongosh --quiet --eval "rs.status().members.forEach(function(m){print(m.name,m.stateStr,m.health)})" mongodb://admin:<pass>@localhost:27017/admin'

Łańcuch root cause (typowy)

Użytkownik: upload nie działa
 → nginx 504 → v42-prod timeout
   → PM2 log: RabbitMQ ETIMEDOUT
     → bms-4: rabbitmq container stopped
       → Fix A: docker run rabbitmq + pm2 restart

 → nginx 502 → kontener w innej sieci Docker
   → Fix: docker network connect + docker restart nginx-proxy

 → 500 na endpoint → PM2 log: OP_QUERY command: insert
   → Mongoose 4.x vs MongoDB 6.x driver mismatch
   → Tylko długoterminowy fix: upgrade driver

4. Naprawianie

OBOWIĄZKOWE przed modyfikacją produkcji

Zawsze powiedz co zamierzasz zrobić i poczekaj na potwierdzenie użytkownika (“tak”) przed zapisem na serwer produkcyjnym.

Zamierzam:
1. docker cp v42-prod:/app/path/file.js /tmp/backup-file.js   (backup)
2. Zmodyfikować /tmp/file.js (zmiana: linia 42, stary → nowy kod)
3. docker cp /tmp/file.js v42-prod:/app/path/file.js
4. docker exec v42-prod pm2 restart app-name
Wykonać? (tak/nie)

Hotfix pattern — plik w kontenerze

# 1. Backup PRZED zmianą
ssh root@94.23.26.113 'docker cp v42-prod:/app/path/controller.js /tmp/backup-controller-$(date +%Y%m%d-%H%M).js'
 
# 2. Pobierz plik do edycji
ssh root@94.23.26.113 'docker cp v42-prod:/app/path/controller.js /tmp/controller.js'
 
# 3. Zrób zmianę (na local dev machine lub przez heredoc na serwerze)
# NIGDY przez CRLF — pliki JS są uruchamiane przez PM2 na Linuxie
 
# 4. Wgraj z powrotem
ssh root@94.23.26.113 'docker cp /tmp/controller.js v42-prod:/app/path/controller.js'
 
# 5. Restart PM2
ssh root@94.23.26.113 'docker exec v42-prod pm2 restart app-name'
 
# 6. Weryfikacja
sleep 5
curl -sw "%{http_code}" https://api.w4.pinbox24.com/api/i18n/langs -o /dev/null
# Oczekiwane: 200

Hotfix — zmiana env var w działającym kontenerze (PM2 restart only, NIE Docker restart)

# Wyciągnij secret z SOPS (bez wyświetlania wartości)
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
$env:THE_PASS = (sops --decrypt --input-type dotenv --output-type dotenv secrets\bms-servers.env.sops `
  | Select-String "^KEY_NAME=").ToString().Split("=",2)[1]
$encoded = [System.Uri]::EscapeDataString($env:THE_PASS)
$envVar = "KEY_NAME=value:${encoded}@host/db?..."
 
ssh root@94.23.26.113 "docker exec -e '$envVar' v42-prod pm2 restart all --update-env"
$env:THE_PASS = ""; $encoded = ""; $envVar = ""

Dokumentuj każdą zmianę

Po każdym hotfixie zapisz:

  • Plik przed (old:) i po (new:) zmianie
  • Timestamp i kontener
  • Który backup gdzie leży (/tmp/backup-controller-YYYYMMDD-HHMM.js)

5. Rollback

Kiedy rollbackować vs fix forward

SytuacjaDecyzja
Fix powoduje nowy błądRollback natychmiast
Fix działa, ale nie w pełniFix forward (nie mieszaj stanów)
Hotfix in-memory, kontener zostanie zrestartowanyRollback nie wystarczy — zaplanuj permanent fix
Nie masz backupuSTOP — nie modyfikuj bez backupu

Rollback — plik w kontenerze

# Przywróć backup
ssh root@94.23.26.113 'docker cp /tmp/backup-controller-YYYYMMDD-HHMM.js v42-prod:/app/path/controller.js'
ssh root@94.23.26.113 'docker exec v42-prod pm2 restart app-name'

Rollback — restart z obrazu Docker (PM2 restart nie pomaga)

# v42-prod z docker-compose
ssh root@94.23.26.113 'cd /root/builds/7N4sbbrB/0/pinbox24/p24-back-ts && CONTAINER_NAME=v42-prod IMAGE_NAME=563740926945.dkr.ecr.eu-central-1.amazonaws.com/v42-prod docker-compose up -d --no-build backend'

UWAGA: In-memory hotfix (zmiana env var przez pm2 restart --update-env) NIE przeżyje docker recreate. Jeśli zrobisz recreate, wszystkie tymczasowe env var wrócą do wartości z docker-compose.yml / backend-environment.env. Zawsze planuj permanent fix przed restartem kontenera.

Rollback — nginx-proxy (sieć Docker)

# Odłącz od sieci dodanej przez pomyłkę
ssh root@94.23.26.113 'docker network disconnect wrongnet v42-prod && docker restart nginx-proxy'

6. Dokumentacja po incydencie

Obowiązkowe kroki PRZED zamknięciem sesji

  1. Dokument incydentu — skopiuj template i wypełnij:

    cp docs/pinbox24/incident-template.md docs/pinbox24/incident-YYYY-MM-DD-slug.md
    

    Wypełnij: symptom, oś czasu, root cause, zmienione pliki, weryfikacja, lekcje.

  2. Zamknij GH issue z komentarzem resolution:

    gh issue comment NNN --repo radieu/p24-infra --body "RESOLVED: <krótki opis fixu>"
    gh issue close NNN --repo radieu/p24-infra
  3. Aktualizuj docs/priorities.md:

    • Oznacz rozwiązane P0/P1 jako done
    • Dodaj nowe ryzyka odkryte podczas incydentu
    • Zaktualizuj datę
  4. Napisz lub zaktualizuj playbook jeśli scenariusz może się powtórzyć:

    docs/playbooks/<komponent>-<scenariusz>.md
    
  5. Commit wszystkiego przed zamknięciem sesji:

    git add docs/pinbox24/incident-YYYY-MM-DD-slug.md docs/priorities.md docs/playbooks/...
    git commit -m "docs: incident YYYY-MM-DD — <slug>, priorities update"

7. Znane słabe punkty — zawsze sprawdź

Architektura (mapa)

KomponentSerwerStackZnana słabość
v42-prod (W4 backend)bms-1Node.js + PM2 w DockerzeWymaga RabbitMQ na bms-4 przy starcie
v32-prod (W3 backend)bms-1Node.js + PM2 w DockerzeDwa niezależne połączenia MongoDB (PMONGODB_URL + MONGODB_URL)
nginx-proxybms-1nginx-proxy autoGhost entries dla zatrzymanych kontenerów (confusing, nie groźne)
MongoDB rs0bms-2/3/4MongoDB 6.xs3-v32-prod używa Mongoose 4.x / driver 2.x — OP_QUERY incompatible
s3-v2-v42-prodbms-1microservice S3v42-prod oczekuje body.result[0], microservice zwraca bare array
RabbitMQbms-4Docker (manual run)NIE w docker-compose → nie przeżywa rebootu bms-4 (issue #1716)
PM2 logsbms-1/var/log/*/pm2/Loguje pełne MongoDB URI przy starcie → wyciek credentials (issue #2397)
iptables / UFWbms-2/3/4ufw + legacy iptablesDROP rules przed UFW mogą blokować RS heartbeat na port 27017

Checklista przy każdym incydencie MongoDB/upload

[ ] Sprawdź rs.status() — PRIMARY up, SECONDARY lag < 10s
[ ] Sprawdź PM2 logs v42-prod pod kątem "Mongoose disconnected" BEZ poprzedzającego "Mongoose connected"
[ ] Sprawdź PM2 logs v32-prod — OBA połączenia: PMONGODB_URL I MONGODB_URL
[ ] Sprawdź czy RabbitMQ działa na bms-4 (port 5672)
[ ] NIE uruchamiaj `pm2 env 0` bez | grep + redact — wyciek URI
[ ] Po każdej rotacji hasła rs0 → zaktualizuj backend-environment.env na bms-1 dla v42 i v32
[ ] URL-encode hasło MongoDB przed wstawieniem do URI (@ ! = # łamią parsowanie)

8. Weryfikacja po naprawie

# W4 i18n (powinno zwrócić 200 z JSON)
Invoke-WebRequest "https://api.w4.pinbox24.com/api/i18n/langs" -TimeoutSec 10 -UseBasicParsing | Select-Object StatusCode
 
# W3 i18n
Invoke-WebRequest "https://api.w3.pinbox24.com/api/i18n/langs" -TimeoutSec 10 -UseBasicParsing | Select-Object StatusCode
 
# Login responds (401/422 = OK; 504 = wciąż zepsuty)
Invoke-WebRequest "https://api.w4.pinbox24.com/api/auth" -Method POST `
  -Body '{"email":"x","password":"x"}' -ContentType "application/json" `
  -TimeoutSec 10 -UseBasicParsing | Select-Object StatusCode
 
# Frontendy ładują się
Invoke-WebRequest "https://w4.pinbox24.com" -TimeoutSec 10 -UseBasicParsing | Select-Object StatusCode
Invoke-WebRequest "https://w3.pinbox24.com" -TimeoutSec 10 -UseBasicParsing | Select-Object StatusCode

Poczekaj ≥5 min i sprawdź Grafanę — czy alert EndpointDown wygasł.


Powiązane playbooks

  • docs/playbooks/pinbox24-w3-w4-outage-diagnosis.md — szczegółowy flowchart 502/504
  • docs/playbooks/mongodb-credential-rotation.md — rotacja hasła rs0 + aktualizacja kontenerów
  • docs/playbooks/bms1-dr-plan.md — DR dla bms-1
  • docs/playbooks/static-api-key-incident-rotation.md — wyciek credentials
  • docs/pinbox24/incident-template.md — szablon dokumentu incydentu

Audit Log — Log to infra_operations

After this operation completes, log it to the infra_operations audit table.

Python (Linux server — bms-4, vps-i1, vps-h1, or similar):

import sys
sys.path.insert(0, '/opt/p24-infra')
from scripts.lib.log_op import log_op
 
log_op(
    actor="claude",  # "radieu" for manual human ops, "claude" for agent
    op_type="other",
    resource="<incident-resource>",
    result="success",  # "success" | "failed" | "skipped"
    detail="Incident response action — see incident ticket for specific resource and detail",
    env="<env>",
    gh_issue=2730,
)

PowerShell (Windows dev machine):

$env:SUPABASE_URL = (Get-Content "C:\code_2026\p24-infra\.env.local" | Select-String "^SUPABASE_URL=").ToString().Split("=",2)[1].Trim()
$env:SUPABASE_SERVICE_KEY = (Get-Content "C:\code_2026\p24-infra\.env.local" | Select-String "^SUPABASE_SERVICE_KEY=").ToString().Split("=",2)[1].Trim()
python -c "
import os, sys
sys.path.insert(0, 'C:/code_2026/p24-infra')
from scripts.lib.log_op import log_op
log_op('claude', 'other', '<incident-resource>', 'success', 'Incident response action — see incident ticket for specific resource and detail', '<env>')
"
$env:SUPABASE_URL = ''; $env:SUPABASE_SERVICE_KEY = ''