- Added new entries to .gitignore for environment files and dynamically generated Docker Compose files. - Updated documentation to reflect the new path for the Vibe Kanban dev server script. - Enhanced task composition scripts to extract TASK_SLOT from TASK_ID, ensuring unique Orleans ports and preventing conflicts. - Removed the old vibe-dev-server script, consolidating functionality into the new structure.
313 lines
12 KiB
Bash
Executable File
313 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
# scripts/vibe-kanban/vibe-setup.sh
|
|
# Setup script for Vibe Kanban - sets up database and Docker services
|
|
# This script runs in the "setup" section of Vibe Kanban
|
|
# Usage: bash scripts/vibe-kanban/vibe-setup.sh [TASK_ID] [PORT_OFFSET]
|
|
# TASK_ID can also come from environment variables or worktree path
|
|
|
|
PORT_OFFSET=${2:-0}
|
|
|
|
# Detect worktree root
|
|
WORKTREE_ROOT="$(pwd)"
|
|
|
|
# Check if we're in a nested structure (Vibe Kanban worktree)
|
|
if [ -d "$WORKTREE_ROOT/managing-apps" ] && [ -d "$WORKTREE_ROOT/managing-apps/src/Managing.Api" ]; then
|
|
WORKTREE_PROJECT_ROOT="$WORKTREE_ROOT/managing-apps"
|
|
elif [ -d "$WORKTREE_ROOT/src/Managing.Api" ]; then
|
|
WORKTREE_PROJECT_ROOT="$WORKTREE_ROOT"
|
|
else
|
|
echo "❌ Cannot find project structure in worktree"
|
|
echo " Current directory: $WORKTREE_ROOT"
|
|
exit 1
|
|
fi
|
|
|
|
echo "📁 Worktree project root: $WORKTREE_PROJECT_ROOT"
|
|
|
|
# TASK_ID file to ensure consistency across runs
|
|
TASK_ID_FILE="$WORKTREE_PROJECT_ROOT/.vibe-task-id"
|
|
|
|
# Try to get TASK_ID from various sources
|
|
TASK_ID=$1
|
|
|
|
# First, check if we have a stored TASK_ID for this worktree (ensures consistency)
|
|
if [ -z "$TASK_ID" ] && [ -f "$TASK_ID_FILE" ]; then
|
|
TASK_ID=$(cat "$TASK_ID_FILE" 2>/dev/null | tr -d '[:space:]')
|
|
if [ -n "$TASK_ID" ]; then
|
|
echo "📋 Using stored TASK_ID from previous setup: $TASK_ID"
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$TASK_ID" ]; then
|
|
# Try environment variables (Vibe Kanban might set these)
|
|
if [ -n "$VIBE_TASK_ID" ]; then
|
|
TASK_ID="$VIBE_TASK_ID"
|
|
echo "📋 Found TASK_ID from VIBE_TASK_ID: $TASK_ID"
|
|
elif [ -n "$VIBE_TASK_NAME" ]; then
|
|
TASK_ID="$VIBE_TASK_NAME"
|
|
echo "📋 Found TASK_ID from VIBE_TASK_NAME: $TASK_ID"
|
|
elif [ -n "$TASK_ID_ENV" ]; then
|
|
TASK_ID="$TASK_ID_ENV"
|
|
echo "📋 Found TASK_ID from TASK_ID_ENV: $TASK_ID"
|
|
elif [ -n "$TASK" ]; then
|
|
TASK_ID="$TASK"
|
|
echo "📋 Found TASK_ID from TASK: $TASK_ID"
|
|
fi
|
|
fi
|
|
|
|
# Try to extract from worktree path (Vibe Kanban worktrees often contain task ID/name)
|
|
if [ -z "$TASK_ID" ]; then
|
|
# Extract task ID from worktree path (e.g., /path/to/worktrees/TASK-123/... or /path/to/worktrees/ticket-name/...)
|
|
# Try UUID format first (Vibe Kanban might use UUIDs)
|
|
DETECTED_TASK=$(echo "$WORKTREE_ROOT" | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' | head -1)
|
|
|
|
# If no UUID, try task ID pattern (e.g., DEV-123, TASK-456)
|
|
if [ -z "$DETECTED_TASK" ]; then
|
|
DETECTED_TASK=$(echo "$WORKTREE_ROOT" | grep -oE '[A-Z]+-[0-9]+' | head -1)
|
|
fi
|
|
|
|
# If still no match, try to get the last directory name (might be task name)
|
|
if [ -z "$DETECTED_TASK" ]; then
|
|
LAST_DIR=$(basename "$WORKTREE_ROOT")
|
|
# Skip common directory names
|
|
if [ "$LAST_DIR" != "managing-apps" ] && [ "$LAST_DIR" != "worktrees" ] && [ "$LAST_DIR" != "Projects" ]; then
|
|
# Generate a numeric ID from the directory name (hash-based for consistency)
|
|
# This ensures the same ticket name always gets the same numeric ID
|
|
HASH=$(echo -n "$LAST_DIR" | shasum -a 256 | cut -d' ' -f1 | head -c 8)
|
|
# Convert hex to decimal and take modulo to get a number between 1-9999
|
|
NUMERIC_ID=$((0x$HASH % 9999 + 1))
|
|
DETECTED_TASK="TASK-$NUMERIC_ID"
|
|
echo "📋 Generated numeric TASK_ID from ticket name '$LAST_DIR': $DETECTED_TASK"
|
|
fi
|
|
fi
|
|
|
|
if [ -n "$DETECTED_TASK" ]; then
|
|
TASK_ID="$DETECTED_TASK"
|
|
echo "📋 Detected TASK_ID from worktree path: $TASK_ID"
|
|
fi
|
|
fi
|
|
|
|
# Fallback to numeric ID based on worktree path hash (ensures consistency)
|
|
if [ -z "$TASK_ID" ]; then
|
|
# Generate a consistent numeric ID from worktree path
|
|
HASH=$(echo -n "$WORKTREE_ROOT" | shasum -a 256 | cut -d' ' -f1 | head -c 8)
|
|
NUMERIC_ID=$((0x$HASH % 9999 + 1))
|
|
TASK_ID="TASK-$NUMERIC_ID"
|
|
echo "📋 Generated consistent numeric TASK_ID from worktree path: $TASK_ID"
|
|
fi
|
|
|
|
# Store TASK_ID for future use (ensures same worktree always uses same TASK_ID)
|
|
echo "$TASK_ID" > "$TASK_ID_FILE"
|
|
echo "💾 Stored TASK_ID for future use: $TASK_ID"
|
|
|
|
# Find main repository (try common locations)
|
|
MAIN_REPO_PATHS=(
|
|
"/Users/oda/Desktop/Projects/managing-apps"
|
|
"$(git -C "$WORKTREE_PROJECT_ROOT" rev-parse --show-toplevel 2>/dev/null || echo '')"
|
|
"$(dirname "$WORKTREE_ROOT" 2>/dev/null)/managing-apps"
|
|
)
|
|
|
|
MAIN_REPO=""
|
|
for path in "${MAIN_REPO_PATHS[@]}"; do
|
|
if [ -n "$path" ] && [ -d "$path" ] && [ -d "$path/scripts" ] && [ -f "$path/scripts/start-task-docker.sh" ]; then
|
|
MAIN_REPO="$path"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ -z "$MAIN_REPO" ]; then
|
|
echo "❌ Cannot find main repository with scripts"
|
|
echo "💡 Tried:"
|
|
for path in "${MAIN_REPO_PATHS[@]}"; do
|
|
echo " - $path"
|
|
done
|
|
exit 1
|
|
fi
|
|
|
|
echo "📁 Main repository: $MAIN_REPO"
|
|
echo "🔧 Setting up environment for task: $TASK_ID"
|
|
|
|
SCRIPT_DIR="$MAIN_REPO/scripts"
|
|
|
|
# Auto-detect port offset if 0 is provided
|
|
if [ "$PORT_OFFSET" = "0" ]; then
|
|
echo "🔍 Auto-detecting available port offset..."
|
|
PORT_OFFSET_FOUND=0
|
|
for offset in $(seq 1 100); do
|
|
POSTGRES_TEST=$((5432 + offset))
|
|
REDIS_TEST=$((6379 + offset))
|
|
API_TEST=$((5000 + offset))
|
|
ORLEANS_SILO_TEST=$((11111 + offset))
|
|
ORLEANS_GATEWAY_TEST=$((30000 + offset))
|
|
|
|
POSTGRES_FREE=true
|
|
REDIS_FREE=true
|
|
API_FREE=true
|
|
ORLEANS_SILO_FREE=true
|
|
ORLEANS_GATEWAY_FREE=true
|
|
|
|
if command -v lsof >/dev/null 2>&1; then
|
|
if lsof -Pi :$POSTGRES_TEST -sTCP:LISTEN -t >/dev/null 2>&1; then
|
|
POSTGRES_FREE=false
|
|
fi
|
|
if lsof -Pi :$REDIS_TEST -sTCP:LISTEN -t >/dev/null 2>&1; then
|
|
REDIS_FREE=false
|
|
fi
|
|
if lsof -Pi :$API_TEST -sTCP:LISTEN -t >/dev/null 2>&1; then
|
|
API_FREE=false
|
|
fi
|
|
if lsof -Pi :$ORLEANS_SILO_TEST -sTCP:LISTEN -t >/dev/null 2>&1; then
|
|
ORLEANS_SILO_FREE=false
|
|
fi
|
|
if lsof -Pi :$ORLEANS_GATEWAY_TEST -sTCP:LISTEN -t >/dev/null 2>&1; then
|
|
ORLEANS_GATEWAY_FREE=false
|
|
fi
|
|
fi
|
|
|
|
if [ "$POSTGRES_FREE" = "true" ] && [ "$REDIS_FREE" = "true" ] && [ "$API_FREE" = "true" ] && [ "$ORLEANS_SILO_FREE" = "true" ] && [ "$ORLEANS_GATEWAY_FREE" = "true" ]; then
|
|
PORT_OFFSET=$offset
|
|
PORT_OFFSET_FOUND=1
|
|
echo "✅ Found available port offset: $PORT_OFFSET"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ "$PORT_OFFSET_FOUND" = "0" ]; then
|
|
echo "❌ Could not find available port offset (checked offsets 1-100)"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
POSTGRES_PORT=$((5432 + PORT_OFFSET))
|
|
API_PORT=$((5000 + PORT_OFFSET))
|
|
REDIS_PORT=$((6379 + PORT_OFFSET))
|
|
DB_NAME="managing_$(echo "$TASK_ID" | tr '[:upper:]' '[:lower:]')"
|
|
ORLEANS_DB_NAME="orleans_$(echo "$TASK_ID" | tr '[:upper:]' '[:lower:]')"
|
|
|
|
# Extract TASK_SLOT from TASK_ID numeric part (e.g., TASK-5439 -> 5439)
|
|
# This ensures unique Orleans ports for each task
|
|
TASK_SLOT=$(echo "$TASK_ID" | grep -oE '[0-9]+' | head -1)
|
|
if [ -z "$TASK_SLOT" ] || [ "$TASK_SLOT" = "0" ]; then
|
|
# Fallback: use a hash-based numeric ID if TASK_ID doesn't contain numbers
|
|
HASH=$(echo -n "$TASK_ID" | shasum -a 256 | cut -d' ' -f1 | head -c 8)
|
|
TASK_SLOT=$((0x$HASH % 9999 + 1))
|
|
echo "⚠️ TASK_ID doesn't contain a number, generated TASK_SLOT: $TASK_SLOT"
|
|
else
|
|
echo "📊 TASK_SLOT extracted from TASK_ID: $TASK_SLOT"
|
|
fi
|
|
|
|
# Calculate Orleans ports based on TASK_SLOT
|
|
ORLEANS_SILO_PORT=$((11111 + (TASK_SLOT - 1) * 10))
|
|
ORLEANS_GATEWAY_PORT=$((30000 + (TASK_SLOT - 1) * 10))
|
|
ORLEANS_DASHBOARD_PORT=$((9999 + (TASK_SLOT - 1)))
|
|
|
|
echo "📊 Port offset: $PORT_OFFSET"
|
|
echo "📊 PostgreSQL: localhost:$POSTGRES_PORT"
|
|
echo "📊 Redis: localhost:$REDIS_PORT"
|
|
echo "📊 API: http://localhost:$API_PORT"
|
|
echo "💾 Database: $DB_NAME"
|
|
|
|
# Verify main database is accessible
|
|
echo "🔍 Verifying main database connection..."
|
|
if ! PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d managing -c '\q' 2>/dev/null; then
|
|
echo "❌ Cannot connect to main database at localhost:5432"
|
|
echo "💡 Starting main database..."
|
|
cd "$MAIN_REPO/src/Managing.Docker"
|
|
if command -v docker &> /dev/null && docker compose version &> /dev/null; then
|
|
docker compose -f docker-compose.yml -f docker-compose.local.yml up -d postgres
|
|
else
|
|
docker-compose -f docker-compose.yml -f docker-compose.local.yml up -d postgres
|
|
fi
|
|
echo "⏳ Waiting for database to start..."
|
|
sleep 15
|
|
fi
|
|
|
|
# Create compose file
|
|
echo "📝 Creating Docker Compose file..."
|
|
bash "$SCRIPT_DIR/create-task-compose.sh" "$TASK_ID" "$PORT_OFFSET"
|
|
COMPOSE_FILE="$MAIN_REPO/src/Managing.Docker/docker-compose.task-${TASK_ID}.yml"
|
|
|
|
# Start services (PostgreSQL and Redis only)
|
|
echo "🐳 Starting PostgreSQL and Redis..."
|
|
cd "$MAIN_REPO/src/Managing.Docker"
|
|
if command -v docker &> /dev/null && docker compose version &> /dev/null; then
|
|
docker compose -f "$COMPOSE_FILE" up -d postgres-${TASK_ID} redis-${TASK_ID}
|
|
else
|
|
docker-compose -f "$COMPOSE_FILE" up -d postgres-${TASK_ID} redis-${TASK_ID}
|
|
fi
|
|
|
|
# Wait for PostgreSQL
|
|
echo "⏳ Waiting for PostgreSQL..."
|
|
for i in {1..60}; do
|
|
if PGPASSWORD=postgres psql -h localhost -p $POSTGRES_PORT -U postgres -d postgres -c '\q' 2>/dev/null; then
|
|
echo "✅ PostgreSQL is ready"
|
|
break
|
|
fi
|
|
if [ $i -eq 60 ]; then
|
|
echo "❌ PostgreSQL not ready after 60 attempts"
|
|
if command -v docker &> /dev/null && docker compose version &> /dev/null; then
|
|
docker compose -f "$COMPOSE_FILE" down
|
|
else
|
|
docker-compose -f "$COMPOSE_FILE" down
|
|
fi
|
|
exit 1
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
# Copy database
|
|
echo "📦 Copying database from main repo..."
|
|
bash "$SCRIPT_DIR/copy-database-for-task.sh" "$TASK_ID" "localhost" "5432" "localhost" "$POSTGRES_PORT"
|
|
|
|
if [ $? -ne 0 ]; then
|
|
echo "❌ Database copy failed"
|
|
if command -v docker &> /dev/null && docker compose version &> /dev/null; then
|
|
docker compose -f "$COMPOSE_FILE" down
|
|
else
|
|
docker-compose -f "$COMPOSE_FILE" down
|
|
fi
|
|
exit 1
|
|
fi
|
|
|
|
# Store configuration for later use (in worktree)
|
|
SETUP_CONFIG_FILE="$WORKTREE_PROJECT_ROOT/.vibe-setup.env"
|
|
echo "💾 Saving setup configuration..."
|
|
cat > "$SETUP_CONFIG_FILE" <<EOF
|
|
TASK_ID=$TASK_ID
|
|
TASK_SLOT=$TASK_SLOT
|
|
PORT_OFFSET=$PORT_OFFSET
|
|
POSTGRES_PORT=$POSTGRES_PORT
|
|
API_PORT=$API_PORT
|
|
REDIS_PORT=$REDIS_PORT
|
|
ORLEANS_SILO_PORT=$ORLEANS_SILO_PORT
|
|
ORLEANS_GATEWAY_PORT=$ORLEANS_GATEWAY_PORT
|
|
ORLEANS_DASHBOARD_PORT=$ORLEANS_DASHBOARD_PORT
|
|
DB_NAME=$DB_NAME
|
|
ORLEANS_DB_NAME=$ORLEANS_DB_NAME
|
|
VIBE_WORKTREE_ROOT=$WORKTREE_PROJECT_ROOT
|
|
MAIN_REPO=$MAIN_REPO
|
|
EOF
|
|
|
|
echo ""
|
|
echo "✅ Setup complete!"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo "📋 Configuration Details:"
|
|
echo " Task ID: $TASK_ID"
|
|
echo " Task Slot: $TASK_SLOT (from TASK_ID numeric part)"
|
|
echo " Port Offset: $PORT_OFFSET"
|
|
echo " PostgreSQL Port: $POSTGRES_PORT"
|
|
echo " Redis Port: $REDIS_PORT"
|
|
echo " API Port: $API_PORT (will be used when starting API)"
|
|
echo " Orleans Silo Port: $ORLEANS_SILO_PORT"
|
|
echo " Orleans Gateway Port: $ORLEANS_GATEWAY_PORT"
|
|
echo " Orleans Dashboard Port: $ORLEANS_DASHBOARD_PORT"
|
|
echo " Database Name: $DB_NAME"
|
|
echo " Orleans Database: $ORLEANS_DB_NAME"
|
|
echo " Configuration File: $SETUP_CONFIG_FILE"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
echo "💡 Next step: Start API and Workers using scripts/vibe-kanban/vibe-dev-server.sh"
|
|
|
|
# Explicit exit with success code to signal Vibe Kanban that setup is complete
|
|
exit 0
|
|
|