Playbook: SOPS Environment Bootstrap — tworzenie świeżych plików od zera
Owner: p24-infra · Updated: 2026-07-05
Status: specyfikacja — częściowo zaimplementowana (patrz §Luki)
Zakres
Używaj tego playbooka gdy:
- Tworzysz plik SOPS dla nowego środowiska (np.
secrets/et-lager.env.sops) - Chcesz zastąpić istniejący plik SOPS świeżymi kluczami (pełna rotacja wszystkich kluczy w jednym przejściu)
- Budujesz nowy projekt i potrzebujesz wygenerować wszystkie poświadczenia od zera
Dla rotacji pojedynczego klucza w istniejącym pliku → użyj docs/playbooks/secret-manager.md §Update an existing key value.
Inwentarz istniejących skryptów
Co MAMY (i do czego służy)
| Skrypt | Cel | Używa w bootstrap? |
|---|---|---|
scripts/rotate/update-sops-keys.ps1 | Czyta klucze z plików tymczasowych i łata istniejący SOPS | ✅ TAK — krok 3 (patch individual keys) |
scripts/rotate/rotate-now.ps1 | Auto-rotuje SMTP_PASSWORD (Mailgun API) + discord_radieu_password (Playwright) | ✅ TAK — Tier 1/2 keys, po stworzeniu pliku |
scripts/rotate/n8n-bms4-api-key.ps1 | Rotuje N8N_API_KEY przez REST | ✅ TAK — dla n8n-bms4 środowiska |
.github/workflows/secrets-sync.yml | Deploys SOPS → serwery, Vercel, GH Secrets | ✅ TAK — krok końcowy (dystrybucja) |
.github/workflows/rotate-schedule.yml | Tygodniowy cron dla Tier 1 kluczy | ❌ NIE — nie nadaje się do bootstrap |
docs/playbooks/secret-manager.md §Add a new key | Bezpieczny wzorzec PowerShell: TEMP_PLAIN + TEMP_ENC + canary + Move-Item | ✅ TAK — krok 2 (tworzenie pliku) |
Czego NIE MAMY (luki — patrz §Co zostało do zrobienia)
| Brakujący element | Opis |
|---|---|
scripts/bootstrap/new-sops-env.ps1 | Skrypt tworzący pusty plik SOPS z szablonu kluczy |
docs/sops-templates/<env>.keys | Maszyna-czytelne szablony listy kluczy per środowisko |
| Bootstrap orchestrator | Skrypt iterujący przez klucze, wywołujący Tier 1 API / Playwright / prompt dla Tier 3 |
| Walidator kompletności | Sprawdza czy wszystkie wymagane klucze są obecne w danym pliku SOPS |
Workflow bootstrap — specyfikacja
Faza 0 — przygotowanie
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
$ENV_NAME = "et-lager" # np. monitoring, n8n-bms4, et-lager, art-agency
$SOPS_FILE = "C:\code_2026\p24-infra\secrets\$ENV_NAME.env.sops"
$TEMP_PLAIN = "C:\code_2026\p24-infra\secrets\$ENV_NAME-bootstrap-tmp.env.sops"
$TEMP_ENC = "C:\code_2026\p24-infra\secrets\$ENV_NAME-bootstrap-enc-tmp.env.sops"Jeśli plik już istnieje: zdecyduj czy:
- a) Patchujesz istniejący (dekrypt → modyfikacja → re-encrypt) — standardowa ścieżka
- b) Budujesz od zera (pusty
$TEMP_PLAIN, następnie dodajesz klucze jeden po jednym)
Ścieżka (b) jest destrukcyjna — stare wartości zostaną utracone. Upewnij się, że stary plik jest zarchiwizowany lub że rotacja była zaplanowana.
Faza 1 — stworzenie szkieletu SOPS
Jeśli plik SOPS nie istnieje:
# Stwórz pusty plik tymczasowy (jeden placeholder key żeby SOPS miał co zaszyfrować)
[System.IO.File]::WriteAllText(
$TEMP_PLAIN,
"BOOTSTRAP_PLACEHOLDER=__replace_me__`n",
[System.Text.UTF8Encoding]::new($false)
)
# Zaszyfruj — .sops.yaml automatycznie dobiera recipients z path_regex
sops --encrypt --input-type dotenv --output-type dotenv --output $TEMP_ENC $TEMP_PLAIN
if ($LASTEXITCODE -ne 0) { throw "Encrypt failed on empty bootstrap" }
# Canary
sops --decrypt --input-type dotenv --output-type dotenv $TEMP_ENC | Out-Null
if ($LASTEXITCODE -ne 0) { Remove-Item $TEMP_PLAIN, $TEMP_ENC -Force -ErrorAction SilentlyContinue; throw "SOPS corrupt" }
Move-Item $TEMP_ENC $SOPS_FILE -Force
Remove-Item $TEMP_PLAIN -Force -ErrorAction SilentlyContinue
Write-Host "Szkielet SOPS: $SOPS_FILE"Faza 2 — wypełnianie kluczami
Dla każdego wymaganego klucza, stosuj tier-routing:
Klucz do bootstrapu
│
├─ Tier 1 (REST API, pełna automatyzacja)
│ └─ Wywołaj provider API → zapisz do $env:NEW_VALUE → SOPS patch
│
├─ Tier 2 (Playwright, admin API z prereqvisites)
│ └─ Wywołaj scripts/rotate/<key>.js z TOKEN_OUT_FILE → odczytaj → SOPS patch
│
└─ Tier 3 (UI-only, 2FA, OAuth)
└─ Człowiek generuje klucz → wkleja w TERMINALU do $env:NEW_VALUE (nie w chat)
└─ secret-manager session robi SOPS patch
Wzorzec SOPS patch dla każdego klucza (z secret-manager.md §Update an existing key value):
# $env:NEW_VALUE zawiera nowy klucz — nigdy nie drukuj jego wartości
$plain = sops --decrypt --input-type dotenv --output-type dotenv $SOPS_FILE
if ($LASTEXITCODE -ne 0) { throw "Decrypt failed" }
if ($plain -match "(?m)^KEY_NAME=") {
# Klucz istnieje — zaktualizuj
$content = ($plain -join "`n") + "`n"
$content = $content -replace "(?m)^KEY_NAME=.*$", "KEY_NAME=$env:NEW_VALUE"
} else {
# Klucz nie istnieje — dodaj (i usuń placeholder jeśli nadal jest)
$filtered = $plain | Where-Object { $_ -notmatch "^BOOTSTRAP_PLACEHOLDER=" }
$content = ($filtered -join "`n") + "`nKEY_NAME=$env:NEW_VALUE`n"
}
[System.IO.File]::WriteAllText($TEMP_PLAIN, $content, [System.Text.UTF8Encoding]::new($false))
$content = ""; $plain = @()
sops --encrypt --input-type dotenv --output-type dotenv --output $TEMP_ENC $TEMP_PLAIN
if ($LASTEXITCODE -ne 0) {
@($TEMP_PLAIN, $TEMP_ENC) | Where-Object { $_ -and (Test-Path $_) } | ForEach-Object { Remove-Item $_ -Force -ErrorAction SilentlyContinue }
throw "Encrypt failed at KEY_NAME"
}
Remove-Item $TEMP_PLAIN -Force -ErrorAction SilentlyContinue
sops --decrypt --input-type dotenv --output-type dotenv $TEMP_ENC | Out-Null
if ($LASTEXITCODE -ne 0) {
Remove-Item $TEMP_ENC -Force -ErrorAction SilentlyContinue
throw "SOPS corrupt after KEY_NAME — NOT replacing production file"
}
Move-Item $TEMP_ENC $SOPS_FILE -Force
$env:NEW_VALUE = ""
Write-Host "KEY_NAME: OK"Faza 3 — walidacja kompletności
Po wypełnieniu wszystkich kluczy:
# Wylistuj wszystkie klucze (bez wartości) i porównaj z oczekiwaną listą
$actualKeys = (sops --decrypt --input-type dotenv --output-type dotenv $SOPS_FILE) |
Where-Object { $_ -match "^[A-Z_]+=." } |
ForEach-Object { $_.Split("=")[0] }
Write-Host "Klucze obecne: $($actualKeys.Count)"
$actualKeys | Sort-Object
# Porownaj z szablonem:
# .\scripts\bootstrap\validate-sops-env.ps1 -Env <env>Faza 4 — commit i dystrybucja
git add secrets\<env>.env.sops
git commit -m "feat(#ISSUE): bootstrap secrets/<env>.env.sops — <N> keys"
git push origin HEAD # branch → PR → merge → secrets-sync auto-triggerPo merge na main — secrets-sync.yml auto-triggeruje i deployuje na wszystkie cele zdefiniowane
dla danego środowiska.
Mapowanie środowisk → cele dystrybucji
| Plik SOPS | Cel dystrybucji |
|---|---|
monitoring.env.sops | vps-i1 /opt/p24-infra/monitoring/.env |
n8n-bms4.env.sops | bms-4 /opt/p24-infra/bms-4/.env |
vps-h1.env.sops | vps-h1 /root/.env (uwaga: SSH broken — issue #1474) |
bms-servers.env.sops | Operator decrypt on demand — NIE deployowane automatycznie |
art-agency.env.sops | Art-Agency/.env.local (manual sync) |
brandpilot.env.sops | Vercel prj_brandpilot |
et-operational-platform.env.sops | Vercel prj_ziLl911FOYLAeukQujL4NjxR4eWy |
et-lager.env.sops | NIE SKONFIGUROWANE W secrets-sync.yml — patrz §Decyzje |
whatsup.env.sops | App env (manual sync) |
Co zostało do zrobienia
Skrypty — brakuje
-
scripts/bootstrap/new-sops-env.ps1— skrypt tworzący nowy plik SOPS z szablonu:- przyjmuje
$Environment(nazwa pliku) - wczytuje
docs/sops-templates/<env>.keys(lista kluczy + tier) - iteruje: Tier 1 → API call, Tier 2 → Playwright, Tier 3 → czeka na
$env:VALUE_<KEY> - po każdym kluczu robi canary
- na końcu robi listę kluczy + walidację kompletności
- przyjmuje
-
docs/sops-templates/<env>.keys— szablony per środowisko (format:KEY_NAME|tier|provider|rotation_cmd):SMTP_PASSWORD|1|mailgun-api|scripts/rotate/rotate-now.ps1 SENTRY_AUTH_TOKEN|2|playwright|scripts/rotate/sentry-auth-token.js MAILGUN_API_KEY|3|browser-ui|manualBrakuje dla:
monitoring,n8n-bms4,et-lager,art-agency,brandpilot,et-operational-platform -
Walidator kompletności — skrypt sprawdzający czy wszystkie wymagane klucze (z szablonu) są obecne w pliku SOPS; uruchamiany jako pre-commit hook lub CI check
Workflow — brakuje
-
secrets-sync.ymlobsługaet-lager— brak jobsync-et-lager(patrz §Decyzje) - Cel dystrybucji dla nowych środowisk — gdy dodajesz nowy plik SOPS, musisz ręcznie
dodać odpowiedni
jobdosecrets-sync.yml
Otwarte decyzje
D1 — Co to znaczy “od nowa” w kontekście istniejących plików?
Dwie interpretacje, różne konsekwencje:
| Opcja | Co się dzieje | Kiedy stosować |
|---|---|---|
| A: patch istniejącego | Dekrypt → rotacja wybranych kluczy → re-encrypt | Rutynowa rotacja, rotacja grupowa |
| B: buduj od zera | Pusty TEMP_PLAIN, dodajesz klucze od nowa | Podejrzenie kompromitacji wszystkich kluczy, nowe środowisko |
Opcja B niszczy bieżące wartości — stary plik musi być zarchiwizowany zanim go usuniesz.
D2 — et-lager.env.sops — gdzie deployować?
Plik secrets/et-lager.env.sops istnieje ale secrets-sync.yml nie ma dla niego job.
Potrzebna decyzja:
- Jaki serwer lub platforma jest celem? (vps-i1? bms-4? Vercel?)
- Jaka ścieżka docelowa?
- Czy dodać job
sync-et-lagerdosecrets-sync.yml?
D3 — Szablony kluczy (docs/sops-templates/) — czy tworzyć?
Plusy: umożliwia walidację kompletności, dokumentuje wymagane klucze, upraszcza bootstrap nowych środowisk.
Minusy: dodatkowy plik do utrzymania, może się rozjechać z rzeczywistością.
Rekomendacja: tak — przynajmniej dla monitoring i n8n-bms4 (największe pliki, najwięcej kluczy).
D4 — Bootstrap orchestrator — skrypt lokalny czy GH Issue workflow?
| Opcja | Opis |
|---|---|
| Lokalny PowerShell | scripts/bootstrap/new-sops-env.ps1 — dev machine, interaktywny, może promptować o Tier 3 keys |
| GH Issue + secrets-manager worker | Issue z listą kluczy → worker na bms-4 generuje co może (Tier 1/2) → komentuje co czeka na człowieka (Tier 3) |
Lokalny PS jest szybszy dla Tier 3 (brak opóźnienia z GH Issue). Worker lepszy dla Tier 1/2 batch. Rekomendacja: oba — lokalny dla bootstrap interaktywny, worker dla rotacji grupowej Tier 1/2.
D5 — Kolejność bootstrapu (DAG zależności per środowisko)
monitoring.env.sops
KROK 1 — brak zależności (generuj pierwsze):
SMTP_PASSWORD [Tier 1: MAILGUN_ADMIN_API_KEY wymagany → musi być w SOPS]
GRAFANA_ADMIN_PASSWORD [Tier 1: bcrypt + grafana-cli]
P24_INFRA_WASABI_* [Tier 1: WASABI_ADMIN_ACCESS_KEY wymagany]
WASABI_ACCESS_KEY/SECRET [Tier 1: jak wyżej]
CF_API_TOKEN + inne CF [Tier 1: CF_GLOBAL_API_KEY wymagany]
LOKI / PROMETHEUS / STATUS basic auth [Tier 1: random gen]
EXTERNAL_DB_SYM_KEY [Tier 1: random gen]
KROK 2 — zależy od Kroku 1:
N8N_BMS4_API_KEY [Tier 1: n8n musi byc uruchomione; wymaga BMS4_N8N_API_KEY z n8n-bms4]
SUPABASE_GRAFANA_PASSWORD [Tier 1: wymaga działającego SUPABASE_ACCESS_TOKEN]
KROK 3 — human-action (rownolegle do Kroku 1/2):
SUPABASE_SERVICE_KEY/ROLE_KEY [Tier 3: Supabase dashboard]
GH_TOKEN [Tier 3: GitHub PAT]
ATRAX_* [Tier 3: Atrax portal]
VERCEL_TOKEN [Tier 3: Vercel dashboard]
KROK 4 — po Kroku 3 (uzywa human-provided values):
WAP_OPENAI_KEY_MINI [Tier 1: wymaga OPENAI_ADMIN_KEY z Kroku 3]
CLOUDFLARE_TOKEN_* dalsze [Tier 1: wymaga CF_GLOBAL_API_KEY]
n8n-bms4.env.sops
UWAGA: N8N_ENCRYPTION_KEY — NIGDY nie rotowac bez migracji credentials n8n.
Jesli trzeba zmienic: dokumentuj wszystkie credentials przechowywane w n8n
i migruj reczne po zmianie klucza.
KROK 1 — brak zależności:
REDIS_PASSWORD [Tier 3: human — przed restart kontenera]
N8N_DB_PASSWORD [Tier 3: human — wymaga ALTER USER + restart]
MONGODB_RS0_ADMIN_PASSWORD [Tier 3: human — P0, sprawdz bms-servers.env.sops]
MONGODB_RS0_PROMETHEUS_PASSWORD [Tier 1: po zmianie admin password]
ANTHROPIC_API_KEY [Tier 1: Anthropic API]
TELEGRAM_BOT_TOKEN [Tier 1: BotFather]
KROK 2 — zalezy od uruchomionego n8n (Krok 1 gotowy + n8n zrestartowany):
BMS4_N8N_API_KEY [Tier 1: n8n REST API]
N8N_GPS_SYNC_WEBHOOK_URL [Tier 1: n8n webhook]
N8N_HU_SP_REPORT_SECRET [Tier 1: random]
KROK 3 — human-action (rownolegle):
GH_TOKEN / GITHUB_PAT_* [Tier 3: GitHub]
LINKEDIN_ACCESS_TOKEN [Tier 2: OAuth refresh]
RABBITMQ_DEFAULT_PASS [Tier 3: human]
GITHUB_APP_PRIVATE_KEY_B64 [Tier 3: GitHub App settings]
et-lager.env.sops
STATUS: niekompletny szablon — uzupelnij docs/sops-templates/et-lager.keys
zanim zaczniesz bootstrap.
KROK 0 — sprawdz et-lager repo:
gh api repos/radieu/et-lager/contents/.env.example | jq -r '.content' | base64 -d
lub: vercel env ls --project <et-lager-project-name>
Uzupelnij docs/sops-templates/et-lager.keys o wszystkie klucze.
KROK 1 — zalezy od zewnetrznych serwisow (musza istniec przed bootstrapem):
MONGODB_URI [Tier 3: MongoDB Atlas → Connect → Application]
UWAGA: URI zawiera uzytkownika i haslo — wygeneruj
dedykowanego database usera w Atlas dla et-lager.
KROK 2 — Supabase (jesli uzywany):
SUPABASE_SERVICE_ROLE_KEY [Tier 3: Supabase dashboard → Settings → API]
NEXT_PUBLIC_SUPABASE_ANON_KEY [Tier 3: jak wyzej]
et-operational-platform.env.sops
KROK 1 — brak zaleznosci:
CRON_SECRET [Tier 1: random]
EXTERNAL_DB_SYM_KEY [Tier 1: sync z monitoring.env.sops — ta sama wartosc]
ANTHROPIC_API_KEY [Tier 1: Anthropic API]
KROK 2 — wymaga dzialajacego n8n (n8n-bms4 musi byc gotowe):
N8N_ATRAX_REPORT_WEBHOOK_URL [Tier 1: n8n webhook — sync z monitoring]
N8N_GPS_SYNC_SECRET [Tier 1: sync z n8n-bms4]
N8N_HU_SP_REPORT_SECRET [Tier 1: sync z n8n-bms4]
UWAGA synchronizacja: kilka kluczy musi byc identycznych w wielu plikach SOPS:
N8N_ATRAX_REPORT_SECRET — monitoring + et-op (ta sama wartosc)
N8N_GPS_SYNC_SECRET — n8n-bms4 + et-op (ta sama wartosc)
EXTERNAL_DB_SYM_KEY — monitoring + et-op (ta sama wartosc)
P24_DISCORD_*_WEBHOOK_URL — monitoring + et-op (ta sama wartosc)
QUEUE_API_KEY — monitoring + et-op + brandpilot + art-agency (ta sama)
ENFORCEMENT (#5276): powyzsze niezmienniki "ta sama wartosc" sa pilnowane przez
scripts/check-secret-invariants.sh (deklaracja w scripts/secret-invariants.conf),
uruchamiane w CI przez .github/workflows/lint-secret-invariants.yml na kazdym PR/pushu
dotykajacym secrets/**. Porownuje SHA-256 wartosci (nigdy nie drukuje wartosci) i failuje
gdy ktorykolwiek mirror sie rozjedzie — to jest guard na klase outage'u #5273. Dodajac tu
nowy klucz cross-project, dopisz go tez do scripts/secret-invariants.conf.
KROK 3 — human-action:
SUPABASE_SERVICE_ROLE_KEY [Tier 3: Supabase dashboard]
NEXT_PUBLIC_SUPABASE_ANON_KEY [Tier 3: Supabase dashboard]
PINBOX24_MONGODB_URI [Tier 3: MongoDB Atlas]
Kto musi dzialac zanim cokolwiek ruszysz
Przed bootstrapem ANY srodowiska — potrzebne master keys:
| Master key | Gdzie | Umozliwia |
|---|---|---|
WASABI_ADMIN_ACCESS_KEY | administration.env.sops | Wasabi IAM Tier 1 keys |
CF_GLOBAL_API_KEY | administration.env.sops | Cloudflare Tier 1 keys |
MAILGUN_ADMIN_API_KEY | monitoring.env.sops | SMTP_PASSWORD Tier 1 |
BMS4_N8N_API_KEY (po uruchomieniu n8n) | n8n-bms4.env.sops | n8n webhook Tier 1 |
| Playwright browser | lokalny | SENTRY, discord_radieu_password, VERCEL Tier 2 |
Powiązane playbooki
| Temat | Plik |
|---|---|
| Bezpieczny zapis SOPS na Windows | docs/playbooks/sops-windows-patterns.md |
| Dodanie pojedynczego klucza | docs/playbooks/secret-manager.md §Add a new key |
| Dystrybucja do serwerów | docs/playbooks/secret-manager.md §Distribution chain |
| Tier-klasyfikacja kluczy | docs/playbooks/secret-rotation-access-matrix.md |
| Bootstrap master keys (age, GH_PAT) | docs/playbooks/master-keys-bootstrap.md |