#!/usr/bin/env bash set -euo pipefail port_in_use() { lsof -nP -iTCP:"$1" -sTCP:LISTEN &>/dev/null } if [[ -z "${WM_SLOT:-}" ]]; then # Scan .env.local files of existing worktrees to find which slots are claimed, # then pick the lowest free slot. This avoids collisions when worktrees are # removed and new ones created (position-based indexing would re-use slots # still held by surviving worktrees). used_slots=() current_dir="$(pwd)" while IFS= read -r wt_path; do [[ "$wt_path" == "$current_dir" ]] && continue if [[ -f "$wt_path/.env.local" ]]; then bp=$(grep '^BACKEND_PORT=' "$wt_path/.env.local" | cut -d= -f2 || true) if [[ -n "$bp" && "$bp" -gt 8000 ]]; then used_slots+=("$(( (bp - 8000) / 10 ))") fi fi done < <(git worktree list --porcelain | sed -n 's/^worktree //p') # Find lowest available slot (slot 0 = 8000/3000 is reserved for main) WM_SLOT=1 while [[ " ${used_slots[*]:-} " == *" $WM_SLOT "* ]]; do ((WM_SLOT++)) done echo "Auto-assigned slot $WM_SLOT (used: ${used_slots[*]:-none})" fi # Slot-based: predictable ports for SSH forwarding # Slot 0 = 8000/3000, slot 1 = 8010/3010, slot 2 = 8020/3020, etc. backend_port=$((8000 + WM_SLOT * 10)) frontend_port=$((3000 + WM_SLOT * 10)) if port_in_use "$backend_port" || port_in_use "$frontend_port"; then echo "WARNING: Slot $WM_SLOT ports ($backend_port/$frontend_port) already in use" >&2 fi # Generate .env.local with port overrides cat > .env.local <> .env.local fi echo "Created .env.local with ports: backend=$backend_port, frontend=$frontend_port" # --- Create per-worktree database --- wt_basename=$(basename "$(pwd)") db_name="windmill_${wt_basename//-/_}" db_conn="postgres://postgres:changeme@127.0.0.1:5432" if command -v psql &>/dev/null; then if psql "$db_conn/postgres" -tc "SELECT 1 FROM pg_database WHERE datname = '${db_name}'" 2>/dev/null | grep -q 1; then echo "Database $db_name already exists" else if [[ "${WM_CLONE_DB:-}" == "1" || "${WM_CLONE_DB:-}" == "true" ]]; then # Terminate active connections so CREATE DATABASE ... TEMPLATE works psql "$db_conn/postgres" -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'windmill' AND pid <> pg_backend_pid();" 2>/dev/null || true psql "$db_conn/postgres" -c "CREATE DATABASE ${db_name} TEMPLATE windmill" 2>/dev/null \ && echo "Created database $db_name (template: windmill)" \ || echo "WARNING: Could not create database $db_name from template windmill" >&2 else psql "$db_conn/postgres" -c "CREATE DATABASE ${db_name}" 2>/dev/null \ && echo "Created database $db_name" \ || echo "WARNING: Could not create database $db_name (is PostgreSQL running?)" >&2 fi fi db_url="${db_conn}/${db_name}?sslmode=disable" # Run migrations against the new database DATABASE_URL="$db_url" sqlx migrate run --source backend/migrations \ && echo "Migrations applied to $db_name" \ || echo "WARNING: Could not run migrations on $db_name" >&2 # Copy license_key from the main windmill database to the new database license_key=$(psql "$db_conn/windmill" -t -A -c "SELECT value FROM global_settings WHERE name = 'license_key'" 2>/dev/null || true) if [[ -n "$license_key" ]]; then psql "$db_url" -c "INSERT INTO global_settings (name, value) VALUES ('license_key', '${license_key}'::jsonb) ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value" 2>/dev/null \ && echo "Copied license_key to $db_name" \ || echo "WARNING: Could not copy license_key to $db_name" >&2 fi # Use export so DATABASE_URL overrides the nix devshell value for child processes cat >> .env.local <&2 fi # --- Copy frontend/node_modules preserving symlinks --- # cp -a preserves .bin/ symlinks that cp -r would dereference, breaking require() paths main_repo_root="$(cd "$(git rev-parse --git-common-dir 2>/dev/null)/.." && pwd)" if [[ -n "$main_repo_root" && -d "$main_repo_root/frontend/node_modules" ]]; then cp -a "$main_repo_root/frontend/node_modules" frontend/ echo "Copied frontend/node_modules (with symlinks preserved)" fi # --- Install cli deps and generate client --- if [[ -n "$main_repo_root" && -d "$main_repo_root/cli/node_modules" ]]; then cp -a "$main_repo_root/cli/node_modules" cli/ echo "Copied cli/node_modules (with symlinks preserved)" fi (cd cli && npm install && npm run gen-client) \ && echo "CLI deps installed and client generated" \ || echo "WARNING: CLI setup failed" >&2 # --- Allow direnv so the nix devshell activates in pane commands --- if command -v direnv &>/dev/null && [ -f .envrc ]; then direnv allow echo "direnv allowed" fi # --- Trust worktree directory in Claude Code --- claude_json="$HOME/.claude.json" if [ -f "$claude_json" ]; then wt_path="$(pwd)" python3 -c " import json, sys path = '$wt_path' with open('$claude_json', 'r') as f: data = json.load(f) projects = data.setdefault('projects', {}) proj = projects.setdefault(path, {}) proj['hasTrustDialogAccepted'] = True proj['hasCompletedProjectOnboarding'] = True with open('$claude_json', 'w') as f: json.dump(data, f, indent=2) " && echo "Added $wt_path to Claude Code trusted directories" \ || echo "Warning: Could not update Claude Code trusted directories" fi # --- Create matching windmill-ee-private worktree --- # Find ee repo: sibling to the main worktree (git toplevel of the main checkout), # then try parent of cwd, then fall back to home ee_repo="" for candidate in \ "${main_repo_root:+${main_repo_root}/../windmill-ee-private}" \ "$(pwd)/../windmill-ee-private" \ "${HOME}/windmill-ee-private" \ "${HOME}/projects/windmill-ee-private"; do if [ -n "$candidate" ] && [ -d "$candidate" ]; then ee_repo="$(cd "$candidate" && pwd)" break fi done if [ -n "$ee_repo" ]; then branch=$(git branch --show-current 2>/dev/null || true) wt_basename=$(basename "$(pwd)") ee_worktree_dir="${ee_repo}__worktrees/${wt_basename}" if [ -n "$branch" ] && [ ! -d "$ee_worktree_dir" ]; then mkdir -p "$(dirname "$ee_worktree_dir")" # Fetch latest so we can check out remote branches git -C "$ee_repo" fetch --quiet 2>/dev/null || true # Try: existing branch, then new branch from main if git -C "$ee_repo" worktree add "$ee_worktree_dir" "$branch" 2>/dev/null; then echo "Created EE worktree at $ee_worktree_dir (branch: $branch)" elif git -C "$ee_repo" worktree add -b "$branch" "$ee_worktree_dir" main 2>/dev/null; then echo "Created EE worktree at $ee_worktree_dir (new branch: $branch from main)" else echo "Warning: Could not create EE worktree for branch $branch" fi elif [ -d "$ee_worktree_dir" ]; then echo "EE worktree already exists at $ee_worktree_dir" fi # Point Claude Code additionalDirectories at the EE worktree if [ -d "$ee_worktree_dir" ]; then ee_rel=$(python3 -c "import os; print(os.path.relpath('$ee_worktree_dir', '$(pwd)'))" 2>/dev/null || echo "$ee_worktree_dir") mkdir -p .claude rust_plugin="" if [[ "${USE_RUST_PLUGIN:-}" == "1" || "${USE_RUST_PLUGIN:-}" == "true" ]]; then rust_plugin=', "enabledPlugins": { "rust-analyzer-lsp@claude-plugins-official": true }' fi cat > .claude/settings.local.json <