diff --git a/Caddyfile.standalone.example b/Caddyfile.standalone.example new file mode 100644 index 00000000..601bbddf --- /dev/null +++ b/Caddyfile.standalone.example @@ -0,0 +1,42 @@ +# Reflector standalone — HTTPS via Caddy (droplet / IP access) +# Copy to Caddyfile: cp Caddyfile.standalone.example Caddyfile +# Run: docker compose -f docker-compose.standalone.yml --profile ollama-cpu up -d +# +# :443 = catch-all inside container; Docker maps host port 3043 → container 443 +# on_demand = generate self-signed cert for IP/SNI on first request (required for bare IP access) +# Browser will warn. Click Advanced → Proceed. +# Access at https://localhost:3043 (or https://YOUR_IP:3043 on droplet) +# Update www/.env.local with: API_URL=https://YOUR_IP:3043, WEBSOCKET_URL=wss://YOUR_IP:3043, SITE_URL=https://YOUR_IP:3043, NEXTAUTH_URL=https://YOUR_IP:3043 + +:443 { + tls internal { + on_demand + } + handle /v1/* { + reverse_proxy server:1250 + } + handle /health { + reverse_proxy server:1250 + } + handle { + reverse_proxy web:3000 + } +} + +# Option B: localhost (comment Option A, uncomment this) +# app.localhost { +# tls internal +# reverse_proxy web:3000 +# } +# api.localhost { +# tls internal +# reverse_proxy server:1250 +# } + +# Option C: Real domain (uncomment and replace example.com) +# app.example.com { +# reverse_proxy web:3000 +# } +# api.example.com { +# reverse_proxy server:1250 +# } diff --git a/docker-compose.standalone.yml b/docker-compose.standalone.yml index b9f7a74d..74494837 100644 --- a/docker-compose.standalone.yml +++ b/docker-compose.standalone.yml @@ -14,9 +14,12 @@ services: extra_hosts: - "host.docker.internal:host-gateway" volumes: - - ./scripts/standalone/Caddyfile:/etc/caddy/Caddyfile:ro + - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy_data:/data - caddy_config:/config + depends_on: + - web + - server server: build: @@ -123,19 +126,18 @@ services: ports: - "3000:3000" command: ["node", "server.js"] + env_file: + - ./www/.env.local environment: NODE_ENV: production - # Browser-facing URLs (host-accessible ports) - API_URL: /server-api - WEBSOCKET_URL: auto - SITE_URL: http://localhost:3000 + # API_URL, WEBSOCKET_URL, SITE_URL, NEXTAUTH_URL from www/.env.local (allows HTTPS) # Server-side URLs (docker-network internal) SERVER_API_URL: http://server:1250 KV_URL: redis://redis:6379 KV_USE_TLS: "false" # Standalone: no external auth provider FEATURE_REQUIRE_LOGIN: "false" - NEXTAUTH_URL: http://localhost:3000 + FEATURE_ROOMS: "false" NEXTAUTH_SECRET: standalone-local-secret # Nullify partial auth vars inherited from base env_file AUTHENTIK_ISSUER: "" diff --git a/docs/docs/installation/setup-standalone.md b/docs/docs/installation/setup-standalone.md index ea9c581f..e8d57fe1 100644 --- a/docs/docs/installation/setup-standalone.md +++ b/docs/docs/installation/setup-standalone.md @@ -13,15 +13,27 @@ 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 / OrbStack / Docker Desktop (any) +- 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 @@ -36,28 +48,28 @@ 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:11434/v1` (Mac) | Ollama endpoint | +| 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:11434/v1` (Mac) | Ollama endpoint | **`www/.env.local`** — key settings: -| Variable | Value | -|----------|-------| -| `API_URL` | `http://localhost:1250` | -| `SERVER_API_URL` | `http://server:1250` | -| `WEBSOCKET_URL` | `ws://localhost:1250` | -| `FEATURE_REQUIRE_LOGIN` | `false` | -| `NEXTAUTH_SECRET` | `standalone-dev-secret-not-for-production` | +| 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. @@ -67,14 +79,14 @@ Standalone uses [Garage](https://garagehq.deuxfleurs.fr/) — a lightweight S3-c **`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` | +| 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. @@ -107,23 +119,25 @@ Run automatically by the `server` container on startup (`runserver.sh` calls `al ### 7. Health check Verifies: + - CPU service responds (transcription + diarization ready) - Server responds at `http://localhost:1250/health` -- Frontend serves at `http://localhost:3000` +- Frontend serves at `http://localhost:3000` (or via Caddy at `https://localhost:3043`) - LLM endpoint reachable from inside containers ## Services -| Service | Port | Purpose | -|---------|------|---------| -| `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) | +| 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 @@ -157,8 +171,89 @@ Expected result: status `ended`, auto-generated `title`, `short_summary`, `long_ 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**: @@ -176,6 +271,7 @@ 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) diff --git a/scripts/install-docker-ubuntu.sh b/scripts/install-docker-ubuntu.sh new file mode 100755 index 00000000..16aef8bb --- /dev/null +++ b/scripts/install-docker-ubuntu.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# +# Install Docker Engine + Compose plugin on Ubuntu. +# Ubuntu's default repos don't include docker-compose-plugin, so we add Docker's official repo. +# +# Usage: +# ./scripts/install-docker-ubuntu.sh +# +# Requires: root or sudo +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# --- Colors --- +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +info() { echo -e "${CYAN}==>${NC} $*"; } +ok() { echo -e "${GREEN} ✓${NC} $*"; } +warn() { echo -e "${YELLOW} !${NC} $*"; } +err() { echo -e "${RED} ✗${NC} $*" >&2; } + +# Use sudo if available and not root; otherwise run directly +if [[ $(id -u) -eq 0 ]]; then + MAYBE_SUDO="" +elif command -v sudo &>/dev/null; then + MAYBE_SUDO="sudo " +else + err "Need root. Run as root or install sudo: apt install sudo" + exit 1 +fi + +# Check Ubuntu +if [[ ! -f /etc/os-release ]]; then + err "Cannot detect OS. This script is for Ubuntu." + exit 1 +fi +source /etc/os-release +if [[ "${ID:-}" != "ubuntu" ]] && [[ "${ID_LIKE:-}" != *"ubuntu"* ]]; then + err "This script is for Ubuntu. Detected: ${ID:-unknown}" + exit 1 +fi + +info "Adding Docker's official repository..." +${MAYBE_SUDO}apt update +${MAYBE_SUDO}apt install -y ca-certificates curl +${MAYBE_SUDO}install -m 0755 -d /etc/apt/keyrings +${MAYBE_SUDO}rm -f /etc/apt/sources.list.d/docker.list /etc/apt/sources.list.d/docker.sources +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | ${MAYBE_SUDO}tee /etc/apt/keyrings/docker.asc > /dev/null +${MAYBE_SUDO}chmod a+r /etc/apt/keyrings/docker.asc +CODENAME="$(. /etc/os-release && echo "${UBUNTU_CODENAME:-${VERSION_CODENAME:-}}")" +[[ -z "$CODENAME" ]] && { err "Could not detect Ubuntu version codename."; exit 1; } +${MAYBE_SUDO}tee /etc/apt/sources.list.d/docker.sources > /dev/null < "$GARAGE_TOML_RUNTIME" fi compose_cmd up -d garage - wait_for_url "http://localhost:3903/health" "Garage admin API" + # Use /metrics for readiness — /health returns 503 until layout is applied + if ! wait_for_url "http://localhost:3903/metrics" "Garage admin API"; then + echo "" + err "Garage container logs:" + compose_cmd logs garage --tail 30 2>&1 || true + exit 1 + fi echo "" # Layout: get node ID, assign, apply (skip if already applied) @@ -376,11 +385,17 @@ ENVEOF ok "Created www/.env.local" fi - env_set "$WWW_ENV" "SITE_URL" "http://localhost:3000" - env_set "$WWW_ENV" "NEXTAUTH_URL" "http://localhost:3000" + # Caddyfile.standalone.example serves API at /v1, /health — use base URL + if [[ -n "${PRIMARY_IP:-}" ]]; then + BASE_URL="https://$PRIMARY_IP:3043" + else + BASE_URL="https://localhost:3043" + fi + env_set "$WWW_ENV" "SITE_URL" "$BASE_URL" + env_set "$WWW_ENV" "NEXTAUTH_URL" "$BASE_URL" env_set "$WWW_ENV" "NEXTAUTH_SECRET" "standalone-dev-secret-not-for-production" - env_set "$WWW_ENV" "API_URL" "http://localhost:1250" - env_set "$WWW_ENV" "WEBSOCKET_URL" "ws://localhost:1250" + env_set "$WWW_ENV" "API_URL" "$BASE_URL" + env_set "$WWW_ENV" "WEBSOCKET_URL" "auto" env_set "$WWW_ENV" "SERVER_API_URL" "http://server:1250" env_set "$WWW_ENV" "FEATURE_REQUIRE_LOGIN" "false" @@ -533,21 +548,38 @@ main() { exit 1 fi - # Ensure Docker Compose V2 plugin is available. - # Check output for "Compose" — without the plugin, `docker compose version` - # may still exit 0 (falling through to `docker version`). - if ! docker compose version 2>/dev/null | grep -qi compose; then - err "Docker Compose plugin not found." - err "Install Docker Desktop, OrbStack, or: brew install docker-compose" - exit 1 - fi + # Docker: Compose plugin, buildx, and daemon. On Ubuntu, auto-install if missing. + docker_ready() { + docker compose version 2>/dev/null | grep -qi compose \ + && docker buildx version &>/dev/null \ + && docker info &>/dev/null + } - # Dockerfiles use RUN --mount which requires BuildKit. - # Docker Desktop/OrbStack bundle it; Colima/bare engine need docker-buildx. - if ! docker buildx version &>/dev/null; then - err "Docker BuildKit (buildx) not found." - err "Install Docker Desktop, OrbStack, or: brew install docker-buildx" - exit 1 + if ! docker_ready; then + RAN_INSTALL=false + if [[ "$OS" == "Linux" ]] && [[ -f /etc/os-release ]] && (source /etc/os-release 2>/dev/null; [[ "${ID:-}" == "ubuntu" || "${ID_LIKE:-}" == *"ubuntu"* ]]); then + info "Docker not ready. Running install-docker-ubuntu.sh..." + "$SCRIPT_DIR/install-docker-ubuntu.sh" || true + RAN_INSTALL=true + [[ -d /run/systemd/system ]] && command -v systemctl &>/dev/null && systemctl start docker 2>/dev/null || true + sleep 2 + fi + if ! docker_ready; then + # Docker may be installed but current shell lacks docker group (needs newgrp) + if [[ "$RAN_INSTALL" == "true" ]] && [[ $(id -u) -ne 0 ]] && command -v sg &>/dev/null && getent group docker &>/dev/null; then + info "Re-running with docker group..." + exec sg docker -c "$(printf '%q' "$0" && printf ' %q' "$@")" + fi + if [[ "$OS" == "Darwin" ]]; then + err "Docker not ready. Install Docker Desktop or OrbStack." + elif [[ "$OS" == "Linux" ]]; then + err "Docker not ready. Run: ./scripts/install-docker-ubuntu.sh" + err "Then run: newgrp docker (or log out and back in), then run this script again." + else + err "Docker not ready. Install Docker with Compose V2 and buildx." + fi + exit 1 + fi fi # LLM_URL_VALUE is set by step_llm, used by later steps @@ -558,6 +590,57 @@ main() { # touch them so compose_cmd works before the steps that populate them. touch "$SERVER_ENV" "$WWW_ENV" + # Ensure garage.toml exists before any compose up (step_llm starts all services including garage) + GARAGE_TOML="$ROOT_DIR/scripts/garage.toml" + GARAGE_TOML_RUNTIME="$ROOT_DIR/data/garage.toml" + mkdir -p "$ROOT_DIR/data" + if [[ -d "$GARAGE_TOML_RUNTIME" ]]; then + rm -rf "$GARAGE_TOML_RUNTIME" + fi + if [[ ! -f "$GARAGE_TOML_RUNTIME" ]]; then + RPC_SECRET=$(openssl rand -hex 32) + sed "s|__GARAGE_RPC_SECRET__|${RPC_SECRET}|" "$GARAGE_TOML" > "$GARAGE_TOML_RUNTIME" + fi + + # Remove containers that may have bad mounts (was directory); force recreate + compose_cmd rm -f -s garage caddy 2>/dev/null || true + + # Detect primary IP for droplet (used for Caddyfile, step_www_env, success message) + PRIMARY_IP="" + if [[ "$OS" == "Linux" ]]; then + PRIMARY_IP=$(hostname -I 2>/dev/null | awk '{print $1}' || true) + if [[ "$PRIMARY_IP" == "127."* ]] || [[ -z "$PRIMARY_IP" ]]; then + PRIMARY_IP=$(ip -4 route get 1 2>/dev/null | sed -n 's/.*src \([0-9.]*\).*/\1/p' || true) + fi + fi + + # Ensure Caddyfile exists before any compose up (step_llm starts caddy) + # On droplet: explicit IP + localhost so Caddy provisions cert at startup (avoids on_demand/SNI issues) + CADDYFILE="$ROOT_DIR/Caddyfile" + if [[ -d "$CADDYFILE" ]]; then + rm -rf "$CADDYFILE" + fi + if [[ -n "$PRIMARY_IP" ]]; then + cat > "$CADDYFILE" << CADDYEOF +# Generated by setup-standalone.sh — explicit IP for droplet (provisions cert at startup) +https://$PRIMARY_IP, localhost { + tls internal + handle /v1/* { + reverse_proxy server:1250 + } + handle /health { + reverse_proxy server:1250 + } + handle { + reverse_proxy web:3000 + } +} +CADDYEOF + ok "Created Caddyfile for $PRIMARY_IP and localhost" + elif [[ ! -f "$CADDYFILE" ]]; then + cp "$ROOT_DIR/Caddyfile.standalone.example" "$CADDYFILE" + fi + step_llm echo "" step_server_env @@ -575,8 +658,14 @@ main() { echo -e " ${GREEN}Reflector is running!${NC}" echo "==========================================" echo "" - echo " App: https://localhost:3043 (accept self-signed cert in browser)" - echo " API: https://localhost:3043/server-api" + if [[ -n "$PRIMARY_IP" ]]; then + echo " App: https://$PRIMARY_IP:3043 (accept self-signed cert in browser)" + echo " API: https://$PRIMARY_IP:3043/v1/" + echo " Local: https://localhost:3043" + else + echo " App: https://localhost:3043 (accept self-signed cert in browser)" + echo " API: https://localhost:3043/v1/" + fi echo "" echo " To stop: docker compose down" echo " To re-run: ./scripts/setup-standalone.sh" diff --git a/scripts/standalone/Caddyfile b/scripts/standalone/Caddyfile deleted file mode 100644 index 0b8ac18b..00000000 --- a/scripts/standalone/Caddyfile +++ /dev/null @@ -1,16 +0,0 @@ -# Standalone Caddyfile — single-origin reverse proxy for local development. -# Routes: -# /server-api/* → FastAPI backend (server:1250) -# /* → Next.js frontend (web:3000) - -localhost { - # API + WebSocket: server has ROOT_PATH=/server-api so path is preserved - handle /server-api/* { - reverse_proxy server:1250 - } - - # Everything else → frontend - handle { - reverse_proxy web:3000 - } -}