mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2026-03-21 22:56:47 +00:00
fix: standalone on ubuntu (#865)
* Standalone on ubuntu * fix: use port 3043 for Caddy, disable rooms, remove dead Caddyfile - Caddy mapped to host port 3043 instead of 80/443 to avoid conflicts - FEATURE_ROOMS=false in standalone web service - Removed scripts/standalone/Caddyfile (dead code on this branch) - Updated all URLs, port checks, docs to reference :3043 --------- Co-authored-by: Igor Loskutov <igor.loskutoff@gmail.com>
This commit is contained in:
87
scripts/install-docker-ubuntu.sh
Executable file
87
scripts/install-docker-ubuntu.sh
Executable file
@@ -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 <<EOF
|
||||
Types: deb
|
||||
URIs: https://download.docker.com/linux/ubuntu
|
||||
Suites: ${CODENAME}
|
||||
Components: stable
|
||||
Signed-By: /etc/apt/keyrings/docker.asc
|
||||
EOF
|
||||
|
||||
info "Installing Docker Engine and Compose plugin..."
|
||||
${MAYBE_SUDO}apt update
|
||||
${MAYBE_SUDO}apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
|
||||
if [[ -d /run/systemd/system ]]; then
|
||||
info "Enabling and starting Docker..."
|
||||
${MAYBE_SUDO}systemctl enable --now docker
|
||||
else
|
||||
err "No systemd. This script requires Ubuntu with systemd (e.g. DigitalOcean droplet)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DOCKER_USER="${SUDO_USER:-${USER:-root}}"
|
||||
if [[ "$DOCKER_USER" != "root" ]]; then
|
||||
info "Adding $DOCKER_USER to docker group..."
|
||||
${MAYBE_SUDO}usermod -aG docker "$DOCKER_USER"
|
||||
fi
|
||||
|
||||
ok "Docker installed successfully."
|
||||
echo ""
|
||||
echo " Log out and back in (or run: newgrp docker) so the group change takes effect."
|
||||
echo " Then verify with: docker compose version"
|
||||
echo ""
|
||||
@@ -308,15 +308,24 @@ step_storage() {
|
||||
# Generate garage.toml from template (fill in RPC secret)
|
||||
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
|
||||
mkdir -p "$ROOT_DIR/data"
|
||||
RPC_SECRET=$(openssl rand -hex 32)
|
||||
sed "s|__GARAGE_RPC_SECRET__|${RPC_SECRET}|" "$GARAGE_TOML" > "$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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user