feat(hardening): M-Hard 6 + 7 — PostgreSQL backend + production server infra

M-Hard 6: PostgreSQL Storage Backend
- @shade/storage-postgres with PostgresStorage + PostgresPrekeyStore
- Drizzle-style raw SQL ensureClientTables / ensurePrekeyServerTables
- All tables prefixed `shade_` to avoid collisions in shared databases
- DELETE ... FOR UPDATE SKIP LOCKED for concurrent OTPK consumption
- Tests skip gracefully without SHADE_TEST_PG_URL, run against real PG when set

M-Hard 7: Production Server Infrastructure
- Structured JSON logger (logger.ts) — SHADE_LOG_LEVEL configurable
- Health endpoints (/health, /healthz, /ready) — Kubernetes-friendly
- Prometheus metrics (/metrics) — counters, histograms, middleware
- Graceful shutdown with SIGTERM/SIGINT handlers + store close
- Production Dockerfile with non-root user, healthcheck, multi-stage build
- docker-compose.yml example for Dokploy with optional PostgreSQL

193 tests passing, 0 failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 17:51:29 +02:00
parent 96a8c210b2
commit 1bd5436506
17 changed files with 1168 additions and 15 deletions

View File

@@ -0,0 +1,50 @@
# Shade Prekey Server — Dokploy/Docker Compose deployment
#
# Usage:
# docker compose up -d
#
# The SQLite database is persisted to a named volume so data survives
# container restarts. To use PostgreSQL instead, set SHADE_PREKEY_PG_URL
# and uncomment the depends_on / postgres service block below.
services:
shade-prekey:
build:
context: ../..
dockerfile: packages/shade-server/Dockerfile
image: shade-prekey-server:latest
restart: unless-stopped
ports:
- "3900:3900"
volumes:
- shade-data:/data
environment:
- PORT=3900
- SHADE_PREKEY_DB_PATH=/data/shade-prekeys.db
- SHADE_LOG_LEVEL=info
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:3900/health"]
interval: 30s
timeout: 5s
start_period: 10s
retries: 3
# Uncomment for PostgreSQL backend:
# depends_on:
# - postgres
# environment:
# - SHADE_PREKEY_PG_URL=postgres://shade:shade@postgres:5432/shade
# Uncomment to use PostgreSQL instead of SQLite:
# postgres:
# image: postgres:16-alpine
# restart: unless-stopped
# environment:
# - POSTGRES_USER=shade
# - POSTGRES_PASSWORD=shade
# - POSTGRES_DB=shade
# volumes:
# - shade-pg-data:/var/lib/postgresql/data
volumes:
shade-data:
# shade-pg-data: