End-to-end encrypted sync backend for HaexHub using Supabase.
- 🔐 End-to-End Encryption - All CRDT logs are encrypted client-side before syncing
- 🔑 Hybrid Vault Key Management - Password-encrypted vault keys stored securely
- 🚀 High Performance - Built with Bun and Hono for maximum speed
- 🛡️ Row Level Security (RLS) - Users can only access their own data
- 🔄 CRDT-based Sync - Conflict-free replicated data types for seamless multi-device sync
- 📡 Realtime Updates - Supabase Realtime for instant synchronization
- 🐳 Docker Ready - Includes Supabase local development stack
- 📦 Self-Hostable - Run on your own infrastructure
HaexHub Client (Desktop/Mobile)
↓ HTTPS + JWT (Encrypted CRDT Logs)
HaexHub Sync Server (this repo)
↓ PostgreSQL with RLS
Supabase (Auth + Database + Realtime)
- Zero-Knowledge Architecture: Server never sees unencrypted data
- Vault Key: 256-bit AES key generated client-side, encrypted with user password (PBKDF2 600k iterations)
- CRDT Logs: All changes are logged as encrypted CRDT operations
- Supabase Auth: Client authenticates directly with Supabase, server validates JWT tokens
- Row Level Security: PostgreSQL RLS ensures users can only access their own data
- Sequence Numbers: Auto-incrementing per-user sequence for efficient sync
git clone https://github.com/haexhub/haex-sync-server.git
cd haex-sync-server
bun installdocker compose up -dThis starts:
- PostgreSQL with Supabase extensions (port 5432)
- Supabase Studio UI (http://localhost:3001)
- Kong API Gateway (port 8000)
- pg_meta API (port 8080)
Wait for services to be healthy:
docker compose ps# Generate migration files
bun run db:generate
# Apply migrations to local database
bun run db:pushCopy .env.example to .env and adjust if needed:
cp .env.example .envDefault values work for local development.
bun run devThe server starts on http://localhost:3000
GET /Response:
{
"name": "haex-sync-server",
"version": "0.1.0",
"status": "ok",
"env": "development"
}POST /auth/register
Content-Type: application/json
{
"email": "user@example.com",
"password": "secure-password"
}POST /auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "secure-password"
}Response:
{
"message": "Login successful",
"user": {
"id": "uuid",
"email": "user@example.com",
"createdAt": "2025-01-01T00:00:00.000Z"
},
"token": "jwt-token"
}All sync endpoints require Authorization: Bearer <token> header.
POST /sync/vault-key
Content-Type: application/json
Authorization: Bearer <token>
{
"vaultId": "vault-uuid",
"encryptedVaultKey": "base64-encrypted-key",
"salt": "base64-salt",
"nonce": "base64-nonce"
}GET /sync/vault-key/:vaultId
Authorization: Bearer <token>POST /sync/push
Content-Type: application/json
Authorization: Bearer <token>
{
"vaultId": "vault-uuid",
"logs": [
{
"encryptedData": "base64-encrypted-log",
"nonce": "base64-nonce",
"haexTimestamp": "hlc-timestamp"
}
]
}POST /sync/pull
Content-Type: application/json
Authorization: Bearer <token>
{
"vaultId": "vault-uuid",
"afterSequence": 100,
"limit": 100
}Response:
{
"logs": [...],
"hasMore": true
}id(uuid, primary key)email(text, unique)password_hash(text)created_at,updated_at(timestamp)
id(uuid, primary key)user_id(uuid, foreign key)vault_id(text)encrypted_vault_key(text) - AES-GCM encrypted with password-derived keysalt,nonce(text) - For PBKDF2/AES-GCMcreated_at,updated_at(timestamp)
id(uuid, primary key)user_id(uuid, foreign key)vault_id(text)encrypted_data(text) - Encrypted CRDT log entrynonce(text) - AES-GCM IVhaex_timestamp(text) - HLC timestamp from clientsequence(integer) - Auto-incrementing per usercreated_at(timestamp)
# Start development server with hot reload
bun run dev
# Start production server
bun run start
# Generate Drizzle migrations
bun run db:generate
# Apply migrations
bun run db:migrate
# Push schema directly to database (development)
bun run db:push
# Open Drizzle Studio (database GUI)
bun run db:studio- Supabase Studio: http://localhost:3001 (full UI, requires Kong)
- Drizzle Studio:
bun run db:studio(lightweight, direct connection)
This project uses GitHub Actions for automated deployment. On every push to main, the workflow:
- Builds the Docker image
- Pushes to GitHub Container Registry (ghcr.io)
- Watchtower on the server automatically pulls and deploys the new image
The Docker image is available at:
ghcr.io/haex-space/haex-sync-server:latest
You can also trigger a build manually via the GitHub Actions UI (workflow_dispatch).
- Create a Supabase project at https://supabase.com
- Get your database connection string
- Deploy as Supabase Edge Function or use a hosting service
- Set environment variables
Use the provided docker-compose.yml as a base and add:
app:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://postgres:postgres@db:5432/postgres
JWT_SECRET: your-production-secret
NODE_ENV: production
depends_on:
- db- Set up PostgreSQL database
- Configure environment variables
- Run migrations:
bun run db:migrate - Start server:
bun run start
| Variable | Description | Default | Required |
|---|---|---|---|
DATABASE_URL |
PostgreSQL connection string | - | ✅ |
JWT_SECRET |
Secret for JWT token signing | - | ✅ |
JWT_EXPIRES_IN |
JWT token expiration | 7d |
❌ |
PORT |
Server port | 3000 |
❌ |
NODE_ENV |
Environment | development |
❌ |
CORS_ORIGIN |
Allowed CORS origins (comma-separated or *) |
* |
❌ |
- ✅ All CRDT logs are encrypted end-to-end (server never sees plaintext)
- ✅ Vault keys are encrypted with password-derived keys (PBKDF2 600k iterations)
- ✅ JWT-based authentication with configurable expiration
- ✅ HTTPS required in production
⚠️ ChangeJWT_SECRETin production⚠️ Use strong database passwords⚠️ ConfigureCORS_ORIGINto specific client URLs in production
- Runtime: Bun - Fast all-in-one JavaScript runtime
- Framework: Hono - Ultrafast web framework
- Database: PostgreSQL (via Supabase)
- ORM: Drizzle - Type-safe SQL toolkit
- Validation: Zod - TypeScript-first schema validation
- Auth: JWT + bcrypt
MIT
- haex-hub - HaexHub Desktop/Mobile Client
- haexhub-sdk - SDK for HaexHub Extensions
For issues and questions, please open an issue on GitHub.