Clawith/setup.sh

472 lines
21 KiB
Bash

#!/usr/bin/env bash
# ────────────────────────────────────────────────
# Clawith — First-time Setup Script
# Sets up backend, frontend, database, and seed data.
# ────────────────────────────────────────────────
set -e
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; CYAN='\033[0;36m'; NC='\033[0m'
ROOT="$(cd "$(dirname "$0")" && pwd)"
# Parse arguments
INSTALL_DEV=false
for arg in "$@"; do
case $arg in
--dev) INSTALL_DEV=true ;;
esac
done
# --- Helper: detect server IP ---
get_server_ip() {
# Try hostname -I (Linux), then ifconfig (macOS), then fallback
local ip
ip=$(hostname -I 2>/dev/null | awk '{print $1}')
[ -z "$ip" ] && ip=$(ifconfig 2>/dev/null | grep 'inet ' | grep -v '127.0.0.1' | head -1 | awk '{print $2}')
[ -z "$ip" ] && ip="<your-server-ip>"
echo "$ip"
}
# --- Check Python version (>= 3.12 required) ---
PYTHON_BIN="${PYTHON_BIN:-python3}"
if command -v "$PYTHON_BIN" &>/dev/null; then
PY_VER=$("$PYTHON_BIN" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
PY_MAJOR=$(echo "$PY_VER" | cut -d. -f1)
PY_MINOR=$(echo "$PY_VER" | cut -d. -f2)
if [ "$PY_MAJOR" -lt 3 ] || ([ "$PY_MAJOR" -eq 3 ] && [ "$PY_MINOR" -lt 12 ]); then
echo -e "${RED}Python $PY_VER detected, but Clawith requires Python >= 3.12.${NC}"
echo ""
echo " Please install Python 3.12+:"
echo " Ubuntu: sudo apt install python3.12 python3.12-venv"
echo " CentOS: sudo dnf install python3.12"
echo " macOS: brew install python@3.12"
echo " Conda: conda create -n clawith python=3.12"
echo ""
echo " Or set PYTHON_BIN to point to a valid python3.12+ binary:"
echo " PYTHON_BIN=/path/to/python3.12 bash setup.sh"
exit 1
fi
fi
# --- Optional package mirror overrides ---
PIP_INSTALL_ARGS=()
if [ -n "${CLAWITH_PIP_INDEX_URL:-}" ]; then
PIP_INSTALL_ARGS+=(--index-url "$CLAWITH_PIP_INDEX_URL")
fi
if [ -n "${CLAWITH_PIP_TRUSTED_HOST:-}" ]; then
PIP_INSTALL_ARGS+=(--trusted-host "$CLAWITH_PIP_TRUSTED_HOST")
fi
NPM_MIRROR="--registry https://registry.npmmirror.com"
echo ""
echo -e "${CYAN}═══════════════════════════════════════${NC}"
echo -e "${CYAN} 🦞 Clawith — First-time Setup${NC}"
echo -e "${CYAN}═══════════════════════════════════════${NC}"
echo ""
# ── 1. Environment file ──────────────────────────
echo -e "${YELLOW}[1/6]${NC} Checking environment file..."
if [ ! -f "$ROOT/.env" ]; then
cp "$ROOT/.env.example" "$ROOT/.env"
echo -e " ${GREEN}${NC} Created .env from .env.example"
echo -e " ${YELLOW}${NC} Please edit .env to set SECRET_KEY and JWT_SECRET_KEY before production use."
else
echo -e " ${GREEN}${NC} .env already exists"
fi
# ── 2. PostgreSQL setup ──────────────────────────
echo ""
echo -e "${YELLOW}[2/6]${NC} Setting up PostgreSQL..."
# --- Helper: find psql binary ---
find_psql() {
# Check PATH first
if command -v psql &>/dev/null; then
command -v psql
return 0
fi
# Search common non-standard locations
local search_paths=(
"/www/server/pgsql/bin"
"/usr/local/pgsql/bin"
"/usr/lib/postgresql/15/bin"
"/usr/lib/postgresql/14/bin"
"/usr/lib/postgresql/16/bin"
"/opt/homebrew/opt/postgresql@15/bin"
"/opt/homebrew/opt/postgresql/bin"
)
for dir in "${search_paths[@]}"; do
if [ -x "$dir/psql" ]; then
echo "$dir"
return 0
fi
done
return 1
}
# --- Helper: find a free port starting from $1 ---
find_free_port() {
local port=$1
while ss -tlnp 2>/dev/null | grep -q ":${port} " || \
lsof -iTCP:${port} -sTCP:LISTEN 2>/dev/null | grep -q LISTEN; do
echo -e " ${YELLOW}${NC} Port $port is in use, trying $((port+1))..."
port=$((port+1))
done
echo "$port"
}
PG_PORT=5432
PG_MANAGED_BY_US=false
if PG_BIN_DIR=$(find_psql 2>/dev/null); then
# If find_psql returned a directory (not a full path), add to PATH
if [ -d "$PG_BIN_DIR" ]; then
export PATH="$PG_BIN_DIR:$PATH"
fi
echo -e " ${GREEN}${NC} Found psql: $(which psql)"
# Check if PG is running and we can connect
if pg_isready -h localhost -p 5432 -q 2>/dev/null; then
echo -e " ${GREEN}${NC} PostgreSQL is running on port 5432"
PG_PORT=5432
# Try to create role and database
ROLE_EXISTS=false
if psql -h localhost -p $PG_PORT -U "$USER" -d postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='clawith'" 2>/dev/null | grep -q 1; then
ROLE_EXISTS=true
echo -e " ${GREEN}${NC} Role 'clawith' already exists"
elif sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='clawith'" 2>/dev/null | grep -q 1; then
ROLE_EXISTS=true
echo -e " ${GREEN}${NC} Role 'clawith' already exists"
fi
if [ "$ROLE_EXISTS" = false ]; then
# Try 1: as current user
if createuser -h localhost -p $PG_PORT clawith 2>/dev/null; then
psql -h localhost -p $PG_PORT -U "$USER" -d postgres -c "ALTER ROLE clawith WITH LOGIN PASSWORD 'clawith';" &>/dev/null
echo -e " ${GREEN}${NC} Created PostgreSQL role: clawith"
# Try 2: via sudo -u postgres (standard Linux setup)
elif sudo -u postgres createuser clawith 2>/dev/null && \
sudo -u postgres psql -c "ALTER ROLE clawith WITH LOGIN PASSWORD 'clawith';" &>/dev/null; then
echo -e " ${GREEN}${NC} Created PostgreSQL role: clawith (via sudo)"
else
echo -e " ${YELLOW}${NC} Could not create role in existing PG — will init a local instance"
PG_BIN_DIR="" # Force local PG setup below
fi
fi
if [ -n "$PG_BIN_DIR" ] || command -v psql &>/dev/null; then
DB_EXISTS=false
if psql -h localhost -p $PG_PORT -U "$USER" -lqt 2>/dev/null | cut -d\| -f1 | grep -qw clawith; then
DB_EXISTS=true
elif sudo -u postgres psql -lqt 2>/dev/null | cut -d\| -f1 | grep -qw clawith; then
DB_EXISTS=true
fi
if [ "$DB_EXISTS" = true ]; then
echo -e " ${GREEN}${NC} Database 'clawith' already exists"
else
if createdb -h localhost -p $PG_PORT -O clawith clawith 2>/dev/null || \
sudo -u postgres createdb -O clawith clawith 2>/dev/null; then
echo -e " ${GREEN}${NC} Created database: clawith"
fi
fi
fi
else
echo -e " ${YELLOW}${NC} PostgreSQL binaries found but service is not running on port 5432"
echo " Will set up a local instance..."
PG_BIN_DIR="" # Force local PG setup below
fi
fi
# --- Local PG instance: install + initdb if needed ---
if [ -z "$PG_BIN_DIR" ] && ! (PGPASSWORD=clawith psql -h localhost -p 5432 -U clawith -d clawith -c "SELECT 1" &>/dev/null); then
echo -e " ${CYAN}${NC} No usable PostgreSQL found — setting up a local instance..."
PG_MANAGED_BY_US=true
PGDATA="$ROOT/.pgdata"
PG_LOCAL="$ROOT/.pg"
# Strategy 1: Install via system package manager (most reliable)
if [ ! -x "$PG_LOCAL/bin/psql" ]; then
INSTALLED_VIA_PKG=false
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
if [ "$OS" = "darwin" ]; then
if command -v brew &>/dev/null; then
echo " Installing PostgreSQL via Homebrew..."
brew install postgresql@15 2>/dev/null && brew services start postgresql@15 2>/dev/null && INSTALLED_VIA_PKG=true
else
echo -e " ${YELLOW}${NC} On macOS, please install Homebrew first, then:"
echo " brew install postgresql@15 && brew services start postgresql@15"
echo " Then re-run: bash setup.sh"
exit 1
fi
elif [ "$OS" = "linux" ]; then
# Check if sudo is available
CAN_SUDO=false
if command -v sudo &>/dev/null; then
if sudo -n true 2>/dev/null || (echo "" | sudo -S true 2>/dev/null); then
CAN_SUDO=true
fi
fi
if [ "$CAN_SUDO" = true ]; then
if command -v apt-get &>/dev/null; then
echo " Installing PostgreSQL via apt..."
sudo apt-get update -qq 2>/dev/null
sudo apt-get install -y -qq postgresql postgresql-client 2>/dev/null && INSTALLED_VIA_PKG=true
elif command -v yum &>/dev/null; then
echo " Installing PostgreSQL via yum..."
sudo yum install -y -q postgresql-server postgresql 2>/dev/null && \
sudo postgresql-setup --initdb 2>/dev/null; \
sudo systemctl start postgresql 2>/dev/null && INSTALLED_VIA_PKG=true
elif command -v dnf &>/dev/null; then
echo " Installing PostgreSQL via dnf..."
sudo dnf install -y -q postgresql-server postgresql 2>/dev/null && \
sudo postgresql-setup --initdb 2>/dev/null; \
sudo systemctl start postgresql 2>/dev/null && INSTALLED_VIA_PKG=true
fi
fi
fi
if [ "$INSTALLED_VIA_PKG" = true ]; then
echo -e " ${GREEN}${NC} PostgreSQL installed via package manager"
# Re-find psql after install
if PG_BIN_DIR=$(find_psql 2>/dev/null); then
if [ -d "$PG_BIN_DIR" ]; then
export PATH="$PG_BIN_DIR:$PATH"
fi
fi
# Wait for PG to be ready
for i in $(seq 1 10); do
if pg_isready -h localhost -p 5432 -q 2>/dev/null; then
PG_PORT=5432
break
fi
sleep 1
done
# Create role and database
if command -v psql &>/dev/null; then
if ! psql -h localhost -p $PG_PORT -U postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='clawith'" 2>/dev/null | grep -q 1; then
sudo -u postgres createuser clawith 2>/dev/null || createuser -h localhost -p $PG_PORT clawith 2>/dev/null || true
sudo -u postgres psql -c "ALTER ROLE clawith WITH LOGIN PASSWORD 'clawith';" 2>/dev/null || \
psql -h localhost -p $PG_PORT -U postgres -c "ALTER ROLE clawith WITH LOGIN PASSWORD 'clawith';" 2>/dev/null || true
echo -e " ${GREEN}${NC} Created role: clawith"
fi
if ! psql -h localhost -p $PG_PORT -U postgres -lqt 2>/dev/null | cut -d\| -f1 | grep -qw clawith; then
sudo -u postgres createdb -O clawith clawith 2>/dev/null || createdb -h localhost -p $PG_PORT -O clawith clawith 2>/dev/null || true
echo -e " ${GREEN}${NC} Created database: clawith"
fi
PG_MANAGED_BY_US=false # System manages PG now
fi
fi
fi
# Strategy 2: Use system PG binaries from non-standard paths for user-space initdb
if [ "$INSTALLED_VIA_PKG" != true ] || [ "$PG_MANAGED_BY_US" = true ]; then
if [ ! -x "$PG_LOCAL/bin/initdb" ]; then
# Try to link from common system paths
for dir in /www/server/pgsql /usr/local/pgsql /usr/lib/postgresql/15 /usr/lib/postgresql/14 /usr/lib/postgresql/16; do
if [ -x "$dir/bin/initdb" ]; then
mkdir -p "$PG_LOCAL"
ln -sf "$dir/bin" "$PG_LOCAL/bin" 2>/dev/null || cp -r "$dir/bin" "$PG_LOCAL/bin" 2>/dev/null
if [ -d "$dir/lib" ]; then
ln -sf "$dir/lib" "$PG_LOCAL/lib" 2>/dev/null || cp -r "$dir/lib" "$PG_LOCAL/lib" 2>/dev/null
fi
if [ -d "$dir/share" ]; then
ln -sf "$dir/share" "$PG_LOCAL/share" 2>/dev/null || cp -r "$dir/share" "$PG_LOCAL/share" 2>/dev/null
fi
echo -e " ${GREEN}${NC} Found system PG binaries at $dir"
break
fi
done
fi
if [ -x "$PG_LOCAL/bin/initdb" ]; then
export PATH="$PG_LOCAL/bin:$PATH"
export LD_LIBRARY_PATH="$PG_LOCAL/lib:${LD_LIBRARY_PATH:-}"
# Find a free port
PG_PORT=$(find_free_port 5432)
# Initialize data directory
if [ ! -f "$PGDATA/PG_VERSION" ]; then
echo " Initializing database cluster..."
initdb -D "$PGDATA" -U postgres --auth=trust -E UTF8 --locale=C >/dev/null 2>&1
# Configure port (handle both GNU and BSD sed)
sed -i "s/#port = 5432/port = $PG_PORT/" "$PGDATA/postgresql.conf" 2>/dev/null || \
sed -i '' "s/#port = 5432/port = $PG_PORT/" "$PGDATA/postgresql.conf" 2>/dev/null
sed -i "s/#listen_addresses = 'localhost'/listen_addresses = 'localhost'/" "$PGDATA/postgresql.conf" 2>/dev/null || \
sed -i '' "s/#listen_addresses = 'localhost'/listen_addresses = 'localhost'/" "$PGDATA/postgresql.conf" 2>/dev/null
echo -e " ${GREEN}${NC} Database cluster initialized (port $PG_PORT)"
else
# Read configured port from existing cluster
PG_PORT=$(grep "^port = " "$PGDATA/postgresql.conf" 2>/dev/null | awk '{print $3}')
PG_PORT=${PG_PORT:-5432}
echo -e " ${GREEN}${NC} Existing data directory found (port $PG_PORT)"
fi
# Start PostgreSQL
if ! pg_isready -h localhost -p "$PG_PORT" -q 2>/dev/null; then
pg_ctl -D "$PGDATA" -l "$PGDATA/pg.log" start >/dev/null 2>&1
sleep 2
if pg_isready -h localhost -p "$PG_PORT" -q 2>/dev/null; then
echo -e " ${GREEN}${NC} PostgreSQL started on port $PG_PORT"
else
echo -e " ${RED}${NC} Failed to start PostgreSQL. Check $PGDATA/pg.log"
exit 1
fi
else
echo -e " ${GREEN}${NC} PostgreSQL already running on port $PG_PORT"
fi
# Create role and database
if ! psql -h localhost -p "$PG_PORT" -U postgres -tAc "SELECT 1 FROM pg_roles WHERE rolname='clawith'" 2>/dev/null | grep -q 1; then
createuser -h localhost -p "$PG_PORT" -U postgres clawith 2>/dev/null || true
psql -h localhost -p "$PG_PORT" -U postgres -c "ALTER ROLE clawith WITH LOGIN PASSWORD 'clawith';" &>/dev/null
echo -e " ${GREEN}${NC} Created role: clawith"
fi
if ! psql -h localhost -p "$PG_PORT" -U postgres -lqt 2>/dev/null | cut -d\| -f1 | grep -qw clawith; then
createdb -h localhost -p "$PG_PORT" -U postgres -O clawith clawith 2>/dev/null
echo -e " ${GREEN}${NC} Created database: clawith"
fi
else
echo -e " ${RED}${NC} Could not set up PostgreSQL automatically."
echo ""
echo " Please install PostgreSQL manually:"
echo ""
echo " Ubuntu/Debian: sudo apt install postgresql"
echo " CentOS/RHEL: sudo yum install postgresql-server"
echo " macOS: brew install postgresql@15"
echo ""
echo " Then re-run: bash setup.sh"
exit 1
fi
fi
fi
# Ensure DATABASE_URL is correct in .env
DB_URL="postgresql+asyncpg://clawith:clawith@localhost:${PG_PORT}/clawith?ssl=disable"
if grep -q "^DATABASE_URL=" "$ROOT/.env" 2>/dev/null; then
# Update existing DATABASE_URL
sed -i "s|^DATABASE_URL=.*|DATABASE_URL=${DB_URL}|" "$ROOT/.env" 2>/dev/null || \
sed -i '' "s|^DATABASE_URL=.*|DATABASE_URL=${DB_URL}|" "$ROOT/.env" 2>/dev/null
elif grep -q "^# DATABASE_URL=" "$ROOT/.env" 2>/dev/null; then
# Uncomment and set
sed -i "s|^# DATABASE_URL=.*|DATABASE_URL=${DB_URL}|" "$ROOT/.env" 2>/dev/null || \
sed -i '' "s|^# DATABASE_URL=.*|DATABASE_URL=${DB_URL}|" "$ROOT/.env" 2>/dev/null
else
echo "DATABASE_URL=${DB_URL}" >> "$ROOT/.env"
fi
echo -e " ${GREEN}${NC} DATABASE_URL set (port $PG_PORT)"
# ── 3. Backend setup ─────────────────────────────
echo ""
echo -e "${YELLOW}[3/6]${NC} Setting up backend..."
cd "$ROOT/backend"
if [ ! -d ".venv" ]; then
echo " Creating Python virtual environment..."
$PYTHON_BIN -m venv .venv
echo -e " ${GREEN}${NC} Virtual environment created"
fi
if [ "$INSTALL_DEV" = true ]; then
PIP_TARGET=".[dev]"
echo " Installing dependencies with dev extras (this may take 2-5 minutes)..."
else
PIP_TARGET="."
echo " Installing dependencies (this may take 1-2 minutes)..."
fi
if .venv/bin/pip install -e "$PIP_TARGET" "${PIP_INSTALL_ARGS[@]}" 2>&1; then
echo -e " ${GREEN}${NC} Backend dependencies installed"
else
echo -e " ${RED}${NC} Failed to install backend dependencies."
echo " Try manually: cd backend && .venv/bin/pip install -e '$PIP_TARGET'"
exit 1
fi
# ── 4. Frontend setup ────────────────────────────
echo ""
echo -e "${YELLOW}[4/6]${NC} Setting up frontend..."
cd "$ROOT/frontend"
if [ ! -d "node_modules" ]; then
if ! command -v npm &>/dev/null; then
echo -e " ${YELLOW}${NC} npm not found. Skipping frontend dependency install."
echo -e " ${YELLOW}${NC} Install Node.js 20+ to enable frontend dev server."
echo -e " ${YELLOW}${NC} You can still use pre-built dist/ or Docker for frontend."
else
echo " Installing npm packages..."
npm install --silent $NPM_MIRROR 2>&1 | tail -1
echo -e " ${GREEN}${NC} Frontend dependencies installed"
fi
else
echo -e " ${GREEN}${NC} Frontend dependencies already installed"
fi
# ── 5. Database setup ────────────────────────────
echo ""
echo -e "${YELLOW}[5/6]${NC} Setting up database..."
cd "$ROOT/backend"
# Source .env for DATABASE_URL
if [ -f "$ROOT/.env" ]; then
set -a
source "$ROOT/.env"
set +a
fi
# ── 6. Seed data ─────────────────────────────────
echo ""
echo -e "${YELLOW}[6/6]${NC} Running database seed..."
if .venv/bin/python seed.py 2>&1 | while IFS= read -r line; do echo " $line"; done; then
echo ""
else
echo ""
echo -e " ${RED}✗ Seed failed.${NC}"
echo " Common fixes:"
echo " 1. Make sure PostgreSQL is running"
echo " 2. Set DATABASE_URL in .env, e.g.:"
echo " DATABASE_URL=postgresql+asyncpg://clawith:clawith@localhost:5432/clawith?ssl=disable"
echo " 3. Create the database first:"
echo " createdb clawith"
echo " 4. If you see 'Ident authentication failed', configure pg_hba.conf:"
echo " Add this line BEFORE other host rules:"
echo " host all clawith 127.0.0.1/32 md5"
echo " Then reload: sudo systemctl reload postgresql"
echo ""
echo " After fixing, re-run: bash setup.sh"
exit 1
fi
# ── Summary ──────────────────────────────────────
SERVER_IP=$(get_server_ip)
echo ""
echo -e "${GREEN}═══════════════════════════════════════${NC}"
echo -e "${GREEN} 🎉 Setup complete!${NC}"
echo -e "${GREEN}═══════════════════════════════════════${NC}"
echo ""
echo " To start the application:"
echo ""
echo -e " ${CYAN}Option A: One-command start${NC}"
echo " bash restart.sh"
echo ""
echo -e " ${CYAN}Option B: Manual start${NC}"
echo " # Terminal 1 — Backend"
echo " cd backend && .venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8008"
echo ""
echo " # Terminal 2 — Frontend"
echo " cd frontend && npx vite --host 0.0.0.0 --port 3008"
echo ""
echo -e " ${CYAN}Option C: Docker${NC}"
echo " docker compose up -d"
echo ""
echo -e " ${CYAN}Access URLs:${NC}"
echo " Local: http://localhost:3008"
echo " Network: http://${SERVER_IP}:3008"
echo ""
echo " The first user to register becomes the platform admin."
echo ""