-
Notifications
You must be signed in to change notification settings - Fork 21
Update self-hosting guide for monorepo architecture #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,61 +12,133 @@ When you self-host the Happy Server, you get: | |
| - **Team control** - Run one server for your whole team | ||
| - **Zero dependencies** - Never worry about a service shutting down | ||
|
|
||
| The entire server is only 1,293 lines of typescript. You can read it yourself to | ||
| verify it just forwards encrypted messages. | ||
| The server is fully open source and part of the [Happy monorepo](https://github.com/slopus/happy). | ||
| You can read the code yourself to verify it just forwards encrypted messages. | ||
|
|
||
| ## Quick Start | ||
| ## Quick Start (Easiest) | ||
|
|
||
| The simplest way to self-host is with the `happy` CLI. No Docker, no database setup needed. | ||
|
|
||
| <Steps> | ||
| ### Install Happy | ||
|
|
||
| ```bash | ||
| npm install -g happy | ||
| ``` | ||
|
|
||
| ### Start the Server | ||
|
|
||
| ```bash | ||
| happy server | ||
| ``` | ||
|
|
||
| On first start, this will: | ||
| - Create a master secret in `~/.happy/server/master-secret` | ||
| - Start an embedded database (PGlite) - no Postgres needed | ||
| - Serve on `http://127.0.0.1:3005` | ||
| - Write the server URL to your local Happy settings | ||
|
|
||
| To expose the server on your local network (for mobile access): | ||
|
|
||
| ```bash | ||
| happy server --host 0.0.0.0 --port 3005 | ||
| ``` | ||
|
|
||
| ### Configure Your Devices | ||
|
|
||
| **On your phone:** | ||
| 1. Open Happy app | ||
| 2. Go to Settings | ||
| 3. Set "Relay Server URL" to `http://your-server:3005` | ||
| 4. Save | ||
|
|
||
| **On your computer:** | ||
| ```bash | ||
| export HAPPY_SERVER_URL="http://your-server:3005" | ||
| ``` | ||
|
|
||
| If you used `happy server`, the CLI automatically picks up the local server URL from your settings. | ||
|
|
||
| </Steps> | ||
|
|
||
| That's it! Your Happy setup now runs through your own server. | ||
|
|
||
| ## Quick Start with Docker | ||
|
|
||
| The standalone Docker image runs everything in a single container with no external dependencies (no Postgres, no Redis, no S3). | ||
|
|
||
| <Steps> | ||
| ### Clone and Build | ||
|
|
||
| ```bash | ||
| # Get the code | ||
| git clone https://github.com/slopus/happy-server | ||
| cd happy-server | ||
| git clone https://github.com/slopus/happy | ||
| cd happy | ||
|
|
||
| # Build with Docker | ||
| docker build -t happy-server:latest . | ||
| docker build -t happy-server -f Dockerfile.server . | ||
| ``` | ||
|
|
||
| ### Run the Server | ||
|
|
||
| ```bash | ||
| docker run -d \ | ||
| --name happy-server \ | ||
| -p 3000:3000 \ | ||
| -e NODE_ENV=production \ | ||
| -e DATABASE_URL="postgresql://postgres:postgres@localhost:5432/happy-server" \ | ||
| -e REDIS_URL="redis://localhost:6379" \ | ||
| -e SEED="your-seed-for-token-generation" \ | ||
| -e PORT=3005 \ | ||
| -p 3005:3005 \ | ||
| -e HANDY_MASTER_SECRET="$(openssl rand -hex 32)" \ | ||
| -v happy-data:/data \ | ||
| --restart unless-stopped \ | ||
| happy-server:latest | ||
| happy-server | ||
| ``` | ||
|
|
||
| **Important**: Replace the environment variable values with your actual configuration: | ||
| - `DATABASE_URL`: Your PostgreSQL connection string | ||
| - `REDIS_URL`: Your Redis connection string | ||
| - `SEED`: A secure random seed for token generation | ||
| - `PORT`: The port the application listens on (3005) | ||
| This uses: | ||
| - **PGlite** - an embedded PostgreSQL database (data stored in `/data/pglite`) | ||
| - **Local filesystem** - for file uploads (stored in `/data/files`) | ||
| - **In-memory event bus** - no Redis needed | ||
|
|
||
| Data persists in the `happy-data` Docker volume across container restarts. | ||
|
|
||
| ### Configure Your Devices | ||
|
|
||
| **On your phone:** | ||
| 1. Open Happy app | ||
| 2. Go to Settings | ||
| 3. Set "Relay Server URL" to `http://your-server:3000` | ||
| 3. Set "Relay Server URL" to `http://your-server:3005` | ||
| 4. Save | ||
|
|
||
| **On your computer:** | ||
| ```bash | ||
| export HAPPY_SERVER_URL="http://your-server:3000" | ||
| # Configure your CLI to use your server | ||
| export HAPPY_SERVER_URL="http://your-server:3005" | ||
| ``` | ||
|
|
||
| </Steps> | ||
|
|
||
| That's it! Your Happy setup now runs through your own server. | ||
| ### Environment Variables | ||
|
|
||
| | Variable | Required | Default | Description | | ||
| |----------|----------|---------|-------------| | ||
| | `HANDY_MASTER_SECRET` | Yes | - | Master secret for auth and encryption | | ||
| | `PORT` | No | `3005` | Server port | | ||
| | `PUBLIC_URL` | No | `http://localhost:3005` | Public base URL for file URLs sent to clients | | ||
| | `DATA_DIR` | No | `/data` | Base data directory | | ||
| | `PGLITE_DIR` | No | `/data/pglite` | PGlite database directory | | ||
|
Comment on lines
+123
to
+124
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
These defaults do not match the server defaults: the Happy server uses Useful? React with 👍 / 👎. |
||
| | `NODE_ENV` | No | `production` | Environment mode | | ||
|
|
||
| ### Optional: External Services | ||
|
|
||
| For larger deployments, you can use external Postgres or Redis instead of the embedded defaults: | ||
|
|
||
| | Variable | Description | | ||
| |----------|-------------| | ||
| | `DATABASE_URL` | PostgreSQL connection URL (bypasses PGlite) | | ||
| | `REDIS_URL` | Redis connection URL (enables cross-process pub/sub) | | ||
| | `S3_HOST` | S3/MinIO host (bypasses local file storage) | | ||
| | `S3_PORT` | S3 port | | ||
| | `S3_ACCESS_KEY` | S3 access key | | ||
| | `S3_SECRET_KEY` | S3 secret key | | ||
| | `S3_BUCKET` | S3 bucket name | | ||
| | `S3_PUBLIC_URL` | Public URL for S3 assets | | ||
| | `S3_USE_SSL` | Whether to use SSL for S3 (`true`/`false`, default `true`) | | ||
|
|
||
| ## Production Setup with HTTPS | ||
|
|
||
|
|
@@ -83,7 +155,7 @@ sudo apt install caddy | |
| # Configure reverse proxy | ||
| sudo tee /etc/caddy/Caddyfile <<EOF | ||
| your-domain.com { | ||
| reverse_proxy localhost:3000 | ||
| reverse_proxy localhost:3005 | ||
| } | ||
| EOF | ||
|
|
||
|
|
@@ -95,31 +167,55 @@ Now use `https://your-domain.com` in your Happy Coder settings. | |
|
|
||
| ## Docker Compose Setup | ||
|
|
||
| For easier management, use Docker Compose: | ||
| For standalone mode (recommended for most users), you only need the Happy Server container: | ||
|
|
||
| ```yaml | ||
| # docker-compose.yml | ||
| services: | ||
| happy-server: | ||
| build: | ||
| context: . | ||
| dockerfile: Dockerfile.server | ||
| ports: | ||
| - "3005:3005" | ||
| restart: unless-stopped | ||
| environment: | ||
| - HANDY_MASTER_SECRET=replace-with-a-strong-random-secret | ||
| volumes: | ||
| - happy-data:/data | ||
|
|
||
| volumes: | ||
| happy-data: | ||
| ``` | ||
|
|
||
| Run with: `docker compose up -d` | ||
|
|
||
| ### Docker Compose with External Postgres and Redis | ||
|
|
||
| For larger teams or production deployments, you can use external Postgres and Redis: | ||
|
|
||
| ```yaml | ||
| # docker-compose.yml | ||
| version: '3.8' | ||
| services: | ||
| happy-server: | ||
| image: happy-server:latest | ||
| build: | ||
| context: . | ||
| dockerfile: Dockerfile.server | ||
| ports: | ||
| - "3000:3000" | ||
| - "3005:3005" | ||
| restart: unless-stopped | ||
| environment: | ||
| - NODE_ENV=production | ||
| - DATABASE_URL=postgresql://postgres:postgres@localhost:5432/happy-server | ||
| - REDIS_URL=redis://localhost:6379 | ||
| - SEED=your-seed-for-token-generation | ||
| - PORT=3005 | ||
| - HANDY_MASTER_SECRET=replace-with-a-strong-random-secret | ||
| - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/happy | ||
| - REDIS_URL=redis://redis:6379 | ||
| depends_on: | ||
| - postgres | ||
| - redis | ||
|
|
||
| postgres: | ||
| image: postgres:15 | ||
| environment: | ||
| - POSTGRES_DB=happy-server | ||
| - POSTGRES_DB=happy | ||
| - POSTGRES_USER=postgres | ||
| - POSTGRES_PASSWORD=postgres | ||
| volumes: | ||
|
|
@@ -139,10 +235,6 @@ volumes: | |
| redis_data: | ||
| ``` | ||
|
|
||
| Run with: `docker-compose up -d` | ||
|
|
||
| This setup includes PostgreSQL and Redis containers that the Happy Server depends on. | ||
|
|
||
| ## Where to Host | ||
|
|
||
| ### Option 1: Home Server (Free) | ||
|
|
@@ -185,12 +277,14 @@ Check server health: | |
| docker logs -f happy-server | ||
|
|
||
| # Check health endpoint | ||
| curl http://your-server:3000/health | ||
| curl http://your-server:3005/health | ||
|
|
||
| # See connection count | ||
| curl http://your-server:3000/stats | ||
| # See connection count | ||
| curl http://your-server:3005/stats | ||
| ``` | ||
|
|
||
| If you enabled metrics (`METRICS_ENABLED=true`), Prometheus metrics are available on port 9090 by default (`METRICS_PORT`). | ||
|
|
||
| ## Backup Your Data | ||
|
|
||
| The server stores encrypted blobs in `/data`. To backup: | ||
|
|
@@ -205,6 +299,49 @@ rsync -av ./data/ backup-location/ | |
|
|
||
| Remember: These backups are encrypted. No one can read them without your device keys. | ||
|
|
||
| ## S3 Storage Configuration | ||
|
|
||
| When using S3 for file storage (set `S3_HOST`), image attachments and other blobs are stored under | ||
| `sessions/<sessionId>/attachments/<id>.enc`. Two bucket-level settings should be applied once at deploy time: | ||
|
|
||
| **1. Lifecycle rule for attachment TTL.** Add a lifecycle rule so objects age out automatically. Pick a TTL that matches your retention policy (30 days is a reasonable default). | ||
|
|
||
| ```bash | ||
| # AWS CLI | ||
| aws s3api put-bucket-lifecycle-configuration --bucket happy-blobs \ | ||
| --lifecycle-configuration '{ | ||
| "Rules": [{ | ||
| "ID": "session-attachments-ttl", | ||
| "Status": "Enabled", | ||
| "Filter": { "Prefix": "sessions/" }, | ||
| "Expiration": { "Days": 30 } | ||
| }] | ||
| }' | ||
|
|
||
| # MinIO | ||
| mc ilm rule add myminio/happy-blobs \ | ||
| --expire-days 30 \ | ||
| --prefix "sessions/" | ||
| ``` | ||
|
|
||
| **2. Server-side encryption (defense-in-depth).** Blobs are already end-to-end encrypted by the client, but enabling AES-256 SSE on the bucket adds an extra layer of protection. | ||
|
|
||
| ```bash | ||
| # AWS CLI | ||
| aws s3api put-bucket-encryption --bucket happy-blobs \ | ||
| --server-side-encryption-configuration '{ | ||
| "Rules": [{ | ||
| "ApplyServerSideEncryptionByDefault": { | ||
| "SSEAlgorithm": "AES256" | ||
| } | ||
| }] | ||
| }' | ||
|
|
||
| # MinIO | ||
| mc encrypt set sse-s3 myminio/happy-blobs | ||
| ``` | ||
|
|
||
| When using local storage mode (no `S3_HOST`), blobs are written under `<DATA_DIR>/files/sessions/<sessionId>/attachments/`. Clean up old session directories on a cron if you want a TTL. | ||
|
|
||
| ## Security Notes | ||
|
|
||
|
|
@@ -291,11 +428,6 @@ spec: | |
| type: ClusterIP | ||
| ``` | ||
|
|
||
| **Configuration Notes**: | ||
| - All ports now consistently use 3005 (the application's actual listening port) | ||
| - Uses the `happy-secrets` secret created in the previous section | ||
| - Update the image name to match your actual container registry | ||
|
|
||
| ## Kubernetes Secrets | ||
|
|
||
| Create a secret file for your environment variables: | ||
|
|
@@ -308,11 +440,12 @@ metadata: | |
| name: happy-secrets | ||
| type: Opaque | ||
| stringData: | ||
| DATABASE_URL: "postgresql://postgres:postgres@postgres-service:5432/happy-server" | ||
| REDIS_URL: "redis://redis-service:6379" | ||
| SEED: "your-secure-seed-for-token-generation" | ||
| HANDY_MASTER_SECRET: "replace-with-a-strong-random-secret" | ||
| PORT: "3005" | ||
| NODE_ENV: "production" | ||
| # Add these if using external Postgres/Redis: | ||
| # DATABASE_URL: "postgresql://postgres:postgres@postgres-service:5432/happy" | ||
| # REDIS_URL: "redis://redis-service:6379" | ||
| ``` | ||
|
|
||
| Apply the secret: | ||
|
|
@@ -321,7 +454,7 @@ kubectl apply -f happy-secrets.yaml | |
| ``` | ||
|
|
||
| **Security Note**: Replace the values with your actual configuration. For production: | ||
| - Use a strong, random SEED value | ||
| - Use a strong, random `HANDY_MASTER_SECRET` value (e.g., `openssl rand -hex 32`) | ||
| - Use proper database credentials | ||
| - Consider using external secret management tools like external-secrets-operator with Vault | ||
|
|
||
|
|
@@ -333,4 +466,4 @@ After setting up your server: | |
| 2. [Set up monitoring](/guides/monitoring) | ||
| 3. [Enable voice coding](/docs/features/voice-coding-with-claude-code) | ||
|
|
||
| Running your own Happy Server takes 3 minutes and gives you permanent control over your Claude Code mobile access. No subscriptions, no lock-in, no surprises. | ||
| Running your own Happy Server takes 3 minutes and gives you permanent control over your Claude Code mobile access. No subscriptions, no lock-in, no surprises. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the Docker examples that claim to use embedded PGlite, this env block never selects the PGlite provider. In the current Happy server code,
packages/happy-server/sources/storage/db.tsdefaultsDB_PROVIDERtopostgres, and the CLI path is what setsDB_PROVIDER=pgliteand runs migrations beforeserve; with this documented command there is also noDATABASE_URL, so the container exits at startup instead of running the one-container setup. Please add the PGlite provider/migration path to the Docker examples, or document that this image requires an external database.Useful? React with 👍 / 👎.