PostgreSQL — pg-alfa (Hetzner)
Self-hosted PG 17 + pgBouncer na Hetzner CPX42. Nahrazuje DO Managed PG (deprecated).
Pro mapování služba → DB viz db-mapping.md.
Server
- Hostname:
pg-alfa/s60-pg - Public IP: 46.224.173.7
- Tailscale IP: 100.125.3.121
- DNS:
pg-alfa.studio60.cz - Hardware: Hetzner CPX42, 8 vCPU AMD, 16 GB RAM, 320 GB SSD, €25.49/mo
PostgreSQL 17 (port 5432)
- Max connections: 500
- SSL: zapnutý (self-signed; nutné pro BadWolf)
- 🔑 pg-alfa je SENTINELŮV server — mám plný SUPERUSER. Spolehlivá cesta k SU:
Tím dělám cokoli (pg_dump/restore, CREATE/DROP DATABASE, clone, role/owner změny). Před hlášením jakéhokoli DB blockeru → SSH na pg-alfa +
sudo -u postgres. Viz memoryfeedback_pg_alfa_is_mine_su. - Admin user (remote):
s60admin— ⚠️ credentials/root/secrets/hetzner-pg/admin.envCHYBÍ na sentinelu (zjištěno 2026-06-28). Pro remotes60adminlogin by se musel regenerovat (ALTER ROLE s60admin PASSWORD …přes peer auth → uložit do admin.env). Peer-auth přes SSH funguje i bez něj. - Clone DB (prod→dev/hub): runbooks/pg-alfa-db-clone.md
pgBouncer (port 6432)
- Mode: transaction (pooling)
- Max client conn: 1000
- Default pool size: 30
- Auth method:
auth_query(od 2026-04-10) — netřeba editovatuserlist.txtpři přidání usera - Bootstrap user:
pgbouncer— credentials/root/secrets/hetzner-pg/
Přístupová politika (UFW + pg_hba)
| Kdo | Jak | Porty |
|---|---|---|
| Hub-alfa, prod-alfa | Tailscale IP (100.125.3.121) | 5432, 6432 |
| Sentinel | Tailscale IP | 5432 (backup) |
| Cerebro (dev) | Public IP (46.224.173.7) | 5432, 6432 — Tailscale IPv4 routing broken (oba Hetzner IPv6) |
| Cortex | Public IP | 5432, 6432 |
| Fess | Public IP | 5432, 6432 |
| Zbytek světa | — | DENY (UFW deny incoming) |
UFW pravidla (aktuální stav, viz pg-alfa:/etc/ufw/)
default deny incoming
100.64.0.0/10 → 22, 5432, 6432 # Tailscale IPv4
fd7a:115c:a1e0::/48 → 22, 5432, 6432 # Tailscale IPv6
tailscale0 → allow all
178.63.52.57 → 5432, 6432 # Cerebro
178.104.15.77 → 5432, 6432 # Cortex (starý — po migraci odebrat)
100.64.0.0/10 → 5432, 6432 # celý Tailscale (tudy poleze cortex-new)
91.99.78.1 → 5432, 6432 # Fess (přesun VPS 2026-07-12; dřív 178.104.22.52)
pg_hba.conf
local peer (postgres user)
local scram-sha-256
127.0.0.1/::1 scram-sha-256 # pgBouncer
100.64.0.0/10 scram-sha-256 # Tailscale IPv4
fd7a:115c:a1e0::/48 scram-sha-256 # Tailscale IPv6
178.63.52.57 scram-sha-256 # Cerebro
178.104.15.77 scram-sha-256 # Cortex (starý — po migraci odebrat)
100.64.0.0/10 scram-sha-256 # Tailscale — cortex-new sem spadá, NENÍ třeba nový řádek
Služby, které potřebují PG přímo (port 5432, ne pgBouncer)
| Služba | Důvod |
|---|---|
| N8N | statement_timeout startup parameter |
| BadWolf | SSL hardcoded (pgBouncer nepodporuje SSL passthrough) |
Ostatní služby běží přes pgBouncer (6432) — viz db-mapping.md.
Debug users
Pattern: každá prod DB může mít RO debug usera (NOLOGIN ve výchozím stavu, povolit jen na dobu ladění).
Existující
s60_billit_debugvs60_billit_prod(heslo:/root/secrets/billit/.env.prod.debug) — ⚠️ LOGIN TRVALE aktivní pro billit dev agenta (jako badwolf; obnoveno 2026-07-12 — granty se ztratily migracemi + heslo file chyběl → reset+re-grant). BYPASSRLS (billit má 15 RLS tabulek → agent vidí napříč tenanty pro integritu). Otužení:default_transaction_read_only=on+statement_timeout=30s+ SELECT-only (i budoucí tabulky přes default privileges nas60_billit_prod_user). Připojení port 5432 direct (obchází pgBouncer/6432). NEdeaktivovat — [[feedback_badwolf_debug_ro_standing]] platí analogicky.s60_badwolf_debugvs60_badwolf_prod(heslo:/root/secrets/badwolf/.env.prod.debug) — pro badwolf dev agenta (cerebro), prod diagnostika/integrita; vytvořeno 2026-07-12, schválil Libor. ⚠️ LOGIN TRVALE aktivní — VÝJIMKA z NOLOGIN-when-done vzoru (Libor 2026-07-12: badwolf = živý CRM pod stálým vývojem/laděním → NEdeaktivovat). Otužení:default_transaction_read_only=on+statement_timeout=30s+ SELECT-only granty (i budoucí tabulky přes default privileges nas60_badwolf_prod_user). Připojení z cerebra = stejná pg-alfa:5432 cesta jako pros60_badwolf_dev.
Povolit / zablokovat
-- Povolit (jen na dobu ladění)
ALTER ROLE s60_billit_debug LOGIN;
-- Zablokovat zpět
ALTER ROLE s60_billit_debug NOLOGIN;
Pravidla pro debug users
- Jen
SELECT(read-only), žádný INSERT/UPDATE/DELETE NOLOGINve výchozím stavu- Povolit jen na dobu ladění, pak ihned zablokovat zpět
- Credentials NIKDY přes Relay API zprávy v plaintextu (archiv)
Fess (GraphRAG, Apache AGE) — DB fess_context
- DB:
fess_context(pg-alfa; přejmenováno zopenclaw_context2026-07-13 — openclaw deprecated). User:fess(rename zopenclaw, SCRAM). Graf:fess_graph(AGE, rename zoc_graphpřes nativníag_catalog.alter_graph('...','RENAME','...')). - Fess connectuje z nové VPS
91.99.78.1(pg_hba + UFW allow 5432/6432, přidáno 2026-07-13; dřív 178.104.22.52). - AGE write gotcha (klíčové):
graph_storeděláCREATE TABLE <label> INHERITS (fess_graph._ag_label_edge/_vertex)za běhu (nové typy entit/vztahů = nové tabulky on-the-fly). Postgres u INHERITS vyžaduje, aby zakladatel VLASTNIL parent tabulky — pouhé GRANTy nestačí. Protofessvlastní celéfess_graph(schema + všechny label tabulky + sekvence +_ag_label_vertex/_ag_label_edge) +USAGE ON ag_catalog. OvěřenoSET ROLE fess; CREATE TABLE … INHERITS. Při dalším AGE onboardingu: owner grafu = ten, kdo přes něj zapisuje, ne s60admin. - Fess čte i KB grafy (
s60_kb,s60_cs_kb— kb_search/graph_recall), tam má jen CONNECT (read).
Migrace stav (2026-04-09)
| Služba | Hub | Prod | Dev |
|---|---|---|---|
| GlitchTip | — | ✅ Hetzner | — |
| N8N | ✅ Hetzner | ✅ Hetzner | — |
| BadWolf | ✅ Hetzner | ✅ Hetzner | ✅ Hetzner |
| Auth | ✅ Hetzner (od 2026-04-10) | ✅ Hetzner (od 2026-04-10) | ✅ Hetzner |
| Pulse | ✅ Hetzner | ✅ Hetzner | ✅ Hetzner |
| BillIt | ✅ Hetzner | ✅ Hetzner | ✅ Hetzner |
| S60Mail | ✅ Hetzner | ✅ Hetzner | ✅ Hetzner |
Zálohy PG databází
- Skript:
/usr/local/bin/s60-backup.sh(cron30 4 * * *), sync na storagebox0 5 * * * - Kam:
/var/backups/postgres/<db>_<YYYYMMDD_HHMM>.sql.gz, retence 14 dní - Jak přidat DB: dopsat do pole
DATABASES=( … )ve skriptu (⚠️ skript není ve verzování — žije jen v/usr/local/bin, zálohy stavu*.bak-<datum>vedle) - Credentials:
/etc/s60-backup.env, dump jakos60adminna:5432s--no-owner --no-acl
⚠️ Nová DB se do rotace NEDOSTANE sama. Kontrolní dotaz, který odhalí nezálohované databáze:
ssh root@100.125.3.121 "sudo -u postgres psql -tAc \"SELECT datname FROM pg_database WHERE NOT datistemplate AND datname NOT IN ('postgres')\"" \
| while read db; do grep -q "$db" /usr/local/bin/s60-backup.sh || echo "NEZÁLOHOVÁNO: $db"; done
Precedent: s60_kb (KB znalostní báze, 375 MB) běžela bez jediné zálohy v rotaci, přestože je od 2026-07-31 jediným domovem dat — odhaleno až když se na to agent kb sám zeptal (T-20260731-017). Doplněno 2026-07-31.
Runbook: přidat nový DB user
Viz docs/docs/runbooks/db-user-setup.md.
Kritická pravidla
- Nový přístup k DB (server, user, DB) → vždy schválit s Liborem (
feedback_pg_access_approval.md) - Sentinel nesmí samostatně přidávat UFW/pg_hba pravidla
- NIKDY nevěřit
.env.example— vždy/root/secrets/