From d7788a52126cd8b687d716dc949347fac00abf77 Mon Sep 17 00:00:00 2001 From: guessthepw Date: Sat, 24 Jan 2026 18:22:37 -0500 Subject: [PATCH] Creates dual-mode development sandbox setup Implements orchestration on macOS and provisioning on Linux for isolated Claude Code environments Adds interactive component selection with visual menu interface Enables secure VM creation with disabled host filesystem access Provides comprehensive toolchain including PostgreSQL, Erlang/Elixir, and browser automation Configures VNC desktop access for OAuth workflows and browser-based tasks --- .gitignore | 1 + CLAUDE.md | 80 ++++ README.md | 297 ++++++++++++++ config.env.example | 6 + setup_env.sh | 984 ++++++++++++++++++++++++++++++++++++--------- 5 files changed, 1178 insertions(+), 190 deletions(-) create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100644 config.env.example mode change 100644 => 100755 setup_env.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2549b3d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config.env diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8e2c0c2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,80 @@ +# CLAUDE.md + +## Project Overview + +This repository contains a dual-mode setup script for creating OrbStack-based development sandboxes tailored for Claude Code with Elixir/Erlang, browser automation, and PostgreSQL. + +## Repository Structure + +``` +setup_env.sh - Main script (macOS host mode + Linux VM provisioning mode) +config.env.example - Example credentials file +config.env - User credentials (gitignored, created on first run) +.gitignore - Ignores config.env +README.md - User-facing documentation +CLAUDE.md - This file (context for Claude Code sessions) +``` + +## How the Script Works + +The script has two modes, detected via `uname -s`: + +- **Darwin (macOS)**: Orchestrator mode. Checks for OrbStack, reads/creates `config.env`, creates a VM, copies itself in, runs itself inside the VM with `--non-interactive`. +- **Linux (VM)**: Provisioning mode. Installs all tools, configures PostgreSQL, sets up VNC, installs Claude Code plugins. + +This means `./setup_env.sh my-vm` on macOS does everything end-to-end. + +## Key Technical Details + +- **Target environment**: OrbStack Ubuntu VM on macOS Apple Silicon (ARM64) +- **Version manager**: mise (manages Node.js, Erlang, Elixir) +- **Language versions**: Configured at the top of `setup_env.sh` as variables (`ERLANG_VERSION`, `ELIXIR_VERSION`) +- **PostgreSQL auth**: Peer for local socket, trust for localhost TCP (127.0.0.1), scram-sha-256 for host network (192.168.0.0/16) +- **Browser**: Chromium (no Chrome ARM64 Linux builds exist), symlinked to `google-chrome` +- **VNC**: TigerVNC + XFCE on display :1 (port 5901), controlled via `vnc-start`/`vnc-stop` helpers in `~/bin` +- **Shared credentials**: `config.env` is created once and reused for all VMs + +## Working on This Project + +### Editing setup_env.sh + +- The macOS host-mode block is at the top (inside the `if [[ "$(uname -s)" == "Darwin" ]]` block) +- The Linux VM-mode block is everything after that conditional +- Version numbers are defined as variables at the top (`ERLANG_VERSION`, `ELIXIR_VERSION`) +- All installation steps must be idempotent (safe to run multiple times) +- Use `log_info`, `log_warn`, `log_error` helpers for output +- New apt packages go in the base dependencies section +- New Claude plugins go in the appropriate array (`ANTHROPIC_PLUGINS` or `SUPERPOWERS_PLUGINS`) +- Redirect verbose output to `$LOG_FILE`, show only meaningful progress to the user +- The `LOG_FILE` variable is only set in VM mode (not available in macOS host mode) +- Each optional component is wrapped in `prompt_install "Name" "Description"` — this handles both interactive prompts and `--yes`/`--non-interactive` auto-accept +- Track dependency flags (`INSTALLED_NODE`, `INSTALLED_CHROMIUM`) to skip dependent components gracefully + +### Script Conventions + +- `set -euo pipefail` is enforced - handle potential failures with `|| true` or explicit checks +- Use `command_exists` to check for already-installed tools +- Use `apt-get` (not `apt`) for scripting reliability +- Quote all variable expansions +- Use `find` for PostgreSQL config paths (version-agnostic) +- The `--non-interactive` flag is for VM mode (implies `--yes`); macOS mode always uses config.env +- `--yes`/`-y` accepts all components without prompting but still allows interactive credential entry +- `MISE_GLOBAL_CONFIG_FILE` and `MISE_CONFIG_DIR` are set to prevent OrbStack host-mount config pollution + +### Testing + +There are no automated tests. To test changes: +1. Remove an existing test VM: `orb delete test-sandbox` +2. Run: `./setup_env.sh test-sandbox` +3. Verify provisioning completes +4. Run again to verify idempotency: `ssh test-sandbox@orb -- bash /tmp/setup_env.sh --non-interactive` (will need env vars) +5. Test VNC: `ssh test-sandbox@orb -- vnc-start`, then `open vnc://test-sandbox.orb.local:5901` +6. Clean up: `orb delete test-sandbox` + +### Security Considerations + +- `config.env` is chmod 600 and gitignored +- PostgreSQL remote access uses scram-sha-256 (not trust) +- The script refuses to run as root inside the VM +- VNC password is required (min 6 chars) +- VNC binds to all interfaces (`-localhost no`) to allow connections from the macOS host — this is intentional for the OrbStack use case diff --git a/README.md b/README.md index e69de29..f5fa24a 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,297 @@ +# OrbStack Development Sandbox + +Disposable, isolated Linux VMs for running Claude Code with `--dangerously-skip-permissions`. One command creates a fully provisioned environment. Blow it away and recreate it in minutes. + +The VM is a real Linux machine with its own filesystem, network, and process space — but you edit files from your Mac, access services on `*.orb.local`, and SSH in without any key setup. All the isolation of a container with none of the friction. + +## Why This Exists + +Running `claude --dangerously-skip-permissions` on your host machine means Claude can execute arbitrary commands, install packages, modify system files, and access everything on your disk. That's powerful for autonomous coding but risky on a machine with your SSH keys, credentials, and personal files. + +This script creates throwaway VMs where Claude can run unrestricted: + +- **Isolated filesystem** — Claude can't touch your macOS files, keys, or configs +- **Isolated network** — services run on their own IP, no port conflicts with your host +- **Disposable** — `orb delete my-sandbox` wipes everything; recreate in one command +- **Multiple VMs** — run separate sandboxes per project with shared git credentials +- **Full access from Mac** — edit files in your editor, browse databases, view running apps + +```bash +# Create a sandbox, SSH in, run Claude unrestricted +./setup_env.sh my-project +ssh my-project@orb +claude --dangerously-skip-permissions +``` + +If anything goes wrong: `orb delete my-project && ./setup_env.sh my-project` + +## Quick Start + +```bash +# Create and provision a VM (one command from macOS) +./setup_env.sh my-sandbox +``` + +On first run, you'll be prompted for git name, email, and a VNC password. These are saved to `config.env` and reused for all future VMs. You'll also get an interactive checklist to select which components to install. + +```bash +# Create additional VMs — reuses config.env, shows component picker +./setup_env.sh my-other-project +./setup_env.sh elixir-playground +``` + +When run manually inside a VM, you're prompted for each component individually: + +```bash +# Inside a VM — interactive, prompts per component +./setup_env.sh + +# Inside a VM — accept all without prompting +./setup_env.sh -y +./setup_env.sh --yes +``` + +## Requirements + +- macOS with Apple Silicon (ARM64) +- [OrbStack](https://orbstack.dev) installed (`brew install orbstack`) + +## What Gets Installed + +All components are optional — deselect what you don't need in the interactive picker. + +| Tool | Version | Purpose | +|------|---------|---------| +| mise | latest | Version manager for runtimes | +| Node.js | LTS | JavaScript runtime | +| Erlang | 28.3.1 | BEAM VM | +| Elixir | 1.19.5-otp-28 | Elixir language | +| Chromium | system | Browser automation target | +| Playwright | latest | Browser testing framework | +| PostgreSQL | system default | Database | +| Ollama | latest | Local LLM inference | +| Claude Code | latest | AI coding assistant | +| TigerVNC + XFCE | system | VNC access for browser login flows | + +## Connecting from macOS + +### SSH + +```bash +# Shell into the VM (no key setup required) +ssh my-sandbox@orb + +# Run a command directly +ssh my-sandbox@orb -- ls ~/projects + +# Run Claude unrestricted +ssh my-sandbox@orb -- claude --dangerously-skip-permissions +``` + +OrbStack handles SSH key configuration automatically. + +### Editing Files + +Your editor connects to the VM over SSH. You edit files as if they were local — full LSP support, syntax highlighting, file tree, integrated terminal. + +**Zed:** +1. `Cmd+Shift+P` -> "Open Remote Folder" +2. Enter: `my-sandbox@orb:~/projects` + +**VS Code / Cursor:** +1. Install the "Remote - SSH" extension +2. `Cmd+Shift+P` -> "Remote-SSH: Connect to Host" +3. Enter: `my-sandbox@orb` +4. Open folder: `~/projects` + +**Finder (direct filesystem access):** +``` +/Volumes/OrbStack/my-sandbox/home// +``` + +### Viewing Running Apps + +Services running in the VM are accessible from your Mac via `.orb.local`: + +```bash +# Phoenix/Rails/Next.js dev server running on port 4000 inside the VM +open http://my-sandbox.orb.local:4000 + +# Or use port forwarding if the app only binds to localhost +ssh -L 4000:localhost:4000 my-sandbox@orb +``` + +PostgreSQL, Redis, or any service listening on the VM's interfaces is reachable at `my-sandbox.orb.local:` from your Mac — no extra configuration. + +## VNC (Browser Access) + +For tasks requiring a visible browser (e.g., Claude Code OAuth login): + +```bash +# Start VNC server inside the VM +ssh my-sandbox@orb -- vnc-start + +# Connect from macOS (opens Screen Sharing.app) +open vnc://my-sandbox.orb.local:5901 + +# Stop when done (saves resources) +ssh my-sandbox@orb -- vnc-stop +``` + +### macOS Screen Sharing (built-in) + +```bash +open vnc://my-sandbox.orb.local:5901 +``` + +Enter your VNC password when prompted. + +### RealVNC Viewer + +1. Download from https://www.realvnc.com/en/connect/download/viewer/ +2. Enter address: `my-sandbox.orb.local:5901` +3. When prompted for credentials, enter your VNC password (username can be left blank) + +### TigerVNC Viewer + +```bash +# Install via Homebrew +brew install tiger-vnc + +# Connect +vncviewer my-sandbox.orb.local:5901 +``` + +Enter your VNC password when prompted. + +### Connection details for any VNC client + +- **Host**: `my-sandbox.orb.local` +- **Port**: `5901` (display `:1`) +- **Password**: the VNC password from `config.env` +- **Resolution**: 1280x800 (configurable in `~/bin/vnc-start`) + +## PostgreSQL + +A superuser matching your Linux username and a `dev` database are created automatically. + +**Auth model:** +- Local socket (`psql dev`): peer auth (OS username must match PG role) +- Localhost TCP (127.0.0.1): trust (passwordless) +- Host network (192.168.0.0/16, i.e., from macOS): scram-sha-256 + +### From inside the VM + +```bash +psql dev # Connect to dev database +psql -l # List databases +createdb myapp_dev # Create a new database +``` + +### From macOS + +```bash +# Direct (requires: brew install libpq) +psql -h my-sandbox.orb.local -U -d dev +``` + +### Connection strings + +``` +# Elixir/Phoenix +postgres://@my-sandbox.orb.local/myapp_dev + +# Generic +host=my-sandbox.orb.local port=5432 dbname=dev user= +``` + +### DataGrip + +1. New Data Source -> PostgreSQL +2. **SSH/SSL** tab: Check "Use SSH tunnel", Host: `my-sandbox@orb`, Auth: Key pair +3. **General** tab: Host: `localhost`, Port: `5432`, User: your VM username, Database: `dev`, No password +4. Test Connection -> Apply + +### DBeaver + +1. New Database Connection -> PostgreSQL +2. **SSH** tab: Check "Use SSH Tunnel", Host: `my-sandbox.orb.local`, Port: `22`, Auth: Public Key +3. **Main** tab: Host: `localhost`, Port: `5432`, Database: `dev`, Username: your VM username, no password +4. Test Connection -> Finish + +### Creating additional databases + +```bash +# From inside the VM +createdb myapp_dev +createdb myapp_test + +# From macOS +ssh my-sandbox@orb -- createdb myapp_dev +``` + +## Claude Code Plugins + +The script installs these plugins at user scope: + +**Anthropic marketplace** (`anthropics/claude-code`): +- code-review, code-simplifier, feature-dev, pr-review-toolkit, security-guidance, frontend-design + +**Superpowers marketplace** (`obra/superpowers`): +- double-shot-latte, elements-of-style, superpowers, superpowers-chrome, superpowers-lab + +**MCP Servers**: +- `playwright` - Browser automation and screenshots +- `superpowers-chrome` - Direct Chrome/Chromium control (headless) + +## Configuration + +Edit the version variables at the top of `setup_env.sh`: + +```bash +ERLANG_VERSION="28.3.1" +ELIXIR_VERSION="1.19.5-otp-28" +``` + +### Shared credentials + +Credentials are stored in `config.env` (gitignored). To reset: + +```bash +rm config.env +./setup_env.sh my-sandbox # will prompt again +``` + +Or copy the example and edit: + +```bash +cp config.env.example config.env +# Edit config.env with your values +``` + +## Managing VMs + +```bash +# List all VMs +orb list + +# Delete a VM (instant cleanup) +orb delete my-sandbox + +# Stop a VM (preserves state, frees resources) +orb stop my-sandbox + +# Start a stopped VM +orb start my-sandbox + +# Nuclear option — delete and recreate +orb delete my-sandbox && ./setup_env.sh my-sandbox +``` + +## Idempotency + +The VM provisioning script is safe to run multiple times. It checks for existing installations before re-installing and avoids appending duplicate configuration lines. + +## Logs + +Each provisioning run creates a log file at `/tmp/setup_env_.log` inside the VM with detailed output from package installations and any errors. diff --git a/config.env.example b/config.env.example new file mode 100644 index 0000000..e22417b --- /dev/null +++ b/config.env.example @@ -0,0 +1,6 @@ +# Copy this file to config.env and fill in your values. +# config.env is gitignored and shared across all VMs you create. + +GIT_NAME="guessthepw" +GIT_EMAIL="admin@guessthe.pw" +VNC_PASSWORD="changeme123" diff --git a/setup_env.sh b/setup_env.sh old mode 100644 new mode 100755 index e6936fa..d227b34 --- a/setup_env.sh +++ b/setup_env.sh @@ -1,126 +1,480 @@ #!/bin/bash -# orbstack-dev-sandbox-setup.sh +# setup_env.sh # ============================================================================ # OrbStack Development Sandbox Setup Script # ============================================================================ # -# Creates a fully configured development environment for Claude Code with -# Elixir/Erlang, browser automation, and PostgreSQL. +# Dual-mode script: +# - On macOS: Orchestrates VM creation via OrbStack +# - On Linux: Provisions the VM with all development tools # -# REQUIREMENTS: -# - macOS with Apple Silicon (ARM64) -# - OrbStack installed (https://orbstack.dev) +# USAGE (from macOS): +# ./setup_env.sh [vm-name] # Creates and provisions a new VM +# ./setup_env.sh my-project # Custom VM name # -# USAGE: -# 1. Create the VM: orb create ubuntu dev-sandbox -# 2. Shell into it: orb shell dev-sandbox -# 3. Run this script: ./orbstack-dev-sandbox-setup.sh +# On first run, prompts for git credentials and VNC password, saves to config.env. +# Subsequent runs reuse config.env (shared across all VMs). # -# WHAT THIS INSTALLS: -# - mise (version manager) -# - Node.js (latest LTS) -# - Erlang 28.3.1 -# - Elixir 1.19.5-otp-28 -# - Claude Code + plugins -# - Chromium (ARM64 native, for browser automation) -# - Playwright -# - PostgreSQL 16 -# -# CONNECTING FROM macOS: -# SSH: ssh dev-sandbox@orb -# Zed: Cmd+Shift+P → "Open Remote Folder" → dev-sandbox@orb:~/projects -# Ghostty: ssh dev-sandbox@orb -# VS Code: Remote-SSH → dev-sandbox@orb -# Files: /Volumes/OrbStack/dev-sandbox/home// -# -# POSTGRESQL: -# The script creates a superuser matching your Linux username and a 'dev' database. -# -# From inside VM: -# psql dev # Connect to dev database -# psql -l # List databases -# createdb myapp_dev # Create new database -# -# From macOS: -# psql -h dev-sandbox.orb.local -U -d dev -#` -# Connection string for Elixir/Phoenix: -# postgres://@dev-sandbox.orb.local/myapp_dev -# -# DBeaver connection (from macOS): -# Host: dev-sandbox.orb.local -# Port: 5432 -# Database: dev -# User: -# Password: (leave blank, uses peer auth over SSH) -# -# Or use SSH tunnel in DBeaver: -# SSH Host: dev-sandbox@orb -# SSH Auth: Use your macOS SSH key -# -# CLAUDE CODE PLUGINS INSTALLED: -# From anthropics/claude-code marketplace: -# - code-review Multi-agent PR review with confidence scoring -# - code-simplifier Cleans up verbose AI-generated code -# - feature-dev 7-phase feature development workflow -# - pr-review-toolkit Specialized review agents -# - security-guidance Security monitoring hook -# - frontend-design UI/UX design guidance skill -# -# From obra/superpowers marketplace: -# - double-shot-latte Stops "Would you like me to continue?" interruptions -# - elements-of-style Writing guidance based on Strunk's classic -# - superpowers Core superpowers functionality -# - superpowers-chrome Browser automation via Chrome DevTools Protocol -# - superpowers-lab Experimental/in-development skills -# -# MCP SERVERS: -# - playwright Browser automation and screenshots -# - superpowers-chrome Direct Chrome/Chromium control (headless) +# 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 -e +set -euo pipefail -# Colors for output +# ============================================================================ +# 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' # No Color +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" +echo " OrbStack Development Sandbox Setup (VM Provisioning)" echo "============================================================================" echo -e "${NC}" +echo "Log file: $LOG_FILE" +echo "" -# Prompt for name and email -read -p "Enter your name for git config: " GIT_NAME -read -p "Enter your email for git config: " GIT_EMAIL +# ============================================================================ +# 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 - echo -e "${RED}Error: Name and email are required.${NC}" - exit 1 + 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 -echo "" -echo -e "${YELLOW}Setting up with:${NC}" -echo " Name: $GIT_NAME" -echo " Email: $GIT_EMAIL" -echo "" -read -p "Continue? (y/n) " -n 1 -r -echo -if [[ ! $REPLY =~ ^[Yy]$ ]]; then - exit 1 -fi +# ============================================================================ +# System update (always runs) +# ============================================================================ +log_info "Updating system" +sudo apt-get update -qq && sudo apt-get upgrade -y -qq >> "$LOG_FILE" 2>&1 -echo "" -echo -e "${GREEN}=== Updating system ===${NC}" -sudo apt update && sudo apt upgrade -y - -echo "" -echo -e "${GREEN}=== Installing base dependencies ===${NC}" -sudo apt install -y \ +# ============================================================================ +# Base dependencies (always runs) +# ============================================================================ +log_info "Installing base dependencies" +sudo apt-get install -y -qq \ curl \ wget \ git \ @@ -139,137 +493,387 @@ sudo apt install -y \ libxml2-utils \ unzip \ inotify-tools \ - jq + jq \ + ripgrep \ + fd-find \ + direnv \ + tmux \ + python3 \ + python3-pip \ + python3-venv >> "$LOG_FILE" 2>&1 -echo "" -echo -e "${GREEN}=== Installing PostgreSQL ===${NC}" -sudo apt install -y postgresql postgresql-contrib libpq-dev +# 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 -# Start and enable PostgreSQL -sudo systemctl enable postgresql -sudo systemctl start postgresql +# 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 -# Create user and dev database -sudo -u postgres createuser -s $USER 2>/dev/null || echo "PostgreSQL user already exists" -createdb dev 2>/dev/null || echo "Database 'dev' already exists" +# Enable direnv hook in bashrc +if ! grep -q 'direnv hook bash' ~/.bashrc; then + echo 'eval "$(direnv hook bash)"' >> ~/.bashrc +fi -# Allow password-less local connections -sudo sed -i "s/local all all peer/local all all trust/" /etc/postgresql/*/main/pg_hba.conf -sudo sed -i "s/host all all 127.0.0.1\/32 scram-sha-256/host all all 127.0.0.1\/32 trust/" /etc/postgresql/*/main/pg_hba.conf +# ============================================================================ +# 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 -# Listen on all interfaces (for connections from macOS host) -echo "listen_addresses = '*'" | sudo tee -a /etc/postgresql/*/main/postgresql.conf -echo "host all all 0.0.0.0/0 trust" | sudo tee -a /etc/postgresql/*/main/pg_hba.conf + sudo systemctl enable postgresql + sudo systemctl start postgresql -sudo systemctl restart 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 -echo "" -echo -e "${GREEN}=== Installing mise ===${NC}" -curl https://mise.run | sh -echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc -export PATH="$HOME/.local/bin:$PATH" -eval "$(~/.local/bin/mise activate bash)" + # 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 -echo "" -echo -e "${GREEN}=== Installing Node.js (latest LTS) via mise ===${NC}" -mise use -g node@lts + # 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) -echo "" -echo -e "${GREEN}=== Installing Erlang 28.3.1 & Elixir 1.19.5-otp-28 via mise ===${NC}" -mise use -g erlang@28.3.1 -mise use -g elixir@1.19.5-otp-28 + if [ -z "$PG_HBA" ] || [ -z "$PG_CONF" ]; then + log_error "Could not find PostgreSQL configuration files" + exit 1 + fi -echo "" -echo -e "${GREEN}=== Installing Chromium (ARM64 native) ===${NC}" -# Note: Google Chrome doesn't have ARM64 Linux builds, so we use Chromium -sudo apt install -y chromium-browser + # 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" -# Create symlink so tools expecting 'google-chrome' work -sudo ln -sf /usr/bin/chromium-browser /usr/bin/google-chrome + # 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 -echo "" -echo -e "${GREEN}=== Installing Claude Code ===${NC}" -npm install -g @anthropic-ai/claude-code + # 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 -echo "" -echo -e "${GREEN}=== Installing Playwright ===${NC}" -npx playwright install --with-deps chromium + # 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 -echo "" -echo -e "${GREEN}=== Configuring Git ===${NC}" + 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 -echo "" -echo -e "${GREEN}=== Setting up Claude Code plugins (system-wide) ===${NC}" +# ============================================================================ +# 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 -# Create system-wide Claude Code settings directory -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 -# Add marketplaces -echo "Adding marketplaces..." -claude plugin marketplace add anthropics/claude-code -claude plugin marketplace add obra/superpowers + # 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" + ) -# Install plugins from Anthropic official marketplace -echo "Installing Anthropic plugins..." -claude plugin install code-review@claude-code-plugins --scope user -claude plugin install code-simplifier@claude-code-plugins --scope user -claude plugin install feature-dev@claude-code-plugins --scope user -claude plugin install pr-review-toolkit@claude-code-plugins --scope user -claude plugin install security-guidance@claude-code-plugins --scope user -claude plugin install frontend-design@claude-code-plugins --scope user + 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..." -claude plugin install double-shot-latte@superpowers-marketplace --scope user -claude plugin install elements-of-style@superpowers-marketplace --scope user -claude plugin install superpowers@superpowers-marketplace --scope user -claude plugin install superpowers-chrome@superpowers-marketplace --scope user -claude plugin install superpowers-lab@superpowers-marketplace --scope user + # 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" + ) -echo "" -echo -e "${GREEN}=== Adding MCP servers ===${NC}" -claude mcp add playwright --scope user -- npx @anthropic-ai/mcp-server-playwright -claude mcp add superpowers-chrome --scope user -- npx github:obra/superpowers-chrome --headless + for plugin in "${SUPERPOWERS_PLUGINS[@]}"; do + claude plugin install "$plugin" --scope user 2>> "$LOG_FILE" || log_warn "Failed to install $plugin" + done -echo "" -echo -e "${GREEN}=== Creating projects directory ===${NC}" + # 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 -echo "" -echo -e "${GREEN}=== Verifying installations ===${NC}" +# ============================================================================ +# Verification +# ============================================================================ +log_info "Verifying installations" echo "---" -echo "Node: $(node --version)" -echo "npm: $(npm --version)" -echo "Erlang: $(erl -eval 'erlang:display(erlang:system_info(otp_release)), halt().' -noshell)" -echo "Elixir: $(elixir --version | head -1)" -echo "Chromium: $(chromium-browser --version)" -echo "Claude: $(claude --version)" -echo "PostgreSQL: $(psql --version)" -echo "mise: $(mise --version)" +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}CONNECT FROM macOS:${NC}" -echo " SSH: ssh dev-sandbox@orb" -echo " Zed: Open Remote Folder → dev-sandbox@orb:~/projects" -echo " Files: /Volumes/OrbStack/dev-sandbox/home/$USER/" +echo -e "${YELLOW}VNC:${NC}" +echo " Start: vnc-start" +echo " Stop: vnc-stop" echo "" echo -e "${YELLOW}POSTGRESQL:${NC}" -echo " From VM: psql dev" -echo " From macOS: psql -h dev-sandbox.orb.local -d dev" -echo " DBeaver host: dev-sandbox.orb.local:5432" +echo " psql dev" echo "" echo -e "${YELLOW}CLAUDE CODE:${NC}" -echo " Run: claude" -echo " Plugins: claude plugin list" +echo " claude" echo ""