Skip to content
This repository was archived by the owner on Feb 14, 2026. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
node_modules
.env
.env.local
dist

# SQLite database
*.db
*.db-journal

.logs/
.claude/
14 changes: 2 additions & 12 deletions .env.dev
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/handy
DATABASE_URL=file:./happy.db
HANDY_MASTER_SECRET=your-super-secret-key-for-local-development
PORT=3005

Expand All @@ -9,18 +9,8 @@ METRICS_PORT=9090
# Uncomment to enable centralized logging for AI debugging (creates .logs directory)
DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING=true

# --- Local S3/MinIO ---
# Defaults for local MinIO started via `yarn s3`
S3_HOST=localhost
S3_PORT=9000
S3_USE_SSL=false
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin
S3_BUCKET=happy
S3_PUBLIC_URL=http://localhost:9000/happy

# --- Voice Feature ---
# 11Labs API for voice conversations (required for voice feature)
# Secret - congiured in .env, not checked in
# Secret - configured in .env, not checked in

NODE_ENV=development
9 changes: 4 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
node_modules
.env
.env.local
dist
.pgdata
.minio

.env.local
.env
# SQLite database
*.db
*.db-journal

.logs/

.claude/
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ COPY --from=builder /app/tsconfig.json ./tsconfig.json
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/sources ./sources
COPY --from=builder /app/prisma ./prisma

# Expose the port the app will run on
EXPOSE 3000

# Command to run the application
CMD ["yarn", "start"]
# prisma db push creates tables if they don't exist (idempotent)
CMD ["sh", "-c", "npx prisma db push --skip-generate && yarn start"]
152 changes: 133 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,147 @@
# Happy Server
# Happy Server (Lite)

Minimal backend for open-source end-to-end encrypted Claude Code clients.
A slimmed-down fork of [Happy Server](https://github.com/anthropics/happy-server) - the synchronization backend for end-to-end encrypted Claude Code clients.

## What is Happy?
This version removes external dependencies (PostgreSQL, Redis, MinIO) in favor of a single SQLite database file, making it trivial to self-host.

Happy Server is the synchronization backbone for secure Claude Code clients. It enables multiple devices to share encrypted conversations while maintaining complete privacy - the server never sees your messages, only encrypted blobs it cannot read.
## What Changed

| Original | This Fork |
|----------|-----------|
| PostgreSQL | SQLite |
| Redis | Removed (unused) |
| MinIO/S3 | Removed (uses GitHub avatar URLs directly) |
| Multiple containers | Single process + file |

## Features

- 🔐 **Zero Knowledge** - The server stores encrypted data but has no ability to decrypt it
- 🎯 **Minimal Surface** - Only essential features for secure sync, nothing more
- 🕵️ **Privacy First** - No analytics, no tracking, no data mining
- 📖 **Open Source** - Transparent implementation you can audit and self-host
- 🔑 **Cryptographic Auth** - No passwords stored, only public key signatures
- ⚡ **Real-time Sync** - WebSocket-based synchronization across all your devices
- 📱 **Multi-device** - Seamless session management across phones, tablets, and computers
- 🔔 **Push Notifications** - Notify when Claude Code finishes tasks or needs permissions (encrypted, we can't see the content)
- 🌐 **Distributed Ready** - Built to scale horizontally when needed
- **Zero Knowledge** - Server stores encrypted data it cannot decrypt
- **Single File Database** - Just SQLite, no external dependencies
- **Easy Self-Hosting** - Deploy anywhere that runs Node.js
- **Real-time Sync** - WebSocket-based sync across devices
- **Cryptographic Auth** - Public key signatures, no passwords

## Requirements

- Node.js 20+
- Yarn

## Local Development

```bash
# Install dependencies
yarn install

# Create database and start server
yarn db:push
yarn dev
```

Server runs at `http://localhost:3005`

## Self-Hosting

### Option 1: Fly.io (Recommended)

**Cost estimate:** ~$1.50/month (8 hours/day usage) or ~$3.15/month (always on)

| Resource | Always On | 8 hrs/day |
|----------|-----------|-----------|
| VM (shared-cpu-1x, 512MB) | $2.68/mo | ~$1.35/mo |
| Volume (1GB) | $0.15/mo | $0.15/mo |
| **Total** | **~$3/mo** | **~$1.50/mo** |

The server auto-scales to zero when no clients are connected. Note: WebSocket connections (mobile app, CLI daemons) keep the server running. It only scales down when all clients disconnect.

**Deployment:**

```bash
# Install Fly CLI and authenticate
brew install flyctl
fly auth login

# Launch app
fly launch --no-deploy
```

When prompted:
- **App name:** Choose a name (e.g., `happy-server`)
- **Region:** Pick one close to you (e.g., `lhr` for London)
- **PostgreSQL/Redis:** No (we use SQLite)

```bash
# Create persistent volume for SQLite database
fly volumes create happy_data --region lhr --size 1

# Set your master secret (used for internal encryption)
fly secrets set HANDY_MASTER_SECRET="$(openssl rand -base64 32)"

# Deploy
fly deploy
```

**Custom domain (optional):**

```bash
fly certs add your-domain.com
```

Then add a DNS record:
- **CNAME:** `your-domain.com` → `your-app.fly.dev`

### Option 2: Any VPS / Docker

```bash
# Build
docker build -t happy-server .

# Run with persistent volume
docker run -d \
-p 3000:3000 \
-v happy_data:/data \
-e DATABASE_URL="file:/data/happy.db" \
-e HANDY_MASTER_SECRET="your-secret-here" \
happy-server
```

### Option 3: Coolify / Railway / etc.

Set these environment variables:
- `DATABASE_URL=file:/data/happy.db`
- `HANDY_MASTER_SECRET=<random-secret>`
- `PORT=3000`

## How It Works
Mount a persistent volume at `/data`.

Your Claude Code clients generate encryption keys locally and use Happy Server as a secure relay. Messages are end-to-end encrypted before leaving your device. The server's job is simple: store encrypted blobs and sync them between your devices in real-time.
## Environment Variables

## Hosting
| Variable | Required | Description |
|----------|----------|-------------|
| `DATABASE_URL` | Yes | SQLite path, e.g. `file:/data/happy.db` |
| `HANDY_MASTER_SECRET` | Yes | Secret for internal encryption (OAuth tokens, etc.) |
| `PORT` | No | Server port (default: 3000) |
| `METRICS_ENABLED` | No | Enable Prometheus metrics (default: false) |

**You don't need to self-host!** Our free cloud Happy Server at `happy-api.slopus.com` is just as secure as running your own. Since all data is end-to-end encrypted before it reaches our servers, we literally cannot read your messages even if we wanted to. The encryption happens on your device, and only you have the keys.
## Architecture

That said, Happy Server is open source and self-hostable if you prefer running your own infrastructure. The security model is identical whether you use our servers or your own.
```
┌─────────────────┐ ┌─────────────────┐
│ Mobile App │ │ CLI Daemon │
│ (has master │ │ (encrypts all │
│ keypair) │ │ data locally) │
└────────┬────────┘ └────────┬────────┘
│ │
│ encrypted blobs │
▼ ▼
┌─────────────────────────────────────────┐
│ Happy Server │
│ │
│ • Stores encrypted data it can't read │
│ • Syncs between devices via WebSocket │
│ • Single SQLite file │
└─────────────────────────────────────────┘
```

## License

MIT - Use it, modify it, deploy it anywhere.
MIT
38 changes: 38 additions & 0 deletions dbsetup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env node

import { spawn } from 'node:child_process'
import fs from 'node:fs'

const env = { ...process.env }

const url = new URL(process.env.DATABASE_URL)
const target = url.protocol === 'file:' && url.pathname

// restore database if not present and replica exists
const newDb = target && !fs.existsSync(target)
if (newDb && process.env.BUCKET_NAME) {
await exec(`npx litestream restore -config litestream.yml -if-replica-exists ${target}`)
}

// prepare database
await exec('npx prisma migrate deploy')

// launch application
if (process.env.BUCKET_NAME) {
await exec(`npx litestream replicate -config litestream.yml -exec ${JSON.stringify(process.argv.slice(2).join(' '))}`)
} else {
await exec(process.argv.slice(2).join(' '))
}

function exec(command) {
const child = spawn(command, { shell: true, stdio: 'inherit', env })
return new Promise((resolve, reject) => {
child.on('exit', code => {
if (code === 0) {
resolve()
} else {
reject(new Error(`${command} failed rc=${code}`))
}
})
})
}
38 changes: 38 additions & 0 deletions fly.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# fly.toml app configuration file generated for happy-server-billowing-dawn-6678 on 2026-01-31T10:58:39Z
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#

app = 'happy-server-billowing-dawn-6678'
primary_region = 'lhr'

[build]

[env]
DATABASE_URL = 'file:/data/happy.db'
METRICS_ENABLED = 'false'
NODE_ENV = 'production'
PORT = '3000'

[[mounts]]
source = 'happy_data'
destination = '/data'

[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = 'stop'
auto_start_machines = true
min_machines_running = 0
processes = ['app']

[http_service.concurrency]
type = 'connections'
hard_limit = 250
soft_limit = 200

[[vm]]
memory = '512mb'
cpu_kind = 'shared'
cpus = 1
memory_mb = 512
22 changes: 9 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,15 @@
"scripts": {
"build": "tsc --noEmit",
"start": "tsx ./sources/main.ts",
"dev": "lsof -ti tcp:3005 | xargs kill -9 && tsx --env-file=.env --env-file=.env.dev ./sources/main.ts",
"dev": "lsof -ti tcp:3005 | xargs kill -9 2>/dev/null; tsx --env-file=.env --env-file=.env.dev ./sources/main.ts",
"test": "vitest run",
"migrate": "dotenv -e .env.dev -- prisma migrate dev",
"migrate:reset": "dotenv -e .env.dev -- prisma migrate reset",
"db:push": "dotenv -e .env.dev -- prisma db push",
"db:reset": "rm -f happy.db && dotenv -e .env.dev -- prisma db push",
"generate": "prisma generate",
"postinstall": "prisma generate",
"db": "docker run -d -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=handy -v $(pwd)/.pgdata:/var/lib/postgresql/data -p 5432:5432 postgres",
"redis": "docker run -d -p 6379:6379 redis",
"s3": "docker run -d --name minio -p 9000:9000 -p 9001:9001 -e MINIO_ROOT_USER=minioadmin -e MINIO_ROOT_PASSWORD=minioadmin -v $(pwd)/.minio/data:/data minio/minio server /data --console-address :9001",
"s3:down": "docker rm -f minio || true",
"s3:init": "dotenv -e .env.dev -- docker run --rm --network container:minio --entrypoint /bin/sh minio/mc -c \"mc alias set local http://localhost:9000 $S3_ACCESS_KEY $S3_SECRET_KEY && mc mb -p local/$S3_BUCKET || true && mc anonymous set download local/$S3_BUCKET\""
"postinstall": "prisma generate"
},
"devDependencies": {
"@flydotio/dockerfile": "^0.7.10",
"@types/chalk": "^2.2.0",
"@types/express": "^4.17.21",
"@types/node": "^20.12.3",
Expand All @@ -36,8 +32,8 @@
"@date-fns/tz": "^1.2.0",
"@fastify/bearer-auth": "^10.1.1",
"@fastify/cors": "^10.0.1",
"@flydotio/litestream": "^1.0.1",
"@prisma/client": "^6.11.1",
"@socket.io/redis-streams-adapter": "^0.2.2",
"@types/jsonwebtoken": "^9.0.10",
"@types/semver": "^7.7.0",
"axios": "^1.6.8",
Expand All @@ -47,17 +43,14 @@
"elevenlabs": "^1.54.0",
"fastify": "^5.2.0",
"fastify-type-provider-zod": "^4.0.2",
"ioredis": "^5.6.1",
"jsonwebtoken": "^9.0.2",
"minio": "^8.0.5",
"octokit": "^5.0.3",
"pino-pretty": "^13.0.0",
"prisma": "^6.11.1",
"prisma-json-types-generator": "^3.5.1",
"privacy-kit": "^0.0.23",
"prom-client": "^15.1.3",
"semver": "^7.7.2",
"sharp": "^0.34.3",
"socket.io": "^4.8.1",
"socket.io-adapter": "^2.5.5",
"tmp": "^0.2.3",
Expand All @@ -68,5 +61,8 @@
"vitest": "^3.2.0",
"zod": "^3.24.2",
"zod-to-json-schema": "^3.24.3"
},
"dockerfile": {
"litestream": true
}
}
Loading