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:
91
packages/shade-storage-postgres/src/ensure-tables.ts
Normal file
91
packages/shade-storage-postgres/src/ensure-tables.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import type { Sql } from 'postgres';
|
||||
|
||||
/**
|
||||
* Auto-create all Shade tables if they don't exist.
|
||||
*
|
||||
* Called on PostgresStorage / PostgresPrekeyStore construction.
|
||||
* Uses raw SQL (not Drizzle migrations) for zero-config deployment.
|
||||
*
|
||||
* All tables prefixed with `shade_` to avoid collisions when sharing
|
||||
* a PostgreSQL instance with another project.
|
||||
*/
|
||||
export async function ensureClientTables(sql: Sql): Promise<void> {
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS shade_identity (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
signing_public_key TEXT NOT NULL,
|
||||
signing_private_key TEXT NOT NULL,
|
||||
dh_public_key TEXT NOT NULL,
|
||||
dh_private_key TEXT NOT NULL
|
||||
)
|
||||
`;
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS shade_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
)
|
||||
`;
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS shade_signed_prekeys (
|
||||
key_id INTEGER PRIMARY KEY,
|
||||
data_json TEXT NOT NULL
|
||||
)
|
||||
`;
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS shade_one_time_prekeys (
|
||||
key_id INTEGER PRIMARY KEY,
|
||||
data_json TEXT NOT NULL
|
||||
)
|
||||
`;
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS shade_sessions (
|
||||
address TEXT PRIMARY KEY,
|
||||
state_json TEXT NOT NULL
|
||||
)
|
||||
`;
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS shade_trusted_identities (
|
||||
address TEXT PRIMARY KEY,
|
||||
identity_key TEXT NOT NULL
|
||||
)
|
||||
`;
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS shade_retired_identities (
|
||||
id SERIAL PRIMARY KEY,
|
||||
data_json TEXT NOT NULL,
|
||||
retired_at BIGINT NOT NULL
|
||||
)
|
||||
`;
|
||||
await sql`
|
||||
CREATE INDEX IF NOT EXISTS shade_retired_at_idx ON shade_retired_identities(retired_at)
|
||||
`;
|
||||
}
|
||||
|
||||
export async function ensurePrekeyServerTables(sql: Sql): Promise<void> {
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS shade_server_identities (
|
||||
address TEXT PRIMARY KEY,
|
||||
identity_signing_key TEXT NOT NULL,
|
||||
identity_dh_key TEXT NOT NULL
|
||||
)
|
||||
`;
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS shade_server_signed_prekeys (
|
||||
address TEXT PRIMARY KEY,
|
||||
key_id INTEGER NOT NULL,
|
||||
public_key TEXT NOT NULL,
|
||||
signature TEXT NOT NULL
|
||||
)
|
||||
`;
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS shade_server_one_time_prekeys (
|
||||
id SERIAL PRIMARY KEY,
|
||||
address TEXT NOT NULL,
|
||||
key_id INTEGER NOT NULL,
|
||||
public_key TEXT NOT NULL
|
||||
)
|
||||
`;
|
||||
await sql`
|
||||
CREATE INDEX IF NOT EXISTS shade_server_otp_address_idx ON shade_server_one_time_prekeys(address)
|
||||
`;
|
||||
}
|
||||
Reference in New Issue
Block a user