feat: add fluxer upstream source and self-hosting documentation
- Clone of github.com/fluxerapp/fluxer (official upstream) - SELF_HOSTING.md: full VM rebuild procedure, architecture overview, service reference, step-by-step setup, troubleshooting, seattle reference - dev/.env.example: all env vars with secrets redacted and generation instructions - dev/livekit.yaml: LiveKit config template with placeholder keys - fluxer-seattle/: existing seattle deployment setup scripts
This commit is contained in:
384
fluxer/SELF_HOSTING.md
Normal file
384
fluxer/SELF_HOSTING.md
Normal file
@@ -0,0 +1,384 @@
|
||||
# Fluxer — Self-Hosting & VM Rebuild Guide
|
||||
|
||||
This document covers how to spin up the Fluxer dev instance from scratch on a new VM.
|
||||
The running reference deployment is at `st.vish.gg` (seattle VM, `66.94.122.170` / `100.82.197.124`).
|
||||
|
||||
---
|
||||
|
||||
## Architecture overview
|
||||
|
||||
All services run as Docker containers via `dev/compose.yaml`, connected on a shared Docker network (`fluxer-shared`).
|
||||
Caddy acts as the internal reverse proxy, routing all traffic on port `8088` to the appropriate service.
|
||||
Cloudflared tunnels external HTTPS traffic (no open inbound ports required).
|
||||
|
||||
```
|
||||
Internet
|
||||
└── Cloudflare Tunnel (cloudflared)
|
||||
└── Caddy :8088
|
||||
├── /api/* → api:8080 (Node.js REST API)
|
||||
├── /gateway/* → gateway:8080 (Erlang WebSocket gateway)
|
||||
├── /media/* → media:8080 (media proxy)
|
||||
├── /admin/* → admin:8080 (Elixir admin panel)
|
||||
├── /marketing/* → marketing:8080 (Elixir marketing site)
|
||||
├── /metrics/* → metrics:8080 (Rust metrics service)
|
||||
├── /livekit/* → livekit:7880 (LiveKit voice/video)
|
||||
├── /s3/* → minio:9000 (S3-compatible object storage)
|
||||
└── /* → /app/dist (built frontend SPA)
|
||||
```
|
||||
|
||||
### Services
|
||||
|
||||
| Container | Image/Build | Purpose |
|
||||
|-----------|-------------|---------|
|
||||
| `caddy` | `caddy:2` | Internal reverse proxy |
|
||||
| `cloudflared` | `cloudflare/cloudflared` | Tunnel (no inbound ports) |
|
||||
| `api` | `node:24-bookworm-slim` | REST API (`tsx watch`) |
|
||||
| `worker` | `node:24-bookworm-slim` | Background job worker |
|
||||
| `gateway` | `erlang:28-slim` | WebSocket gateway (compiled with rebar3) |
|
||||
| `media` | build from `fluxer_media_proxy/` | Media proxy |
|
||||
| `admin` | build from `fluxer_admin/` | Admin panel |
|
||||
| `marketing` | build from `fluxer_marketing/` | Marketing site |
|
||||
| `docs` | `node:24-bookworm-slim` | Docs site |
|
||||
| `metrics` | build from `fluxer_metrics/` | Metrics aggregator |
|
||||
| `postgres` | `postgres:17` | Primary database |
|
||||
| `cassandra` | `scylladb/scylla:latest` | Time-series / message data |
|
||||
| `redis` | `valkey/valkey:latest` | Cache / pub-sub |
|
||||
| `minio` | `minio/minio` | S3-compatible object storage |
|
||||
| `meilisearch` | `getmeili/meilisearch:v1.25.0` | Full-text search |
|
||||
| `clamav` | `clamav/clamav:latest` | Virus scanning (can be disabled) |
|
||||
| `livekit` | `livekit/livekit-server:latest` | Voice & video |
|
||||
| `cassandra-migrate` | `debian:bookworm-slim` | One-shot DB migration runner |
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Debian/Ubuntu VM (tested on Debian 12 bookworm)
|
||||
- Docker + Docker Compose plugin installed
|
||||
- `git` installed
|
||||
- A Cloudflare tunnel token (from Cloudflare Zero Trust dashboard)
|
||||
- Domain pointed at Cloudflare (e.g. `st.vish.gg`)
|
||||
- At least 4 vCPU / 8GB RAM recommended (ScyllaDB + ClamAV are hungry)
|
||||
|
||||
### Install Docker (if not present)
|
||||
|
||||
```bash
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
systemctl enable --now docker
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step-by-step rebuild
|
||||
|
||||
### 1. Clone the repo
|
||||
|
||||
```bash
|
||||
git clone https://github.com/fluxerapp/fluxer /root/fluxer
|
||||
cd /root/fluxer
|
||||
```
|
||||
|
||||
### 2. Create the shared Docker network
|
||||
|
||||
Must be created before starting any containers — it's declared `external: true` in the compose file.
|
||||
|
||||
```bash
|
||||
docker network create fluxer-shared
|
||||
```
|
||||
|
||||
### 3. Build the frontend app
|
||||
|
||||
Caddy serves the pre-built SPA from `fluxer_app/dist/`. This must be built before starting Caddy,
|
||||
otherwise it will mount an empty directory and serve nothing.
|
||||
|
||||
```bash
|
||||
# Install pnpm
|
||||
corepack enable pnpm
|
||||
|
||||
# Install workspace deps
|
||||
pnpm install
|
||||
|
||||
# Build the app (outputs to fluxer_app/dist/)
|
||||
cd fluxer_app
|
||||
pnpm build
|
||||
cd ..
|
||||
```
|
||||
|
||||
> Note: The build requires Node 22+ and pnpm. If the VM doesn't have Node installed globally,
|
||||
> run the build inside a container:
|
||||
> ```bash
|
||||
> docker run --rm -v $(pwd):/workspace -w /workspace/fluxer_app node:24-bookworm-slim \
|
||||
> bash -lc "corepack enable pnpm && CI=true pnpm install && pnpm build"
|
||||
> ```
|
||||
|
||||
### 4. Build the cassandra-migrate binary
|
||||
|
||||
The Cassandra migration runner is a Rust binary that must be pre-compiled.
|
||||
|
||||
```bash
|
||||
# Install Rust if needed
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source ~/.cargo/env
|
||||
|
||||
# Build
|
||||
cd scripts/cassandra-migrate
|
||||
cargo build --release
|
||||
cd ../..
|
||||
```
|
||||
|
||||
The compose file expects the binary at `scripts/cassandra-migrate/target/release/cassandra-migrate`.
|
||||
|
||||
### 5. Set up the environment file
|
||||
|
||||
```bash
|
||||
cp dev/.env.example dev/.env
|
||||
```
|
||||
|
||||
Edit `dev/.env` and fill in:
|
||||
|
||||
| Variable | What to set |
|
||||
|----------|-------------|
|
||||
| `FLUXER_APP_ENDPOINT` | Your public URL e.g. `https://st.vish.gg` |
|
||||
| `FLUXER_API_PUBLIC_ENDPOINT` | `https://st.vish.gg/api` |
|
||||
| `FLUXER_GATEWAY_ENDPOINT` | `wss://st.vish.gg/gateway` |
|
||||
| `FLUXER_MARKETING_ENDPOINT` | `https://st.vish.gg` |
|
||||
| `FLUXER_ADMIN_ENDPOINT` | `https://st.vish.gg/admin` |
|
||||
| `FLUXER_MEDIA_ENDPOINT` | `https://st.vish.gg/media` |
|
||||
| `FLUXER_INVITE_ENDPOINT` | `https://st.vish.gg` |
|
||||
| `CLOUDFLARE_TUNNEL_TOKEN` | Token from Cloudflare Zero Trust dashboard |
|
||||
| `SECRET_KEY_BASE` | Generate: `openssl rand -hex 64` |
|
||||
| `GATEWAY_RPC_SECRET` | Generate: `openssl rand -hex 32` |
|
||||
| `GATEWAY_ADMIN_SECRET` | Generate: `openssl rand -hex 32` |
|
||||
| `MEDIA_PROXY_SECRET_KEY` | Generate: `openssl rand -hex 32` |
|
||||
| `SUDO_MODE_SECRET` | Generate: `openssl rand -hex 32` |
|
||||
| `LIVEKIT_API_KEY` | Generate: `openssl rand -hex 16` |
|
||||
| `LIVEKIT_API_SECRET` | Generate: `openssl rand -hex 32` |
|
||||
| `VAPID_PUBLIC_KEY` | Generate with web-push: `npx web-push generate-vapid-keys` |
|
||||
| `VAPID_PRIVATE_KEY` | (paired with above) |
|
||||
| `ADMIN_OAUTH2_CLIENT_ID` | OAuth2 app ID (from Fluxer admin panel after first boot) |
|
||||
| `ADMIN_OAUTH2_CLIENT_SECRET` | OAuth2 secret |
|
||||
| `PASSKEY_RP_ID` | Your domain e.g. `st.vish.gg` |
|
||||
| `PASSKEY_ALLOWED_ORIGINS` | `https://st.vish.gg` |
|
||||
|
||||
Everything else can stay at its default dev value on first boot.
|
||||
|
||||
### 6. Set up the LiveKit config
|
||||
|
||||
```bash
|
||||
cp dev/livekit.yaml dev/livekit.yaml
|
||||
```
|
||||
|
||||
Edit `dev/livekit.yaml` — replace the API key/secret with the values you set in `.env`:
|
||||
|
||||
```yaml
|
||||
keys:
|
||||
"<LIVEKIT_API_KEY>": "<LIVEKIT_API_SECRET>"
|
||||
|
||||
webhook:
|
||||
api_key: "<LIVEKIT_API_KEY>"
|
||||
urls:
|
||||
- "http://api:8080/webhooks/livekit"
|
||||
```
|
||||
|
||||
### 7. Start the stack
|
||||
|
||||
```bash
|
||||
cd /root/fluxer/dev
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
On first boot, `cassandra-migrate` will run automatically and apply schema migrations.
|
||||
ScyllaDB takes ~90 seconds to become healthy before migrations run.
|
||||
|
||||
Watch progress:
|
||||
```bash
|
||||
docker compose logs -f cassandra-migrate
|
||||
docker compose logs -f api
|
||||
```
|
||||
|
||||
### 8. Verify everything is up
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
Expected running containers: `caddy`, `cloudflared`, `api`, `worker`, `gateway`, `media`, `admin`,
|
||||
`marketing`, `docs`, `metrics`, `clamav`, `meilisearch`, `redis`, `minio`, `livekit`
|
||||
|
||||
Expected exited (one-shot): `cassandra-migrate`, `minio-setup`
|
||||
|
||||
The app should be accessible at your configured domain via the Cloudflare tunnel.
|
||||
|
||||
---
|
||||
|
||||
## Managing the stack
|
||||
|
||||
### Start / stop
|
||||
|
||||
```bash
|
||||
cd /root/fluxer/dev
|
||||
|
||||
# Start all
|
||||
docker compose up -d
|
||||
|
||||
# Stop all (keep volumes)
|
||||
docker compose down
|
||||
|
||||
# Stop and wipe all data
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
### View logs
|
||||
|
||||
```bash
|
||||
# All services
|
||||
docker compose logs -f
|
||||
|
||||
# Specific service
|
||||
docker compose logs -f api
|
||||
docker compose logs -f gateway
|
||||
```
|
||||
|
||||
### Rebuild a specific service after code changes
|
||||
|
||||
```bash
|
||||
# Rebuild and restart one service
|
||||
docker compose up -d --build media
|
||||
|
||||
# Rebuild frontend app and restart caddy
|
||||
cd /root/fluxer
|
||||
pnpm --filter fluxer_app build
|
||||
docker compose -f dev/compose.yaml restart caddy
|
||||
```
|
||||
|
||||
### Update from upstream
|
||||
|
||||
```bash
|
||||
cd /root/fluxer
|
||||
git pull origin main
|
||||
|
||||
# Rebuild frontend
|
||||
pnpm install
|
||||
pnpm --filter fluxer_app build
|
||||
|
||||
# Restart services that auto-install deps (api, worker, docs)
|
||||
docker compose -f dev/compose.yaml restart api worker docs
|
||||
|
||||
# Rebuild services with Dockerfiles
|
||||
docker compose -f dev/compose.yaml up -d --build media admin marketing metrics
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration reference
|
||||
|
||||
### Key files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `dev/compose.yaml` | Main docker compose for the dev stack |
|
||||
| `dev/.env` | All environment variables (not committed — see `.env.example`) |
|
||||
| `dev/Caddyfile.dev` | Caddy reverse proxy routing config |
|
||||
| `dev/livekit.yaml` | LiveKit server config (API keys, webhook URL) |
|
||||
|
||||
### Port mappings (host)
|
||||
|
||||
| Port | Service |
|
||||
|------|---------|
|
||||
| `8088` | Caddy (all traffic enters here, then routed internally) |
|
||||
| `7880` | LiveKit HTTP |
|
||||
| `7882/udp` | LiveKit RTC |
|
||||
| `7999/udp` | LiveKit RTC |
|
||||
| `9042` | ScyllaDB CQL (direct access) |
|
||||
| `8123` | ClickHouse (only with `--profile clickhouse`) |
|
||||
|
||||
All other services communicate internally over `fluxer-shared` Docker network.
|
||||
|
||||
### Optional profiles
|
||||
|
||||
```bash
|
||||
# Enable ClickHouse-backed metrics
|
||||
docker compose --profile clickhouse up -d
|
||||
|
||||
# Enable search (meilisearch is included by default in dev)
|
||||
docker compose --profile search up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cloudflare tunnel setup
|
||||
|
||||
1. Go to Cloudflare Zero Trust → Networks → Tunnels → Create tunnel
|
||||
2. Name it (e.g. `fluxer-seattle`)
|
||||
3. Copy the tunnel token → set as `CLOUDFLARE_TUNNEL_TOKEN` in `dev/.env`
|
||||
4. Add a public hostname:
|
||||
- Subdomain: `st` (or your chosen subdomain)
|
||||
- Domain: `vish.gg`
|
||||
- Service: `http://localhost:8088`
|
||||
5. The `cloudflared` container will connect automatically on `docker compose up`
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Gateway won't start
|
||||
|
||||
The Erlang gateway compiles from source on first start (`rebar3 compile`) — this takes 2-5 minutes.
|
||||
Watch with `docker compose logs -f gateway`. If it exits, check for missing deps or compilation errors.
|
||||
|
||||
### ScyllaDB/Cassandra migration fails
|
||||
|
||||
ScyllaDB needs ~90 seconds to fully initialise. The `cassandra-migrate` container has a `sleep 30`
|
||||
but on slow machines it may still fail. Restart it manually:
|
||||
|
||||
```bash
|
||||
docker compose run --rm cassandra-migrate
|
||||
```
|
||||
|
||||
### API exits immediately
|
||||
|
||||
Almost always a missing or malformed `.env` value. Check:
|
||||
|
||||
```bash
|
||||
docker compose logs api | head -30
|
||||
```
|
||||
|
||||
Common causes: `DATABASE_URL` unreachable, missing `SECRET_KEY_BASE`, malformed `REDIS_URL`.
|
||||
|
||||
### Frontend shows blank page
|
||||
|
||||
The `fluxer_app/dist/` directory is empty — the app hasn't been built yet.
|
||||
Run the build step (Step 3 above) then restart Caddy:
|
||||
|
||||
```bash
|
||||
docker compose restart caddy
|
||||
```
|
||||
|
||||
### ClamAV takes forever to start
|
||||
|
||||
ClamAV downloads its virus definition database on first start (~500MB). This is normal.
|
||||
It won't be marked healthy until the download completes (~5-10 min on first boot).
|
||||
ClamAV can be disabled by setting `CLAMAV_ENABLED=false` in `.env` — the other services don't depend on it.
|
||||
|
||||
---
|
||||
|
||||
## Seattle reference deployment
|
||||
|
||||
The running instance at `st.vish.gg` uses this exact setup. Key details:
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Host | Seattle VM (`66.94.122.170` / Tailscale `100.82.197.124`) |
|
||||
| SSH | `ssh root@seattle` or `ssh root@seattle-tailscale` |
|
||||
| Install path | `/root/fluxer/` |
|
||||
| Compose path | `/root/fluxer/dev/` |
|
||||
| Domain | `st.vish.gg` |
|
||||
| Tunnel | Cloudflare Zero Trust (`cloudflared`) |
|
||||
| `AUTO_JOIN_INVITE_CODE` | `FRIENDS` (anyone with this code can register) |
|
||||
|
||||
To check status on seattle:
|
||||
|
||||
```bash
|
||||
ssh seattle-tailscale "cd /root/fluxer/dev && docker compose ps"
|
||||
ssh seattle-tailscale "cd /root/fluxer/dev && docker compose logs --tail 20 api"
|
||||
```
|
||||
Reference in New Issue
Block a user