501 lines
14 KiB
Markdown
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
|
|
```
|