pulse-zax/README-SELFHOSTED-SUPABASE.md

501 lines
14 KiB
Markdown

# Self-Hosted Supabase Installation Guide
This guide walks you through deploying Pulse with a fully self-hosted Supabase stack (PostgreSQL + GoTrue + Kong) using Docker Compose. No external Supabase project required.
---
## Table of Contents
1. [System Requirements](#system-requirements)
2. [Prerequisites](#prerequisites)
3. [Install Docker](#install-docker)
4. [Install Bun](#install-bun)
5. [Clone the Repository](#clone-the-repository)
6. [Generate Secrets](#generate-secrets)
7. [Configure Environment](#configure-environment)
8. [Build and Start](#build-and-start)
9. [Set Up HTTPS](#set-up-https)
10. [Firewall Configuration](#firewall-configuration)
11. [Claim Server Ownership](#claim-server-ownership)
12. [Verify Installation](#verify-installation)
13. [Updating Pulse](#updating-pulse)
14. [OAuth Setup (Optional)](#oauth-setup-optional)
15. [Federation Setup (Optional)](#federation-setup-optional)
16. [Troubleshooting](#troubleshooting)
---
## System Requirements
| Resource | Minimum | Recommended |
|----------|---------|-------------|
| CPU | 2 cores | 4 cores |
| RAM | 2 GB | 4 GB |
| Disk | 10 GB | 20 GB+ (depends on file uploads) |
| OS | Ubuntu 22.04 LTS | Ubuntu 24.04 LTS |
| Network | Public IP | Public IP + domain name |
This deployment runs four Docker containers: PostgreSQL, GoTrue (auth), Kong (API gateway), and the Pulse application.
---
## Prerequisites
- A server with Ubuntu 22.04+ and root/sudo access
- A domain name with an A record pointing to your server's public IP (for HTTPS)
- SSH access to your server
- Git installed (`sudo apt install git` if not already present)
---
## Install Docker
```bash
# Update system packages
sudo apt update && sudo apt upgrade -y
# Install required packages
sudo apt install -y ca-certificates curl gnupg
# Add Docker's official GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Add Docker repository
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker Engine + Compose
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Add your user to the docker group
sudo usermod -aG docker $USER
newgrp docker
# Verify
docker --version
docker compose version
```
---
## Install Bun
Bun is needed to run the key generator script:
```bash
curl -fsSL https://bun.sh/install | bash
source ~/.bashrc
bun --version
```
---
## Clone the Repository
```bash
sudo mkdir -p /opt/pulse
sudo chown $USER:$USER /opt/pulse
git clone https://github.com/plsechat/pulse-chat.git /opt/pulse
cd /opt/pulse
```
---
## Generate Secrets
Generate a JWT secret and two Supabase API keys:
```bash
cd /opt/pulse
bun docker/generate-keys.ts
```
Output:
```
JWT_SECRET=<base64 string>
SUPABASE_ANON_KEY=<jwt token>
SUPABASE_SERVICE_ROLE_KEY=<jwt token>
```
**Copy all three values** for the next step.
---
## Configure Environment
```bash
cp .env.supabase.example .env
nano .env
```
Fill in the values:
```env
# PostgreSQL password — strong and URL-safe (no / = + characters)
POSTGRES_PASSWORD=ChangeMeToSomethingSecure123
# Paste the three values from generate-keys.ts
JWT_SECRET=<paste here>
SUPABASE_ANON_KEY=<paste here>
SUPABASE_SERVICE_ROLE_KEY=<paste here>
# Your public domain (must match your DNS A record)
SITE_URL=https://your-domain.com
```
> **Important:**
> - `POSTGRES_PASSWORD` must be URL-safe. Avoid `/`, `=`, `+`, and other special characters.
> - `SITE_URL` must start with `https://` for production.
> - The `JWT_SECRET` generated by `generate-keys.ts` already meets the 32-character minimum.
### Environment Variable Reference
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `POSTGRES_PASSWORD` | Yes | — | Password for PostgreSQL |
| `JWT_SECRET` | Yes | — | Secret key for signing JWTs (min 32 chars) |
| `SUPABASE_ANON_KEY` | Yes | — | Supabase public/anonymous API key |
| `SUPABASE_SERVICE_ROLE_KEY` | Yes | — | Supabase admin service role key |
| `SITE_URL` | Yes | — | Public URL (e.g., `https://pulse.example.com`) |
| `PULSE_PORT` | No | `4991` | Host port for Pulse |
| `JWT_EXPIRY` | No | `3600` | Token expiry in seconds |
| `PUBLIC_IP` | No | auto | Public IP for WebRTC |
| `GOOGLE_OAUTH_ENABLED` | No | `false` | Enable Google login |
| `GOOGLE_OAUTH_CLIENT_ID` | No | — | Google OAuth client ID |
| `GOOGLE_OAUTH_SECRET` | No | — | Google OAuth client secret |
| `DISCORD_OAUTH_ENABLED` | No | `false` | Enable Discord login |
| `DISCORD_OAUTH_CLIENT_ID` | No | — | Discord OAuth client ID |
| `DISCORD_OAUTH_SECRET` | No | — | Discord OAuth client secret |
| `FACEBOOK_OAUTH_ENABLED` | No | `false` | Enable Facebook login |
| `FACEBOOK_OAUTH_CLIENT_ID` | No | — | Facebook OAuth client ID |
| `FACEBOOK_OAUTH_SECRET` | No | — | Facebook OAuth client secret |
| `TWITCH_OAUTH_ENABLED` | No | `false` | Enable Twitch login |
| `TWITCH_OAUTH_CLIENT_ID` | No | — | Twitch OAuth client ID |
| `TWITCH_OAUTH_SECRET` | No | — | Twitch OAuth client secret |
| `ADDITIONAL_REDIRECT_URLS` | No | — | Extra OAuth callback URLs |
| `REGISTRATION_DISABLED` | No | `false` | Block new registrations (existing users can still log in; valid invite codes bypass) |
| `GIPHY_API_KEY` | No | — | Giphy API key for GIF search |
---
## Build and Start
```bash
cd /opt/pulse
docker compose -f docker-compose-supabase.yml up -d
```
The first launch takes a few minutes while Docker downloads the required images.
### Verify containers are running
```bash
docker compose -f docker-compose-supabase.yml ps
```
You should see four containers, all `Up`:
```
NAME IMAGE STATUS
pulse-db supabase/postgres:15.6.1.143 Up (healthy)
pulse-auth supabase/gotrue:v2.170.0 Up
pulse-kong kong:3.4 Up
pulse pulse-pulse Up
```
### Check logs
```bash
docker logs pulse
```
### Test the health endpoint
```bash
curl http://localhost:4991/healthz
```
Should return `{"status":"ok","timestamp":...}`.
---
## Set Up HTTPS
Pulse does not handle TLS itself. Use a reverse proxy for HTTPS.
### Option A: Caddy (recommended)
```bash
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
```
Configure `/etc/caddy/Caddyfile`:
```
your-domain.com {
handle /auth/v1/* {
reverse_proxy localhost:8000
}
handle {
reverse_proxy localhost:4991
}
}
```
```bash
sudo systemctl restart caddy
sudo systemctl enable caddy
```
Caddy automatically obtains and renews Let's Encrypt certificates.
### Option B: Nginx
```bash
sudo apt install nginx certbot python3-certbot-nginx
```
Create `/etc/nginx/sites-available/pulse`:
```nginx
server {
server_name your-domain.com;
location /auth/v1/ {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://127.0.0.1:4991;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
}
}
```
```bash
sudo ln -s /etc/nginx/sites-available/pulse /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl restart nginx
sudo certbot --nginx -d your-domain.com
```
---
## Firewall Configuration
```bash
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw allow 40000:40020/udp # WebRTC voice/video
sudo ufw allow 40000:40020/tcp # WebRTC TCP fallback
sudo ufw enable
```
| Port | Protocol | Purpose |
|------|----------|---------|
| 22 | TCP | SSH access |
| 80 | TCP | HTTP (redirects to HTTPS) |
| 443 | TCP | HTTPS (web + WebSocket) |
| 4991 | TCP | Pulse (only if no reverse proxy) |
| 40000-40020 | UDP + TCP | WebRTC media (voice/video/screen share) |
> **Note:** Docker manipulates iptables directly and can bypass ufw rules. Ports mapped in `docker-compose-supabase.yml` may be publicly accessible even if ufw doesn't allow them. The compose file binds Kong (port 8000) to `127.0.0.1` so it is only accessible from the reverse proxy — do not change this to `0.0.0.0`.
---
## Claim Server Ownership
1. Open `https://your-domain.com` in your browser
2. Register a new account
3. Find the ownership token in the server logs:
```bash
docker logs pulse 2>&1 | grep -i token
```
4. In the Pulse web interface, open browser DevTools (F12)
5. Go to the Console tab and run: `useToken('your_token_here')`
6. Your account is now the server owner
> The ownership token is printed once on first start. Save it somewhere secure.
---
## Verify Installation
- [ ] `https://your-domain.com` loads the login page
- [ ] You can register and log in
- [ ] You claimed ownership with the token
- [ ] Text channels work (send/receive messages)
- [ ] File uploads work
- [ ] Voice channels work (join, speak, hear others)
- [ ] `docker compose -f docker-compose-supabase.yml ps` shows all 4 containers healthy
---
## Updating Pulse
```bash
cd /opt/pulse
docker compose -f docker-compose-supabase.yml pull
docker compose -f docker-compose-supabase.yml up -d
```
This pulls the latest published image and restarts the containers. Database migrations are handled automatically on startup.
---
## OAuth Setup (Optional)
Pulse supports OAuth login via Google, Discord, Facebook, and Twitch through GoTrue. All OAuth configuration is done through your `.env` file — do not edit `docker-compose-supabase.yml` directly.
### Google OAuth
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Create a project and go to **APIs & Services > Credentials**
3. Create an **OAuth 2.0 Client ID** (Web application)
4. Set the authorized redirect URI to: `https://your-domain.com/auth/v1/callback`
5. Add to your `.env`:
```env
GOOGLE_OAUTH_ENABLED=true
GOOGLE_OAUTH_CLIENT_ID=your-client-id
GOOGLE_OAUTH_SECRET=your-client-secret
```
6. Restart: `docker compose -f docker-compose-supabase.yml up -d`
### Discord OAuth
1. Go to [Discord Developer Portal](https://discord.com/developers/applications)
2. Create an application, go to **OAuth2**, add redirect: `https://your-domain.com/auth/v1/callback`
3. Add to your `.env`:
```env
DISCORD_OAUTH_ENABLED=true
DISCORD_OAUTH_CLIENT_ID=your-client-id
DISCORD_OAUTH_SECRET=your-client-secret
```
4. Restart: `docker compose -f docker-compose-supabase.yml up -d`
The same pattern applies for Facebook and Twitch — replace `DISCORD` with `FACEBOOK` or `TWITCH` in the variable names.
---
## Federation Setup (Optional)
Federation lets multiple Pulse instances connect so users can discover and join servers across instances.
1. Edit the config file:
```bash
nano data/pulse/config.ini
```
2. Add or modify:
```ini
[federation]
enabled=true
domain=your-domain.com
```
3. Restart:
```bash
docker compose -f docker-compose-supabase.yml restart pulse
```
### Connect Two Instances
On Instance A: Go to **Server Settings > Federation** > **Generate Keys** > **Add Instance** (enter Instance B's domain).
On Instance B: **Server Settings > Federation** > **Generate Keys** > Accept Instance A's request.
---
## Troubleshooting
### Containers won't start
```bash
docker logs pulse-db
docker logs pulse-auth
docker logs pulse-kong
docker logs pulse
```
Common issues:
- **pulse-db**: `POSTGRES_PASSWORD` contains special characters. Use only alphanumeric characters.
- **pulse-auth**: Database connection failed. Check `db` container is healthy: `docker compose -f docker-compose-supabase.yml ps`
- **pulse-kong**: Config error. Verify `docker/kong-supabase.yml` is valid YAML.
### GoTrue auth fails
```bash
docker exec pulse curl -s http://kong:8000/auth/v1/health
```
The `supabase_auth_admin` password is synced automatically on every container start via `docker/db-entrypoint.sh`. If you change `POSTGRES_PASSWORD` in your `.env`, just restart:
```bash
docker compose -f docker-compose-supabase.yml up -d
```
The DB container will re-sync the password automatically.
### WebRTC voice/video not working
1. Check UDP ports are open: `sudo ufw status | grep 40000`
2. Check Docker port mapping: `docker port pulse`
3. If behind NAT, forward ports 40000-40020/UDP from your router
4. Check public IP detection: `docker logs pulse 2>&1 | grep -i "public ip"`
### Database issues
```bash
docker exec -it pulse-db psql -U postgres
\dt
SELECT pg_size_pretty(pg_database_size('postgres'));
```
### Reset everything
```bash
cd /opt/pulse
docker compose -f docker-compose-supabase.yml down -v
rm -rf data/
docker compose -f docker-compose-supabase.yml up -d
```
### View real-time logs
```bash
docker compose -f docker-compose-supabase.yml logs -f
docker compose -f docker-compose-supabase.yml logs -f pulse
```