Direct messages between peers are now end-to-end encrypted. The
broker only ever sees {nonce, ciphertext} — plaintext lives on the
two endpoints.
apps/cli/src/crypto/envelope.ts:
- encryptDirect(message, recipientPubkeyHex, senderSecretKeyHex)
→ {nonce, ciphertext} via crypto_box_easy, 24-byte fresh nonce
- decryptDirect(envelope, senderPubkeyHex, recipientSecretKeyHex)
→ plaintext or null (null on MAC failure / malformed input)
- ed25519 keys (from Step 17) are converted to X25519 on the fly via
crypto_sign_ed25519_{pk,sk}_to_curve25519 — one signing keypair
covers both signing + encryption roles.
BrokerClient.send():
- if targetSpec is a 64-hex pubkey → encrypt via crypto_box
- else (broadcast "*" or channel "#foo") → base64-wrapped plaintext
(shared-key encryption for channels lands in a later step)
InboundPush now carries:
- plaintext: string | null (decrypted body, null if decryption failed
OR it's a non-direct message)
- kind: "direct" | "broadcast" | "channel" | "unknown"
MCP check_messages formatter reads plaintext directly.
side-fixes pulled in during 18a:
- apps/broker/scripts/seed-test-mesh.ts now generates real ed25519
keypairs (the previous "aaaa…" / "bbbb…" fillers weren't valid
curve points, so crypto_sign_ed25519_pk_to_curve25519 rejected
them). Seed output now includes secretKey for each peer.
- apps/broker/src/broker.ts drainForMember wraps the atomic claim in
a CTE + outer ORDER BY so FIFO ordering is SQL-sourced, not
JS-sorted (Postgres microsecond timestamps collapse to the same
Date.getTime() milliseconds otherwise).
- vitest.config.ts fileParallelism: false — test files share
DB state via cleanupAllTestMeshes afterAll, so running them in
parallel caused one file's cleanup to race another's inserts.
- integration/health.test.ts "returns 200" now uses waitFullyHealthy
(a 200-only waiter) instead of waitHealthyOrAny — prevents a race
with the startup DB ping.
verified live:
- apps/cli/scripts/roundtrip.ts (direct A→B): ciphertext in DB is
opaque bytes (not base64-plaintext), decrypted correctly on arrival
- apps/cli/scripts/join-roundtrip.ts (full join → encrypted send):
PASSED
- 48/48 broker tests green
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TurboStarter Kit
Full-stack monorepo built with Next.js, Expo, Turborepo, and pnpm workspaces.
Prerequisites
Project Structure
apps/
web/ # Next.js web application (port 3000)
mobile/ # Expo React Native app
packages/
ai/ # AI provider integrations
analytics/ # Analytics providers
api/ # tRPC API layer
auth/ # Authentication (BetterAuth)
billing/ # Payment providers (Stripe, Lemon Squeezy, Polar)
cms/ # Content management
db/ # Database (Drizzle ORM + PostgreSQL)
email/ # Email providers (Resend, Sendgrid, etc.)
i18n/ # Internationalization
monitoring/# Monitoring (Sentry, PostHog)
shared/ # Shared utilities and config
storage/ # File storage (S3/MinIO)
ui/ # Shared UI components
Quick Start
1. Install dependencies
pnpm install
2. Configure environment variables
Copy the example env files:
# Root env (database, product name, URL)
cp .env.example .env
# Web app env (auth, billing, email, storage, AI, etc.)
cp apps/web/.env.example apps/web/.env.local
Root .env — minimum required variables:
DATABASE_URL="postgresql://turbostarter:turbostarter@localhost:5440/core"
PRODUCT_NAME="TurboStarter"
URL="http://localhost:3000"
DEFAULT_LOCALE="en"
Note: The database port is
5440(mapped from Docker), not the default5432.
apps/web/.env.local — key variables to configure:
| Variable | Description | Required |
|---|---|---|
BETTER_AUTH_SECRET |
Auth token signing secret | Yes |
NEXT_PUBLIC_AUTH_PASSWORD |
Enable password auth (true/false) |
Yes |
NEXT_PUBLIC_URL |
Public URL of the web app | Yes |
STRIPE_SECRET_KEY |
Stripe key (if using Stripe billing) | Optional |
RESEND_API_KEY |
Resend key (if using Resend email) | Optional |
S3_* |
S3/MinIO storage credentials | Optional |
OPENAI_API_KEY |
OpenAI key (if using AI features) | Optional |
For local MinIO storage, use these S3 settings in apps/web/.env.local:
S3_REGION="us-east-1"
S3_BUCKET="uploads"
S3_ENDPOINT="http://localhost:9000"
S3_ACCESS_KEY_ID="minioadmin"
S3_SECRET_ACCESS_KEY="minioadmin"
See apps/web/.env.example for the full list of available variables.
3. Start infrastructure (Docker Compose)
Start PostgreSQL and MinIO:
docker compose up -d
Wait for services to be healthy:
docker compose up -d --wait
Or use the built-in shortcut:
pnpm services:start
4. Set up the database
Run migrations and seed data:
pnpm services:setup
This runs docker compose up -d --wait, then applies database migrations and seeds initial data.
5. Start development
pnpm dev
The web app will be available at http://localhost:3000.
Docker Commands
Infrastructure Services
| Command | Description |
|---|---|
docker compose up -d |
Start all services (PostgreSQL + MinIO) |
docker compose down |
Stop all services |
docker compose logs -f |
Follow service logs |
docker compose ps |
Show service status |
Or use the pnpm shortcuts:
| Command | Description |
|---|---|
pnpm services:start |
Start Docker services and wait for healthy |
pnpm services:stop |
Stop Docker services |
pnpm services:logs |
Follow Docker service logs |
pnpm services:status |
Show Docker service status |
pnpm services:setup |
Start services + run DB migrations + seed |
Service URLs
| Service | URL | Credentials |
|---|---|---|
| Web App | http://localhost:3000 | — |
| PostgreSQL | localhost:5440 | turbostarter / turbostarter |
| MinIO API | http://localhost:9000 | minioadmin / minioadmin |
| MinIO Console | http://localhost:9001 | minioadmin / minioadmin |
Production Build (Docker)
Build and run the web app as a production Docker image:
docker build -t turbostarter-web .
docker run -p 3000:3000 --env-file apps/web/.env.local turbostarter-web
Development Commands
| Command | Description |
|---|---|
pnpm dev |
Start all apps in development mode |
pnpm build |
Build all packages and apps |
pnpm lint |
Run ESLint across the monorepo |
pnpm format |
Check formatting with Prettier |
pnpm format:fix |
Fix formatting |
pnpm typecheck |
Run TypeScript type checking |
pnpm test |
Run tests |
pnpm auth:seed |
Seed auth dev accounts |
Database Commands
Run from the root (or within packages/db):
| Command | Description |
|---|---|
pnpm --filter @turbostarter/db db:migrate |
Run database migrations |
pnpm --filter @turbostarter/db db:push |
Push schema changes |
pnpm --filter @turbostarter/db db:generate |
Generate new migration |
pnpm --filter @turbostarter/db db:studio |
Open Drizzle Studio |
pnpm --filter @turbostarter/db db:reset |
Reset database |
pnpm --filter @turbostarter/db db:seed |
Seed database |
Dev Login Credentials
After running pnpm services:setup or pnpm auth:seed:
| Role | Password | |
|---|---|---|
| User | me+user@turbostarter.dev |
Pa$$w0rd |
| Admin | me+admin@turbostarter.dev |
Pa$$w0rd |