mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2026-03-30 10:56:47 +00:00
311 lines
15 KiB
Markdown
311 lines
15 KiB
Markdown
---
|
|
sidebar_position: 2
|
|
title: Standalone Local Setup
|
|
---
|
|
|
|
# Standalone Local Setup
|
|
|
|
**The goal**: a clueless user clones the repo, runs one script, and has a working Reflector instance locally. No cloud accounts, no API keys, no manual env file editing.
|
|
|
|
```bash
|
|
git clone https://github.com/monadical-sas/reflector.git
|
|
cd reflector
|
|
./scripts/setup-standalone.sh
|
|
```
|
|
|
|
On Ubuntu, the setup script installs Docker automatically if missing.
|
|
|
|
The script is idempotent — safe to re-run at any time. It detects what's already set up and skips completed steps.
|
|
|
|
## Prerequisites
|
|
|
|
- Docker with Compose V2 plugin (Docker Desktop, OrbStack, or Docker Engine + compose plugin)
|
|
- Mac (Apple Silicon) or Linux
|
|
- 16GB+ RAM (32GB recommended for 14B LLM models)
|
|
- **Mac only**: [Ollama](https://ollama.com/download) installed (`brew install ollama`)
|
|
|
|
### Installing Docker (if not already installed)
|
|
|
|
**Ubuntu**: The setup script runs `install-docker-ubuntu.sh` automatically when Docker is missing. Or run it manually:
|
|
|
|
```bash
|
|
./scripts/install-docker-ubuntu.sh
|
|
```
|
|
|
|
**Mac**: Install [Docker Desktop](https://www.docker.com/products/docker-desktop/) or [OrbStack](https://orbstack.dev/).
|
|
|
|
## What the script does
|
|
|
|
### 1. LLM inference via Ollama
|
|
|
|
**Mac**: starts Ollama natively (Metal GPU acceleration). Pulls the LLM model. Docker containers reach it via `host.docker.internal:11435`.
|
|
|
|
**Linux**: starts containerized Ollama via `docker-compose.standalone.yml` profile (`ollama-gpu` with NVIDIA, `ollama-cpu` without). Pulls model inside the container.
|
|
|
|
### 2. Environment files
|
|
|
|
Generates `server/.env` and `www/.env.local` with standalone defaults:
|
|
|
|
**`server/.env`** — key settings:
|
|
|
|
| Variable | Value | Why |
|
|
| --------------------- | -------------------------------------------------- | ----------------------------------- |
|
|
| `DATABASE_URL` | `postgresql+asyncpg://...@postgres:5432/reflector` | Docker-internal hostname |
|
|
| `REDIS_HOST` | `redis` | Docker-internal hostname |
|
|
| `CELERY_BROKER_URL` | `redis://redis:6379/1` | Docker-internal hostname |
|
|
| `AUTH_BACKEND` | `none` | No Authentik in standalone |
|
|
| `TRANSCRIPT_BACKEND` | `modal` | HTTP API to self-hosted CPU service |
|
|
| `TRANSCRIPT_URL` | `http://cpu:8000` | Docker-internal CPU service |
|
|
| `DIARIZATION_BACKEND` | `modal` | HTTP API to self-hosted CPU service |
|
|
| `DIARIZATION_URL` | `http://cpu:8000` | Docker-internal CPU service |
|
|
| `TRANSLATION_BACKEND` | `passthrough` | No Modal |
|
|
| `LLM_URL` | `http://host.docker.internal:11435/v1` (Mac) | Ollama endpoint |
|
|
|
|
**`www/.env.local`** — key settings:
|
|
|
|
| Variable | Value |
|
|
| ----------------------- | ------------------------------------------ |
|
|
| `API_URL` | `https://localhost:3043` or `https://YOUR_IP:3043` (Linux) |
|
|
| `SERVER_API_URL` | `http://server:1250` |
|
|
| `WEBSOCKET_URL` | `auto` |
|
|
| `FEATURE_REQUIRE_LOGIN` | `false` |
|
|
| `NEXTAUTH_SECRET` | `standalone-dev-secret-not-for-production` |
|
|
|
|
If env files already exist (including symlinks from worktree setup), the script resolves symlinks and ensures all standalone-critical vars are set. Existing vars not related to standalone are preserved.
|
|
|
|
### 3. Object storage (Garage)
|
|
|
|
Standalone uses [Garage](https://garagehq.deuxfleurs.fr/) — a lightweight S3-compatible object store running in Docker. The setup script starts Garage, initializes the layout, creates a bucket and access key, and writes the credentials to `server/.env`.
|
|
|
|
**`server/.env`** — storage settings added by the script:
|
|
|
|
| Variable | Value | Why |
|
|
| ------------------------------------------ | -------------------- | ------------------------------------- |
|
|
| `TRANSCRIPT_STORAGE_BACKEND` | `aws` | Uses the S3-compatible storage driver |
|
|
| `TRANSCRIPT_STORAGE_AWS_ENDPOINT_URL` | `http://garage:3900` | Docker-internal Garage S3 API |
|
|
| `TRANSCRIPT_STORAGE_AWS_BUCKET_NAME` | `reflector-media` | Created by the script |
|
|
| `TRANSCRIPT_STORAGE_AWS_REGION` | `garage` | Must match Garage config |
|
|
| `TRANSCRIPT_STORAGE_AWS_ACCESS_KEY_ID` | _(auto-generated)_ | Created by `garage key create` |
|
|
| `TRANSCRIPT_STORAGE_AWS_SECRET_ACCESS_KEY` | _(auto-generated)_ | Created by `garage key create` |
|
|
|
|
The `TRANSCRIPT_STORAGE_AWS_ENDPOINT_URL` setting enables S3-compatible backends. When set, the storage driver uses path-style addressing and routes all requests to the custom endpoint. When unset (production AWS), behavior is unchanged.
|
|
|
|
Garage config template lives at `scripts/garage.toml`. The setup script generates `data/garage.toml` (gitignored) with a random RPC secret and mounts it read-only into the container. Single-node, `replication_factor=1`.
|
|
|
|
> **Note**: Presigned URLs embed the Garage Docker hostname (`http://garage:3900`). This is fine — the server proxies S3 responses to the browser. Modal GPU workers cannot reach internal Garage, but standalone doesn't use Modal.
|
|
|
|
### 4. Transcription and diarization
|
|
|
|
Standalone runs the self-hosted ML service (`gpu/self_hosted/`) in a CPU-only Docker container named `cpu`. This is the same FastAPI service used for Modal.com GPU deployments, but built with `Dockerfile.cpu` (no NVIDIA CUDA dependencies). The compose service is named `cpu` (not `gpu`) to make clear it runs without GPU acceleration; the source code lives in `gpu/self_hosted/` because it's shared with the GPU deployment.
|
|
|
|
The `modal` backend name is reused — it just means "HTTP API client". Setting `TRANSCRIPT_URL` / `DIARIZATION_URL` to `http://cpu:8000` routes requests to the local container instead of Modal.com.
|
|
|
|
On first start, the service downloads pyannote speaker diarization models (~1GB) from a public S3 bundle. Models are cached in a Docker volume (`gpu_cache`) so subsequent starts are fast. No HuggingFace token or API key needed.
|
|
|
|
> **Performance**: CPU-only transcription and diarization work but are slow (~15 min for a 3 min file). For faster processing on Linux with NVIDIA GPU, use `--profile gpu-nvidia` instead (see `docker-compose.standalone.yml`).
|
|
|
|
### 5. Docker services
|
|
|
|
```bash
|
|
docker compose up -d postgres redis garage cpu server worker beat web
|
|
```
|
|
|
|
All services start in a single command. Garage and `cpu` are already started by earlier steps but included for idempotency. No Hatchet in standalone mode — LLM processing (summaries, topics, titles) runs via Celery tasks.
|
|
|
|
### 6. Database migrations
|
|
|
|
Run automatically by the `server` container on startup (`runserver.sh` calls `alembic upgrade head`). No manual step needed.
|
|
|
|
### 7. Health check
|
|
|
|
Verifies:
|
|
|
|
- CPU service responds (transcription + diarization ready)
|
|
- Server responds at `http://localhost:1250/health`
|
|
- Frontend serves at `http://localhost:3000` (or via Caddy at `https://localhost:3043`)
|
|
- LLM endpoint reachable from inside containers
|
|
|
|
## Services
|
|
|
|
| Service | Port | Purpose |
|
|
| ---------- | ---------- | -------------------------------------------------- |
|
|
| `caddy` | 3043 | Reverse proxy (HTTPS, self-signed cert) |
|
|
| `server` | 1250 | FastAPI backend (runs migrations on start) |
|
|
| `web` | 3000 | Next.js frontend |
|
|
| `postgres` | 5432 | PostgreSQL database |
|
|
| `redis` | 6379 | Cache + Celery broker |
|
|
| `garage` | 3900, 3903 | S3-compatible object storage (S3 API + admin API) |
|
|
| `cpu` | — | Self-hosted transcription + diarization (CPU-only) |
|
|
| `worker` | — | Celery worker (live pipeline post-processing) |
|
|
| `beat` | — | Celery beat (scheduled tasks) |
|
|
|
|
## Testing programmatically
|
|
|
|
After the setup script completes, verify the full pipeline (upload, transcription, diarization, LLM summary) via the API:
|
|
|
|
```bash
|
|
# 1. Create a transcript
|
|
TRANSCRIPT_ID=$(curl -s -X POST 'http://localhost:1250/v1/transcripts' \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"name":"test-upload"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
|
|
echo "Created: $TRANSCRIPT_ID"
|
|
|
|
# 2. Upload an audio file (single-chunk upload)
|
|
curl -s "http://localhost:1250/v1/transcripts/${TRANSCRIPT_ID}/record/upload?chunk_number=0&total_chunks=1" \
|
|
-X POST -F "chunk=@/path/to/audio.mp3"
|
|
|
|
# 3. Poll until processing completes (status: ended or error)
|
|
while true; do
|
|
STATUS=$(curl -s "http://localhost:1250/v1/transcripts/${TRANSCRIPT_ID}" \
|
|
| python3 -c "import sys,json; print(json.load(sys.stdin)['status'])")
|
|
echo "Status: $STATUS"
|
|
case "$STATUS" in ended|error) break;; esac
|
|
sleep 10
|
|
done
|
|
|
|
# 4. Check the result
|
|
curl -s "http://localhost:1250/v1/transcripts/${TRANSCRIPT_ID}" | python3 -m json.tool
|
|
```
|
|
|
|
Expected result: status `ended`, auto-generated `title`, `short_summary`, `long_summary`, and `transcript` text with `Speaker 0` / `Speaker 1` labels.
|
|
|
|
CPU-only processing is slow (~15 min for a 3 min audio file). Diarization finishes in ~3 min, transcription takes the rest.
|
|
|
|
## Enabling HTTPS (droplet via IP)
|
|
|
|
To serve Reflector over HTTPS on a droplet accessed by IP (self-signed certificate):
|
|
|
|
1. **Copy the Caddyfile** (no edits needed — `:443` catches all HTTPS inside container, mapped to host port 3043):
|
|
```bash
|
|
cp Caddyfile.standalone.example Caddyfile
|
|
```
|
|
|
|
2. **Update `www/.env.local`** with HTTPS URLs (port 3043):
|
|
```env
|
|
API_URL=https://YOUR_IP:3043
|
|
WEBSOCKET_URL=wss://YOUR_IP:3043
|
|
SITE_URL=https://YOUR_IP:3043
|
|
NEXTAUTH_URL=https://YOUR_IP:3043
|
|
```
|
|
|
|
3. **Restart services**:
|
|
```bash
|
|
docker compose -f docker-compose.standalone.yml --profile ollama-cpu up -d
|
|
```
|
|
(Use `ollama-gpu` instead of `ollama-cpu` if you have an NVIDIA GPU.)
|
|
|
|
4. **Access** at `https://YOUR_IP:3043`. The browser will warn about the self-signed cert — click **Advanced** → **Proceed to YOUR_IP (unsafe)**. All traffic (page, API, WebSocket) uses the same origin, so accepting once is enough.
|
|
|
|
## Troubleshooting
|
|
|
|
### ERR_SSL_PROTOCOL_ERROR when accessing https://YOUR_IP
|
|
|
|
You do **not** need a domain — the setup works with an IP address. This error usually means Caddy isn't serving TLS on port 3043. Check in order:
|
|
|
|
1. **Caddyfile** — must use the `:443` catch-all (container-internal; Docker maps host 3043 → container 443):
|
|
```bash
|
|
cp Caddyfile.standalone.example Caddyfile
|
|
```
|
|
|
|
2. **Firewall** — allow port 3043 (common on DigitalOcean):
|
|
```bash
|
|
sudo ufw allow 3043
|
|
sudo ufw status
|
|
```
|
|
|
|
3. **Caddy running** — verify and restart:
|
|
```bash
|
|
docker compose -f docker-compose.standalone.yml ps
|
|
docker compose -f docker-compose.standalone.yml logs caddy --tail 20
|
|
docker compose -f docker-compose.standalone.yml --profile ollama-cpu up -d
|
|
```
|
|
|
|
4. **Test from the droplet** — if this works, the issue is external (firewall, network):
|
|
```bash
|
|
curl -vk https://localhost:3043
|
|
```
|
|
|
|
5. **localhost works but external IP fails** — Re-run the setup script; it generates a Caddyfile with your droplet IP explicitly, so Caddy provisions the cert at startup:
|
|
```bash
|
|
./scripts/setup-standalone.sh
|
|
```
|
|
Or manually create `Caddyfile` with your IP (replace 138.197.162.116):
|
|
```
|
|
https://138.197.162.116, localhost {
|
|
tls internal
|
|
handle /v1/* { reverse_proxy server:1250 }
|
|
handle /health { reverse_proxy server:1250 }
|
|
handle { reverse_proxy web:3000 }
|
|
}
|
|
```
|
|
Then restart: `docker compose -f docker-compose.standalone.yml --profile ollama-cpu up -d`
|
|
|
|
6. **Still failing?** Try HTTP (no TLS) — create `Caddyfile`:
|
|
```
|
|
:80 {
|
|
handle /v1/* { reverse_proxy server:1250 }
|
|
handle /health { reverse_proxy server:1250 }
|
|
handle { reverse_proxy web:3000 }
|
|
}
|
|
```
|
|
Update `www/.env.local`: `API_URL=http://YOUR_IP:3043`, `WEBSOCKET_URL=ws://YOUR_IP:3043`, `SITE_URL=http://YOUR_IP:3043`, `NEXTAUTH_URL=http://YOUR_IP:3043`. Restart, then access `http://YOUR_IP:3043`.
|
|
|
|
### Docker not ready
|
|
|
|
If setup fails with "Docker not ready", on Ubuntu run `./scripts/install-docker-ubuntu.sh`. If Docker is installed but you're not root, run `newgrp docker` then run the setup script again.
|
|
|
|
### Port conflicts (most common issue)
|
|
|
|
If the frontend or backend behaves unexpectedly (e.g., env vars seem ignored, changes don't take effect), **check for port conflicts first**:
|
|
|
|
```bash
|
|
# Check what's listening on key ports
|
|
lsof -i :3000 # frontend
|
|
lsof -i :1250 # backend
|
|
lsof -i :5432 # postgres
|
|
lsof -i :3900 # Garage S3 API
|
|
lsof -i :6379 # Redis
|
|
|
|
# Kill stale processes on a port
|
|
lsof -ti :3000 | xargs kill
|
|
```
|
|
|
|
Common causes:
|
|
|
|
- A stale `next dev` or `pnpm dev` process from another terminal/worktree
|
|
- Another Docker Compose project (different worktree) with containers on the same ports — the setup script only manages its own project; containers from other projects must be stopped manually (`docker ps` to find them, `docker stop` to kill them)
|
|
|
|
The setup script checks ports 3000, 1250, 5432, 6379, 3900, 3903 for conflicts before starting services. It ignores OrbStack/Docker Desktop port forwarding processes (which always bind these ports but are not real conflicts).
|
|
|
|
### OrbStack false port-conflict warnings (Mac)
|
|
|
|
If you use OrbStack as your Docker runtime, `lsof` will show OrbStack binding ports like 3000, 1250, etc. even when no containers are running. This is OrbStack's port forwarding mechanism — not a real conflict. The setup script filters these out automatically.
|
|
|
|
### Re-enabling authentication
|
|
|
|
Standalone runs without authentication (`FEATURE_REQUIRE_LOGIN=false`, `AUTH_BACKEND=none`). To re-enable:
|
|
|
|
1. In `www/.env.local`: set `FEATURE_REQUIRE_LOGIN=true`, uncomment `AUTHENTIK_ISSUER` and `AUTHENTIK_REFRESH_TOKEN_URL`
|
|
2. In `server/.env`: set `AUTH_BACKEND=authentik` (or your backend), configure `AUTH_JWT_AUDIENCE`
|
|
3. Restart: `docker compose -f docker-compose.standalone.yml up -d --force-recreate web server`
|
|
|
|
## What's NOT covered
|
|
|
|
These require external accounts and infrastructure that can't be scripted:
|
|
|
|
- **Live meeting rooms** — requires Daily.co account, S3 bucket, IAM roles
|
|
- **Authentication** — requires Authentik deployment and OAuth configuration
|
|
- **Hatchet workflows** — requires separate Hatchet setup for multitrack processing
|
|
- **Production deployment** — see [Deployment Guide](./overview)
|
|
|
|
## Current status
|
|
|
|
All steps implemented. The setup script handles everything end-to-end:
|
|
|
|
- Step 1 (Ollama/LLM) — implemented
|
|
- Step 2 (environment files) — implemented
|
|
- Step 3 (object storage / Garage) — implemented
|
|
- Step 4 (transcription/diarization) — implemented (self-hosted GPU service)
|
|
- Steps 5-7 (Docker, migrations, health) — implemented
|
|
- **Unified script**: `scripts/setup-standalone.sh`
|