feat: Livekit - Selfhost video room solution (#946)

* feat: Livekit bare no recording nor pipeline

* feat: full livekit pipeline

* fix: caddy hatchet with livekit

* fix: caddy livekit

* fix: hatchet tls

* fix: agg to webm for no padding

* fix: reflector user id on participants and duration fix

* fix: better docs and internal review fixes

* fix: remove video files livekit
This commit is contained in:
Juan Diego García
2026-04-07 11:55:16 -05:00
committed by GitHub
parent b570d202dc
commit bc8338fa4f
41 changed files with 3731 additions and 146 deletions

View File

@@ -26,6 +26,12 @@
# (If omitted, configure an external OpenAI-compatible LLM in server/.env)
#
# Optional flags:
# --livekit Enable LiveKit self-hosted video platform (generates credentials,
# starts livekit-server + livekit-egress containers)
# --ip IP Set the server's IP address for all URLs. Implies --caddy
# (self-signed HTTPS, required for browser mic/camera access).
# Mutually exclusive with --domain. Use for LAN or cloud VM access.
# On Linux, IP is auto-detected; on macOS, use --ip to specify it.
# --garage Use Garage for local S3-compatible storage
# --caddy Enable Caddy reverse proxy with auto-SSL
# --domain DOMAIN Use a real domain for Caddy (enables Let's Encrypt auto-HTTPS)
@@ -42,10 +48,10 @@
# --build Build backend and frontend images from source instead of pulling
#
# Examples:
# ./scripts/setup-selfhosted.sh --gpu --ollama-gpu --garage --caddy
# ./scripts/setup-selfhosted.sh --gpu --ollama-gpu --garage --caddy --domain reflector.example.com
# ./scripts/setup-selfhosted.sh --cpu --ollama-cpu --garage --caddy
# ./scripts/setup-selfhosted.sh --hosted --garage --caddy
# ./scripts/setup-selfhosted.sh --gpu --ollama-gpu --livekit --garage --caddy
# ./scripts/setup-selfhosted.sh --gpu --ollama-gpu --livekit --garage --caddy --domain reflector.example.com
# ./scripts/setup-selfhosted.sh --cpu --ollama-cpu --livekit --garage --caddy
# ./scripts/setup-selfhosted.sh --hosted --livekit --garage --caddy
# ./scripts/setup-selfhosted.sh --cpu --padding modal --garage --caddy
# ./scripts/setup-selfhosted.sh --gpu --translation passthrough --garage --caddy
# ./scripts/setup-selfhosted.sh --cpu --diarization modal --translation modal --garage
@@ -58,9 +64,11 @@
# Config memory: after a successful run, flags are saved to data/.selfhosted-last-args.
# Re-running with no arguments replays the saved configuration automatically.
#
# The script auto-detects Daily.co (DAILY_API_KEY) and Whereby (WHEREBY_API_KEY)
# from server/.env. If Daily.co is configured, Hatchet workflow services are
# started automatically for multitrack recording processing.
# The script auto-detects Daily.co (DAILY_API_KEY), Whereby (WHEREBY_API_KEY),
# and LiveKit (LIVEKIT_API_KEY) from server/.env.
# - Daily.co: enables Hatchet workflow services for multitrack recording processing.
# - LiveKit: enables livekit-server + livekit-egress containers (self-hosted,
# generates livekit.yaml and egress.yaml configs automatically).
#
# Idempotent — safe to re-run at any time.
#
@@ -207,8 +215,10 @@ fi
MODEL_MODE="" # gpu or cpu (required, mutually exclusive)
OLLAMA_MODE="" # ollama-gpu or ollama-cpu (optional)
USE_GARAGE=false
USE_LIVEKIT=false
USE_CADDY=false
CUSTOM_DOMAIN="" # optional domain for Let's Encrypt HTTPS
CUSTOM_IP="" # optional --ip override (mutually exclusive with --caddy)
BUILD_IMAGES=false # build backend/frontend from source
ADMIN_PASSWORD="" # optional admin password for password auth
CUSTOM_CA="" # --custom-ca: path to dir or CA cert file
@@ -261,7 +271,16 @@ for i in "${!ARGS[@]}"; do
OLLAMA_MODEL="${ARGS[$next_i]}"
SKIP_NEXT=true ;;
--garage) USE_GARAGE=true ;;
--livekit) USE_LIVEKIT=true ;;
--caddy) USE_CADDY=true ;;
--ip)
next_i=$((i + 1))
if [[ $next_i -ge ${#ARGS[@]} ]] || [[ "${ARGS[$next_i]}" == --* ]]; then
err "--ip requires an IP address (e.g. --ip 192.168.0.100)"
exit 1
fi
CUSTOM_IP="${ARGS[$next_i]}"
SKIP_NEXT=true ;;
--build) BUILD_IMAGES=true ;;
--password)
next_i=$((i + 1))
@@ -356,6 +375,16 @@ for i in "${!ARGS[@]}"; do
esac
done
# --- Validate flag combinations ---
if [[ -n "$CUSTOM_IP" ]] && [[ -n "$CUSTOM_DOMAIN" ]]; then
err "--ip and --domain are mutually exclusive. Use --ip for IP-based access, or --domain for domain-based access."
exit 1
fi
# --ip implies --caddy (browsers require HTTPS for mic/camera access on non-localhost)
if [[ -n "$CUSTOM_IP" ]]; then
USE_CADDY=true
fi
# --- Save CLI args for config memory (re-run without flags) ---
if [[ $# -gt 0 ]]; then
mkdir -p "$ROOT_DIR/data"
@@ -505,6 +534,112 @@ if [[ "$HAS_OVERRIDES" == "true" ]]; then
MODE_DISPLAY="$MODE_DISPLAY (overrides: transcript=$EFF_TRANSCRIPT, diarization=$EFF_DIARIZATION, translation=$EFF_TRANSLATION, padding=$EFF_PADDING, mixdown=$EFF_MIXDOWN)"
fi
# =========================================================
# LiveKit config generation helper
# =========================================================
_generate_livekit_config() {
local lk_key lk_secret lk_url
lk_key=$(env_get "$SERVER_ENV" "LIVEKIT_API_KEY" || true)
lk_secret=$(env_get "$SERVER_ENV" "LIVEKIT_API_SECRET" || true)
lk_url=$(env_get "$SERVER_ENV" "LIVEKIT_URL" || true)
if [[ -z "$lk_key" ]] || [[ -z "$lk_secret" ]]; then
warn "LIVEKIT_API_KEY or LIVEKIT_API_SECRET not set — generating random credentials"
lk_key="reflector_$(openssl rand -hex 8)"
lk_secret="$(openssl rand -hex 32)"
env_set "$SERVER_ENV" "LIVEKIT_API_KEY" "$lk_key"
env_set "$SERVER_ENV" "LIVEKIT_API_SECRET" "$lk_secret"
env_set "$SERVER_ENV" "LIVEKIT_URL" "ws://livekit-server:7880"
ok "Generated LiveKit API credentials"
fi
# Set internal URL for server->livekit communication
if ! env_has_key "$SERVER_ENV" "LIVEKIT_URL" || [[ -z "$(env_get "$SERVER_ENV" "LIVEKIT_URL" || true)" ]]; then
env_set "$SERVER_ENV" "LIVEKIT_URL" "ws://livekit-server:7880"
fi
# Set public URL based on deployment mode.
# When Caddy is enabled (HTTPS), LiveKit WebSocket is proxied through Caddy
# at /lk-ws to avoid mixed-content blocking (browsers block ws:// on https:// pages).
# When no Caddy, browsers connect directly to LiveKit on port 7880.
local public_lk_url
if [[ "$USE_CADDY" == "true" ]]; then
if [[ -n "$CUSTOM_DOMAIN" ]]; then
public_lk_url="wss://${CUSTOM_DOMAIN}/lk-ws"
elif [[ -n "$PRIMARY_IP" ]]; then
public_lk_url="wss://${PRIMARY_IP}/lk-ws"
else
public_lk_url="wss://localhost/lk-ws"
fi
else
if [[ -n "$PRIMARY_IP" ]]; then
public_lk_url="ws://${PRIMARY_IP}:7880"
else
public_lk_url="ws://localhost:7880"
fi
fi
env_set "$SERVER_ENV" "LIVEKIT_PUBLIC_URL" "$public_lk_url"
env_set "$SERVER_ENV" "DEFAULT_VIDEO_PLATFORM" "livekit"
# LiveKit storage: always sync from transcript storage config.
# Endpoint URL must match (changes between Caddy/no-Caddy runs).
local ts_bucket ts_region ts_key ts_secret ts_endpoint
ts_bucket=$(env_get "$SERVER_ENV" "TRANSCRIPT_STORAGE_AWS_BUCKET_NAME" 2>/dev/null || echo "reflector-bucket")
ts_region=$(env_get "$SERVER_ENV" "TRANSCRIPT_STORAGE_AWS_REGION" 2>/dev/null || echo "us-east-1")
ts_key=$(env_get "$SERVER_ENV" "TRANSCRIPT_STORAGE_AWS_ACCESS_KEY_ID" 2>/dev/null || true)
ts_secret=$(env_get "$SERVER_ENV" "TRANSCRIPT_STORAGE_AWS_SECRET_ACCESS_KEY" 2>/dev/null || true)
ts_endpoint=$(env_get "$SERVER_ENV" "TRANSCRIPT_STORAGE_AWS_ENDPOINT_URL" 2>/dev/null || true)
env_set "$SERVER_ENV" "LIVEKIT_STORAGE_AWS_BUCKET_NAME" "$ts_bucket"
env_set "$SERVER_ENV" "LIVEKIT_STORAGE_AWS_REGION" "$ts_region"
[[ -n "$ts_key" ]] && env_set "$SERVER_ENV" "LIVEKIT_STORAGE_AWS_ACCESS_KEY_ID" "$ts_key"
[[ -n "$ts_secret" ]] && env_set "$SERVER_ENV" "LIVEKIT_STORAGE_AWS_SECRET_ACCESS_KEY" "$ts_secret"
[[ -n "$ts_endpoint" ]] && env_set "$SERVER_ENV" "LIVEKIT_STORAGE_AWS_ENDPOINT_URL" "$ts_endpoint"
if [[ -z "$ts_key" ]] || [[ -z "$ts_secret" ]]; then
warn "LiveKit storage: S3 credentials not found — Track Egress recording will fail!"
warn "Configure LIVEKIT_STORAGE_AWS_ACCESS_KEY_ID and LIVEKIT_STORAGE_AWS_SECRET_ACCESS_KEY in server/.env"
warn "Or run with --garage to auto-configure local S3 storage"
else
ok "LiveKit storage: synced from transcript storage config"
fi
# Generate livekit.yaml
cat > "$ROOT_DIR/livekit.yaml" << LKEOF
port: 7880
rtc:
tcp_port: 7881
port_range_start: 44200
port_range_end: 44300
redis:
address: redis:6379
keys:
${lk_key}: ${lk_secret}
webhook:
urls:
- http://server:1250/v1/livekit/webhook
api_key: ${lk_key}
logging:
level: info
room:
empty_timeout: 300
max_participants: 0
LKEOF
ok "Generated livekit.yaml"
# Generate egress.yaml (Track Egress only — no composite video)
cat > "$ROOT_DIR/egress.yaml" << EGEOF
api_key: ${lk_key}
api_secret: ${lk_secret}
ws_url: ws://livekit-server:7880
redis:
address: redis:6379
health_port: 7082
log_level: info
session_limits:
file_output_max_duration: 4h
EGEOF
ok "Generated egress.yaml"
}
# =========================================================
# Step 0: Prerequisites
# =========================================================
@@ -737,13 +872,23 @@ step_server_env() {
fi
else
if [[ -n "$PRIMARY_IP" ]]; then
server_base_url="http://$PRIMARY_IP"
server_base_url="http://$PRIMARY_IP:1250"
else
server_base_url="http://localhost:1250"
fi
fi
env_set "$SERVER_ENV" "BASE_URL" "$server_base_url"
env_set "$SERVER_ENV" "CORS_ORIGIN" "$server_base_url"
# CORS: allow the frontend origin (port 3000, not the API port)
local cors_origin="${server_base_url}"
if [[ "$USE_CADDY" != "true" ]]; then
# Without Caddy, frontend is on port 3000, API on 1250
cors_origin="${server_base_url/:1250/:3000}"
# Safety: if substitution didn't change anything, construct explicitly
if [[ "$cors_origin" == "$server_base_url" ]] && [[ -n "$PRIMARY_IP" ]]; then
cors_origin="http://${PRIMARY_IP}:3000"
fi
fi
env_set "$SERVER_ENV" "CORS_ORIGIN" "$cors_origin"
# WebRTC: advertise host IP in ICE candidates so browsers can reach the server
if [[ -n "$PRIMARY_IP" ]]; then
@@ -951,8 +1096,21 @@ step_server_env() {
# Hatchet is always required (file, live, and multitrack pipelines all use it)
env_set "$SERVER_ENV" "HATCHET_CLIENT_SERVER_URL" "http://hatchet:8888"
env_set "$SERVER_ENV" "HATCHET_CLIENT_HOST_PORT" "hatchet:7077"
env_set "$SERVER_ENV" "HATCHET_CLIENT_TLS_STRATEGY" "none"
ok "Hatchet connectivity configured (workflow engine for processing pipelines)"
# BIND_HOST controls whether server/web ports are exposed on all interfaces
local root_env="$ROOT_DIR/.env"
touch "$root_env"
if [[ "$USE_CADDY" == "true" ]]; then
# With Caddy, services stay on localhost (Caddy is the public entry point)
env_set "$root_env" "BIND_HOST" "127.0.0.1"
elif [[ -n "$PRIMARY_IP" ]]; then
# Without Caddy + detected IP, expose on all interfaces for direct access
env_set "$root_env" "BIND_HOST" "0.0.0.0"
ok "BIND_HOST=0.0.0.0 (ports exposed for direct access)"
fi
ok "server/.env ready"
}
@@ -980,18 +1138,26 @@ step_www_env() {
base_url="https://localhost"
fi
else
# No Caddy — user's proxy handles SSL. Use http for now, they'll override.
# No Caddy — clients connect directly to services on their ports.
if [[ -n "$PRIMARY_IP" ]]; then
base_url="http://$PRIMARY_IP"
base_url="http://$PRIMARY_IP:3000"
else
base_url="http://localhost"
base_url="http://localhost:3000"
fi
fi
# API_URL: with Caddy, same origin (443 proxies both); without Caddy, API is on port 1250
local api_url="$base_url"
if [[ "$USE_CADDY" != "true" ]]; then
api_url="${base_url/:3000/:1250}"
# fallback if no port substitution happened (e.g. localhost without port)
[[ "$api_url" == "$base_url" ]] && api_url="${base_url}:1250"
fi
env_set "$WWW_ENV" "SITE_URL" "$base_url"
env_set "$WWW_ENV" "NEXTAUTH_URL" "$base_url"
env_set "$WWW_ENV" "NEXTAUTH_SECRET" "$NEXTAUTH_SECRET"
env_set "$WWW_ENV" "API_URL" "$base_url"
env_set "$WWW_ENV" "API_URL" "$api_url"
env_set "$WWW_ENV" "WEBSOCKET_URL" "auto"
env_set "$WWW_ENV" "SERVER_API_URL" "http://server:1250"
env_set "$WWW_ENV" "KV_URL" "redis://redis:6379"
@@ -1014,14 +1180,17 @@ step_www_env() {
fi
# Enable rooms if any video platform is configured in server/.env
local _daily_key="" _whereby_key=""
local _daily_key="" _whereby_key="" _livekit_key=""
if env_has_key "$SERVER_ENV" "DAILY_API_KEY"; then
_daily_key=$(env_get "$SERVER_ENV" "DAILY_API_KEY")
fi
if env_has_key "$SERVER_ENV" "WHEREBY_API_KEY"; then
_whereby_key=$(env_get "$SERVER_ENV" "WHEREBY_API_KEY")
fi
if [[ -n "$_daily_key" ]] || [[ -n "$_whereby_key" ]]; then
if env_has_key "$SERVER_ENV" "LIVEKIT_API_KEY"; then
_livekit_key=$(env_get "$SERVER_ENV" "LIVEKIT_API_KEY")
fi
if [[ -n "$_daily_key" ]] || [[ -n "$_whereby_key" ]] || [[ -n "$_livekit_key" ]]; then
env_set "$WWW_ENV" "FEATURE_ROOMS" "true"
ok "Rooms feature enabled (video platform configured)"
fi
@@ -1110,7 +1279,13 @@ step_garage() {
# Write S3 credentials to server/.env
env_set "$SERVER_ENV" "TRANSCRIPT_STORAGE_BACKEND" "aws"
env_set "$SERVER_ENV" "TRANSCRIPT_STORAGE_AWS_ENDPOINT_URL" "http://garage:3900"
# Endpoint URL: use public IP when no Caddy so presigned URLs work in the browser.
# With Caddy, internal hostname is fine (Caddy proxies or browser never sees presigned URLs directly).
if [[ "$USE_CADDY" != "true" ]] && [[ -n "$PRIMARY_IP" ]]; then
env_set "$SERVER_ENV" "TRANSCRIPT_STORAGE_AWS_ENDPOINT_URL" "http://${PRIMARY_IP}:3900"
else
env_set "$SERVER_ENV" "TRANSCRIPT_STORAGE_AWS_ENDPOINT_URL" "http://garage:3900"
fi
env_set "$SERVER_ENV" "TRANSCRIPT_STORAGE_AWS_BUCKET_NAME" "reflector-media"
env_set "$SERVER_ENV" "TRANSCRIPT_STORAGE_AWS_REGION" "garage"
if [[ "$created_key" == "true" ]]; then
@@ -1188,6 +1363,22 @@ step_caddyfile() {
rm -rf "$caddyfile"
fi
# LiveKit reverse proxy snippet (inserted into Caddyfile when --livekit is active)
# LiveKit reverse proxy snippet (inserted into Caddyfile when --livekit is active).
# Strips /lk-ws prefix so LiveKit server sees requests at its root /.
local lk_proxy_block=""
if [[ "$LIVEKIT_DETECTED" == "true" ]]; then
lk_proxy_block="
handle_path /lk-ws/* {
reverse_proxy livekit-server:7880
}
handle_path /lk-ws {
reverse_proxy livekit-server:7880
}"
fi
local hatchet_proxy_block=""
if [[ -n "$TLS_CERT_PATH" ]] && [[ -n "$CUSTOM_DOMAIN" ]]; then
# Custom domain with user-provided TLS certificate (from --custom-ca directory)
cat > "$caddyfile" << CADDYEOF
@@ -1199,7 +1390,7 @@ $CUSTOM_DOMAIN {
}
handle /health {
reverse_proxy server:1250
}
}${lk_proxy_block}${hatchet_proxy_block}
handle {
reverse_proxy web:3000
}
@@ -1216,7 +1407,7 @@ $CUSTOM_DOMAIN {
}
handle /health {
reverse_proxy server:1250
}
}${lk_proxy_block}${hatchet_proxy_block}
handle {
reverse_proxy web:3000
}
@@ -1225,17 +1416,19 @@ CADDYEOF
ok "Created Caddyfile for $CUSTOM_DOMAIN (Let's Encrypt auto-HTTPS)"
elif [[ -n "$PRIMARY_IP" ]]; then
# No domain, IP only: catch-all :443 with self-signed cert
# (IP connections don't send SNI, so we can't match by address)
# on_demand generates certs dynamically for any hostname/IP on first request
cat > "$caddyfile" << CADDYEOF
# Generated by setup-selfhosted.sh — self-signed cert for IP access
:443 {
tls internal
tls internal {
on_demand
}
handle /v1/* {
reverse_proxy server:1250
}
handle /health {
reverse_proxy server:1250
}
}${lk_proxy_block}${hatchet_proxy_block}
handle {
reverse_proxy web:3000
}
@@ -1249,21 +1442,8 @@ CADDYEOF
ok "Caddyfile already exists"
fi
# Add Hatchet dashboard route if Daily.co is detected
if [[ "$DAILY_DETECTED" == "true" ]]; then
if ! grep -q "hatchet" "$caddyfile" 2>/dev/null; then
cat >> "$caddyfile" << CADDYEOF
# Hatchet workflow dashboard (Daily.co multitrack processing)
:8888 {
tls internal
reverse_proxy hatchet:8888
}
CADDYEOF
ok "Added Hatchet dashboard route to Caddyfile (port 8888)"
else
ok "Hatchet dashboard route already in Caddyfile"
fi
if [[ "$DAILY_DETECTED" == "true" ]] || [[ "$LIVEKIT_DETECTED" == "true" ]]; then
ok "Hatchet dashboard available at port 8888"
fi
}
@@ -1467,7 +1647,7 @@ step_health() {
info "Waiting for Hatchet workflow engine..."
local hatchet_ok=false
for i in $(seq 1 60); do
if curl -sf http://localhost:8888/api/live > /dev/null 2>&1; then
if compose_cmd exec -T hatchet curl -sf http://localhost:8888/api/live > /dev/null 2>&1; then
hatchet_ok=true
break
fi
@@ -1515,7 +1695,7 @@ step_hatchet_token() {
# Wait for hatchet to be healthy
local hatchet_ok=false
for i in $(seq 1 60); do
if curl -sf http://localhost:8888/api/live > /dev/null 2>&1; then
if compose_cmd exec -T hatchet curl -sf http://localhost:8888/api/live > /dev/null 2>&1; then
hatchet_ok=true
break
fi
@@ -1586,12 +1766,19 @@ main() {
[[ "$BUILD_IMAGES" == "true" ]] && echo " Build: from source"
echo ""
# Detect primary IP
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)
# Detect primary IP (--ip overrides auto-detection)
if [[ -n "$CUSTOM_IP" ]]; then
PRIMARY_IP="$CUSTOM_IP"
ok "Using provided IP: $PRIMARY_IP"
else
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
elif [[ "$OS" == "Darwin" ]]; then
PRIMARY_IP=$(detect_lan_ip)
fi
fi
@@ -1621,14 +1808,21 @@ main() {
# Auto-detect video platforms from server/.env (after step_server_env so file exists)
DAILY_DETECTED=false
WHEREBY_DETECTED=false
LIVEKIT_DETECTED=false
if env_has_key "$SERVER_ENV" "DAILY_API_KEY" && [[ -n "$(env_get "$SERVER_ENV" "DAILY_API_KEY")" ]]; then
DAILY_DETECTED=true
fi
if env_has_key "$SERVER_ENV" "WHEREBY_API_KEY" && [[ -n "$(env_get "$SERVER_ENV" "WHEREBY_API_KEY")" ]]; then
WHEREBY_DETECTED=true
fi
# LiveKit: enabled via --livekit flag OR pre-existing LIVEKIT_API_KEY in env
if [[ "$USE_LIVEKIT" == "true" ]]; then
LIVEKIT_DETECTED=true
elif env_has_key "$SERVER_ENV" "LIVEKIT_API_KEY" && [[ -n "$(env_get "$SERVER_ENV" "LIVEKIT_API_KEY")" ]]; then
LIVEKIT_DETECTED=true
fi
ANY_PLATFORM_DETECTED=false
[[ "$DAILY_DETECTED" == "true" || "$WHEREBY_DETECTED" == "true" ]] && ANY_PLATFORM_DETECTED=true
[[ "$DAILY_DETECTED" == "true" || "$WHEREBY_DETECTED" == "true" || "$LIVEKIT_DETECTED" == "true" ]] && ANY_PLATFORM_DETECTED=true
# Conditional profile activation for Daily.co
if [[ "$DAILY_DETECTED" == "true" ]]; then
@@ -1636,6 +1830,13 @@ main() {
ok "Daily.co detected — enabling Hatchet workflow services"
fi
# Conditional profile activation for LiveKit
if [[ "$LIVEKIT_DETECTED" == "true" ]]; then
COMPOSE_PROFILES+=("livekit")
_generate_livekit_config
ok "LiveKit enabled — livekit-server + livekit-egress"
fi
# Generate .env.hatchet for hatchet dashboard config (always needed)
local hatchet_server_url hatchet_cookie_domain
if [[ -n "$CUSTOM_DOMAIN" ]]; then
@@ -1683,10 +1884,12 @@ EOF
echo " App: https://localhost (accept self-signed cert in browser)"
echo " API: https://localhost/v1/"
fi
elif [[ -n "$PRIMARY_IP" ]]; then
echo " App: http://$PRIMARY_IP:3000"
echo " API: http://$PRIMARY_IP:1250"
else
echo " No Caddy — point your reverse proxy at:"
echo " Frontend: web:3000 (or localhost:3000 from host)"
echo " API: server:1250 (or localhost:1250 from host)"
echo " App: http://localhost:3000"
echo " API: http://localhost:1250"
fi
echo ""
if [[ "$HAS_OVERRIDES" == "true" ]]; then
@@ -1702,6 +1905,7 @@ EOF
[[ "$USES_OLLAMA" != "true" ]] && echo " LLM: External (configure in server/.env)"
[[ "$DAILY_DETECTED" == "true" ]] && echo " Video: Daily.co (live rooms + multitrack processing via Hatchet)"
[[ "$WHEREBY_DETECTED" == "true" ]] && echo " Video: Whereby (live rooms)"
[[ "$LIVEKIT_DETECTED" == "true" ]] && echo " Video: LiveKit (self-hosted, live rooms + track egress)"
[[ "$ANY_PLATFORM_DETECTED" != "true" ]] && echo " Video: None (rooms disabled)"
if [[ "$USE_CUSTOM_CA" == "true" ]]; then
echo " CA: Custom (certs/ca.crt)"