mirror of
https://github.com/wirenboard/agent-vm.git
synced 2026-07-09 16:00:54 +00:00
first commit
This commit is contained in:
commit
f865122778
4 changed files with 302 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.DS_Store
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2026 Sylvain Zimmer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
99
README.md
Normal file
99
README.md
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
# agent-vm
|
||||
|
||||
Run AI coding agents inside sandboxed Linux VMs. The agent gets full autonomy while your host system stays safe.
|
||||
|
||||
Uses [Lima](https://lima-vm.io/) to create lightweight Debian VMs on macOS and Linux. Ships with dev tools, Docker, and a headless Chrome browser with [Chrome DevTools MCP](https://github.com/ChromeDevTools/chrome-devtools-mcp) pre-configured.
|
||||
|
||||
Currently supports [Claude Code](https://claude.ai/code). Other agents (Codex, etc.) can be added in the future.
|
||||
|
||||
Feedbacks welcome!
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- macOS or Linux
|
||||
- [Lima](https://lima-vm.io/docs/installation/) (installed automatically via Homebrew if available)
|
||||
- A [Claude subscription](https://claude.ai/) (Pro, Max, or Team)
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
git clone https://github.com/sylvinus/agent-vm.git
|
||||
cd agent-vm
|
||||
|
||||
# Add to your shell config
|
||||
echo "source $(pwd)/claude-vm.sh" >> ~/.zshrc # zsh
|
||||
echo "source $(pwd)/claude-vm.sh" >> ~/.bashrc # or bash
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### One-time setup
|
||||
|
||||
```bash
|
||||
claude-vm-setup
|
||||
```
|
||||
|
||||
Creates a VM template with dev tools, Docker, Chromium, and Claude Code pre-installed. You'll be prompted to authenticate Claude during setup.
|
||||
|
||||
### Run Claude in a VM
|
||||
|
||||
```bash
|
||||
cd your-project
|
||||
claude-vm
|
||||
```
|
||||
|
||||
Clones the template into a fresh VM, mounts your current directory, and runs `claude --dangerously-skip-permissions`. The VM is deleted when Claude exits.
|
||||
|
||||
### Debug shell
|
||||
|
||||
```bash
|
||||
claude-vm-shell
|
||||
```
|
||||
|
||||
Same as `claude-vm` but drops you into a bash shell instead.
|
||||
|
||||
## Customization
|
||||
|
||||
### Per-user: `~/.claude-vm.setup.sh`
|
||||
|
||||
Create this file in your home directory to install extra tools into the VM template. It runs once during `claude-vm-setup`, as the default VM user (with sudo available):
|
||||
|
||||
```bash
|
||||
# ~/.claude-vm.setup.sh
|
||||
sudo apt-get install -y postgresql-client
|
||||
pip install pandas numpy
|
||||
```
|
||||
|
||||
### Per-project: `.claude-vm.runtime.sh`
|
||||
|
||||
Create this file at the root of any project. It runs inside the cloned VM each time you call `claude-vm`, just before Claude starts. Use it for project-specific setup like installing dependencies or starting services:
|
||||
|
||||
```bash
|
||||
# your-project/.claude-vm.runtime.sh
|
||||
npm install
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
1. **`claude-vm-setup`** creates a Debian 13 VM with Lima, installs dev tools + Chrome + Claude Code, and stops it as a reusable template
|
||||
2. **`claude-vm`** clones the template, mounts your working directory read-write, runs optional `.claude-vm.runtime.sh`, then launches Claude with full permissions
|
||||
3. On exit, the cloned VM is stopped and deleted. The template persists for reuse
|
||||
|
||||
Ports opened inside the VM (e.g. by Docker containers) are automatically forwarded to your host by Lima.
|
||||
|
||||
## What's in the VM
|
||||
|
||||
| Category | Packages |
|
||||
|----------|----------|
|
||||
| Core | git, curl, wget, build-essential, jq |
|
||||
| Python | python3, pip, venv |
|
||||
| Node.js | Node.js 22 (via NodeSource) |
|
||||
| Search | ripgrep, fd-find |
|
||||
| Browser | Chromium (headless), xvfb |
|
||||
| Containers | docker |
|
||||
| AI | Claude Code, Chrome DevTools MCP server |
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
181
claude-vm.sh
Normal file
181
claude-vm.sh
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# agent-vm / claude-vm: Run Claude Code inside a sandboxed Lima VM
|
||||
# Part of https://github.com/sylvinus/agent-vm
|
||||
#
|
||||
# Source this file in your shell config:
|
||||
# source /path/to/agent-vm/claude-vm.sh
|
||||
#
|
||||
# Functions:
|
||||
# claude-vm-setup - Create the VM template (run once)
|
||||
# claude-vm - Run Claude in a fresh VM with cwd mounted
|
||||
# claude-vm-shell - Open a debug shell in a fresh VM
|
||||
|
||||
CLAUDE_VM_TEMPLATE="claude-template"
|
||||
|
||||
claude-vm-setup() {
|
||||
if ! command -v limactl &>/dev/null; then
|
||||
if command -v brew &>/dev/null; then
|
||||
echo "Installing Lima..."
|
||||
brew install lima
|
||||
else
|
||||
echo "Error: Lima is required. Install from https://lima-vm.io/docs/installation/" >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
limactl stop "$CLAUDE_VM_TEMPLATE" &>/dev/null
|
||||
limactl delete "$CLAUDE_VM_TEMPLATE" --force &>/dev/null
|
||||
|
||||
echo "Creating VM template..."
|
||||
limactl create --name="$CLAUDE_VM_TEMPLATE" template:debian-13 \
|
||||
--set '.mounts=[]' \
|
||||
--disk=20 \
|
||||
--memory=8 \
|
||||
--tty=false
|
||||
limactl start "$CLAUDE_VM_TEMPLATE"
|
||||
|
||||
# Disable needrestart's interactive prompts
|
||||
limactl shell "$CLAUDE_VM_TEMPLATE" sudo bash -c 'mkdir -p /etc/needrestart/conf.d && echo "\$nrconf{restart} = '"'"'a'"'"';" > /etc/needrestart/conf.d/no-prompt.conf'
|
||||
|
||||
echo "Installing base packages..."
|
||||
limactl shell "$CLAUDE_VM_TEMPLATE" sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
limactl shell "$CLAUDE_VM_TEMPLATE" sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
git curl wget build-essential \
|
||||
python3 python3-pip python3-venv \
|
||||
jq ripgrep fd-find htop \
|
||||
unzip zip \
|
||||
docker.io
|
||||
|
||||
# Add user to docker group
|
||||
limactl shell "$CLAUDE_VM_TEMPLATE" bash -c 'sudo usermod -aG docker $(whoami)'
|
||||
|
||||
# Install Node.js 22 (needed for MCP servers)
|
||||
echo "Installing Node.js 22..."
|
||||
limactl shell "$CLAUDE_VM_TEMPLATE" bash -c "curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -"
|
||||
limactl shell "$CLAUDE_VM_TEMPLATE" sudo DEBIAN_FRONTEND=noninteractive apt-get install -y nodejs
|
||||
|
||||
# Install Chromium and dependencies for headless browsing
|
||||
echo "Installing Chromium..."
|
||||
limactl shell "$CLAUDE_VM_TEMPLATE" sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
chromium \
|
||||
fonts-liberation \
|
||||
xvfb
|
||||
# Symlink so tools looking for google-chrome find Chromium
|
||||
limactl shell "$CLAUDE_VM_TEMPLATE" sudo ln -sf /usr/bin/chromium /usr/bin/google-chrome
|
||||
limactl shell "$CLAUDE_VM_TEMPLATE" sudo ln -sf /usr/bin/chromium /usr/bin/google-chrome-stable
|
||||
limactl shell "$CLAUDE_VM_TEMPLATE" bash -c 'sudo mkdir -p /opt/google/chrome && sudo ln -sf /usr/bin/chromium /opt/google/chrome/chrome'
|
||||
|
||||
# Install Claude Code
|
||||
echo "Installing Claude Code..."
|
||||
limactl shell "$CLAUDE_VM_TEMPLATE" bash -c "curl -fsSL https://claude.ai/install.sh | bash"
|
||||
limactl shell "$CLAUDE_VM_TEMPLATE" bash -c 'echo "export PATH=\$HOME/.local/bin:\$HOME/.claude/local/bin:\$PATH" >> ~/.bashrc'
|
||||
|
||||
# Authenticate Claude (saves token in template, inherited by clones)
|
||||
echo "Setting up Claude authentication..."
|
||||
limactl shell "$CLAUDE_VM_TEMPLATE" bash -lc "claude setup-token"
|
||||
|
||||
# Configure Chrome DevTools MCP server for Claude
|
||||
echo "Configuring Chrome MCP server..."
|
||||
limactl shell "$CLAUDE_VM_TEMPLATE" bash << 'VMEOF'
|
||||
CONFIG="$HOME/.claude.json"
|
||||
if [ -f "$CONFIG" ]; then
|
||||
jq '.mcpServers["chrome-devtools"] = {
|
||||
"command": "npx",
|
||||
"args": ["-y", "chrome-devtools-mcp@latest", "--headless=true", "--isolated=true"]
|
||||
}' "$CONFIG" > "$CONFIG.tmp" && mv "$CONFIG.tmp" "$CONFIG"
|
||||
else
|
||||
cat > "$CONFIG" << 'JSON'
|
||||
{
|
||||
"mcpServers": {
|
||||
"chrome-devtools": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "chrome-devtools-mcp@latest", "--headless=true", "--isolated=true"]
|
||||
}
|
||||
}
|
||||
}
|
||||
JSON
|
||||
fi
|
||||
VMEOF
|
||||
|
||||
# Run user's custom setup script if it exists
|
||||
local user_setup="$HOME/.claude-vm.setup.sh"
|
||||
if [ -f "$user_setup" ]; then
|
||||
echo "Running custom setup from $user_setup..."
|
||||
limactl shell "$CLAUDE_VM_TEMPLATE" bash < "$user_setup"
|
||||
fi
|
||||
|
||||
limactl stop "$CLAUDE_VM_TEMPLATE"
|
||||
|
||||
echo "Template ready. Run 'claude-vm' in any project directory."
|
||||
}
|
||||
|
||||
claude-vm() {
|
||||
local vm_name="claude-$(basename "$(pwd)" | tr -cs 'a-zA-Z0-9-' '-')-$$"
|
||||
local host_dir="$(pwd)"
|
||||
|
||||
if ! limactl list -q 2>/dev/null | grep -q "^${CLAUDE_VM_TEMPLATE}$"; then
|
||||
echo "Error: Template VM not found. Run 'claude-vm-setup' first." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
_claude_vm_cleanup() {
|
||||
echo "Cleaning up VM..."
|
||||
limactl stop "$vm_name" &>/dev/null
|
||||
limactl delete "$vm_name" --force &>/dev/null
|
||||
}
|
||||
trap _claude_vm_cleanup EXIT INT TERM
|
||||
|
||||
echo "Starting VM '$vm_name'..."
|
||||
limactl clone "$CLAUDE_VM_TEMPLATE" "$vm_name" \
|
||||
--set ".mounts=[{\"location\":\"${host_dir}\",\"writable\":true}]" \
|
||||
--tty=false &>/dev/null
|
||||
|
||||
limactl start "$vm_name" &>/dev/null
|
||||
|
||||
# Run project-specific runtime script if it exists
|
||||
if [ -f "${host_dir}/.claude-vm.runtime.sh" ]; then
|
||||
echo "Running project runtime setup..."
|
||||
limactl shell --workdir "$host_dir" "$vm_name" bash -l < "${host_dir}/.claude-vm.runtime.sh"
|
||||
fi
|
||||
|
||||
limactl shell --workdir "$host_dir" "$vm_name" claude --dangerously-skip-permissions
|
||||
|
||||
_claude_vm_cleanup
|
||||
trap - EXIT INT TERM
|
||||
}
|
||||
|
||||
claude-vm-shell() {
|
||||
local vm_name="claude-debug-$$"
|
||||
local host_dir="$(pwd)"
|
||||
|
||||
if ! limactl list -q 2>/dev/null | grep -q "^${CLAUDE_VM_TEMPLATE}$"; then
|
||||
echo "Error: Template VM not found. Run 'claude-vm-setup' first." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
_claude_vm_shell_cleanup() {
|
||||
echo "Cleaning up VM..."
|
||||
limactl stop "$vm_name" &>/dev/null
|
||||
limactl delete "$vm_name" --force &>/dev/null
|
||||
}
|
||||
trap _claude_vm_shell_cleanup EXIT INT TERM
|
||||
|
||||
limactl clone "$CLAUDE_VM_TEMPLATE" "$vm_name" \
|
||||
--set ".mounts=[{\"location\":\"${host_dir}\",\"writable\":true}]" \
|
||||
--tty=false &>/dev/null
|
||||
|
||||
limactl start "$vm_name" &>/dev/null
|
||||
|
||||
# Run project-specific runtime script if it exists
|
||||
if [ -f "${host_dir}/.claude-vm.runtime.sh" ]; then
|
||||
limactl shell --workdir "$host_dir" "$vm_name" bash -l < "${host_dir}/.claude-vm.runtime.sh"
|
||||
fi
|
||||
|
||||
echo "VM: $vm_name | Dir: $host_dir"
|
||||
echo "Type 'exit' to stop and delete the VM"
|
||||
limactl shell --workdir "$host_dir" "$vm_name" bash -l
|
||||
|
||||
_claude_vm_shell_cleanup
|
||||
trap - EXIT INT TERM
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue