#!/bin/bash # setup_env.sh # ============================================================================ # OrbStack Development Sandbox Setup Script # ============================================================================ # # Dual-mode script: # - On macOS: Orchestrates VM creation via OrbStack # - On Linux: Provisions the VM with all development tools # # USAGE (from macOS): # ./setup_env.sh [vm-name] # Creates and provisions a new VM # ./setup_env.sh my-project # Custom VM name # # On first run, prompts for git credentials and VNC password, saves to config.env. # Subsequent runs reuse config.env (shared across all VMs). # # USAGE (from inside a VM): # ./setup_env.sh # Interactive mode (prompts per component) # ./setup_env.sh -y # Accept all components without prompting # ./setup_env.sh --non-interactive # Requires env vars, implies -y # # ============================================================================ set -euo pipefail # ============================================================================ # Configuration - edit versions here # ============================================================================ ERLANG_VERSION="28.3.1" ELIXIR_VERSION="1.19.5-otp-28" # ============================================================================ # Helpers # ============================================================================ RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' log_info() { echo -e "${GREEN}=== $1 ===${NC}" if [ -n "${LOG_FILE:-}" ]; then echo "[$(date +%H:%M:%S)] INFO: $1" >> "$LOG_FILE" fi } log_warn() { echo -e "${YELLOW}WARNING: $1${NC}" if [ -n "${LOG_FILE:-}" ]; then echo "[$(date +%H:%M:%S)] WARN: $1" >> "$LOG_FILE" fi } log_error() { echo -e "${RED}ERROR: $1${NC}" >&2 if [ -n "${LOG_FILE:-}" ]; then echo "[$(date +%H:%M:%S)] ERROR: $1" >> "$LOG_FILE" fi } command_exists() { command -v "$1" &>/dev/null } # ============================================================================ # macOS Host Mode # ============================================================================ if [[ "$(uname -s)" == "Darwin" ]]; then echo -e "${GREEN}" echo "============================================================================" echo " OrbStack Development Sandbox - Host Setup" echo "============================================================================" echo -e "${NC}" # Check for OrbStack if ! command_exists orb; then log_error "OrbStack is not installed." echo "" echo "Install OrbStack from: https://orbstack.dev" echo " brew install orbstack" echo "" echo "After installing, run this script again." exit 1 fi # VM name from argument or default VM_NAME="${1:-dev-sandbox}" # Locate config.env relative to this script SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CONFIG_FILE="$SCRIPT_DIR/config.env" # Create or load config if [ -f "$CONFIG_FILE" ]; then source "$CONFIG_FILE" echo -e "${YELLOW}Using saved config from: $CONFIG_FILE${NC}" echo " Name: $GIT_NAME" echo " Email: $GIT_EMAIL" echo " VNC Pass: ******" echo "" else echo "First run — creating config file for shared credentials." echo "" read -rp "Enter your name for git config: " GIT_NAME read -rp "Enter your email for git config: " GIT_EMAIL while true; do read -rsp "Enter VNC password (min 6 chars): " VNC_PASSWORD echo if [ ${#VNC_PASSWORD} -ge 6 ]; then break fi echo "Password must be at least 6 characters." done if [ -z "$GIT_NAME" ] || [ -z "$GIT_EMAIL" ]; then log_error "Name and email are required." exit 1 fi cat > "$CONFIG_FILE" < 0)) && ((CURSOR--)) || true ;; "[B") # Down ((CURSOR < COMP_COUNT - 1)) && ((CURSOR++)) || true ;; esac ;; " ") # Toggle if [ "${COMP_SELECTED[$CURSOR]}" = "1" ]; then COMP_SELECTED[$CURSOR]=0 else COMP_SELECTED[$CURSOR]=1 fi ;; "a"|"A") # Select all for ((i=0; i/dev/null | grep -qw "$VM_NAME"; then log_error "VM '$VM_NAME' already exists." echo " To delete it: orb delete $VM_NAME" echo " To shell in: ssh $VM_NAME@orb" exit 1 fi echo -e "${YELLOW}Creating VM: $VM_NAME${NC}" orb create ubuntu "$VM_NAME" echo "" echo -e "${YELLOW}Disabling host filesystem access inside VM...${NC}" # Remove macOS home directory access for security isolation # OrbStack mounts the host home at /mnt/mac and symlinks /Users -> /mnt/mac/Users orb run -m "$VM_NAME" sudo bash -c " umount /mnt/mac 2>/dev/null || true rm -rf /mnt/mac rm -f /Users mkdir -p /Users # Prevent remount on reboot by masking the mount echo '# Disabled by setup_env.sh for security isolation' > /etc/fstab.d/orbstack-mac 2>/dev/null || true " echo "" echo -e "${YELLOW}Copying setup script into VM...${NC}" orb push -m "$VM_NAME" "$SCRIPT_DIR/setup_env.sh" /tmp/setup_env.sh echo "" if [ -n "$SKIP_EXPORTS" ]; then echo -e "${YELLOW}Skipping:${NC} $SKIP_EXPORTS" fi echo -e "${YELLOW}Running provisioning inside VM (this will take a while)...${NC}" echo "" orb run -m "$VM_NAME" bash -c "${SKIP_EXPORTS}export GIT_NAME='$GIT_NAME'; export GIT_EMAIL='$GIT_EMAIL'; export VNC_PASSWORD='$VNC_PASSWORD'; bash /tmp/setup_env.sh --non-interactive" echo "" echo -e "${GREEN}============================================================================${NC}" echo -e "${GREEN} VM '$VM_NAME' is ready!${NC}" echo -e "${GREEN}============================================================================${NC}" echo "" echo -e "${YELLOW}CONNECT:${NC}" echo " SSH: ssh $VM_NAME@orb" echo " Zed: Open Remote Folder -> $VM_NAME@orb:~/projects" echo " VS Code: Remote-SSH -> $VM_NAME@orb" echo " Files: /Volumes/OrbStack/$VM_NAME/home/\$(whoami)/" echo "" echo -e "${YELLOW}VNC (for browser login):${NC}" echo " Start: ssh $VM_NAME@orb -- vnc-start" echo " Connect: open vnc://$VM_NAME.orb.local:5901" echo " Stop: ssh $VM_NAME@orb -- vnc-stop" echo "" echo -e "${YELLOW}POSTGRESQL:${NC}" echo " From VM: psql dev" echo " From macOS: psql -h $VM_NAME.orb.local -d dev" echo "" echo -e "${YELLOW}CLAUDE CODE:${NC}" echo " ssh $VM_NAME@orb -- claude" echo "" exit 0 fi # ============================================================================ # Linux VM Mode (everything below runs inside the VM) # ============================================================================ LOG_FILE="/tmp/setup_env_$(date +%Y%m%d_%H%M%S).log" cleanup() { local exit_code=$? if [ $exit_code -ne 0 ]; then log_error "Setup failed (exit code: $exit_code). Log saved to: $LOG_FILE" log_error "Review the log for details on what went wrong." fi } trap cleanup EXIT # Prevent mise from reading host config via OrbStack mount (/Users/john/.mise.toml) export MISE_GLOBAL_CONFIG_FILE="$HOME/.config/mise/config.toml" export MISE_CONFIG_DIR="$HOME/.config/mise" export MISE_DATA_DIR="$HOME/.local/share/mise" export MISE_IGNORED_CONFIG_PATHS="/Users" export MISE_YES=1 # ============================================================================ # Argument parsing # ============================================================================ NON_INTERACTIVE=false AUTO_ACCEPT=false for arg in "$@"; do case $arg in --non-interactive) NON_INTERACTIVE=true AUTO_ACCEPT=true ;; --yes|-y) AUTO_ACCEPT=true ;; *) log_error "Unknown argument: $arg" echo "Usage: $0 [--non-interactive] [--yes|-y]" exit 1 ;; esac done # ============================================================================ # Component selection helper # ============================================================================ prompt_install() { local id="$1" local name="$2" local description="$3" # Check for SKIP_=1 env var (set by macOS host mode component selector) local skip_var="SKIP_${id^^}" if [ "${!skip_var:-}" = "1" ]; then log_warn "Skipping $name (deselected)" return 1 fi if [ "$AUTO_ACCEPT" = true ]; then log_info "$name" echo " $description" return 0 fi echo "" echo -e "${GREEN}=== $name ===${NC}" echo " $description" read -rp " Install? (Y/n) " -n 1 REPLY echo if [[ "$REPLY" =~ ^[Nn]$ ]]; then log_warn "Skipping $name" return 1 fi return 0 } # Track what was installed for dependency checks INSTALLED_NODE=false INSTALLED_CHROMIUM=false # ============================================================================ # Pre-flight checks # ============================================================================ if [ "$(id -u)" -eq 0 ]; then log_error "Do not run this script as root. It will use sudo where needed." exit 1 fi if ! command_exists sudo; then log_error "sudo is required but not installed." exit 1 fi echo -e "${GREEN}" echo "============================================================================" echo " OrbStack Development Sandbox Setup (VM Provisioning)" echo "============================================================================" echo -e "${NC}" echo "Log file: $LOG_FILE" echo "" # ============================================================================ # Git configuration prompts # ============================================================================ if [ "$NON_INTERACTIVE" = true ]; then if [ -z "${GIT_NAME:-}" ] || [ -z "${GIT_EMAIL:-}" ]; then log_error "In non-interactive mode, GIT_NAME and GIT_EMAIL env vars are required." exit 1 fi if [ -z "${VNC_PASSWORD:-}" ]; then log_error "In non-interactive mode, VNC_PASSWORD env var is required (min 6 chars)." exit 1 fi else read -rp "Enter your name for git config: " GIT_NAME read -rp "Enter your email for git config: " GIT_EMAIL if [ -z "$GIT_NAME" ] || [ -z "$GIT_EMAIL" ]; then log_error "Name and email are required." exit 1 fi while true; do read -rsp "Enter VNC password (min 6 chars): " VNC_PASSWORD echo if [ ${#VNC_PASSWORD} -ge 6 ]; then break fi echo "Password must be at least 6 characters." done echo "" echo -e "${YELLOW}Setting up with:${NC}" echo " Name: $GIT_NAME" echo " Email: $GIT_EMAIL" echo " Erlang: $ERLANG_VERSION" echo " Elixir: $ELIXIR_VERSION" echo "" fi # ============================================================================ # System update (always runs) # ============================================================================ log_info "Updating system" sudo apt-get update -qq && sudo apt-get upgrade -y -qq >> "$LOG_FILE" 2>&1 # ============================================================================ # Base dependencies (always runs) # ============================================================================ log_info "Installing base dependencies" sudo apt-get install -y -qq \ curl \ wget \ git \ build-essential \ autoconf \ libncurses5-dev \ libssl-dev \ libwxgtk3.2-dev \ libgl1-mesa-dev \ libglu1-mesa-dev \ libpng-dev \ libssh-dev \ unixodbc-dev \ xsltproc \ fop \ libxml2-utils \ unzip \ inotify-tools \ jq \ ripgrep \ fd-find \ direnv \ tmux \ python3 \ python3-pip \ python3-venv >> "$LOG_FILE" 2>&1 # Create symlink for fd (Ubuntu packages it as fdfind) if [ ! -L /usr/local/bin/fd ] && command_exists fdfind; then sudo ln -sf "$(which fdfind)" /usr/local/bin/fd fi # Install yq and watchexec (not in apt) log_info "Installing yq and watchexec" if ! command_exists yq; then YQ_URL="https://github.com/mikefarah/yq/releases/latest/download/yq_linux_arm64" sudo curl -fsSL "$YQ_URL" -o /usr/local/bin/yq sudo chmod +x /usr/local/bin/yq fi if ! command_exists watchexec; then WATCHEXEC_VERSION="2.3.2" WATCHEXEC_URL="https://github.com/watchexec/watchexec/releases/download/v${WATCHEXEC_VERSION}/watchexec-${WATCHEXEC_VERSION}-aarch64-unknown-linux-gnu.tar.xz" curl -fsSL "$WATCHEXEC_URL" | sudo tar -xJ --strip-components=1 -C /usr/local/bin/ --wildcards '*/watchexec' fi # Enable direnv hook in bashrc if ! grep -q 'direnv hook bash' ~/.bashrc; then echo 'eval "$(direnv hook bash)"' >> ~/.bashrc fi # ============================================================================ # PostgreSQL # ============================================================================ if prompt_install "postgresql" "PostgreSQL" "Database server for local development"; then sudo apt-get install -y -qq postgresql postgresql-contrib libpq-dev >> "$LOG_FILE" 2>&1 sudo systemctl enable postgresql sudo systemctl start postgresql # Create user (idempotent) if ! sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='$USER'" | grep -q 1; then sudo -u postgres createuser -s "$USER" echo " Created PostgreSQL superuser: $USER" else log_warn "PostgreSQL user '$USER' already exists, skipping" fi # Create dev database (idempotent) if ! psql -lqt 2>/dev/null | cut -d \| -f 1 | grep -qw dev; then createdb dev echo " Created database: dev" else log_warn "Database 'dev' already exists, skipping" fi # Configure pg_hba.conf idempotently PG_HBA=$(find /etc/postgresql -name pg_hba.conf 2>/dev/null | head -1) PG_CONF=$(find /etc/postgresql -name postgresql.conf 2>/dev/null | head -1) if [ -z "$PG_HBA" ] || [ -z "$PG_CONF" ]; then log_error "Could not find PostgreSQL configuration files" exit 1 fi # Local socket auth: keep peer (authenticates via OS username, passwordless for matching user) # Local TCP (127.0.0.1): trust (passwordless for localhost convenience) sudo sed -i 's/^host\s\+all\s\+all\s\+127\.0\.0\.1\/32\s\+scram-sha-256$/host all all 127.0.0.1\/32 trust/' "$PG_HBA" # Verify sed actually changed the localhost line (catches format mismatches) if sudo grep -qE '^host\s+all\s+all\s+127\.0\.0\.1/32\s+scram-sha-256' "$PG_HBA"; then log_warn "pg_hba.conf localhost sed replacement did not match. The line format may differ from expected. Check manually: $PG_HBA" fi # Allow connections from OrbStack host network (idempotent) if ! sudo grep -q "# OrbStack host access" "$PG_HBA"; then echo "# OrbStack host access" | sudo tee -a "$PG_HBA" > /dev/null echo "host all all 192.168.0.0/16 scram-sha-256" | sudo tee -a "$PG_HBA" > /dev/null fi # Listen on all interfaces (idempotent - replace commented default, then ensure uncommented line exists) sudo sed -i 's/^#listen_addresses = .*/listen_addresses = '"'"'*'"'"'/' "$PG_CONF" if ! sudo grep -q "^listen_addresses" "$PG_CONF"; then echo "listen_addresses = '*'" | sudo tee -a "$PG_CONF" > /dev/null fi sudo systemctl restart postgresql fi # ============================================================================ # mise (version manager) # ============================================================================ if prompt_install "mise" "mise" "Version manager for Node.js, Erlang, and Elixir runtimes"; then if command_exists mise; then log_warn "mise already installed, skipping" else curl -fsSL https://mise.run | sh fi # Ensure mise activation is in bashrc (idempotent, independent of install check) if ! grep -q 'mise activate bash' ~/.bashrc; then echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc fi export PATH="$HOME/.local/bin:$PATH" eval "$(~/.local/bin/mise activate bash)" fi # ============================================================================ # Node.js # ============================================================================ if prompt_install "node" "Node.js (LTS)" "JavaScript runtime, required for Claude Code and Playwright"; then if ! command_exists mise; then log_warn "mise not installed, cannot install Node.js via mise. Skipping." else mise use -g node@lts INSTALLED_NODE=true fi fi # ============================================================================ # Erlang & Elixir # ============================================================================ if prompt_install "erlang" "Erlang $ERLANG_VERSION & Elixir $ELIXIR_VERSION" "BEAM VM and Elixir language for functional programming"; then if ! command_exists mise; then log_warn "mise not installed, cannot install Erlang/Elixir via mise. Skipping." else mise use -g "erlang@$ERLANG_VERSION" mise use -g "elixir@$ELIXIR_VERSION" fi fi # ============================================================================ # Chromium # ============================================================================ if prompt_install "chromium" "Chromium" "Browser for automation, testing, and VNC-based login flows"; then if command_exists chromium-browser || command_exists chromium; then log_warn "Chromium already installed, skipping" else # Ubuntu defaults chromium-browser to a snap package which hangs in # non-interactive/container environments. Use the .deb from the # Ubuntu universe repo via apt preference pinning, or install # chromium directly (non-snap) on newer Ubuntu. # First try the non-snap 'chromium' package, fall back to downloading # via Playwright if that also pulls snap. if apt-cache showpkg chromium 2>/dev/null | grep -q "^Package:"; then # Prevent snap-based install by blocking snapd trigger sudo apt-get install -y -qq chromium --no-install-recommends >> "$LOG_FILE" 2>&1 || true fi # If that didn't work (snap redirect or not available), install via Playwright if ! command_exists chromium-browser && ! command_exists chromium; then log_warn "apt chromium unavailable or snap-based, will use Playwright's bundled Chromium" fi fi # Create symlink so tools expecting 'google-chrome' work CHROMIUM_BIN="" if command_exists chromium-browser; then CHROMIUM_BIN="$(which chromium-browser)" elif command_exists chromium; then CHROMIUM_BIN="$(which chromium)" fi if [ -n "$CHROMIUM_BIN" ] && [ ! -L /usr/bin/google-chrome ]; then sudo ln -sf "$CHROMIUM_BIN" /usr/bin/google-chrome fi INSTALLED_CHROMIUM=true fi # ============================================================================ # VNC Server + Desktop Environment # ============================================================================ if prompt_install "vnc" "VNC + XFCE Desktop" "Remote desktop for browser-based login flows (e.g., Claude Code OAuth)"; then sudo apt-get install -y -qq \ tigervnc-standalone-server \ xfce4 \ xfce4-terminal \ dbus-x11 >> "$LOG_FILE" 2>&1 # Configure VNC password mkdir -p ~/.vnc echo "$VNC_PASSWORD" | vncpasswd -f > ~/.vnc/passwd chmod 600 ~/.vnc/passwd # Create xstartup cat > ~/.vnc/xstartup <<'EOF' #!/bin/sh unset SESSION_MANAGER unset DBUS_SESSION_BUS_ADDRESS exec startxfce4 EOF chmod +x ~/.vnc/xstartup # Create helper scripts in ~/bin mkdir -p ~/bin cat > ~/bin/vnc-start <<'EOF' #!/bin/bash # Use short hostname (strip any domain suffix) + .orb.local for OrbStack DNS VNC_HOST="$(hostname -s).orb.local" if pgrep -f "Xtigervnc :1" > /dev/null 2>&1; then echo "VNC is already running on display :1" echo "Connect: open vnc://${VNC_HOST}:5901" exit 0 fi vncserver :1 -geometry 1280x800 -depth 24 -localhost no echo "VNC started on display :1" echo "Connect: open vnc://${VNC_HOST}:5901" EOF chmod +x ~/bin/vnc-start cat > ~/bin/vnc-stop <<'EOF' #!/bin/bash vncserver -kill :1 2>/dev/null && echo "VNC stopped" || echo "VNC was not running" EOF chmod +x ~/bin/vnc-stop # Add ~/bin to PATH if not already there if ! grep -q 'export PATH="$HOME/bin:$PATH"' ~/.bashrc; then echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc fi fi # ============================================================================ # Ollama # ============================================================================ if prompt_install "ollama" "Ollama" "Local LLM runner for offline inference"; then if command_exists ollama; then log_warn "Ollama already installed, skipping" else curl -fsSL https://ollama.com/install.sh | sudo sh >> "$LOG_FILE" 2>&1 fi fi # ============================================================================ # Claude Code # ============================================================================ if prompt_install "claude" "Claude Code" "AI coding assistant CLI from Anthropic"; then if [ "$INSTALLED_NODE" = false ] && ! command_exists node; then log_warn "Node.js not available. Claude Code requires Node.js. Skipping." else if command_exists claude; then log_warn "Claude Code already installed, upgrading" fi npm install -g @anthropic-ai/claude-code >> "$LOG_FILE" 2>&1 fi fi # ============================================================================ # Playwright # ============================================================================ if prompt_install "playwright" "Playwright" "Browser testing and automation framework"; then if [ "$INSTALLED_NODE" = false ] && ! command_exists node; then log_warn "Node.js not available. Playwright requires Node.js. Skipping." elif [ "$INSTALLED_CHROMIUM" = false ]; then log_warn "Chromium not selected. Playwright requires a browser. Skipping." else # Install Playwright with its bundled Chromium and system deps. # This also serves as the Chromium install if apt-based install failed. npx --yes playwright install --with-deps chromium >> "$LOG_FILE" 2>&1 # If no system chromium was installed, symlink Playwright's bundled one if ! command_exists chromium-browser && ! command_exists chromium; then PW_CHROMIUM=$(find "$HOME/.cache/ms-playwright" -name "chrome" -type f 2>/dev/null | head -1) if [ -n "$PW_CHROMIUM" ]; then sudo ln -sf "$PW_CHROMIUM" /usr/local/bin/chromium sudo ln -sf "$PW_CHROMIUM" /usr/local/bin/google-chrome echo " Using Playwright's bundled Chromium as system browser" fi fi fi fi # ============================================================================ # Git configuration (always runs) # ============================================================================ log_info "Configuring Git" git config --global user.name "$GIT_NAME" git config --global user.email "$GIT_EMAIL" git config --global init.defaultBranch main git config --global pull.rebase false # ============================================================================ # Claude Code plugins # ============================================================================ if prompt_install "plugins" "Claude Code Plugins" "Code review, feature dev, and browser automation plugins + MCP servers"; then if ! command_exists claude; then log_warn "Claude Code not installed. Skipping plugins." else mkdir -p ~/.config/claude # Add marketplaces (idempotent - claude handles duplicates) echo " Adding marketplaces..." claude plugin marketplace add anthropics/claude-code 2>> "$LOG_FILE" || true claude plugin marketplace add obra/superpowers 2>> "$LOG_FILE" || true # Install plugins from Anthropic official marketplace echo " Installing Anthropic plugins..." ANTHROPIC_PLUGINS=( "code-review@claude-code-plugins" "code-simplifier@claude-code-plugins" "feature-dev@claude-code-plugins" "pr-review-toolkit@claude-code-plugins" "security-guidance@claude-code-plugins" "frontend-design@claude-code-plugins" ) for plugin in "${ANTHROPIC_PLUGINS[@]}"; do claude plugin install "$plugin" --scope user 2>> "$LOG_FILE" || log_warn "Failed to install $plugin" done # Install plugins from superpowers marketplace echo " Installing superpowers plugins..." SUPERPOWERS_PLUGINS=( "double-shot-latte@superpowers-marketplace" "elements-of-style@superpowers-marketplace" "superpowers@superpowers-marketplace" "superpowers-chrome@superpowers-marketplace" "superpowers-lab@superpowers-marketplace" ) for plugin in "${SUPERPOWERS_PLUGINS[@]}"; do claude plugin install "$plugin" --scope user 2>> "$LOG_FILE" || log_warn "Failed to install $plugin" done # MCP servers echo " Adding MCP servers..." claude mcp add playwright --scope user -- npx @anthropic-ai/mcp-server-playwright 2>> "$LOG_FILE" || true claude mcp add superpowers-chrome --scope user -- npx github:obra/superpowers-chrome --headless 2>> "$LOG_FILE" || true fi fi # ============================================================================ # Projects directory # ============================================================================ mkdir -p ~/projects # ============================================================================ # Verification # ============================================================================ log_info "Verifying installations" echo "---" echo "Node: $(node --version 2>/dev/null || echo 'not installed')" echo "npm: $(npm --version 2>/dev/null || echo 'not installed')" echo "Erlang: $(erl -eval 'erlang:display(erlang:system_info(otp_release)), halt().' -noshell 2>/dev/null || echo 'not installed')" echo "Elixir: $(elixir --version 2>/dev/null | head -1 || echo 'not installed')" echo "Chromium: $(chromium-browser --version 2>/dev/null || chromium --version 2>/dev/null || echo 'not installed')" echo "Claude: $(claude --version 2>/dev/null || echo 'not installed')" echo "PostgreSQL: $(psql --version 2>/dev/null || echo 'not installed')" echo "mise: $(mise --version 2>/dev/null || echo 'not installed')" echo "VNC: $(vncserver -version 2>&1 | head -1 || echo 'not installed')" echo "Ollama: $(ollama --version 2>/dev/null || echo 'not installed')" echo "ripgrep: $(rg --version 2>/dev/null | head -1 || echo 'not installed')" echo "fd: $(fd --version 2>/dev/null || echo 'not installed')" echo "yq: $(yq --version 2>/dev/null || echo 'not installed')" echo "direnv: $(direnv --version 2>/dev/null || echo 'not installed')" echo "watchexec: $(watchexec --version 2>/dev/null || echo 'not installed')" echo "tmux: $(tmux -V 2>/dev/null || echo 'not installed')" echo "Python: $(python3 --version 2>/dev/null || echo 'not installed')" echo "---" # ============================================================================ # Done # ============================================================================ echo "" echo -e "${GREEN}============================================================================${NC}" echo -e "${GREEN} Setup complete!${NC}" echo -e "${GREEN}============================================================================${NC}" echo "" echo "Log file: $LOG_FILE" echo "" echo "Restart your shell: source ~/.bashrc" echo "" echo -e "${YELLOW}VNC:${NC}" echo " Start: vnc-start" echo " Stop: vnc-stop" echo "" echo -e "${YELLOW}POSTGRESQL:${NC}" echo " psql dev" echo "" echo -e "${YELLOW}CLAUDE CODE:${NC}" echo " claude" echo ""