docs(fase-13): history doc + deployment server admin runbook + CLAUDE.md status
Wraps Fase 13:
- docs/history/fase-13-server-admin.md captures the full fase
breakdown (model, RPCs, UI, store wiring, tests, verification,
deviations from plan, scope out).
- docs/deployment.md gains a Server administration section with the
SERVER_ADMIN_EMAIL bootstrap recipe + the "first prod boot has no
users yet" gotcha + manual re-run command (with the </dev/null
redirect for the heredoc-stdin gotcha) + last-admin guard note +
audit log query snippet.
- CLAUDE.md "Project Status" updated: MVP2 now 5/6, Fase 13 ✅, test
totals, the one pre-existing SV-02 flake noted.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -60,6 +60,49 @@ COMMIT;
|
||||
```
|
||||
CASCADE also empties `auth.mfa_factors`, `auth.mfa_amr_claims`, `auth.mfa_challenges`, `auth.one_time_tokens` — expected. Never touch `auth.schema_migrations` or `public._applied_migrations` (they track what DDL has been applied).
|
||||
|
||||
## Server administration (Fase 13)
|
||||
|
||||
The instance has a global `server_admin` role separate from the per-collective `admin` role. Server admins can list/soft-delete/restore/hard-delete any collective, remove members, set server-level section visibility defaults, and read the append-only audit log at `/admin/audit`.
|
||||
|
||||
### Promoting the first admin
|
||||
|
||||
Set `SERVER_ADMIN_EMAIL` in `.env` to the operator's email BEFORE first deploy. `infra/db-init/10-server-admin-seed.sh` runs once on first volume init and inserts the matching user into `public.server_admins`.
|
||||
|
||||
**Gotcha**: `docker-entrypoint-initdb.d` fires before any user has signed in via Keycloak, so `public.users` is empty and the script will log `WARNING: no public.users row matches email <...>, skipping`. To actually bootstrap:
|
||||
|
||||
1. Bring up the stack normally (`docker compose --env-file .env -f infra/docker-compose.erosi.yml up -d`).
|
||||
2. Sign in once via Keycloak using `SERVER_ADMIN_EMAIL` to populate `public.users`.
|
||||
3. Re-run the bootstrap script manually:
|
||||
```bash
|
||||
docker compose -f infra/docker-compose.erosi.yml exec -T db \
|
||||
bash /docker-entrypoint-initdb.d/10-server-admin-seed.sh </dev/null
|
||||
```
|
||||
The `</dev/null` redirect is mandatory per the heredoc-stdin gotcha (the surrounding bash heredoc on the SSH side would otherwise be drained).
|
||||
|
||||
After this `server_admins` is non-empty and the script is a no-op forever.
|
||||
|
||||
### Promoting subsequent admins
|
||||
|
||||
Use `/admin/admins` → "Promote user" with the target's email. Or directly in SQL:
|
||||
```sql
|
||||
INSERT INTO public.server_admins (user_id, granted_by)
|
||||
SELECT id, '<your-user-id>'::uuid FROM public.users WHERE email = '<target>'
|
||||
ON CONFLICT (user_id) DO NOTHING;
|
||||
```
|
||||
|
||||
### Last-admin guard
|
||||
|
||||
`revoke_server_admin()` refuses to remove the only remaining admin (`P0003 'last_admin'`). If you really need to demote the last admin (e.g. handover), promote the replacement FIRST.
|
||||
|
||||
### Audit log
|
||||
|
||||
Every privileged RPC writes to `public.admin_actions`. Read it from `/admin/audit` or with:
|
||||
```sql
|
||||
SELECT created_at, actor_id, action, target_type, target_id, payload
|
||||
FROM public.admin_actions ORDER BY created_at DESC LIMIT 100;
|
||||
```
|
||||
The log is append-only via RLS — there are no INSERT/UPDATE/DELETE policies, only the SECURITY DEFINER RPCs can write. A `postgres` superuser shell can still delete rows; the threat model is "malicious admin via the product UI", not "operator with DB access".
|
||||
|
||||
## Not yet configured on ambrosio
|
||||
|
||||
- SMTP (Resend)
|
||||
|
||||
Reference in New Issue
Block a user