Incident: W4 PM2 Crash-Loop + Inline Preview Broken (2026-07-06)
| Pole | Wartość |
|---|---|
| ID | INCIDENT-2026-07-06-001 |
| Platforma | W4 (w4.pinbox24.com, bms-1, ns367522) |
| Severność | P0 (aplikacja crash-loop, inline preview 403) |
| Status | RESOLVED — crash-loop fixed 2026-07-06T |
| Wykryto | 2026-07-06T~03:00Z |
| Rozwiązano (crash) | 2026-07-06T~07:00Z |
| Rozwiązano (inline preview) | 2026-07-06T~14:30Z (session 49) |
| PR | #3015 (MERGED) |
Symptomy
- PM2 crash-loop: oba workery
v42-prod_backendrestartowały co kilka sekund (restart count >10) - Inline preview plików 403:
GET /api/offices/files/:fileId/getSignedUrlgenerował URL podpisany skasowanym kluczem Wasabi →AccessDenied - Sensor cron noise: co minutę w logach
$$$ Cron for Sensor test data every 1 min - WSDL missing:
ENOENT: .../dist/wsdl/UslugaBIRzewnPubl-ver11-test.wsdlprzy wywołaniach GUS API
Oś czasu
| Czas (UTC) | Zdarzenie |
|---|---|
| ~03:00 | PM2 crash-loop wykryty — restart co kilka sekund |
| ~04:00 | Root cause crash: TypeError: Cannot read property 'phoneNumbers' of undefined w socketInitUser.helper.js |
| ~04:30 | Null guard dodany do connectWithPhoneNumbers — dwa przypadki: userProfileDoc null + channelList undefined |
| ~05:00 | Sensor cron wyłączony w app.js (komentarz) |
| ~05:12 | Obraz v42-prod:merged-20260706-0512 zbudowany przez drugi session Claude (łączy getSignedUrl + wszystkie patche) |
| ~05:30 | Obraz uruchomiony z błędnym entrypoint (sleep infinity zamiast docker-entrypoint.sh) → PM2 nie startuje |
| ~05:45 | Fix entrypoint: docker run --entrypoint docker-entrypoint.sh ... → PM2 online |
| ~06:00 | WSDL brakujący → skopiowany z overlay /var/lib/docker/overlay2/.../diff/app/node_modules/... |
| ~06:15 | getSignedUrl endpoint dostępny: HTTP 200 |
| ~06:30 | Test inline preview: 403 Forbidden z Wasabi — klucz AJ00**** (s3v2-prod-bms1) nie ma dostępu do bucketu pinbox24 |
| ~07:00 | Analiza IAM: prawidłowy user to pinbox24-bms1-s3 (klucze WKRQ****, LGQ2****) — oczekuje na autoryzację rotacji |
Root causes
1. PM2 crash-loop — phoneNumbers TypeError
Plik: v42-prod:/app/dist/globalHelpers/socketHelper/socketInitUser.helper.js
Funkcja connectWithPhoneNumbers wywoływała userData.channelList.phoneNumbers.map(...) bez sprawdzenia czy channelList istnieje. Jeśli user nie miał channelList po pętli przez userProfileDoc.myConnectedChannel, wartość była undefined.
Dwa null-case:
userProfileDoc— null z MongoDB (user usunięty lub nie znaleziony)userData.channelList— undefined gdymyConnectedChanneljest puste
2. Merged image — błędny entrypoint
Drugi Claude session zbudował obraz z kontenera uruchomionego komendą sleep 3600. Przy docker commit ENTRYPOINT stał się ["/usr/bin/sleep"], CMD ["3600"]. PM2 nigdy nie startował.
3. WSDL missing
Paczka @pobidowski/gus-api-regon ma plik WSDL w src/wsdl/, ale skompilowany kod oczekuje go w dist/wsdl/. Brakowało kroku kopiowania przy buildzie.
4. Inline preview 403 — skasowany klucz Wasabi
backend-environment.env miał s3Bucket_api_accessKeyId=VCUC7X6A1G... (skasowany podczas rotacji 2026-07-01). Podmieniono na AJ00**** (s3v2-prod-bms1) ale ten user nie ma dostępu do prywatnego bucketu pinbox24.
Prawidłowy IAM user: pinbox24-bms1-s3 z kluczami WKRQ**** i LGQ2****.
Zmienione pliki / kontenery
W kontenerze v42-prod (obraz v42-prod:merged-20260706-0512)
| Plik | Zmiana | Rollback |
|---|---|---|
/app/dist/globalHelpers/socketHelper/socketInitUser.helper.js | Dwa null guardy w connectWithPhoneNumbers | Przywróć backend-environment.env.bak.pre-bms-rs0-20260706044245 i zrestartuj z poprzednim obrazem |
/app/dist/app.js | Wykomentowany officeCron_helper_1.sensorTestData() | Odkomentuj linie cron |
/app/dist/middleware/socket.middleware.js | setTimeout w try-catch + null guard dla app_1.io.sockets[socket.id] | (był już w poprzednim session) |
/app/dist/config/redis.config.js | Host 172.18.0.9:6379 hardcoded (redis-v42 container) | Zmień z powrotem na env var OVH Valkey |
/app/dist/globalHelpers/socketHelper/socket.helper.js | Port 6379, host "172.18.0.9", TLS/auth usunięte | Przywróć config Valkey |
/app/dist/apps/files/file.route.js | Nowa trasa GET /api/offices/files/:fileId/getSignedUrl | Usuń route |
/app/dist/apps/files/file.controller.js | exports.getSignedUrl — wywołuje helper | Usuń controller method |
/app/dist/apps/files/file.helper.js | getSignedUrlHelper — szuka FileModel, wywołuje s3getSignedUrl | Usuń helper method |
/app/dist/globalHelpers/uploadAwsS3.helper.js | s3getSignedUrl — generuje signed URL z ResponseContentDisposition: 'inline', 30min expiry | Usuń funkcję |
/app/dist/wsdl/UslugaBIRzewnPubl-ver11-test.wsdl | Skopiowany z overlay do dist/wsdl/ | Usuń plik (zrestartuj) |
Na serwerze bms-1
| Plik | Zmiana | Rollback |
|---|---|---|
/home/p24-server-scripts/v4/v42/backend-environment.env | s3Bucket_api_accessKeyId=AJ00**** (tymczasowy, zły bucket) | Przywróć bak.pre-bms-rs0-20260706044245 |
/root/patch-v42.sh | Skrypt re-apply wszystkich 7 patchy po docker recreate | Usuń plik |
Redis container redis-v42 | Nowy kontener Redis 7 Alpine na 172.18.0.9:6379, sieć prod-v-4-net | docker stop redis-v42 && docker rm redis-v42 |
Weryfikacja fix (PM2 crash)
docker exec v42-prod pm2 list
# → 2x v42-prod_backend: online, cluster mode
curl -s http://localhost:3000/api/health
# → HTTP 200
docker exec v42-prod pm2 logs --nostream --lines 50 | grep -c "phoneNumbers"
# → 0 (brak crash)RESOLVED: inline preview fix (session 49 — 2026-07-06T~14:30Z)
Rotacja klucza IAM Wasabi zakończona pomyślnie:
| Krok | Status | Szczegóły |
|---|---|---|
| Usuń stary klucz | ✅ | URH4**** usunięty (secret był utracony z poprzedniej próby) |
| Utwórz nowy klucz | ✅ | B7MW**** dla user pinbox24-bms1-s3 |
| Zaktualizuj SOPS | ✅ | secrets/pinbox24-w4.env.sops — V42_s3Bucket_api_accessKeyId + secret zaktualizowane, canary OK, PR #3015 MERGED |
| Zaktualizuj bms-1 | ✅ | /home/p24-server-scripts/v4/v42/backend-environment.env s3Bucket_api_accessKeyId=B7MW****; backup .bak.s3rot-* utworzony |
| Reload PM2 | ✅ | docker exec v42-prod pm2 reload all --update-env — oba workery online |
| Test S3 | ✅ | aws s3 ls s3://pinbox24/ --endpoint-url https://s3.eu-central-1.wasabisys.com → 14 obiektów dostępnych |
| Health check | ✅ | bash /root/health_check_v42.sh → exit 0, ALL OK |
Pozostałe dziś w nocy (po 20:00):
- Otwórz
w4.pinbox24.comw przeglądarce → plik → Preview → PDF powinien otworzyć się inline (weryfikacja w przeglądarce) - Usuń stary klucz
WKRQ****z Wasabi IAM dlapinbox24-bms1-s3(po potwierdzeniu inline preview)
Health check script: /root/health_check_v42.sh na bms-1 (też w repo: scripts/health_check_v42.sh)
Runbook rotacji: docs/pinbox24/runbook-rotate-pinbox24-bms1-s3-key.md
Długoterminowy fix (całość)
- Klucz S3 w SOPS: Wypełnij
V42_s3Bucket_api_accessKeyId+V42_s3Bucket_api_secretAccessKeywsecrets/pinbox24-w4.env.sopsz wartościami IAM userpinbox24-bms1-s3 - Baked image: Zamroź obraz
v42-prod:merged-20260706-0512jakov42-prod:stable-20260706z:- Poprawnym entrypoint (
docker-entrypoint.sh) - Wszystkimi patchami wbudowanymi (nie tylko w kontenerze)
patch-v42.shuwzględniający WSDL copy
- Poprawnym entrypoint (
- Redis trwały: Dodaj
redis-v42dodocker-compose.ymldla v42 - CI/CD: Zaktualizuj pipeline żeby patch był częścią budowania obrazu, nie hotfixem
- Monitoring: Alert na PM2 restart count > 3 w ciągu 5 minut
Lekcje
docker commitna kontenerze uruchomionym komendąsleepzapisuje zły entrypoint — zawsze budować image przezdocker buildlub commitować z--change "ENTRYPOINT ..."- Rotacja kluczy Wasabi musi obejmować WSZYSTKICH konsumentów danego bucketu przed usunięciem starego klucza
secrets/pinbox24-w4.env.sopsmiał puste wartości dlaV42_s3Bucket_api_accessKeyId— SOPS jest bezużyteczny jako disaster recovery jeśli wartości są puste- Null guard to nie luksus — każde
.map()na potencjalnie undefined musi mieć guard - WSDL i inne static files muszą być częścią buildu Docker, nie ręcznego
cp