Compare commits

...

12 commits
v0.5.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
guessthepw
b3ed5e66a5 Add CHANGELOG.md with version history
Documents all releases from v0.1.0 through v0.6.0 following
Keep a Changelog format with semantic versioning.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 09:28:50 -05:00
guessthepw
9ee89df424 Improves security and simplifies version management
Switches all tools to use latest versions by default instead of pinning specific versions, reducing maintenance overhead while still allowing customization for Erlang and Elixir.

Enhances security measures by documenting input validation patterns, safe config loading practices, and credential handling procedures. Updates PostgreSQL authentication to use scram-sha-256 for all TCP connections.

Clarifies that VNC passwords are never stored and must be entered each time, improving the security posture of credential management.

Simplifies tool installation by removing version pinning constraints and using native package managers where appropriate.
2026-01-25 09:25:57 -05:00
6 changed files with 1850 additions and 123 deletions

169
CHANGELOG.md Normal file
View file

@ -0,0 +1,169 @@
# Changelog
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/),
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
### Added
- CHANGELOG.md with version history following Keep a Changelog format
## [0.6.0] - 2025-01-25
### Changed
- All tools now use latest versions by default instead of pinning specific versions
- PostgreSQL authentication uses scram-sha-256 for all TCP connections
- Simplified tool installation by removing version pinning constraints
### Security
- VNC passwords are never stored and must be entered each time
- Added documentation for input validation patterns and safe config loading
## [0.5.0] - 2025-01-25
### Security
- Prevents shell injection through input validation and safe parameter passing
- Replaces direct sourcing with manual config parsing to avoid code execution
- Downloads and validates install scripts before execution instead of piping
- Uses base64 encoding for secure VM parameter transmission
- Adds checksum verification for binary downloads
- Creates secure temporary directories and files with proper permissions
## [0.4.0] - 2025-01-25
### Changed
- Replaces sequential installation with parallel step execution
- Introduces real-time progress dashboard with spinner and status
- Removes color variables to improve terminal compatibility
- Restructures logging with per-step files for better debugging
### Performance
- Significantly reduces total setup time by running independent steps concurrently
## [0.3.0] - 2025-01-25
### Added
- Dual-mode operation: orchestration on macOS, provisioning on Linux
- Interactive component selection with visual menu interface
- VNC desktop access for OAuth workflows and browser-based tasks
### Security
- Secure VM creation with disabled host filesystem access
## [0.2.0] - 2025-01-25
### Added
- OrbStack development sandbox setup script
- mise version manager with Node.js, Erlang, and Elixir support
- PostgreSQL 16 with remote access configuration
- Claude Code integration with multiple plugin marketplaces
- Chromium browser and Playwright for automation tasks
## [0.1.0] - 2025-01-25
### Added
- Initial project structure

View file

@ -1,17 +1,42 @@
# 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)
setup_env_windows.ps1 - Windows script (Hyper-V with cloud-init)
WINDOWS_PLAN.md - Windows implementation plan and security rationale
config.env.example - Example credentials file config.env.example - Example credentials file
config.env - User credentials (gitignored, created on first run) config.env - User credentials (gitignored, created on first run)
.gitignore - Ignores config.env .gitignore - Ignores config.env
README.md - User-facing documentation README.md - User-facing documentation
CHANGELOG.md - Version history (Keep a Changelog format)
CLAUDE.md - This file (context for Claude Code sessions) CLAUDE.md - This file (context for Claude Code sessions)
``` ```
@ -28,11 +53,11 @@ This means `./setup_env.sh my-vm` on macOS does everything end-to-end.
- **Target environment**: OrbStack Ubuntu VM on macOS Apple Silicon (ARM64) - **Target environment**: OrbStack Ubuntu VM on macOS Apple Silicon (ARM64)
- **Version manager**: mise (manages Node.js, Erlang, Elixir) - **Version manager**: mise (manages Node.js, Erlang, Elixir)
- **Language versions**: Configured at the top of `setup_env.sh` as variables (`ERLANG_VERSION`, `ELIXIR_VERSION`) - **Versions**: All tools use latest by default. Erlang/Elixir versions can be configured at the top of `setup_env.sh` (`ERLANG_VERSION`, `ELIXIR_VERSION`) - set to "latest" or pin to specific versions
- **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) - **PostgreSQL auth**: Peer for local socket, scram-sha-256 for all TCP connections (localhost and network)
- **Browser**: Chromium (no Chrome ARM64 Linux builds exist), symlinked to `google-chrome` - **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` - **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 - **Shared credentials**: `config.env` stores git name/email only; VNC password is prompted each time (never stored)
## Working on This Project ## Working on This Project
@ -61,9 +86,40 @@ 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 (macOS/Linux)
- **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`)
- **Credential passing**: Base64-encode values before passing to VM via `orb run` to prevent shell injection
- **Download helper**: Use `download_verified_binary()` for binary downloads (supports optional checksum verification)
- **Temp files**: Always use `mktemp` with restrictive permissions (`chmod 600`/`700`)
- **Symlinks**: Check with `[ ! -e path ]` (not `[ ! -L path ]`) to avoid overwriting existing files
- **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
@ -71,10 +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 - `config.env` is chmod 600 / owner-only ACL and gitignored (stores git name/email only, never passwords)
- PostgreSQL remote access uses scram-sha-256 (not trust) - 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) - 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 - 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
- External scripts (mise, ollama) are downloaded to temp files and validated before execution
- Host filesystem access is disabled inside the VM for isolation
- Windows: Ubuntu image downloads are verified against SHA256 checksums

115
README.md
View file

@ -1,20 +1,27 @@
# OrbStack Development Sandbox # Secure AI Coding Sandboxes
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. 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,14 +32,14 @@ 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)
./setup_env.sh my-sandbox ./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. On first run, you'll be prompted for git commit author name and email. 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. If you select VNC, you'll be prompted for a VNC password (this is never stored and must be entered each time).
```bash ```bash
# Create additional VMs — reuses config.env, shows component picker # Create additional VMs — reuses config.env, shows component picker
@ -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.
@ -64,13 +125,17 @@ All components are optional — deselect what you don't need in the interactive
|------|---------|---------| |------|---------|---------|
| mise | latest | Version manager for runtimes | | mise | latest | Version manager for runtimes |
| Node.js | LTS | JavaScript runtime | | Node.js | LTS | JavaScript runtime |
| Erlang | 28.3.1 | BEAM VM | | Erlang | latest | BEAM VM |
| Elixir | 1.19.5-otp-28 | Elixir language | | Elixir | latest | Elixir language |
| Chromium | system | Browser automation target | | Chromium | system | Browser automation target |
| 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 |
| watchexec | latest | File watcher (via cargo) |
| TigerVNC + XFCE | system | VNC access for browser login flows | | TigerVNC + XFCE | system | VNC access for browser login flows |
## Connecting from macOS ## Connecting from macOS
@ -177,7 +242,7 @@ A superuser matching your Linux username and a `dev` database are created automa
**Auth model:** **Auth model:**
- Local socket (`psql dev`): peer auth (OS username must match PG role) - Local socket (`psql dev`): peer auth (OS username must match PG role)
- Localhost TCP (127.0.0.1): trust (passwordless) - Localhost TCP (127.0.0.1): scram-sha-256 (password required)
- Host network (192.168.0.0/16, i.e., from macOS): scram-sha-256 - Host network (192.168.0.0/16, i.e., from macOS): scram-sha-256
### From inside the VM ### From inside the VM
@ -246,16 +311,18 @@ The script installs these plugins at user scope:
## Configuration ## Configuration
Edit the version variables at the top of `setup_env.sh`: All tools are configured to use the latest versions by default. Erlang and Elixir versions can be customized in `setup_env.sh`:
```bash ```bash
ERLANG_VERSION="28.3.1" ERLANG_VERSION="latest" # or pin to specific version like "28.3.1"
ELIXIR_VERSION="1.19.5-otp-28" ELIXIR_VERSION="latest" # or pin to specific version like "1.19.5-otp-28"
``` ```
### Shared credentials ### Shared credentials
Credentials are stored in `config.env` (gitignored). To reset: Git credentials (name, email) are stored in `config.env` (gitignored). VNC passwords are never stored and must be entered each time you create a VM with VNC enabled.
To reset git credentials:
```bash ```bash
rm config.env rm config.env
@ -294,4 +361,16 @@ The VM provisioning script is safe to run multiple times. It checks for existing
## Logs ## Logs
Each provisioning run creates a log file at `/tmp/setup_env_<timestamp>.log` inside the VM with detailed output from package installations and any errors. Each provisioning run creates a log file at `/tmp/setup_env_<timestamp>.log` inside the VM with detailed output from package installations and any errors. Log files are created with mode 600 (owner-only access).
## Security
The script includes several security hardening measures:
- **Input validation**: VM names, VNC passwords, and git credentials are validated against strict patterns
- **No credential storage**: VNC passwords are prompted each time and never written to disk
- **Safe credential passing**: Values are base64-encoded when passed to the VM to prevent shell injection
- **Config file security**: `config.env` is created with mode 600 and parsed safely (not sourced)
- **Secure temp files**: Uses `mktemp` with restrictive permissions for all temporary files
- **Host filesystem isolation**: macOS home directory access is disabled inside the VM
- **PostgreSQL hardening**: Uses peer auth for local sockets, scram-sha-256 for network connections

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

@ -25,17 +25,11 @@
set -euo pipefail set -euo pipefail
# ============================================================================ # ============================================================================
# Configuration - edit versions here # Configuration
# ============================================================================ # ============================================================================
ERLANG_VERSION="28.3.1" # Use 'latest' for mise-managed tools to always get the newest version
ELIXIR_VERSION="1.19.5-otp-28" ERLANG_VERSION="latest"
PLAYWRIGHT_VERSION="1.50.1" ELIXIR_VERSION="latest"
# Claude Code version - check https://www.npmjs.com/package/@anthropic-ai/claude-code for latest
CLAUDE_CODE_VERSION="1.0.16"
# yq version - check https://github.com/mikefarah/yq/releases for latest
YQ_VERSION="4.45.1"
# watchexec version - check https://github.com/watchexec/watchexec/releases for latest
WATCHEXEC_VERSION="2.3.2"
# ============================================================================ # ============================================================================
# Helpers # Helpers
@ -290,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
@ -396,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
@ -469,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}"
@ -922,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)"
@ -1046,20 +1066,23 @@ install_ollama() {
install_claude() { install_claude() {
export PATH="$HOME/.local/share/mise/shims:$HOME/.local/bin:$PATH" export PATH="$HOME/.local/share/mise/shims:$HOME/.local/bin:$PATH"
# Security: Pin to specific version to prevent supply chain attacks npm install -g @anthropic-ai/claude-code
npm install -g "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}"
} }
install_playwright() { install_playwright() {
export PATH="$HOME/.local/share/mise/shims:$HOME/.local/bin:$PATH" export PATH="$HOME/.local/share/mise/shims:$HOME/.local/bin:$PATH"
# Security: Install specific version globally, then use it (avoids npx auto-download) npm install -g playwright
npm install -g "playwright@${PLAYWRIGHT_VERSION}"
playwright install --with-deps chromium playwright install --with-deps chromium
# 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"
@ -1067,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
@ -1086,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() {
@ -1105,7 +1187,7 @@ install_base_deps() {
sudo ln -sf "$(which fdfind)" /usr/local/bin/fd sudo ln -sf "$(which fdfind)" /usr/local/bin/fd
fi fi
# yq - with architecture detection, version pinning, and checksum verification # yq - with architecture detection (always latest)
if ! command_exists yq; then if ! command_exists yq; then
local yq_arch local yq_arch
local arch local arch
@ -1114,65 +1196,41 @@ install_base_deps() {
amd64) yq_arch="amd64" ;; amd64) yq_arch="amd64" ;;
arm64) yq_arch="arm64" ;; arm64) yq_arch="arm64" ;;
esac esac
local yq_binary="yq_linux_${yq_arch}" local yq_url="https://github.com/mikefarah/yq/releases/latest/download/yq_linux_${yq_arch}"
local yq_url="https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/${yq_binary}"
local yq_checksum_url="https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/checksums"
# Fetch and verify checksum
local temp_dir
temp_dir=$(mktemp -d)
curl -fsSL "$yq_checksum_url" -o "$temp_dir/checksums"
local expected_checksum
expected_checksum=$(grep "${yq_binary}$" "$temp_dir/checksums" | awk '{print $1}')
if [ -n "$expected_checksum" ]; then
download_verified_binary "$yq_url" "/usr/local/bin/yq" "$expected_checksum"
else
log_error "Could not find checksum for yq, downloading without verification"
download_verified_binary "$yq_url" "/usr/local/bin/yq" download_verified_binary "$yq_url" "/usr/local/bin/yq"
fi fi
rm -rf "$temp_dir"
fi
# watchexec - with architecture detection, version pinning, and checksum verification # watchexec - install via cargo (always latest)
if ! command_exists watchexec; then if ! command_exists watchexec; then
local watchexec_arch if command_exists cargo; then
local arch cargo install watchexec-cli
arch=$(detect_architecture) else
case "$arch" in # Security: Download rustup script first, validate, then execute (Issue #3)
amd64) watchexec_arch="x86_64-unknown-linux-gnu" ;; # Same pattern as mise/ollama - never pipe directly to shell
arm64) watchexec_arch="aarch64-unknown-linux-gnu" ;; local rustup_script
esac rustup_script=$(mktemp)
local watchexec_tarball="watchexec-${WATCHEXEC_VERSION}-${watchexec_arch}.tar.xz" curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o "$rustup_script"
local watchexec_url="https://github.com/watchexec/watchexec/releases/download/v${WATCHEXEC_VERSION}/${watchexec_tarball}"
local watchexec_checksum_url="https://github.com/watchexec/watchexec/releases/download/v${WATCHEXEC_VERSION}/SHA512SUMS"
local temp_dir # Verify it's a shell script
temp_dir=$(mktemp -d) 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"
# Fetch checksum file rm -f "$rustup_script"
curl -fsSL "$watchexec_checksum_url" -o "$temp_dir/SHA512SUMS" 2>/dev/null || true
local expected_checksum
expected_checksum=$(grep "${watchexec_tarball}$" "$temp_dir/SHA512SUMS" 2>/dev/null | awk '{print $1}')
if curl -fsSL "$watchexec_url" -o "$temp_dir/watchexec.tar.xz"; then
# Verify checksum if available (SHA512 for watchexec)
if [ -n "$expected_checksum" ]; then
local actual_checksum
actual_checksum=$(sha512sum "$temp_dir/watchexec.tar.xz" | awk '{print $1}')
if [ "$actual_checksum" != "$expected_checksum" ]; then
log_error "Checksum mismatch for watchexec"
rm -rf "$temp_dir"
return 1 return 1
fi 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 fi
tar -xJf "$temp_dir/watchexec.tar.xz" -C "$temp_dir" --strip-components=1
sudo mv "$temp_dir/watchexec" /usr/local/bin/watchexec sh "$rustup_script" -y
sudo chmod +x /usr/local/bin/watchexec rm -f "$rustup_script"
else source "$HOME/.cargo/env"
log_error "Failed to download watchexec" cargo install watchexec-cli
fi fi
rm -rf "$temp_dir"
fi fi
# direnv hook # direnv hook
@ -1254,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"
@ -1267,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
@ -1320,19 +1389,23 @@ 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[erlang]}" = "done" ] && [ "${STEP_STATUS[tidewave]}" != "skipped" ] && start_step "tidewave" install_tidewave
[ "${STEP_STATUS[node]}" = "done" ] && [ "${STEP_STATUS[claude]}" != "skipped" ] && start_step "claude" install_claude [ "${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 [ "${STEP_STATUS[node]}" = "done" ] && [ "${STEP_STATUS[playwright]}" != "skipped" ] && start_step "playwright" install_playwright
wait_for_running_steps wait_for_running_steps
@ -1407,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