- Created history/v0.1.0.md with full deployment notes - Added k3s implementation section to reference.md - Fixed auth command: `claude` not `claude /login` - Documented issues encountered and solutions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1693 lines
42 KiB
Markdown
1693 lines
42 KiB
Markdown
# Multi-monorepo Claude Code infrastructure: Complete reference guide
|
|
|
|
This guide covers deploying claudebox across multiple monorepos with Discord control, using Claude Pro or Team subscription authentication. This architecture provides complete project isolation, parallel AI development, and team-based access control without API keys.
|
|
|
|
## Architecture overview
|
|
|
|
**Pattern: Multi-claudebox with single-bot routing**
|
|
|
|
This architecture runs separate Docker containers for each monorepo, managed by a single Discord bot that routes commands based on channel context. Each container maintains independent dependencies, git state, network policies, and Claude authentication sessions.
|
|
|
|
```
|
|
Physical Layout:
|
|
├── Remote VM (Ubuntu 22.04+)
|
|
│ ├── Docker Container: claudebox-project-a
|
|
│ ├── Docker Container: claudebox-project-b
|
|
│ └── Docker Container: claudebox-project-c
|
|
│ └── Discord Bot Process (routes to containers)
|
|
│
|
|
Discord Server:
|
|
├── #project-a-dev → claudebox-project-a
|
|
├── #project-b-dev → claudebox-project-b
|
|
└── #project-c-dev → claudebox-project-c
|
|
```
|
|
|
|
**Why this pattern:**
|
|
- Dependency isolation (Project A: Node 18, Project B: Node 20, Project C: Python 3.11)
|
|
- Parallel execution (Claude works on all projects simultaneously)
|
|
- Security boundaries (network policies and file access per project)
|
|
- Resource allocation (CPU/memory limits per project)
|
|
- Independent git state (no cross-contamination)
|
|
|
|
---
|
|
|
|
## Subscription requirements and authentication
|
|
|
|
**Required subscriptions:**
|
|
|
|
You need ONE of the following:
|
|
- **Claude Pro** ($20/month per developer) - individual use
|
|
- **Claude Team** ($30/month per developer, 5 minimum) - team collaboration with centralized billing
|
|
- **Claude Enterprise** - contact Anthropic sales for multi-team deployments
|
|
|
|
**Authentication model:**
|
|
|
|
Claude Code authenticates via OAuth to your claude.ai account. Each developer on your team needs their own subscription, and each will authenticate their claudebox instances with their personal credentials. This is fundamentally different from API-based usage—you're using the web subscription's usage limits, not paying per token.
|
|
|
|
**Per-developer or shared authentication:**
|
|
|
|
You have two deployment options:
|
|
|
|
1. **Per-developer claudebox** (Recommended for teams):
|
|
- Each developer has their own VM with their own claudebox instances
|
|
- Each authenticates with their personal Claude subscription
|
|
- Usage tracked per developer
|
|
- No credential sharing
|
|
|
|
2. **Shared claudebox with team account** (Single shared development server):
|
|
- One VM running all claudebox instances
|
|
- Authenticate with a shared Claude Team account
|
|
- All developers use same Discord bot
|
|
- Usage pooled across team
|
|
|
|
This guide assumes **shared claudebox** for simplicity, but the architecture works for both models.
|
|
|
|
---
|
|
|
|
## Initial VM setup and prerequisites
|
|
|
|
**Provision your remote VM:**
|
|
|
|
Minimum specifications:
|
|
- **CPU**: 8 cores (for 3 parallel containers)
|
|
- **RAM**: 16GB (allocate 4GB per container minimum)
|
|
- **Storage**: 100GB SSD (monorepos + Docker images + build artifacts)
|
|
- **OS**: Ubuntu 22.04 LTS or Debian 12
|
|
|
|
Cloud provider recommendations:
|
|
- AWS: t3.2xlarge or c6i.2xlarge
|
|
- GCP: n2-standard-8
|
|
- Azure: Standard_D8s_v3
|
|
- DigitalOcean: CPU-Optimized 8GB/4vCPU droplet
|
|
|
|
**Install system dependencies:**
|
|
|
|
```bash
|
|
# Update system
|
|
sudo apt update && sudo apt upgrade -y
|
|
|
|
# Install Docker
|
|
curl -fsSL https://get.docker.com -o get-docker.sh
|
|
sudo sh get-docker.sh
|
|
sudo usermod -aG docker $USER
|
|
newgrp docker
|
|
|
|
# Install Node.js (for Claude Code CLI)
|
|
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
|
sudo apt install -y nodejs
|
|
|
|
# Install Deno (for Discord bot)
|
|
curl -fsSL https://deno.land/install.sh | sh
|
|
echo 'export DENO_INSTALL="$HOME/.deno"' >> ~/.bashrc
|
|
echo 'export PATH="$DENO_INSTALL/bin:$PATH"' >> ~/.bashrc
|
|
source ~/.bashrc
|
|
|
|
# Install Git and development tools
|
|
sudo apt install -y git build-essential curl wget vim
|
|
|
|
# Install Docker Compose
|
|
sudo apt install -y docker-compose-plugin
|
|
```
|
|
|
|
**Clone your monorepos:**
|
|
|
|
```bash
|
|
mkdir -p ~/projects
|
|
cd ~/projects
|
|
|
|
# Clone your monorepos
|
|
git clone https://github.com/yourorg/monorepo-a.git
|
|
git clone https://github.com/yourorg/monorepo-b.git
|
|
git clone https://github.com/yourorg/monorepo-c.git
|
|
```
|
|
|
|
---
|
|
|
|
## Claude Code CLI installation and authentication
|
|
|
|
**Install Claude Code globally:**
|
|
|
|
```bash
|
|
npm install -g @anthropic-ai/claude-code
|
|
```
|
|
|
|
**Authenticate with your subscription:**
|
|
|
|
```bash
|
|
claude /login
|
|
```
|
|
|
|
This opens a browser window where you'll sign in with your Claude Pro/Team account. The authentication token is saved to `~/.claude/.credentials.json` and will be mounted into each container.
|
|
|
|
**Verify authentication:**
|
|
|
|
```bash
|
|
claude --version
|
|
ls -la ~/.claude/.credentials.json
|
|
```
|
|
|
|
The credentials file should exist and contain your authentication state. This single authentication covers all three claudebox instances since they'll mount the same credentials directory.
|
|
|
|
**Important: Credential persistence**
|
|
|
|
Your authentication lasts approximately 30 days. When it expires:
|
|
1. Run `claude /login` again on the host VM
|
|
2. Restart all claudebox containers to pick up new credentials
|
|
3. No need to re-authenticate inside containers
|
|
|
|
---
|
|
|
|
## Installing and configuring claudebox
|
|
|
|
**Download and install claudebox:**
|
|
|
|
```bash
|
|
cd ~
|
|
wget https://github.com/RchGrav/claudebox/releases/latest/download/claudebox.run
|
|
chmod +x claudebox.run
|
|
./claudebox.run
|
|
|
|
# Add to PATH
|
|
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
|
|
source ~/.bashrc
|
|
|
|
# Verify installation
|
|
claudebox info
|
|
```
|
|
|
|
**Initialize claudebox for each project:**
|
|
|
|
```bash
|
|
# Project A
|
|
cd ~/projects/monorepo-a
|
|
claudebox init project-a
|
|
|
|
# Project B
|
|
cd ~/projects/monorepo-b
|
|
claudebox init project-b
|
|
|
|
# Project C
|
|
cd ~/projects/monorepo-c
|
|
claudebox init project-c
|
|
```
|
|
|
|
This creates isolated configuration directories:
|
|
```
|
|
~/.claudebox/
|
|
├── project-a/
|
|
│ ├── .claude/ # Will mount host credentials
|
|
│ ├── firewall/ # Network allowlist
|
|
│ └── workspace/ # Symlink to ~/projects/monorepo-a
|
|
├── project-b/
|
|
│ └── ...
|
|
└── project-c/
|
|
└── ...
|
|
```
|
|
|
|
**Install profiles for each project:**
|
|
|
|
Profiles define the development environment for each container.
|
|
|
|
```bash
|
|
# Project A: JavaScript/TypeScript monorepo with Turborepo
|
|
claudebox profile javascript typescript
|
|
claudebox profile project-a --install
|
|
|
|
# Project B: Python monorepo with Poetry
|
|
claudebox profile python ml
|
|
claudebox profile project-b --install
|
|
|
|
# Project C: Rust monorepo
|
|
claudebox profile rust
|
|
claudebox profile project-c --install
|
|
```
|
|
|
|
**Configure project-specific profiles:**
|
|
|
|
Create custom profile files for monorepo tooling:
|
|
|
|
```ini
|
|
# ~/.claudebox/profiles/project-a.ini
|
|
[base]
|
|
name = JavaScript Monorepo (Turborepo)
|
|
extends = javascript,typescript
|
|
|
|
[packages]
|
|
# System packages
|
|
apt = build-essential python3 git
|
|
|
|
# Node.js tools
|
|
npm = turbo nx pnpm typescript eslint prettier
|
|
|
|
[environment]
|
|
NODE_VERSION = 20
|
|
PNPM_HOME = /root/.local/share/pnpm
|
|
PATH = $PNPM_HOME:$PATH
|
|
|
|
[startup]
|
|
# Install dependencies on container start
|
|
commands =
|
|
cd /workspace && pnpm install
|
|
turbo daemon start
|
|
|
|
[firewall]
|
|
# Allow access to package registries
|
|
allow = registry.npmjs.org,github.com,*.github.com
|
|
|
|
[resource_limits]
|
|
cpus = 2.0
|
|
memory = 4g
|
|
```
|
|
|
|
```ini
|
|
# ~/.claudebox/profiles/project-b.ini
|
|
[base]
|
|
name = Python Monorepo (Poetry)
|
|
extends = python,ml
|
|
|
|
[packages]
|
|
apt = python3.11 python3-pip python3-venv
|
|
pip = poetry pytest black mypy pandas numpy torch
|
|
|
|
[environment]
|
|
PYTHON_VERSION = 3.11
|
|
POETRY_VIRTUALENVS_IN_PROJECT = true
|
|
|
|
[startup]
|
|
commands =
|
|
cd /workspace && poetry install
|
|
poetry run python --version
|
|
|
|
[firewall]
|
|
allow = pypi.org,files.pythonhosted.org,github.com
|
|
|
|
[resource_limits]
|
|
cpus = 4.0
|
|
memory = 8g
|
|
```
|
|
|
|
```ini
|
|
# ~/.claudebox/profiles/project-c.ini
|
|
[base]
|
|
name = Rust Monorepo
|
|
extends = rust
|
|
|
|
[packages]
|
|
apt = build-essential pkg-config libssl-dev
|
|
cargo = cargo-workspaces cargo-watch cargo-edit
|
|
|
|
[environment]
|
|
RUST_VERSION = stable
|
|
CARGO_HOME = /usr/local/cargo
|
|
|
|
[startup]
|
|
commands =
|
|
cd /workspace && cargo fetch
|
|
cargo build --workspace --release
|
|
|
|
[firewall]
|
|
allow = crates.io,static.crates.io,github.com
|
|
|
|
[resource_limits]
|
|
cpus = 3.0
|
|
memory = 6g
|
|
```
|
|
|
|
---
|
|
|
|
## Docker compose orchestration
|
|
|
|
Create a centralized orchestration file for all containers.
|
|
|
|
**Create infrastructure directory:**
|
|
|
|
```bash
|
|
mkdir -p ~/claude-infra
|
|
cd ~/claude-infra
|
|
```
|
|
|
|
**Create docker-compose.yml:**
|
|
|
|
```yaml
|
|
# ~/claude-infra/docker-compose.yml
|
|
version: '3.8'
|
|
|
|
services:
|
|
claudebox-project-a:
|
|
image: ghcr.io/rchgrav/claudebox:latest
|
|
container_name: claudebox-project-a
|
|
hostname: project-a-dev
|
|
|
|
volumes:
|
|
# Mount monorepo
|
|
- ~/projects/monorepo-a:/workspace:rw
|
|
|
|
# Mount shared Claude credentials (READ-ONLY)
|
|
- ~/.claude:/root/.claude:ro
|
|
|
|
# Mount project-specific config
|
|
- ~/.claudebox/project-a/firewall:/etc/claudebox/firewall:rw
|
|
|
|
# Shared caches for faster builds
|
|
- npm-cache:/root/.npm
|
|
- pnpm-store:/root/.local/share/pnpm/store
|
|
|
|
environment:
|
|
- PROJECT_NAME=project-a
|
|
- WORKSPACE=/workspace
|
|
- CLAUDEBOX_PROFILE=project-a
|
|
|
|
working_dir: /workspace
|
|
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: '2.0'
|
|
memory: 4G
|
|
reservations:
|
|
cpus: '1.0'
|
|
memory: 2G
|
|
|
|
networks:
|
|
- claude-network-a
|
|
|
|
restart: unless-stopped
|
|
|
|
# Keep container running
|
|
command: tail -f /dev/null
|
|
|
|
claudebox-project-b:
|
|
image: ghcr.io/rchgrav/claudebox:latest
|
|
container_name: claudebox-project-b
|
|
hostname: project-b-dev
|
|
|
|
volumes:
|
|
- ~/projects/monorepo-b:/workspace:rw
|
|
- ~/.claude:/root/.claude:ro
|
|
- ~/.claudebox/project-b/firewall:/etc/claudebox/firewall:rw
|
|
- pip-cache:/root/.cache/pip
|
|
- poetry-cache:/root/.cache/pypoetry
|
|
|
|
environment:
|
|
- PROJECT_NAME=project-b
|
|
- WORKSPACE=/workspace
|
|
- CLAUDEBOX_PROFILE=project-b
|
|
- PYTHON_VERSION=3.11
|
|
|
|
working_dir: /workspace
|
|
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: '4.0'
|
|
memory: 8G
|
|
reservations:
|
|
cpus: '2.0'
|
|
memory: 4G
|
|
|
|
networks:
|
|
- claude-network-b
|
|
|
|
restart: unless-stopped
|
|
command: tail -f /dev/null
|
|
|
|
claudebox-project-c:
|
|
image: ghcr.io/rchgrav/claudebox:latest
|
|
container_name: claudebox-project-c
|
|
hostname: project-c-dev
|
|
|
|
volumes:
|
|
- ~/projects/monorepo-c:/workspace:rw
|
|
- ~/.claude:/root/.claude:ro
|
|
- ~/.claudebox/project-c/firewall:/etc/claudebox/firewall:rw
|
|
- cargo-registry:/usr/local/cargo/registry
|
|
- cargo-git:/usr/local/cargo/git
|
|
|
|
environment:
|
|
- PROJECT_NAME=project-c
|
|
- WORKSPACE=/workspace
|
|
- CLAUDEBOX_PROFILE=project-c
|
|
- RUST_VERSION=stable
|
|
|
|
working_dir: /workspace
|
|
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: '3.0'
|
|
memory: 6G
|
|
reservations:
|
|
cpus: '1.5'
|
|
memory: 3G
|
|
|
|
networks:
|
|
- claude-network-c
|
|
|
|
restart: unless-stopped
|
|
command: tail -f /dev/null
|
|
|
|
networks:
|
|
claude-network-a:
|
|
driver: bridge
|
|
claude-network-b:
|
|
driver: bridge
|
|
claude-network-c:
|
|
driver: bridge
|
|
|
|
volumes:
|
|
npm-cache:
|
|
pnpm-store:
|
|
pip-cache:
|
|
poetry-cache:
|
|
cargo-registry:
|
|
cargo-git:
|
|
```
|
|
|
|
**Container lifecycle management:**
|
|
|
|
```bash
|
|
# Start all containers
|
|
cd ~/claude-infra
|
|
docker compose up -d
|
|
|
|
# View status
|
|
docker compose ps
|
|
|
|
# View logs
|
|
docker compose logs -f claudebox-project-a
|
|
|
|
# Stop all containers
|
|
docker compose stop
|
|
|
|
# Restart specific container
|
|
docker compose restart claudebox-project-b
|
|
|
|
# Remove all containers (keeps volumes)
|
|
docker compose down
|
|
|
|
# Full cleanup including volumes
|
|
docker compose down -v
|
|
```
|
|
|
|
---
|
|
|
|
## Discord bot setup and configuration
|
|
|
|
**Create Discord application:**
|
|
|
|
1. Go to https://discord.com/developers/applications
|
|
2. Click **New Application**, name it "Claude-MultiProject"
|
|
3. Navigate to **Bot** section
|
|
4. Click **Add Bot**
|
|
5. Enable **Message Content Intent**
|
|
6. Copy the **Bot Token** (save securely)
|
|
7. Copy the **Application ID** from General Information
|
|
|
|
**Invite bot to your server:**
|
|
|
|
1. Go to **OAuth2** → **URL Generator**
|
|
2. Select scopes: `bot`, `applications.commands`
|
|
3. Select permissions:
|
|
- Send Messages
|
|
- Use Slash Commands
|
|
- Read Message History
|
|
- Embed Links
|
|
- Attach Files
|
|
- Manage Messages (for cleanup)
|
|
4. Copy generated URL and open in browser
|
|
5. Select your server and authorize
|
|
|
|
**Clone and configure the Discord bot:**
|
|
|
|
```bash
|
|
cd ~/claude-infra
|
|
git clone https://github.com/zebbern/claude-code-discord.git discord-bot
|
|
cd discord-bot
|
|
```
|
|
|
|
**Create environment configuration:**
|
|
|
|
```bash
|
|
# ~/claude-infra/discord-bot/.env
|
|
DISCORD_TOKEN=your_bot_token_here
|
|
APPLICATION_ID=your_application_id_here
|
|
|
|
# Project routing configuration
|
|
PROJECT_A_CONTAINER=claudebox-project-a
|
|
PROJECT_B_CONTAINER=claudebox-project-b
|
|
PROJECT_C_CONTAINER=claudebox-project-c
|
|
|
|
# Channel mapping (will configure in code)
|
|
# This is just documentation
|
|
CHANNEL_PROJECT_A=project-a-dev
|
|
CHANNEL_PROJECT_B=project-b-dev
|
|
CHANNEL_PROJECT_C=project-c-dev
|
|
```
|
|
|
|
**Create enhanced routing configuration:**
|
|
|
|
```typescript
|
|
// ~/claude-infra/discord-bot/config.ts
|
|
export interface ProjectConfig {
|
|
container: string;
|
|
workdir: string;
|
|
profile: string;
|
|
allowedRoles: string[];
|
|
description: string;
|
|
monorepo: {
|
|
tool: 'turbo' | 'nx' | 'poetry' | 'cargo';
|
|
packages: string[];
|
|
};
|
|
}
|
|
|
|
export const PROJECT_CONFIGS: Record<string, ProjectConfig> = {
|
|
'project-a-dev': {
|
|
container: 'claudebox-project-a',
|
|
workdir: '/workspace',
|
|
profile: 'javascript typescript',
|
|
allowedRoles: ['project-a-devs', 'admin', 'engineering'],
|
|
description: 'JavaScript/TypeScript monorepo with Turborepo',
|
|
monorepo: {
|
|
tool: 'turbo',
|
|
packages: ['@myorg/api', '@myorg/web', '@myorg/shared']
|
|
}
|
|
},
|
|
'project-b-dev': {
|
|
container: 'claudebox-project-b',
|
|
workdir: '/workspace',
|
|
profile: 'python ml',
|
|
allowedRoles: ['project-b-devs', 'admin', 'data-science'],
|
|
description: 'Python monorepo with Poetry for ML workflows',
|
|
monorepo: {
|
|
tool: 'poetry',
|
|
packages: ['ml-pipeline', 'data-processing', 'inference-service']
|
|
}
|
|
},
|
|
'project-c-dev': {
|
|
container: 'claudebox-project-c',
|
|
workdir: '/workspace',
|
|
profile: 'rust',
|
|
allowedRoles: ['project-c-devs', 'admin', 'systems'],
|
|
description: 'Rust monorepo with Cargo workspaces',
|
|
monorepo: {
|
|
tool: 'cargo',
|
|
packages: ['core', 'cli', 'server']
|
|
}
|
|
}
|
|
};
|
|
|
|
// Monorepo context templates
|
|
export const MONOREPO_CONTEXT = {
|
|
turbo: `This is a Turborepo monorepo. When making changes:
|
|
- Use 'turbo run <task>' for builds/tests
|
|
- Changes may affect multiple packages
|
|
- Check package.json workspaces for dependencies
|
|
- Use 'turbo run build --filter=<package>' for specific packages`,
|
|
|
|
nx: `This is an Nx monorepo. When making changes:
|
|
- Use 'nx run <project>:<target>' for tasks
|
|
- Check nx.json for task configuration
|
|
- Use 'nx affected:build' for changed packages`,
|
|
|
|
poetry: `This is a Poetry monorepo. When making changes:
|
|
- Use 'poetry run <command>' for scripts
|
|
- Update pyproject.toml for dependencies
|
|
- Run 'poetry install' after dependency changes`,
|
|
|
|
cargo: `This is a Cargo workspace. When making changes:
|
|
- Use 'cargo build --workspace' for all packages
|
|
- Update Cargo.toml in workspace root
|
|
- Use 'cargo build -p <package>' for specific crates`
|
|
};
|
|
```
|
|
|
|
**Create enhanced bot with routing:**
|
|
|
|
```typescript
|
|
// ~/claude-infra/discord-bot/bot.ts
|
|
import { Client, GatewayIntentBits, REST, Routes, SlashCommandBuilder } from 'discord.js';
|
|
import { exec } from 'child_process';
|
|
import { promisify } from 'util';
|
|
import { PROJECT_CONFIGS, MONOREPO_CONTEXT } from './config.ts';
|
|
|
|
const execAsync = promisify(exec);
|
|
|
|
const client = new Client({
|
|
intents: [
|
|
GatewayIntentBits.Guilds,
|
|
GatewayIntentBits.GuildMessages,
|
|
GatewayIntentBits.MessageContent,
|
|
],
|
|
});
|
|
|
|
// Helper: Check user permissions
|
|
function hasPermission(member: any, allowedRoles: string[]): boolean {
|
|
return member.roles.cache.some((role: any) =>
|
|
allowedRoles.includes(role.name.toLowerCase())
|
|
);
|
|
}
|
|
|
|
// Helper: Execute command in container
|
|
async function execInContainer(
|
|
container: string,
|
|
command: string,
|
|
cwd: string = '/workspace'
|
|
): Promise<string> {
|
|
const dockerCmd = `docker exec -w ${cwd} ${container} bash -c "${command.replace(/"/g, '\\"')}"`;
|
|
|
|
try {
|
|
const { stdout, stderr } = await execAsync(dockerCmd, {
|
|
maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large outputs
|
|
timeout: 300000 // 5 minute timeout
|
|
});
|
|
return stdout || stderr;
|
|
} catch (error: any) {
|
|
throw new Error(`Container execution failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Helper: Get project config from channel
|
|
function getProjectConfig(channelName: string) {
|
|
const config = PROJECT_CONFIGS[channelName];
|
|
if (!config) {
|
|
throw new Error(`No configuration found for channel: ${channelName}`);
|
|
}
|
|
return config;
|
|
}
|
|
|
|
// Command: /claude
|
|
const claudeCommand = new SlashCommandBuilder()
|
|
.setName('claude')
|
|
.setDescription('Send a prompt to Claude Code')
|
|
.addStringOption(option =>
|
|
option.setName('prompt')
|
|
.setDescription('Your prompt for Claude')
|
|
.setRequired(true)
|
|
)
|
|
.addStringOption(option =>
|
|
option.setName('mode')
|
|
.setDescription('Execution mode')
|
|
.addChoices(
|
|
{ name: 'normal', value: 'normal' },
|
|
{ name: 'auto-accept', value: 'auto' },
|
|
{ name: 'plan-only', value: 'plan' }
|
|
)
|
|
);
|
|
|
|
// Command: /status
|
|
const statusCommand = new SlashCommandBuilder()
|
|
.setName('status')
|
|
.setDescription('Check container and project status');
|
|
|
|
// Command: /shell
|
|
const shellCommand = new SlashCommandBuilder()
|
|
.setName('shell')
|
|
.setDescription('Execute shell command in project container')
|
|
.addStringOption(option =>
|
|
option.setName('command')
|
|
.setDescription('Shell command to execute')
|
|
.setRequired(true)
|
|
);
|
|
|
|
// Command: /git
|
|
const gitCommand = new SlashCommandBuilder()
|
|
.setName('git')
|
|
.setDescription('Execute git command')
|
|
.addStringOption(option =>
|
|
option.setName('args')
|
|
.setDescription('Git arguments (e.g., "status", "diff HEAD")')
|
|
.setRequired(true)
|
|
);
|
|
|
|
// Command: /monorepo-info
|
|
const monorepoInfoCommand = new SlashCommandBuilder()
|
|
.setName('monorepo-info')
|
|
.setDescription('Show monorepo structure and packages');
|
|
|
|
// Register commands
|
|
const commands = [
|
|
claudeCommand,
|
|
statusCommand,
|
|
shellCommand,
|
|
gitCommand,
|
|
monorepoInfoCommand,
|
|
].map(cmd => cmd.toJSON());
|
|
|
|
// Handle interactions
|
|
client.on('interactionCreate', async interaction => {
|
|
if (!interaction.isCommand()) return;
|
|
|
|
try {
|
|
const channelName = interaction.channel?.name || '';
|
|
const config = getProjectConfig(channelName);
|
|
|
|
// Check permissions
|
|
if (!hasPermission(interaction.member, config.allowedRoles)) {
|
|
await interaction.reply({
|
|
content: `⛔ You don't have permission to use Claude Code on ${config.description}. Required roles: ${config.allowedRoles.join(', ')}`,
|
|
ephemeral: true
|
|
});
|
|
return;
|
|
}
|
|
|
|
await interaction.deferReply();
|
|
|
|
switch (interaction.commandName) {
|
|
case 'claude': {
|
|
const prompt = interaction.options.getString('prompt', true);
|
|
const mode = interaction.options.getString('mode') || 'normal';
|
|
|
|
// Add monorepo context to prompt
|
|
const monorepContext = MONOREPO_CONTEXT[config.monorepo.tool];
|
|
const enhancedPrompt = `${monorepContext}\n\nUser request: ${prompt}`;
|
|
|
|
const modeFlag = mode === 'auto' ? '--dangerously-skip-permissions' :
|
|
mode === 'plan' ? '--plan-only' : '';
|
|
|
|
const output = await execInContainer(
|
|
config.container,
|
|
`claude "${enhancedPrompt}" ${modeFlag}`,
|
|
config.workdir
|
|
);
|
|
|
|
// Split long responses
|
|
const chunks = output.match(/[\s\S]{1,1900}/g) || [];
|
|
for (const chunk of chunks) {
|
|
await interaction.followUp({
|
|
content: `\`\`\`\n${chunk}\n\`\`\``
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'status': {
|
|
const containerStatus = await execAsync(`docker inspect ${config.container} --format '{{.State.Status}}'`);
|
|
const diskUsage = await execInContainer(config.container, 'df -h /workspace | tail -1');
|
|
const gitStatus = await execInContainer(config.container, 'git status -s', config.workdir);
|
|
|
|
await interaction.editReply({
|
|
content: `📊 **Project Status: ${config.description}**\n\n` +
|
|
`Container: ${containerStatus.stdout.trim()}\n` +
|
|
`Disk: ${diskUsage}\n` +
|
|
`\`\`\`\n${gitStatus || 'No changes'}\n\`\`\``
|
|
});
|
|
break;
|
|
}
|
|
|
|
case 'shell': {
|
|
const command = interaction.options.getString('command', true);
|
|
|
|
// Safety check for destructive commands
|
|
const dangerous = ['rm -rf', 'dd if=', 'mkfs', '> /dev/'];
|
|
if (dangerous.some(d => command.includes(d))) {
|
|
await interaction.editReply('⛔ Potentially destructive command blocked. Use with caution.');
|
|
return;
|
|
}
|
|
|
|
const output = await execInContainer(config.container, command, config.workdir);
|
|
await interaction.editReply({
|
|
content: `\`\`\`bash\n$ ${command}\n${output.slice(0, 1900)}\n\`\`\``
|
|
});
|
|
break;
|
|
}
|
|
|
|
case 'git': {
|
|
const args = interaction.options.getString('args', true);
|
|
const output = await execInContainer(config.container, `git ${args}`, config.workdir);
|
|
await interaction.editReply({
|
|
content: `\`\`\`\n${output.slice(0, 1900)}\n\`\`\``
|
|
});
|
|
break;
|
|
}
|
|
|
|
case 'monorepo-info': {
|
|
const packages = config.monorepo.packages.join('\n- ');
|
|
const tree = await execInContainer(config.container, 'tree -L 2 -d', config.workdir);
|
|
|
|
await interaction.editReply({
|
|
content: `📦 **Monorepo Structure**\n\n` +
|
|
`Tool: ${config.monorepo.tool}\n` +
|
|
`Packages:\n- ${packages}\n\n` +
|
|
`\`\`\`\n${tree.slice(0, 1500)}\n\`\`\``
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
|
|
} catch (error: any) {
|
|
console.error('Command error:', error);
|
|
await interaction.editReply({
|
|
content: `❌ Error: ${error.message}`
|
|
});
|
|
}
|
|
});
|
|
|
|
// Bot ready
|
|
client.on('ready', async () => {
|
|
console.log(`✅ Bot logged in as ${client.user?.tag}`);
|
|
|
|
// Register slash commands
|
|
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN!);
|
|
|
|
try {
|
|
await rest.put(
|
|
Routes.applicationCommands(process.env.APPLICATION_ID!),
|
|
{ body: commands }
|
|
);
|
|
console.log('✅ Slash commands registered');
|
|
} catch (error) {
|
|
console.error('Failed to register commands:', error);
|
|
}
|
|
});
|
|
|
|
// Start bot
|
|
client.login(process.env.DISCORD_TOKEN);
|
|
```
|
|
|
|
**Start the Discord bot:**
|
|
|
|
```bash
|
|
cd ~/claude-infra/discord-bot
|
|
deno run --allow-all bot.ts
|
|
```
|
|
|
|
**Run bot as systemd service:**
|
|
|
|
```ini
|
|
# /etc/systemd/system/claude-discord-bot.service
|
|
[Unit]
|
|
Description=Claude Code Discord Bot (Multi-Project)
|
|
After=network.target docker.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=youruser
|
|
WorkingDirectory=/home/youruser/claude-infra/discord-bot
|
|
EnvironmentFile=/home/youruser/claude-infra/discord-bot/.env
|
|
ExecStart=/home/youruser/.deno/bin/deno run --allow-all bot.ts
|
|
Restart=always
|
|
RestartSec=10
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
```bash
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable claude-discord-bot
|
|
sudo systemctl start claude-discord-bot
|
|
sudo systemctl status claude-discord-bot
|
|
```
|
|
|
|
---
|
|
|
|
## Discord server organization
|
|
|
|
**Create structured channel layout:**
|
|
|
|
```
|
|
Your Discord Server
|
|
│
|
|
├── 📁 INFRASTRUCTURE
|
|
│ ├── #system-status (bot health, container stats)
|
|
│ ├── #announcements (maintenance windows, updates)
|
|
│ └── #admin-commands (restricted admin-only channel)
|
|
│
|
|
├── 📁 PROJECT A - JavaScript Monorepo
|
|
│ ├── #project-a-dev (Claude commands)
|
|
│ ├── #project-a-logs (git commits, CI/CD)
|
|
│ └── #project-a-discuss (team chat)
|
|
│
|
|
├── 📁 PROJECT B - Python ML
|
|
│ ├── #project-b-dev
|
|
│ ├── #project-b-logs
|
|
│ └── #project-b-discuss
|
|
│
|
|
├── 📁 PROJECT C - Rust Systems
|
|
│ ├── #project-c-dev
|
|
│ ├── #project-c-logs
|
|
│ └── #project-c-discuss
|
|
│
|
|
└── 📁 RESOURCES
|
|
├── #documentation (setup guides, workflows)
|
|
└── #troubleshooting (common issues, solutions)
|
|
```
|
|
|
|
**Configure role-based permissions:**
|
|
|
|
Create Discord roles:
|
|
- `admin` - Full access to all projects
|
|
- `project-a-devs` - Access to Project A
|
|
- `project-b-devs` - Access to Project B
|
|
- `project-c-devs` - Access to Project C
|
|
- `engineering` - Read access to all projects
|
|
- `observers` - View-only access
|
|
|
|
Set channel permissions:
|
|
1. Each `#project-X-dev` channel: Only respective role can send messages
|
|
2. Each `#project-X-logs` channel: Read-only for all, bot can post
|
|
3. `#admin-commands`: Admin-only
|
|
4. `#system-status`: Bot posts, all can view
|
|
|
|
---
|
|
|
|
## Management scripts and automation
|
|
|
|
**Create container management wrapper:**
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# ~/claude-infra/manage.sh
|
|
|
|
set -e
|
|
|
|
COMPOSE_FILE="$HOME/claude-infra/docker-compose.yml"
|
|
|
|
function usage() {
|
|
cat << EOF
|
|
Claude Multi-Project Manager
|
|
|
|
Usage: ./manage.sh <command> [options]
|
|
|
|
Commands:
|
|
start [project] Start all containers or specific project
|
|
stop [project] Stop all containers or specific project
|
|
restart [project] Restart containers
|
|
status Show status of all containers
|
|
logs <project> View logs for project
|
|
exec <project> <cmd> Execute command in project container
|
|
shell <project> Open shell in project container
|
|
rebuild [project] Rebuild container images
|
|
backup Backup all container state
|
|
restore <file> Restore from backup
|
|
health Run health checks on all projects
|
|
update Update claudebox and bot
|
|
|
|
Projects: project-a, project-b, project-c
|
|
|
|
Examples:
|
|
./manage.sh start project-a
|
|
./manage.sh logs project-b
|
|
./manage.sh exec project-c "git status"
|
|
./manage.sh shell project-a
|
|
EOF
|
|
}
|
|
|
|
function get_container() {
|
|
case "$1" in
|
|
project-a) echo "claudebox-project-a" ;;
|
|
project-b) echo "claudebox-project-b" ;;
|
|
project-c) echo "claudebox-project-c" ;;
|
|
*) echo "Unknown project: $1" >&2; exit 1 ;;
|
|
esac
|
|
}
|
|
|
|
case "$1" in
|
|
start)
|
|
if [ -z "$2" ]; then
|
|
docker compose -f "$COMPOSE_FILE" up -d
|
|
echo "✅ All containers started"
|
|
else
|
|
CONTAINER=$(get_container "$2")
|
|
docker compose -f "$COMPOSE_FILE" up -d "$CONTAINER"
|
|
echo "✅ Started $CONTAINER"
|
|
fi
|
|
;;
|
|
|
|
stop)
|
|
if [ -z "$2" ]; then
|
|
docker compose -f "$COMPOSE_FILE" stop
|
|
echo "✅ All containers stopped"
|
|
else
|
|
CONTAINER=$(get_container "$2")
|
|
docker compose -f "$COMPOSE_FILE" stop "$CONTAINER"
|
|
echo "✅ Stopped $CONTAINER"
|
|
fi
|
|
;;
|
|
|
|
restart)
|
|
if [ -z "$2" ]; then
|
|
docker compose -f "$COMPOSE_FILE" restart
|
|
echo "✅ All containers restarted"
|
|
else
|
|
CONTAINER=$(get_container "$2")
|
|
docker compose -f "$COMPOSE_FILE" restart "$CONTAINER"
|
|
echo "✅ Restarted $CONTAINER"
|
|
fi
|
|
;;
|
|
|
|
status)
|
|
docker compose -f "$COMPOSE_FILE" ps
|
|
echo ""
|
|
echo "Resource usage:"
|
|
docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}" \
|
|
claudebox-project-a claudebox-project-b claudebox-project-c
|
|
;;
|
|
|
|
logs)
|
|
if [ -z "$2" ]; then
|
|
echo "Error: Specify project" >&2
|
|
exit 1
|
|
fi
|
|
CONTAINER=$(get_container "$2")
|
|
docker compose -f "$COMPOSE_FILE" logs -f --tail=100 "$CONTAINER"
|
|
;;
|
|
|
|
exec)
|
|
if [ -z "$2" ] || [ -z "$3" ]; then
|
|
echo "Error: Specify project and command" >&2
|
|
exit 1
|
|
fi
|
|
CONTAINER=$(get_container "$2")
|
|
shift 2
|
|
docker exec -it "$CONTAINER" bash -c "$@"
|
|
;;
|
|
|
|
shell)
|
|
if [ -z "$2" ]; then
|
|
echo "Error: Specify project" >&2
|
|
exit 1
|
|
fi
|
|
CONTAINER=$(get_container "$2")
|
|
docker exec -it "$CONTAINER" bash
|
|
;;
|
|
|
|
rebuild)
|
|
if [ -z "$2" ]; then
|
|
docker compose -f "$COMPOSE_FILE" build --no-cache
|
|
docker compose -f "$COMPOSE_FILE" up -d
|
|
else
|
|
CONTAINER=$(get_container "$2")
|
|
docker compose -f "$COMPOSE_FILE" build --no-cache "$CONTAINER"
|
|
docker compose -f "$COMPOSE_FILE" up -d "$CONTAINER"
|
|
fi
|
|
echo "✅ Rebuild complete"
|
|
;;
|
|
|
|
backup)
|
|
BACKUP_DIR="$HOME/claude-backups/$(date +%Y%m%d-%H%M%S)"
|
|
mkdir -p "$BACKUP_DIR"
|
|
|
|
# Backup container state
|
|
for project in project-a project-b project-c; do
|
|
CONTAINER=$(get_container "$project")
|
|
echo "Backing up $CONTAINER..."
|
|
docker export "$CONTAINER" | gzip > "$BACKUP_DIR/${CONTAINER}.tar.gz"
|
|
done
|
|
|
|
# Backup configurations
|
|
cp -r ~/.claudebox "$BACKUP_DIR/config"
|
|
|
|
echo "✅ Backup saved to $BACKUP_DIR"
|
|
;;
|
|
|
|
health)
|
|
echo "Running health checks..."
|
|
echo ""
|
|
|
|
for project in project-a project-b project-c; do
|
|
CONTAINER=$(get_container "$project")
|
|
echo "Checking $project..."
|
|
|
|
# Container running?
|
|
if docker ps --filter "name=$CONTAINER" --format '{{.Names}}' | grep -q "$CONTAINER"; then
|
|
echo " ✅ Container running"
|
|
else
|
|
echo " ❌ Container not running"
|
|
continue
|
|
fi
|
|
|
|
# Claude authenticated?
|
|
if docker exec "$CONTAINER" bash -c "[ -f /root/.claude/.credentials.json ]"; then
|
|
echo " ✅ Claude authenticated"
|
|
else
|
|
echo " ⚠️ Claude not authenticated"
|
|
fi
|
|
|
|
# Workspace accessible?
|
|
if docker exec "$CONTAINER" bash -c "[ -d /workspace ]"; then
|
|
echo " ✅ Workspace mounted"
|
|
else
|
|
echo " ❌ Workspace not mounted"
|
|
fi
|
|
|
|
# Git repository?
|
|
if docker exec "$CONTAINER" bash -c "cd /workspace && git status >/dev/null 2>&1"; then
|
|
echo " ✅ Git repository valid"
|
|
else
|
|
echo " ⚠️ Not a git repository"
|
|
fi
|
|
|
|
echo ""
|
|
done
|
|
;;
|
|
|
|
update)
|
|
echo "Updating Claude Code CLI..."
|
|
npm install -g @anthropic-ai/claude-code
|
|
|
|
echo "Updating Discord bot..."
|
|
cd ~/claude-infra/discord-bot
|
|
git pull
|
|
|
|
echo "Updating containers..."
|
|
docker compose -f "$COMPOSE_FILE" pull
|
|
docker compose -f "$COMPOSE_FILE" up -d
|
|
|
|
echo "✅ Update complete"
|
|
;;
|
|
|
|
*)
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
```
|
|
|
|
**Make executable:**
|
|
|
|
```bash
|
|
chmod +x ~/claude-infra/manage.sh
|
|
|
|
# Add alias for convenience
|
|
echo "alias claude-manage='~/claude-infra/manage.sh'" >> ~/.bashrc
|
|
source ~/.bashrc
|
|
```
|
|
|
|
**Create automatic checkpoint script:**
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# ~/claude-infra/auto-checkpoint.sh
|
|
|
|
# Commits work in progress for all projects every hour
|
|
|
|
for project in project-a project-b project-c; do
|
|
CONTAINER="claudebox-$project"
|
|
|
|
echo "Checkpointing $project..."
|
|
docker exec "$CONTAINER" bash -c "
|
|
cd /workspace && \
|
|
git add -A && \
|
|
git commit -m 'Auto-checkpoint: $(date +"%Y-%m-%d %H:%M:%S")' >/dev/null 2>&1 || true
|
|
"
|
|
done
|
|
|
|
echo "Checkpoints created at $(date)"
|
|
```
|
|
|
|
**Schedule with cron:**
|
|
|
|
```bash
|
|
chmod +x ~/claude-infra/auto-checkpoint.sh
|
|
|
|
# Add to crontab
|
|
crontab -e
|
|
|
|
# Add line:
|
|
0 * * * * /home/youruser/claude-infra/auto-checkpoint.sh >> /home/youruser/claude-infra/checkpoint.log 2>&1
|
|
```
|
|
|
|
**Create health monitoring:**
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# ~/claude-infra/health-monitor.sh
|
|
|
|
WEBHOOK_URL="your_discord_webhook_url"
|
|
|
|
function send_alert() {
|
|
curl -X POST "$WEBHOOK_URL" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"content\": \"🚨 $1\"}"
|
|
}
|
|
|
|
# Check each container
|
|
for container in claudebox-project-a claudebox-project-b claudebox-project-c; do
|
|
if ! docker ps --format '{{.Names}}' | grep -q "^${container}$"; then
|
|
send_alert "Container $container is not running!"
|
|
|
|
# Attempt restart
|
|
docker start "$container"
|
|
sleep 5
|
|
|
|
if docker ps --format '{{.Names}}' | grep -q "^${container}$"; then
|
|
send_alert "Container $container restarted successfully"
|
|
else
|
|
send_alert "Failed to restart container $container"
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Check disk space
|
|
DISK_USAGE=$(df -h /home | tail -1 | awk '{print $5}' | sed 's/%//')
|
|
if [ "$DISK_USAGE" -gt 85 ]; then
|
|
send_alert "Disk usage is at ${DISK_USAGE}% - cleanup recommended"
|
|
fi
|
|
|
|
# Check memory
|
|
MEM_USAGE=$(free | grep Mem | awk '{printf "%.0f", $3/$2 * 100}')
|
|
if [ "$MEM_USAGE" -gt 90 ]; then
|
|
send_alert "Memory usage is at ${MEM_USAGE}% - may need to restart containers"
|
|
fi
|
|
```
|
|
|
|
**Schedule health checks:**
|
|
|
|
```bash
|
|
chmod +x ~/claude-infra/health-monitor.sh
|
|
|
|
# Add to crontab (every 5 minutes)
|
|
crontab -e
|
|
|
|
# Add:
|
|
*/5 * * * * /home/youruser/claude-infra/health-monitor.sh
|
|
```
|
|
|
|
---
|
|
|
|
## Operational workflows
|
|
|
|
**Daily developer workflow:**
|
|
|
|
1. **Check system status** (via Discord):
|
|
```
|
|
/status
|
|
```
|
|
|
|
2. **Start working on a feature**:
|
|
```
|
|
/git checkout -b feature/new-api-endpoint
|
|
/claude Create a new REST endpoint for user profile updates in the api package. Follow our existing patterns.
|
|
```
|
|
|
|
3. **Review changes**:
|
|
```
|
|
/git diff
|
|
/git status
|
|
```
|
|
|
|
4. **Run tests**:
|
|
```
|
|
/shell turbo run test --filter=@myorg/api
|
|
```
|
|
|
|
5. **Commit work**:
|
|
```
|
|
/git add .
|
|
/git commit -m "feat(api): add user profile update endpoint"
|
|
/git push origin feature/new-api-endpoint
|
|
```
|
|
|
|
**Cross-project coordination:**
|
|
|
|
When changes in one project affect another:
|
|
|
|
1. Make changes in Project A:
|
|
```
|
|
In #project-a-dev:
|
|
/claude Update the API client types to match the new endpoint schema
|
|
```
|
|
|
|
2. Export the changes:
|
|
```
|
|
/git diff HEAD~1 src/types/api.ts > /tmp/api-changes.patch
|
|
```
|
|
|
|
3. Apply to Project B:
|
|
```
|
|
In #project-b-dev:
|
|
/shell cat /tmp/api-changes.patch
|
|
/claude Review this API change from project-a and update our integration accordingly: [paste diff]
|
|
```
|
|
|
|
**Emergency rollback:**
|
|
|
|
If Claude makes unwanted changes:
|
|
|
|
```
|
|
/git status
|
|
/git diff HEAD
|
|
/git checkout .
|
|
/git clean -fd
|
|
```
|
|
|
|
Or restore from auto-checkpoint:
|
|
|
|
```
|
|
/git reflog
|
|
/git reset --hard HEAD@{1}
|
|
```
|
|
|
|
**Subscription usage monitoring:**
|
|
|
|
Since you're using claude.ai subscriptions (not API), monitor usage via:
|
|
|
|
1. Go to https://claude.ai/settings
|
|
2. Check "Usage" tab for your subscription tier limits
|
|
3. Usage resets monthly based on your subscription date
|
|
|
|
Each container shares the same subscription, so total usage is aggregate across all three projects.
|
|
|
|
**Credential refresh:**
|
|
|
|
When your 30-day authentication expires:
|
|
|
|
```bash
|
|
# On the VM
|
|
claude /login
|
|
|
|
# Restart containers to pick up new credentials
|
|
claude-manage restart
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
**Container won't start:**
|
|
|
|
```bash
|
|
# Check logs
|
|
claude-manage logs project-a
|
|
|
|
# Common issues:
|
|
# 1. Port conflict
|
|
docker ps -a | grep claudebox
|
|
|
|
# 2. Volume mount permissions
|
|
ls -la ~/projects/monorepo-a
|
|
|
|
# 3. Credentials missing
|
|
ls -la ~/.claude/.credentials.json
|
|
```
|
|
|
|
**"Not authenticated" error in container:**
|
|
|
|
```bash
|
|
# Re-authenticate on host
|
|
claude /login
|
|
|
|
# Verify credentials exist
|
|
cat ~/.claude/.credentials.json
|
|
|
|
# Restart containers
|
|
claude-manage restart
|
|
|
|
# Verify mount inside container
|
|
docker exec claudebox-project-a ls -la /root/.claude/
|
|
```
|
|
|
|
**Discord bot not responding:**
|
|
|
|
```bash
|
|
# Check bot process
|
|
sudo systemctl status claude-discord-bot
|
|
|
|
# View bot logs
|
|
sudo journalctl -u claude-discord-bot -f
|
|
|
|
# Common issues:
|
|
# 1. Invalid token
|
|
grep DISCORD_TOKEN ~/claude-infra/discord-bot/.env
|
|
|
|
# 2. Missing permissions
|
|
# Check bot has "Use Slash Commands" in Discord server settings
|
|
|
|
# 3. Commands not registered
|
|
# Wait 1 hour or restart bot
|
|
sudo systemctl restart claude-discord-bot
|
|
```
|
|
|
|
**Claude Code command hangs:**
|
|
|
|
```bash
|
|
# Check container CPU/memory
|
|
docker stats claudebox-project-a
|
|
|
|
# Kill hung process inside container
|
|
docker exec claudebox-project-a pkill -f claude
|
|
|
|
# Or restart container
|
|
claude-manage restart project-a
|
|
```
|
|
|
|
**Monorepo build failures:**
|
|
|
|
```bash
|
|
# Clear caches
|
|
claude-manage exec project-a "rm -rf node_modules .turbo && pnpm install"
|
|
claude-manage exec project-b "poetry cache clear --all pypi"
|
|
claude-manage exec project-c "cargo clean"
|
|
|
|
# Rebuild container from scratch
|
|
claude-manage rebuild project-a
|
|
```
|
|
|
|
**Disk space issues:**
|
|
|
|
```bash
|
|
# Check usage
|
|
df -h
|
|
|
|
# Clean Docker
|
|
docker system prune -a --volumes
|
|
docker volume prune
|
|
|
|
# Clean build artifacts
|
|
claude-manage exec project-a "turbo run clean"
|
|
claude-manage exec project-b "find /workspace -type d -name __pycache__ -exec rm -rf {} +"
|
|
claude-manage exec project-c "cargo clean"
|
|
```
|
|
|
|
---
|
|
|
|
## Security considerations
|
|
|
|
**Credential security:**
|
|
|
|
- Never commit `.env` files to git
|
|
- Restrict `~/.claude/.credentials.json` permissions: `chmod 600 ~/.claude/.credentials.json`
|
|
- Use SSH keys for git operations, not passwords
|
|
- Rotate Discord bot token if exposed
|
|
|
|
**Container isolation:**
|
|
|
|
- Each container has separate network namespace
|
|
- Firewall rules prevent unauthorized egress
|
|
- Containers run as non-root user (UID 1000)
|
|
- No privileged mode
|
|
|
|
**Discord permissions:**
|
|
|
|
- Use role-based access control
|
|
- Audit channel permissions monthly
|
|
- Restrict `/shell` and destructive commands to admins
|
|
- Enable 2FA for all Discord accounts
|
|
|
|
**Git security:**
|
|
|
|
- Always work on feature branches
|
|
- Require code review for main/master
|
|
- Use GPG signing for commits
|
|
- Never commit secrets or API keys
|
|
|
|
**VM hardening:**
|
|
|
|
```bash
|
|
# Setup UFW firewall
|
|
sudo ufw default deny incoming
|
|
sudo ufw default allow outgoing
|
|
sudo ufw allow 22/tcp # SSH
|
|
sudo ufw enable
|
|
|
|
# Disable password authentication
|
|
sudo sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
|
|
sudo systemctl restart sshd
|
|
|
|
# Setup fail2ban
|
|
sudo apt install fail2ban
|
|
sudo systemctl enable fail2ban
|
|
```
|
|
|
|
**Backup strategy:**
|
|
|
|
```bash
|
|
# Weekly full backup
|
|
0 2 * * 0 /home/youruser/claude-infra/manage.sh backup
|
|
|
|
# Keep last 4 weeks
|
|
find ~/claude-backups -type d -mtime +28 -exec rm -rf {} +
|
|
```
|
|
|
|
---
|
|
|
|
## Subscription usage optimization
|
|
|
|
**Usage limits by tier:**
|
|
|
|
- **Claude Pro**: Approximately 5x more usage than free tier, resets monthly
|
|
- **Claude Team**: Higher limits, usage pooled across team members
|
|
- **Usage tracked per subscription**, not per container
|
|
|
|
**Strategies to optimize usage:**
|
|
|
|
1. **Use planning mode first**:
|
|
```
|
|
/claude --mode plan "Design the new API endpoint structure"
|
|
```
|
|
Reviews design without making changes.
|
|
|
|
2. **Batch related tasks**:
|
|
```
|
|
/claude "Update all API endpoints to use the new error handling pattern, then update tests, then update documentation"
|
|
```
|
|
Single conversation vs. three separate ones.
|
|
|
|
3. **Use shell commands for simple operations**:
|
|
```
|
|
/shell grep -r "TODO" src/
|
|
/git log --oneline -10
|
|
```
|
|
Don't waste Claude on simple lookups.
|
|
|
|
4. **Enable auto-checkpoints**:
|
|
Prevents needing to regenerate work if you hit usage limits mid-task.
|
|
|
|
5. **Schedule heavy operations**:
|
|
Run large refactors at the start of your billing cycle when usage resets.
|
|
|
|
**Monitor usage per project:**
|
|
|
|
Create a tracking webhook:
|
|
|
|
```typescript
|
|
// Add to bot.ts
|
|
async function logUsage(project: string, prompt: string) {
|
|
await fetch('YOUR_TRACKING_ENDPOINT', {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
project,
|
|
timestamp: new Date(),
|
|
promptLength: prompt.length,
|
|
user: interaction.user.id
|
|
})
|
|
});
|
|
}
|
|
```
|
|
|
|
This helps identify which projects consume the most usage.
|
|
|
|
---
|
|
|
|
## Conclusion
|
|
|
|
This infrastructure provides production-grade multi-project Claude Code access with strong isolation, team collaboration, and subscription-based authentication. The multi-container architecture scales horizontally—add more projects by extending the docker-compose file and updating the bot's PROJECT_CONFIGS.
|
|
|
|
**Quick reference:**
|
|
|
|
```bash
|
|
# Management
|
|
claude-manage start # Start all containers
|
|
claude-manage status # Check system status
|
|
claude-manage logs project-a # View logs
|
|
claude-manage shell project-b # Open shell
|
|
claude-manage health # Run health checks
|
|
|
|
# Discord commands
|
|
/claude <prompt> # Main interaction
|
|
/status # Project status
|
|
/shell <command> # Execute shell
|
|
/git <args> # Git operations
|
|
/monorepo-info # Show structure
|
|
|
|
# Maintenance
|
|
claude # Interactive mode (triggers auth if needed)
|
|
claude-manage restart # Restart containers
|
|
claude-manage update # Update everything
|
|
```
|
|
|
|
For advanced configurations, refer to the individual project documentation:
|
|
- claudebox: https://github.com/RchGrav/claudebox
|
|
- claude-code-discord: https://github.com/zebbern/claude-code-discord
|
|
|
|
---
|
|
|
|
## rdev: K3s Implementation Notes
|
|
|
|
This section documents our actual implementation running on k3s instead of a standalone VM.
|
|
|
|
### Architecture Difference
|
|
|
|
The reference guide above describes a VM-based deployment with Docker Compose. Our implementation uses:
|
|
|
|
- **Kubernetes (k3s)** instead of Docker Compose
|
|
- **StatefulSets** instead of standalone containers
|
|
- **Longhorn PVCs** instead of host volume mounts
|
|
- **GitHub Container Registry** instead of local images
|
|
|
|
```
|
|
k3s cluster (orchard9-k3sf)
|
|
└── rdev namespace
|
|
├── claudebox-0 (StatefulSet pod)
|
|
│ ├── Claude Code CLI
|
|
│ ├── /workspace (PVC: 20Gi)
|
|
│ └── /root/.claude (PVC: 1Gi)
|
|
└── Future: discord-bot, claudebox-pantheon, claudebox-aeries
|
|
```
|
|
|
|
### Key Commands
|
|
|
|
```bash
|
|
# REQUIRED: Set kubeconfig before any kubectl command
|
|
export KUBECONFIG=~/.kube/orchard9-k3sf.yaml
|
|
|
|
# Interactive Claude session (triggers OAuth if not authenticated)
|
|
kubectl exec -it -n rdev claudebox-0 -- claude
|
|
|
|
# Run Claude with a prompt
|
|
kubectl exec -it -n rdev claudebox-0 -- claude "your prompt here"
|
|
|
|
# Shell access
|
|
kubectl exec -it -n rdev claudebox-0 -- bash
|
|
|
|
# Check status
|
|
kubectl get pods -n rdev
|
|
|
|
# View logs
|
|
kubectl logs -n rdev claudebox-0
|
|
```
|
|
|
|
### Authentication
|
|
|
|
Claude authenticates via OAuth on first run. Auth persists in the `/root/.claude` PVC:
|
|
|
|
```bash
|
|
kubectl exec -it -n rdev claudebox-0 -- claude
|
|
# Follow the URL to authenticate
|
|
# Auth persists across pod restarts
|
|
```
|
|
|
|
### Image
|
|
|
|
```
|
|
ghcr.io/orchard9/rdev-claudebox:v0.1.0
|
|
```
|
|
|
|
Built for `linux/amd64` (k3s node architecture).
|
|
|
|
### Differences from Reference Guide
|
|
|
|
| Reference Guide | rdev Implementation |
|
|
|-----------------|---------------------|
|
|
| VM with Docker Compose | k3s with Kustomize |
|
|
| `docker exec` | `kubectl exec` |
|
|
| Host volume mounts | Longhorn PVCs |
|
|
| `~/.claude/.credentials.json` | PVC at `/root/.claude` |
|
|
| claudebox binary | Custom Dockerfile |
|
|
| Deno Discord bot | TBD (v0.4+) |
|
|
|
|
### Version History
|
|
|
|
See `history/` directory for detailed release notes.
|