Add setupcommands

This commit is contained in:
Onur Solmaz 2025-05-26 14:46:34 +02:00
parent da0755dd19
commit 627ff0df6f
4 changed files with 225 additions and 1 deletions

169
docs/setup-commands.md Normal file
View file

@ -0,0 +1,169 @@
# Setup Commands
This document explains how to run custom setup commands in your Claude Sandbox container.
## Overview
Setup commands allow you to automatically run initialization scripts when your container starts. This is useful for:
- Installing project dependencies
- Setting up databases
- Configuring environment-specific settings
- Installing additional tools
## Configuration
Add a `setupCommands` array to your `claude-sandbox.config.json`:
```json
{
"setupCommands": [
"npm install",
"pip install -r requirements.txt",
"sudo apt-get update && sudo apt-get install -y postgresql-client",
"createdb myapp_dev || true"
]
}
```
## Execution Order
Setup commands run:
1. **After** workspace files are copied
2. **After** git branch is created
3. **Before** Claude Code starts (if auto-start is enabled)
4. **As the `claude` user** (with sudo access)
## Examples
### Node.js Project
```json
{
"setupCommands": [
"npm install",
"npm run build",
"npm run db:migrate"
]
}
```
### Python Project
```json
{
"setupCommands": [
"pip install -r requirements.txt",
"python manage.py migrate",
"python manage.py collectstatic --noinput"
]
}
```
### Installing System Packages
```json
{
"setupCommands": [
"sudo apt-get update",
"sudo apt-get install -y redis-server postgresql-client",
"sudo service redis-server start"
]
}
```
### Complex Setup
```json
{
"setupCommands": [
"# Install dependencies",
"npm install && pip install -r requirements.txt",
"# Set up database",
"sudo service postgresql start",
"createdb myapp_dev || true",
"npm run db:migrate",
"# Start background services",
"redis-server --daemonize yes",
"npm run workers:start &"
]
}
```
## Best Practices
1. **Use `|| true`** for commands that might fail but shouldn't stop setup:
```json
["createdb myapp_dev || true"]
```
2. **Chain related commands** with `&&`:
```json
["cd frontend && npm install && npm run build"]
```
3. **Add comments** for clarity:
```json
["# Install Python dependencies", "pip install -r requirements.txt"]
```
4. **Test commands** in a regular container first:
```bash
docker run -it claude-code-sandbox:latest bash
# Test your commands here
```
## Error Handling
- Commands are run sequentially
- If a command fails (non-zero exit code), subsequent commands still run
- Failed commands show an error message but don't stop the container
- To stop on first error, add `"set -e"` as the first command
## Working Directory
All commands run in `/workspace` (your project root) as the `claude` user.
## Environment Variables
Commands have access to:
- All environment variables from your config
- Standard container environment
- `HOME=/home/claude`
- `USER=claude`
## Limitations
- Commands run synchronously (one at a time)
- Long-running commands will delay container startup
- Background processes should be daemonized
- Output is prefixed with `>` for clarity
## Troubleshooting
### Command Not Found
Ensure the tool is installed in the Docker image or install it in your setup commands:
```json
{
"setupCommands": [
"sudo apt-get update && sudo apt-get install -y <package>"
]
}
```
### Permission Denied
The `claude` user has passwordless sudo access. Prefix commands with `sudo` if needed:
```json
{
"setupCommands": [
"sudo systemctl start postgresql"
]
}
```
### Command Hangs
Ensure commands don't wait for user input. Use flags like `-y` or `--yes`:
```json
{
"setupCommands": [
"sudo apt-get install -y package-name"
]
}
```

View file

@ -10,6 +10,7 @@ const DEFAULT_CONFIG: SandboxConfig = {
autoCreatePR: true,
autoStartClaude: true,
claudeConfigPath: path.join(os.homedir(), '.claude.json'),
setupCommands: [], // Example: ["npm install", "pip install -r requirements.txt"]
allowedTools: ['*'], // All tools allowed in sandbox
// maxThinkingTokens: 100000,
// bashTimeout: 600000, // 10 minutes

View file

@ -348,7 +348,7 @@ exec claude --dangerously-skip-permissions' > /start-claude.sh && \\
// First, set up the git branch and create startup script
try {
console.log(chalk.green("Setting up git branch and startup script..."));
console.log(chalk.gray("Setting up git branch and startup script..."));
// Create different startup scripts based on autoStartClaude setting
const startupScript = this.config.autoStartClaude
@ -410,6 +410,59 @@ exec /bin/bash`;
});
console.log(chalk.green("✓ Container setup completed"));
// Execute custom setup commands if provided
if (this.config.setupCommands && this.config.setupCommands.length > 0) {
console.log(chalk.blue("Running custom setup commands..."));
for (const command of this.config.setupCommands) {
console.log(chalk.gray(` Running: ${command}`));
const cmdExec = await container.exec({
Cmd: ["/bin/bash", "-c", command],
AttachStdout: true,
AttachStderr: true,
WorkingDir: "/workspace",
User: "claude",
});
const cmdStream = await cmdExec.start({});
// Wait for command to complete
await new Promise<void>((resolve, reject) => {
let hasError = false;
cmdStream.on("data", (chunk) => {
process.stdout.write(chalk.gray(" > ") + chunk.toString());
});
cmdStream.on("end", async () => {
// Check exit code
try {
const info = await cmdExec.inspect();
if (info.ExitCode !== 0) {
console.error(chalk.red(` ✗ Command failed with exit code ${info.ExitCode}`));
hasError = true;
} else {
console.log(chalk.green(` ✓ Command completed successfully`));
}
} catch (e) {
// Ignore inspection errors
}
if (hasError && this.config.setupCommands?.includes("set -e")) {
reject(new Error(`Setup command failed: ${command}`));
} else {
resolve();
}
});
cmdStream.on("error", reject);
});
}
console.log(chalk.green("✓ All setup commands completed"));
}
} catch (error) {
console.error(chalk.red("Setup failed:"), error);
throw error;

View file

@ -7,6 +7,7 @@ export interface SandboxConfig {
autoCreatePR?: boolean;
autoStartClaude?: boolean;
claudeConfigPath?: string;
setupCommands?: string[];
environment?: Record<string, string>;
volumes?: string[];
allowedTools?: string[];