#!/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="" 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 ""