Compare commits

..

10 commits
v0.7.0 ... main

Author SHA1 Message Date
guessthepw
2fb83ada34 Updates documentation for Windows security features
README.md:
- Add skip parameters example (-SkipVNC, -SkipOllama)
- Document VNC password prompt and minimum length
- Update requirements to show ISO creation fallbacks

CLAUDE.md:
- Add Windows script editing section
- Add Windows security patterns section
- Add Windows testing instructions
- Update VNC password minimum from 6 to 8 chars
- Document checksum verification for Windows

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 13:36:00 -05:00
guessthepw
ca12925111 Adds security hardening to Windows Hyper-V script
Security improvements:
- SHA256 checksum verification for Ubuntu image downloads
- Strict input validation for all user inputs (git name/email, passwords)
- Blocks shell metacharacters to prevent injection attacks
- Config file created with restricted ACL from the start
- VNC password minimum increased to 8 characters
- Security reminder to remove cloud-init ISO after first boot

Reliability improvements:
- ARM64 architecture detection for Windows on ARM
- Log file creation for troubleshooting
- Automatic cleanup on failure (VM, disk, ISO)
- Hosts file backup before modification

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 13:23:08 -05:00
guessthepw
65790ee3e2 Implements full Windows Hyper-V provisioning
Rewrites setup_env_windows.ps1 to fully implement WINDOWS_PLAN.md with:
- Fixed cloud-init password handling using chpasswd
- Multiple ISO creation fallbacks (oscdimg/WSL/IMAPI2)
- Component skip parameters for VNC, PostgreSQL, Ollama, Playwright
- VNC password support via base64 encoding
- BITS transfer for reliable downloads
- SSH readiness checking before showing connection info

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 12:52:58 -05:00
guessthepw
cc1277cd98 Fix Tidewave install, add Python to mise, document Windows plan
Fixes:
- Tidewave CLI now downloads from correct repo (tidewave_app)
  with proper musl binary naming convention

Features:
- Python runtime managed by mise instead of system apt
- Python added as selectable component in interactive menu

Documentation:
- WINDOWS_PLAN.md explains Hyper-V vs WSL2 security tradeoffs
- Documents CVEs affecting WSL2 (2024-20681, 2025-9074, 2025-53788)
- Describes full implementation architecture and workflow

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 12:48:35 -05:00
guessthepw
77093a0ce6 Adds Python support via mise version manager
Integrates Python as a selectable component alongside existing Node.js and Erlang options
Updates component descriptions to reflect mise's expanded language support
Includes pip upgrade during Python installation for package management

Fixes Tidewave CLI download URL and architecture detection for improved reliability
2026-01-25 12:47:45 -05:00
guessthepw
70c2559d40 Add Windows Hyper-V support for maximum security isolation
Creates setup_env_windows.ps1 PowerShell script that:
- Provisions full Hyper-V VMs (not WSL2) for complete isolation
- Uses Ubuntu cloud images with cloud-init for automated setup
- Generates SSH keys for passwordless access
- Adds VMs to hosts file for easy <name>.local access
- Disables integration services by default for security

Hyper-V provides stronger isolation than WSL2:
- Separate kernel per VM
- Complete filesystem isolation (no /mnt/c mount)
- Own network stack (no firewall bypass)
- No ability to launch Windows programs from Linux

Also updates README with cross-platform quick start guides
and security comparison between WSL2 and Hyper-V.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 12:18:25 -05:00
guessthepw
2861664a03 Add OpenCode and Tidewave CLI support
- OpenCode: Open-source AI coding assistant (npm install -g opencode-ai)
  Supports multiple LLM providers including OpenAI, Anthropic, Gemini

- Tidewave: Elixir/Phoenix MCP server for AI-powered development
  Downloads binary from GitHub releases with ELF validation
  Enables runtime introspection, SQL queries, and code evaluation

Both tools are optional components in the interactive installer.
Tidewave is automatically skipped if Erlang is not selected.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 12:16:06 -05:00
guessthepw
63bcc0aea3 Add error checking for base64 decode in VM bootstrap
Ensures early failure with clear error messages if credential
decoding fails during VM provisioning.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 09:36:02 -05:00
guessthepw
26501daa4e Fix critical security vulnerabilities from audit
- Rustup: Download script to temp file with shebang/size validation
  before execution, matching mise/ollama pattern (line 1119)

- SKIP_EXPORTS: Refactor from embedded shell commands to base64-encoded
  list decoded safely in VM, eliminating injection risk (line 478)

- Playwright symlink: Validate path is executable and within expected
  cache directory before creating system symlinks (line 1053)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 09:34:24 -05:00
guessthepw
cbc379c0cc Add project memory system with versioning guidelines
Establishes CLAUDE.md, CHANGELOG.md, and README.md as persistent
project memory. Adds documentation update triggers and semantic
versioning rules to ensure context is maintained across sessions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 09:29:37 -05:00
6 changed files with 1729 additions and 45 deletions

View file

@ -5,6 +5,108 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.14.1] - 2025-01-25
### Changed
- Updated README.md with Windows skip parameters and VNC password info
- Updated CLAUDE.md with Windows script documentation and security patterns
- Fixed VNC password minimum documentation (6 → 8 chars)
## [0.14.0] - 2025-01-25
### Security
- Add SHA256 checksum verification for Ubuntu cloud image downloads (prevents MITM attacks)
- Add strict input validation for git name, email, VM password, and VNC password
- Validate loaded config.env values to detect tampering
- VNC password minimum increased from 6 to 8 characters
- Block shell metacharacters in all user inputs to prevent injection
- Config file created with restricted ACL from the start (no race condition window)
- Add security reminder to remove cloud-init ISO after first boot (contains passwords)
### Added
- ARM64 architecture detection for Windows on ARM devices
- Log file creation at `$env:TEMP\setup_env_windows_<timestamp>.log`
- Cleanup on failure: automatically removes partial VM, disk, and ISO on error
- Hosts file backup before modification (removed on success, kept on failure)
- Input validation functions: `Test-GitName`, `Test-GitEmail`, `Test-VMPassword`, `Test-VNCPassword`
- Checksum caching to avoid re-downloading verification data
### Changed
- Ubuntu image URL now uses detected architecture instead of hardcoded amd64
- All major operations now log to file for troubleshooting
- VM creation wrapped in try/catch with automatic cleanup on failure
## [0.13.0] - 2025-01-25
### Changed
- Rewrote `setup_env_windows.ps1` to fully implement WINDOWS_PLAN.md
- Password handling uses cloud-init `chpasswd` with plaintext (type: text) instead of broken hash generation
- Multiple ISO creation methods with fallback chain: oscdimg → WSL genisoimage → IMAPI2 COM
- Downloads use BITS transfer for reliability with progress reporting
- SSH readiness checking with timeout before displaying connection info
### Added
- Component skip parameters: `-SkipVNC`, `-SkipPostgreSQL`, `-SkipOllama`, `-SkipPlaywright`
- VNC password support via base64 encoding in cloud-init
- Automatic hosts file cleanup when VM is deleted with `-Force`
- Proper prerequisite checking for Hyper-V, Windows edition, and admin privileges
### Fixed
- Cloud-init password configuration (was using bash syntax in PowerShell)
- ISO creation now works without Windows ADK by using WSL or IMAPI2 fallbacks
- Hosts file handling with proper admin privilege elevation
## [0.12.0] - 2025-01-25
### Added
- Python runtime management via mise (alongside Node.js, Erlang, Elixir)
- `WINDOWS_PLAN.md` documenting Hyper-V implementation strategy and security rationale
### Fixed
- Tidewave CLI download URL (now uses correct `tidewave_app` repo with musl binaries)
### Changed
- Python is now a selectable component managed by mise instead of system apt
## [0.11.0] - 2025-01-25
### Added
- Windows support via Hyper-V for maximum security isolation
- `setup_env_windows.ps1` PowerShell script with full VM provisioning
- Ubuntu cloud image support with cloud-init automation
- SSH key generation for passwordless VM access on Windows
- Hosts file integration for easy `<vmname>.local` access
### Security
- Hyper-V provides stronger isolation than WSL2 (separate kernel, network, filesystem)
- No host integration services enabled by default
## [0.10.0] - 2025-01-25
### Added
- OpenCode: Open-source AI coding assistant with multi-provider support
- Tidewave CLI: Elixir/Phoenix MCP server for AI-powered development
- New component selection options for OpenCode and Tidewave
## [0.9.1] - 2025-01-25
### Fixed
- Add error checking for base64 decode operations in VM provisioning
- Add `set -e` to VM bootstrap script for early failure detection
## [0.9.0] - 2025-01-25
### Security
- Fix rustup pipe-to-shell vulnerability: now downloads to temp file with validation before execution
- Fix SKIP_EXPORTS command injection risk: refactored to use base64-encoded list instead of shell command string
- Fix Playwright symlink path validation: validates executable and path prefix before creating symlinks
## [0.8.0] - 2025-01-25
### Added
- Project memory system using CLAUDE.md, CHANGELOG.md, and README.md
- Versioning rules and documentation update guidelines in CLAUDE.md
## [0.7.0] - 2025-01-25 ## [0.7.0] - 2025-01-25
### Added ### Added

View file

@ -1,18 +1,43 @@
# CLAUDE.md # CLAUDE.md
## Project Memory
This project uses three documentation files as persistent memory. **You must keep these files up to date when making changes:**
| File | Purpose | Update When |
|------|---------|-------------|
| `CLAUDE.md` | Technical context for Claude Code sessions | Adding new patterns, conventions, or implementation details |
| `CHANGELOG.md` | Version history following Keep a Changelog format | Every commit (add entry, bump version) |
| `README.md` | User-facing documentation | Changing user-visible behavior, adding features, or modifying usage |
### Versioning Rules
- Use semantic versioning (MAJOR.MINOR.PATCH)
- Increment PATCH for bug fixes and minor updates
- Increment MINOR for new features and enhancements
- Increment MAJOR for breaking changes
- Tag every commit with its version: `git tag -a vX.Y.Z -m "Description"`
## Project Overview ## 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. This repository provides scripts for creating isolated development sandboxes for Claude Code on macOS (OrbStack) and Windows (Hyper-V). Both platforms offer full VM isolation for safely running AI coding assistants with elevated permissions.
**Supported platforms:**
- **macOS**: OrbStack VMs (ARM64 Apple Silicon)
- **Windows**: Hyper-V VMs (maximum security, stronger than WSL2)
## Repository Structure ## Repository Structure
``` ```
setup_env.sh - Main script (macOS host mode + Linux VM provisioning mode) setup_env.sh - macOS script (OrbStack host mode + Linux VM provisioning)
config.env.example - Example credentials file setup_env_windows.ps1 - Windows script (Hyper-V with cloud-init)
config.env - User credentials (gitignored, created on first run) WINDOWS_PLAN.md - Windows implementation plan and security rationale
.gitignore - Ignores config.env config.env.example - Example credentials file
README.md - User-facing documentation config.env - User credentials (gitignored, created on first run)
CLAUDE.md - This file (context for Claude Code sessions) .gitignore - Ignores config.env
README.md - User-facing documentation
CHANGELOG.md - Version history (Keep a Changelog format)
CLAUDE.md - This file (context for Claude Code sessions)
``` ```
## How the Script Works ## How the Script Works
@ -61,7 +86,7 @@ This means `./setup_env.sh my-vm` on macOS does everything end-to-end.
- `--yes`/`-y` accepts all components without prompting but still allows interactive credential entry - `--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 - `MISE_GLOBAL_CONFIG_FILE` and `MISE_CONFIG_DIR` are set to prevent OrbStack host-mount config pollution
### Security Patterns ### Security Patterns (macOS/Linux)
- **Input validation**: Use `validate_vm_name()`, `validate_vnc_password()`, `validate_safe_input()` for all user inputs - **Input validation**: Use `validate_vm_name()`, `validate_vnc_password()`, `validate_safe_input()` for all user inputs
- **Safe config loading**: Use `load_config_safely()` which parses key=value pairs without shell execution (never `source`) - **Safe config loading**: Use `load_config_safely()` which parses key=value pairs without shell execution (never `source`)
@ -71,9 +96,30 @@ This means `./setup_env.sh my-vm` on macOS does everything end-to-end.
- **Symlinks**: Check with `[ ! -e path ]` (not `[ ! -L path ]`) to avoid overwriting existing files - **Symlinks**: Check with `[ ! -e path ]` (not `[ ! -L path ]`) to avoid overwriting existing files
- **Architecture detection**: Use `detect_architecture()` for portable binary downloads - **Architecture detection**: Use `detect_architecture()` for portable binary downloads
### Editing setup_env_windows.ps1
- PowerShell script for Windows Hyper-V VM creation
- Uses Ubuntu cloud images with cloud-init for automated provisioning
- Supports ARM64 architecture detection for Windows on ARM
- Skip parameters: `-SkipVNC`, `-SkipPostgreSQL`, `-SkipOllama`, `-SkipPlaywright`
- ISO creation fallback chain: oscdimg → WSL genisoimage → IMAPI2 COM
- Log file created at `$env:TEMP\setup_env_windows_<timestamp>.log`
### Security Patterns (Windows)
- **Input validation**: Use `Test-GitName`, `Test-GitEmail`, `Test-VMPassword`, `Test-VNCPassword` for all user inputs
- **Config file security**: ACL set to owner-only before writing content (no race condition)
- **Config validation**: Loaded config.env values are re-validated to detect tampering
- **Checksum verification**: Ubuntu image verified against SHA256 checksums from Canonical
- **Cleanup on failure**: `Invoke-Cleanup` removes partial VM, disk, and ISO on errors
- **Hosts file backup**: Created before modification, removed on success, kept on failure
- **Cloud-init ISO**: Contains passwords in plaintext; user reminded to remove after first boot
### Testing ### Testing
There are no automated tests. To test changes: There are no automated tests. To test changes:
**macOS:**
1. Remove an existing test VM: `orb delete test-sandbox` 1. Remove an existing test VM: `orb delete test-sandbox`
2. Run: `./setup_env.sh test-sandbox` 2. Run: `./setup_env.sh test-sandbox`
3. Verify provisioning completes 3. Verify provisioning completes
@ -81,13 +127,21 @@ There are no automated tests. To test changes:
5. Test VNC: `ssh test-sandbox@orb -- vnc-start`, then `open vnc://test-sandbox.orb.local:5901` 5. Test VNC: `ssh test-sandbox@orb -- vnc-start`, then `open vnc://test-sandbox.orb.local:5901`
6. Clean up: `orb delete test-sandbox` 6. Clean up: `orb delete test-sandbox`
**Windows:**
1. Remove existing test VM: `Stop-VM -Name test-sandbox -Force; Remove-VM -Name test-sandbox -Force`
2. Run: `.\setup_env_windows.ps1 -VMName test-sandbox`
3. Verify provisioning completes (check `/var/log/provision.log` in VM)
4. Test SSH: `ssh -i $env:USERPROFILE\.ssh\id_ed25519_test-sandbox dev@test-sandbox.local`
5. Clean up: `Stop-VM -Name test-sandbox -Force; Remove-VM -Name test-sandbox -Force`
### Security Considerations ### Security Considerations
- `config.env` is chmod 600 and gitignored (stores git name/email only, never VNC password) - `config.env` is chmod 600 / owner-only ACL and gitignored (stores git name/email only, never passwords)
- PostgreSQL uses scram-sha-256 for all TCP connections (peer auth for local socket) - PostgreSQL uses scram-sha-256 for all TCP connections (peer auth for local socket)
- The script refuses to run as root inside the VM - The script refuses to run as root inside the VM
- VNC password is required (min 6 chars), validated to block shell metacharacters, never stored - VNC password is required (min 8 chars), validated to block shell metacharacters, never stored
- VNC binds to all interfaces (`-localhost no`) to allow connections from the macOS host — this is intentional for the OrbStack use case and documented in the vnc-start script - VNC binds to all interfaces (`-localhost no`) to allow connections from the host — this is intentional and documented
- All user inputs are validated before use to prevent command injection - All user inputs are validated before use to prevent command injection
- External scripts (mise, ollama) are downloaded to temp files and validated before execution - External scripts (mise, ollama) are downloaded to temp files and validated before execution
- Host filesystem access is disabled inside the VM for isolation - Host filesystem access is disabled inside the VM for isolation
- Windows: Ubuntu image downloads are verified against SHA256 checksums

View file

@ -1,20 +1,27 @@
# OrbStack Development Sandbox # Secure AI Coding Sandboxes
sDisposable, 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. 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. The VM is a real Linux machine with its own filesystem, network, and process space — complete isolation from your host with easy access for development.
| Platform | Script | Isolation Level |
|----------|--------|-----------------|
| **macOS** | `setup_env.sh` (OrbStack) | Full VM isolation |
| **Windows** | `setup_env_windows.ps1` (Hyper-V) | Full VM isolation |
## Why This Exists ## 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. 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: These scripts create throwaway VMs where Claude can run unrestricted:
- **Isolated filesystem** — Claude can't touch your macOS files, keys, or configs - **Isolated filesystem** — Claude can't touch your host files, keys, or configs
- **Isolated network** — services run on their own IP, no port conflicts with your host - **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 - **Disposable**delete and recreate in one command
- **Multiple VMs** — run separate sandboxes per project with shared git credentials - **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 - **Full access from host** — edit files in your editor, browse databases, view running apps
## Quick Start (macOS)
```bash ```bash
# Create a sandbox, SSH in, run Claude unrestricted # Create a sandbox, SSH in, run Claude unrestricted
@ -25,7 +32,7 @@ claude --dangerously-skip-permissions
If anything goes wrong: `orb delete my-project && ./setup_env.sh my-project` If anything goes wrong: `orb delete my-project && ./setup_env.sh my-project`
## Quick Start ### macOS Setup
```bash ```bash
# Create and provision a VM (one command from macOS) # Create and provision a VM (one command from macOS)
@ -51,11 +58,65 @@ When run manually inside a VM, you're prompted for each component individually:
./setup_env.sh --yes ./setup_env.sh --yes
``` ```
## Requirements ### macOS Requirements
- macOS with Apple Silicon (ARM64) - macOS with Apple Silicon (ARM64)
- [OrbStack](https://orbstack.dev) installed (`brew install orbstack`) - [OrbStack](https://orbstack.dev) installed (`brew install orbstack`)
## Quick Start (Windows)
```powershell
# Run as Administrator
.\setup_env_windows.ps1 -VMName my-project
# Connect via SSH (after provisioning)
ssh -i $env:USERPROFILE\.ssh\id_ed25519_my-project dev@my-project.local
# Run Claude unrestricted
claude --dangerously-skip-permissions
```
If anything goes wrong: `Remove-VM -Name my-project -Force` then run the script again.
### Windows Setup
```powershell
# Create and provision a VM (run as Administrator)
.\setup_env_windows.ps1 -VMName my-sandbox
# Customize resources
.\setup_env_windows.ps1 -VMName my-sandbox -MemoryGB 16 -DiskGB 100 -CPUs 8
# Skip optional components
.\setup_env_windows.ps1 -VMName my-sandbox -SkipVNC -SkipOllama
```
On first run, you'll be prompted for:
- Git commit author name and email (saved to `config.env`)
- VM user password (min 8 chars, not stored)
- VNC password if VNC is enabled (min 8 chars, not stored)
### Windows Requirements
- Windows 10/11 Pro, Enterprise, or Education (Hyper-V not available on Home)
- Hyper-V enabled: `Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All`
- Administrator privileges
- One of: Windows ADK, WSL with genisoimage, or Windows 10+ (IMAPI2 fallback)
### Why Hyper-V (not WSL2)?
WSL2 is convenient but provides weaker isolation:
- Shares kernel with all WSL2 instances
- Host filesystem mounted by default
- Can launch Windows executables from Linux
- Network traffic bypasses Windows firewall
Hyper-V provides **maximum security**:
- Separate kernel per VM
- Complete filesystem isolation
- Own network stack
- No Windows integration by default
## What Gets Installed ## What Gets Installed
All components are optional — deselect what you don't need in the interactive picker. All components are optional — deselect what you don't need in the interactive picker.
@ -70,7 +131,9 @@ All components are optional — deselect what you don't need in the interactive
| Playwright | latest | Browser testing framework | | Playwright | latest | Browser testing framework |
| PostgreSQL | system default | Database | | PostgreSQL | system default | Database |
| Ollama | latest | Local LLM inference | | Ollama | latest | Local LLM inference |
| Claude Code | latest | AI coding assistant | | Claude Code | latest | AI coding assistant (Anthropic) |
| OpenCode | latest | Open-source AI coding assistant (multi-provider) |
| Tidewave | latest | Elixir/Phoenix MCP server for AI tools |
| yq | latest | YAML processor | | yq | latest | YAML processor |
| watchexec | latest | File watcher (via cargo) | | watchexec | latest | File watcher (via cargo) |
| TigerVNC + XFCE | system | VNC access for browser login flows | | TigerVNC + XFCE | system | VNC access for browser login flows |

257
WINDOWS_PLAN.md Normal file
View file

@ -0,0 +1,257 @@
# Windows Hyper-V Implementation Plan
This document describes the plan for implementing secure, isolated development sandboxes on Windows using Hyper-V instead of WSL2.
## Why Hyper-V Over WSL2
### WSL2 Security Limitations
| Issue | Impact |
|-------|--------|
| **Shared kernel** | All WSL2 instances share Microsoft's Linux kernel |
| **Host filesystem access** | `/mnt/c` mounted by default, can access Windows files |
| **Bidirectional execution** | Linux can launch Windows executables |
| **Firewall bypass** | WSL2 outbound traffic bypasses Windows firewall rules |
| **Process visibility** | Windows has limited visibility into WSL2 processes |
| **Credential exposure** | Linux processes can potentially access Windows credentials |
### CVE History
- **CVE-2024-20681**: Local privilege escalation to SYSTEM via WSL
- **CVE-2025-9074**: Container escape via WSL2 filesystem sharing
- **CVE-2025-53788**: Undisclosed flaw in WSL/host communication
### Hyper-V Isolation Model
| Feature | Benefit |
|---------|---------|
| **Separate kernel** | Each VM runs its own Linux kernel |
| **Complete filesystem isolation** | No access to Windows files by default |
| **Own network stack** | Separate IP, no firewall bypass |
| **No Windows integration** | Cannot launch Windows programs |
| **Snapshot support** | Can checkpoint and restore VM state |
| **Hardware isolation** | Uses VT-x/AMD-V virtualization |
## Implementation Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ Windows Host │
│ │
│ ┌─────────────────────┐ ┌─────────────────────────────────┐ │
│ │ setup_env_windows.ps1│ │ Hyper-V Manager │ │
│ │ │ │ │ │
│ │ • Check prereqs │───▶│ ┌──────────────────────────┐ │ │
│ │ • Download image │ │ │ Ubuntu VM │ │ │
│ │ • Create cloud-init │ │ │ │ │ │
│ │ • Provision VM │ │ │ • Isolated filesystem │ │ │
│ │ • Configure network │ │ │ • Own network (NAT) │ │ │
│ └─────────────────────┘ │ │ • Claude Code + tools │ │ │
│ │ │ • No Windows access │ │ │
│ │ └──────────────────────────┘ │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
## Script Workflow
### Phase 1: Prerequisites Check
```powershell
# Verify environment
1. Check Administrator privileges
2. Check Windows edition (Pro/Enterprise/Education required)
3. Check Hyper-V enabled
4. Check for existing VM (handle -Force flag)
5. Find suitable network switch
```
### Phase 2: Configuration
```powershell
# Collect credentials
1. Load config.env if exists (git name/email)
2. Prompt for missing values
3. Prompt for VM password (not stored)
4. Generate SSH key pair for VM access
```
### Phase 3: Image Preparation
```powershell
# Prepare Ubuntu cloud image
1. Download Ubuntu cloud image (VHDX format)
2. Cache in $env:ProgramData\HyperV-DevSandbox\Images
3. Copy and resize for new VM
4. Generate cloud-init ISO with:
- User account configuration
- SSH key injection
- Package installation
- Setup script embedding
```
### Phase 4: VM Creation
```powershell
# Create Hyper-V VM
1. Create Generation 2 VM (UEFI)
2. Configure resources (CPU, RAM, disk)
3. Disable Secure Boot (for Ubuntu)
4. Attach cloud-init ISO
5. Disable integration services for isolation
6. Enable nested virtualization (for Docker)
```
### Phase 5: Provisioning
```powershell
# Start and provision
1. Start VM
2. Wait for IP assignment
3. Add to hosts file (<vmname>.local)
4. Cloud-init runs setup_env.sh internally
5. Display connection instructions
```
## Cloud-Init Configuration
```yaml
#cloud-config
hostname: <vmname>
users:
- name: dev
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys:
- <generated-public-key>
packages:
- openssh-server
- curl
- git
write_files:
- path: /tmp/setup_env.sh
content: <base64-encoded-script>
runcmd:
- bash /tmp/setup_env.sh --non-interactive --yes
```
## Security Hardening
### VM Configuration
```powershell
# Disable integration services for maximum isolation
Disable-VMIntegrationService -Name "Guest Service Interface"
Disable-VMIntegrationService -Name "Heartbeat"
# Keep Time Synchronization and Shutdown for usability
```
### Network Isolation Options
1. **NAT (Default)**: VM can access internet, isolated from LAN
2. **Internal Only**: VM can only communicate with host
3. **Private**: Completely isolated network
### Firewall Rules (Optional)
```powershell
# Allow SSH access only
New-NetFirewallRule -Name "HyperV-SSH-$VMName" `
-Direction Inbound -LocalPort 22 -Protocol TCP `
-RemoteAddress <VM-IP> -Action Allow
```
## Requirements
### Windows Edition
- Windows 10/11 Pro
- Windows 10/11 Enterprise
- Windows 10/11 Education
- Windows Server 2016+
**Not supported**: Windows 10/11 Home (no Hyper-V)
### Hardware
- 64-bit processor with SLAT (Second Level Address Translation)
- VM Monitor Mode extensions (VT-x on Intel, AMD-V on AMD)
- Minimum 4 GB RAM (8+ GB recommended)
- BIOS/UEFI virtualization enabled
### Software
- Hyper-V enabled: `Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All`
- Windows ADK (for cloud-init ISO creation): [Download](https://docs.microsoft.com/en-us/windows-hardware/get-started/adk-install)
## Usage
### Create Sandbox
```powershell
# Basic usage (run as Administrator)
.\setup_env_windows.ps1 -VMName my-project
# With custom resources
.\setup_env_windows.ps1 -VMName my-project -MemoryGB 16 -DiskGB 100 -CPUs 8
# Replace existing VM
.\setup_env_windows.ps1 -VMName my-project -Force
```
### Connect to Sandbox
```powershell
# Via SSH (recommended)
ssh -i $env:USERPROFILE\.ssh\id_ed25519_<vmname> dev@<vmname>.local
# Via Hyper-V console
vmconnect localhost <vmname>
```
### Manage Sandbox
```powershell
# Stop VM
Stop-VM -Name <vmname>
# Start VM
Start-VM -Name <vmname>
# Get VM IP
(Get-VM -Name <vmname> | Get-VMNetworkAdapter).IPAddresses
# Delete VM completely
Stop-VM -Name <vmname> -Force
Remove-VM -Name <vmname> -Force
Remove-Item -Path "$env:ProgramData\HyperV-DevSandbox\VMs\<vmname>" -Recurse -Force
```
## Comparison: OrbStack vs Hyper-V
| Feature | OrbStack (macOS) | Hyper-V (Windows) |
|---------|------------------|-------------------|
| Host OS | macOS only | Windows only |
| Setup complexity | Low | Medium |
| Isolation | Full VM | Full VM |
| DNS | `*.orb.local` auto | Manual hosts file |
| SSH | Automatic | Key-based |
| Filesystem sharing | Configurable | Disabled by default |
| Performance | Near-native | Near-native |
| Nested virtualization | Yes | Yes |
## Known Limitations
1. **Windows ADK Required**: Cloud-init ISO creation requires `oscdimg` from Windows ADK
2. **Manual DNS**: No automatic DNS like OrbStack's `*.orb.local`
3. **Key Management**: SSH keys generated per-VM, stored in user's .ssh folder
4. **No Clipboard Sharing**: Disabled for security (can be enabled if needed)
5. **Generation 2 Only**: Uses UEFI, no legacy BIOS support
## Future Improvements
- [ ] Add option for WSL2 (for users who prefer convenience over security)
- [ ] Implement automatic DNS via Windows hosts file or local DNS server
- [ ] Add PowerShell ISO creation without Windows ADK dependency
- [ ] Support for VM templates/checkpoints
- [ ] Integration with Windows Terminal for easy access
- [ ] Optional WinRM configuration for PowerShell remoting

View file

@ -284,30 +284,34 @@ if [[ "$(uname -s)" == "Darwin" ]]; then
# ---- Interactive component selection ---- # ---- Interactive component selection ----
# Component IDs and descriptions # Component IDs and descriptions
COMP_IDS=(postgresql mise node erlang chromium vnc ollama claude playwright plugins) COMP_IDS=(postgresql mise node python erlang chromium vnc ollama claude opencode playwright plugins)
COMP_NAMES=( COMP_NAMES=(
"PostgreSQL" "PostgreSQL"
"mise" "mise"
"Node.js (LTS)" "Node.js (LTS)"
"Python (latest)"
"Erlang/Elixir" "Erlang/Elixir"
"Chromium" "Chromium"
"VNC + XFCE" "VNC + XFCE"
"Ollama" "Ollama"
"Claude Code" "Claude Code"
"OpenCode"
"Playwright" "Playwright"
"Claude Plugins" "Claude Plugins + Tidewave"
) )
COMP_DESCS=( COMP_DESCS=(
"Database server for local development" "Database server for local development"
"Version manager for Node.js, Erlang, Elixir" "Version manager for Node.js, Python, Erlang, Elixir"
"JavaScript runtime (required for Claude Code)" "JavaScript runtime (required for Claude/OpenCode)"
"Python runtime managed by mise"
"BEAM VM + Elixir functional language" "BEAM VM + Elixir functional language"
"Browser for automation and testing" "Browser for automation and testing"
"Remote desktop for browser-based login flows" "Remote desktop for browser-based login flows"
"Local LLM runner for offline inference" "Local LLM runner for offline inference"
"AI coding assistant CLI from Anthropic" "AI coding assistant CLI from Anthropic"
"Open-source AI coding assistant (multi-provider)"
"Browser testing and automation framework" "Browser testing and automation framework"
"Code review, feature dev, browser automation plugins" "Code review, Tidewave MCP, browser automation"
) )
# All selected by default # All selected by default
@ -390,12 +394,12 @@ if [[ "$(uname -s)" == "Darwin" ]]; then
echo "" echo ""
# Build SKIP env var exports for unselected components # Build list of skipped component IDs (space-separated, safe characters only)
SKIP_EXPORTS="" # Security: Only hardcoded component IDs from COMP_IDS are used, no user input
SKIP_LIST=""
for ((i=0; i<COMP_COUNT; i++)); do for ((i=0; i<COMP_COUNT; i++)); do
if [ "${COMP_SELECTED[$i]}" = "0" ]; then if [ "${COMP_SELECTED[$i]}" = "0" ]; then
UPPER_ID=$(echo "${COMP_IDS[$i]}" | tr '[:lower:]' '[:upper:]') SKIP_LIST="${SKIP_LIST}${COMP_IDS[$i]} "
SKIP_EXPORTS="${SKIP_EXPORTS}export SKIP_${UPPER_ID}=1; "
fi fi
done done
@ -463,19 +467,32 @@ if [[ "$(uname -s)" == "Darwin" ]]; then
orb push -m "$VM_NAME" "$SCRIPT_DIR/setup_env.sh" /tmp/setup_env.sh orb push -m "$VM_NAME" "$SCRIPT_DIR/setup_env.sh" /tmp/setup_env.sh
echo "" echo ""
if [ -n "$SKIP_EXPORTS" ]; then if [ -n "$SKIP_LIST" ]; then
echo -e "${YELLOW}Skipping:${NC} $SKIP_EXPORTS" echo -e "${YELLOW}Skipping:${NC} $SKIP_LIST"
fi fi
echo -e "${YELLOW}Running provisioning inside VM (this will take a while)...${NC}" echo -e "${YELLOW}Running provisioning inside VM (this will take a while)...${NC}"
echo "" echo ""
# Security: Use base64 encoding to safely pass values to VM (Issue #1) # Security: Use base64 encoding to safely pass all values to VM
# This prevents any shell injection through special characters # This prevents any shell injection through special characters
GIT_NAME_B64=$(printf '%s' "$GIT_NAME" | base64) GIT_NAME_B64=$(printf '%s' "$GIT_NAME" | base64)
GIT_EMAIL_B64=$(printf '%s' "$GIT_EMAIL" | base64) GIT_EMAIL_B64=$(printf '%s' "$GIT_EMAIL" | base64)
VNC_PASSWORD_B64=$(printf '%s' "$VNC_PASSWORD" | base64) VNC_PASSWORD_B64=$(printf '%s' "$VNC_PASSWORD" | base64)
SKIP_LIST_B64=$(printf '%s' "$SKIP_LIST" | base64)
orb run -m "$VM_NAME" bash -c "${SKIP_EXPORTS}export GIT_NAME=\$(echo '$GIT_NAME_B64' | base64 -d); export GIT_EMAIL=\$(echo '$GIT_EMAIL_B64' | base64 -d); export VNC_PASSWORD=\$(echo '$VNC_PASSWORD_B64' | base64 -d); bash /tmp/setup_env.sh --non-interactive" # Security: All user-controlled values are base64-encoded before passing to VM
# The decode script sets SKIP_* env vars from the safe SKIP_LIST
orb run -m "$VM_NAME" bash -c "
set -e
export GIT_NAME=\$(echo '$GIT_NAME_B64' | base64 -d) || { echo 'Failed to decode GIT_NAME'; exit 1; }
export GIT_EMAIL=\$(echo '$GIT_EMAIL_B64' | base64 -d) || { echo 'Failed to decode GIT_EMAIL'; exit 1; }
export VNC_PASSWORD=\$(echo '$VNC_PASSWORD_B64' | base64 -d) || { echo 'Failed to decode VNC_PASSWORD'; exit 1; }
for comp in \$(echo '$SKIP_LIST_B64' | base64 -d); do
upper=\$(echo \"\$comp\" | tr '[:lower:]' '[:upper:]')
export \"SKIP_\${upper}=1\"
done
bash /tmp/setup_env.sh --non-interactive
"
echo "" echo ""
echo -e "${GREEN}============================================================================${NC}" echo -e "${GREEN}============================================================================${NC}"
@ -916,6 +933,15 @@ install_node() {
export PATH="$HOME/.local/share/mise/shims:$PATH" export PATH="$HOME/.local/share/mise/shims:$PATH"
} }
install_python() {
export PATH="$HOME/.local/bin:$PATH"
eval "$(~/.local/bin/mise activate bash)"
mise use -g python@latest
export PATH="$HOME/.local/share/mise/shims:$PATH"
# Ensure pip is available via mise python
python -m pip install --upgrade pip
}
install_erlang() { install_erlang() {
export PATH="$HOME/.local/bin:$PATH" export PATH="$HOME/.local/bin:$PATH"
eval "$(~/.local/bin/mise activate bash)" eval "$(~/.local/bin/mise activate bash)"
@ -1050,8 +1076,13 @@ install_playwright() {
# Symlink Playwright's Chromium if no system one (check for existing files) # Symlink Playwright's Chromium if no system one (check for existing files)
if ! command_exists chromium-browser && ! command_exists chromium; then if ! command_exists chromium-browser && ! command_exists chromium; then
local pw_chrome=$(find "$HOME/.cache/ms-playwright" -name "chrome" -type f 2>/dev/null | head -1) local pw_chrome
if [ -n "$pw_chrome" ]; then pw_chrome=$(find "$HOME/.cache/ms-playwright" -name "chrome" -type f 2>/dev/null | head -1)
# Security: Validate the found path before creating symlinks
# - Must not be empty
# - Must be an executable file
# - Must be within the expected playwright cache directory
if [ -n "$pw_chrome" ] && [ -x "$pw_chrome" ] && [[ "$pw_chrome" == "$HOME/.cache/ms-playwright"* ]]; then
[ ! -e /usr/local/bin/chromium ] && sudo ln -sf "$pw_chrome" /usr/local/bin/chromium [ ! -e /usr/local/bin/chromium ] && sudo ln -sf "$pw_chrome" /usr/local/bin/chromium
[ ! -e /usr/local/bin/google-chrome ] && sudo ln -sf "$pw_chrome" /usr/local/bin/google-chrome [ ! -e /usr/local/bin/google-chrome ] && sudo ln -sf "$pw_chrome" /usr/local/bin/google-chrome
echo "Using Playwright's bundled Chromium" echo "Using Playwright's bundled Chromium"
@ -1059,6 +1090,59 @@ install_playwright() {
fi fi
} }
install_opencode() {
export PATH="$HOME/.local/share/mise/shims:$HOME/.local/bin:$PATH"
if command_exists opencode; then
echo "OpenCode already installed"
return 0
fi
npm install -g opencode-ai
}
install_tidewave() {
if command_exists tidewave; then
echo "Tidewave already installed"
return 0
fi
# Download Tidewave CLI binary from tidewave_app releases
# Binary naming: tidewave-cli-<arch>-unknown-linux-musl
local machine_arch
machine_arch=$(uname -m)
local tidewave_arch=""
case "$machine_arch" in
x86_64) tidewave_arch="x86_64" ;;
aarch64) tidewave_arch="aarch64" ;;
*)
log_error "Unsupported architecture for Tidewave: $machine_arch"
return 1
;;
esac
# Get latest release URL from GitHub (tidewave_app repo)
local release_url="https://github.com/tidewave-ai/tidewave_app/releases/latest/download/tidewave-cli-${tidewave_arch}-unknown-linux-musl"
# Security: Download to temp file first, validate, then install
local temp_file
temp_file=$(mktemp)
if curl -fsSL "$release_url" -o "$temp_file"; then
# Verify it's an executable (ELF binary)
if file "$temp_file" | grep -q "ELF"; then
sudo mv "$temp_file" /usr/local/bin/tidewave
sudo chmod +x /usr/local/bin/tidewave
echo "Tidewave CLI installed"
else
log_error "Downloaded tidewave is not a valid binary"
rm -f "$temp_file"
return 1
fi
else
log_error "Failed to download Tidewave CLI"
rm -f "$temp_file"
return 1
fi
}
install_plugins() { install_plugins() {
export PATH="$HOME/.local/share/mise/shims:$HOME/.local/bin:$PATH" export PATH="$HOME/.local/share/mise/shims:$HOME/.local/bin:$PATH"
mkdir -p ~/.config/claude mkdir -p ~/.config/claude
@ -1078,6 +1162,12 @@ install_plugins() {
echo "Adding MCP servers..." echo "Adding MCP servers..."
claude mcp add playwright --scope user -- npx @anthropic-ai/mcp-server-playwright 2>/dev/null || true claude mcp add playwright --scope user -- npx @anthropic-ai/mcp-server-playwright 2>/dev/null || true
claude mcp add superpowers-chrome --scope user -- npx github:obra/superpowers-chrome --headless 2>/dev/null || true claude mcp add superpowers-chrome --scope user -- npx github:obra/superpowers-chrome --headless 2>/dev/null || true
# Note: Tidewave MCP is configured per-project when running a Phoenix app
# Use: claude mcp add --transport http tidewave http://localhost:4000/tidewave/mcp
echo ""
echo "Tidewave MCP: Add per-project with your Phoenix app running:"
echo " claude mcp add --transport http tidewave http://localhost:PORT/tidewave/mcp"
} }
install_base_deps() { install_base_deps() {
@ -1115,8 +1205,29 @@ install_base_deps() {
if command_exists cargo; then if command_exists cargo; then
cargo install watchexec-cli cargo install watchexec-cli
else else
# Fallback: install cargo first, then watchexec # Security: Download rustup script first, validate, then execute (Issue #3)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y # Same pattern as mise/ollama - never pipe directly to shell
local rustup_script
rustup_script=$(mktemp)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o "$rustup_script"
# Verify it's a shell script
if ! head -1 "$rustup_script" | grep -qE '^#!\s*/(bin|usr/bin)/(env\s+)?(ba)?sh'; then
log_error "Downloaded rustup script doesn't have a valid shell shebang"
rm -f "$rustup_script"
return 1
fi
# Check file size is reasonable (not empty, not huge)
local script_size
script_size=$(wc -c < "$rustup_script")
if [ "$script_size" -lt 100 ] || [ "$script_size" -gt 2000000 ]; then
log_error "Downloaded rustup script has suspicious size: $script_size bytes"
rm -f "$rustup_script"
return 1
fi
sh "$rustup_script" -y
rm -f "$rustup_script"
source "$HOME/.cargo/env" source "$HOME/.cargo/env"
cargo install watchexec-cli cargo install watchexec-cli
fi fi
@ -1201,12 +1312,15 @@ register_step "base" "System packages (git, build tools, curl)"
register_step "postgresql" "PostgreSQL database server" register_step "postgresql" "PostgreSQL database server"
register_step "mise" "mise version manager" register_step "mise" "mise version manager"
register_step "node" "Node.js LTS runtime" register_step "node" "Node.js LTS runtime"
register_step "python" "Python (mise-managed)"
register_step "erlang" "Erlang/OTP VM" register_step "erlang" "Erlang/OTP VM"
register_step "elixir" "Elixir language" register_step "elixir" "Elixir language"
register_step "chromium" "Chromium browser" register_step "chromium" "Chromium browser"
register_step "vnc" "VNC server + XFCE desktop" register_step "vnc" "VNC server + XFCE desktop"
register_step "ollama" "Ollama local LLM runner" register_step "ollama" "Ollama local LLM runner"
register_step "claude" "Claude Code CLI" register_step "claude" "Claude Code CLI"
register_step "opencode" "OpenCode CLI"
register_step "tidewave" "Tidewave CLI (Elixir MCP)"
register_step "playwright" "Playwright browser automation" register_step "playwright" "Playwright browser automation"
register_step "plugins" "Claude Code plugins" register_step "plugins" "Claude Code plugins"
@ -1214,21 +1328,29 @@ register_step "plugins" "Claude Code plugins"
should_skip "postgresql" && skip_step "postgresql" should_skip "postgresql" && skip_step "postgresql"
should_skip "mise" && skip_step "mise" should_skip "mise" && skip_step "mise"
should_skip "node" && skip_step "node" should_skip "node" && skip_step "node"
should_skip "python" && skip_step "python"
should_skip "erlang" && skip_step "erlang" should_skip "erlang" && skip_step "erlang"
should_skip "chromium" && skip_step "chromium" should_skip "chromium" && skip_step "chromium"
should_skip "vnc" && skip_step "vnc" should_skip "vnc" && skip_step "vnc"
should_skip "ollama" && skip_step "ollama" should_skip "ollama" && skip_step "ollama"
should_skip "claude" && skip_step "claude" should_skip "claude" && skip_step "claude"
should_skip "opencode" && skip_step "opencode"
should_skip "playwright" && skip_step "playwright" should_skip "playwright" && skip_step "playwright"
should_skip "plugins" && skip_step "plugins" should_skip "plugins" && skip_step "plugins"
# Elixir skipped if Erlang skipped # Elixir skipped if Erlang skipped
[ "${STEP_STATUS[erlang]}" = "skipped" ] && skip_step "elixir" [ "${STEP_STATUS[erlang]}" = "skipped" ] && skip_step "elixir"
# Claude/Playwright skipped if Node skipped # Tidewave skipped if Erlang skipped (it's for Phoenix/Elixir projects)
[ "${STEP_STATUS[erlang]}" = "skipped" ] && skip_step "tidewave"
# Python skipped if mise skipped
[ "${STEP_STATUS[mise]}" = "skipped" ] && skip_step "python"
# Claude/OpenCode/Playwright skipped if Node skipped
if [ "${STEP_STATUS[node]}" = "skipped" ]; then if [ "${STEP_STATUS[node]}" = "skipped" ]; then
[ "${STEP_STATUS[mise]}" = "skipped" ] || true # mise can still install
skip_step "claude" skip_step "claude"
skip_step "opencode"
skip_step "playwright" skip_step "playwright"
skip_step "plugins" skip_step "plugins"
fi fi
@ -1267,20 +1389,24 @@ run_step_sync "base" install_base_deps || true
wait_for_running_steps wait_for_running_steps
# ============================================================================ # ============================================================================
# Phase 3: Node + Erlang (parallel, both need mise) # Phase 3: Node + Python + Erlang (parallel, all need mise)
# ============================================================================ # ============================================================================
if [ "${STEP_STATUS[mise]}" = "done" ]; then if [ "${STEP_STATUS[mise]}" = "done" ]; then
[ "${STEP_STATUS[node]}" != "skipped" ] && start_step "node" install_node [ "${STEP_STATUS[node]}" != "skipped" ] && start_step "node" install_node
[ "${STEP_STATUS[python]}" != "skipped" ] && start_step "python" install_python
[ "${STEP_STATUS[erlang]}" != "skipped" ] && start_step "erlang" install_erlang [ "${STEP_STATUS[erlang]}" != "skipped" ] && start_step "erlang" install_erlang
wait_for_running_steps wait_for_running_steps
fi fi
# ============================================================================ # ============================================================================
# Phase 4: Elixir + Claude + Playwright (Elixir needs Erlang, others need Node) # Phase 4: Elixir + Claude + OpenCode + Playwright + Tidewave
# (Elixir/Tidewave need Erlang, others need Node)
# ============================================================================ # ============================================================================
[ "${STEP_STATUS[erlang]}" = "done" ] && [ "${STEP_STATUS[elixir]}" != "skipped" ] && start_step "elixir" install_elixir [ "${STEP_STATUS[erlang]}" = "done" ] && [ "${STEP_STATUS[elixir]}" != "skipped" ] && start_step "elixir" install_elixir
[ "${STEP_STATUS[node]}" = "done" ] && [ "${STEP_STATUS[claude]}" != "skipped" ] && start_step "claude" install_claude [ "${STEP_STATUS[erlang]}" = "done" ] && [ "${STEP_STATUS[tidewave]}" != "skipped" ] && start_step "tidewave" install_tidewave
[ "${STEP_STATUS[node]}" = "done" ] && [ "${STEP_STATUS[playwright]}" != "skipped" ] && start_step "playwright" install_playwright [ "${STEP_STATUS[node]}" = "done" ] && [ "${STEP_STATUS[claude]}" != "skipped" ] && start_step "claude" install_claude
[ "${STEP_STATUS[node]}" = "done" ] && [ "${STEP_STATUS[opencode]}" != "skipped" ] && start_step "opencode" install_opencode
[ "${STEP_STATUS[node]}" = "done" ] && [ "${STEP_STATUS[playwright]}" != "skipped" ] && start_step "playwright" install_playwright
wait_for_running_steps wait_for_running_steps
@ -1354,6 +1480,12 @@ fi
if [ "${STEP_STATUS[claude]}" = "done" ]; then if [ "${STEP_STATUS[claude]}" = "done" ]; then
echo -e "${CYAN}Claude:${NC} claude" echo -e "${CYAN}Claude:${NC} claude"
fi fi
if [ "${STEP_STATUS[opencode]}" = "done" ]; then
echo -e "${CYAN}OpenCode:${NC} opencode"
fi
if [ "${STEP_STATUS[tidewave]}" = "done" ]; then
echo -e "${CYAN}Tidewave:${NC} tidewave (run in Phoenix project dir)"
fi
if [ "${STEP_STATUS[ollama]}" = "done" ]; then if [ "${STEP_STATUS[ollama]}" = "done" ]; then
echo -e "${CYAN}Ollama:${NC} ollama run llama3.2" echo -e "${CYAN}Ollama:${NC} ollama run llama3.2"
fi fi

1076
setup_env_windows.ps1 Normal file

File diff suppressed because it is too large Load diff