Initial commit: rdev v0.1 base case

- Dockerfile for claudebox with Claude Code CLI
- Kustomize manifests for k3s deployment
- Scripts for credentials, deploy, and verify
- README with quick start guide

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jordan 2026-01-24 19:24:07 -07:00
commit 17aeb1c25b
13 changed files with 2160 additions and 0 deletions

19
.gitignore vendored Normal file
View File

@ -0,0 +1,19 @@
# Credentials - never commit
.claude/
*.credentials*
*.key
*.pem
# OS
.DS_Store
Thumbs.db
# IDE
.idea/
.vscode/
*.swp
*.swo
# Build artifacts
*.tar
*.gz

46
CLAUDE.md Normal file
View File

@ -0,0 +1,46 @@
# rdev - Remote Developer
Run Claude Code in isolated Kubernetes pods, controlled via Discord.
## Quick Reference
```bash
# Set kubeconfig for k3s (REQUIRED before any kubectl)
export KUBECONFIG=~/.kube/orchard9-k3sf.yaml
# Deploy
kubectl apply -k deployments/k8s/base
# Verify
kubectl exec -n rdev claudebox-0 -- claude --version
# Test Claude
kubectl exec -it -n rdev claudebox-0 -- claude "say hello"
```
## Architecture
```
k3s cluster (rdev namespace)
├── claudebox-0 (StatefulSet)
│ ├── Claude Code CLI
│ ├── /workspace (PVC)
│ └── /root/.claude (credentials secret)
└── Future: discord-bot, more claudebox pods
```
## Development
This is v0.1 - base case only. See docs/reference.md for full vision.
## Deploying to k3s
1. Build and push image (or use pre-built)
2. Create Claude credentials secret
3. Apply manifests: `kubectl apply -k deployments/k8s/base`
## Constraints
- **ON-PREM k3s** - not GKE, always set KUBECONFIG
- **Kustomize only** - no ArgoCD
- **Manual deploys** - no CI/CD pipelines yet

39
Dockerfile Normal file
View File

@ -0,0 +1,39 @@
# rdev claudebox - Claude Code in a container
# v0.1 - Base case
FROM ubuntu:22.04
# Prevent interactive prompts during package installation
ENV DEBIAN_FRONTEND=noninteractive
# Install system dependencies
RUN apt-get update && apt-get install -y \
curl \
wget \
git \
vim \
build-essential \
ca-certificates \
gnupg \
&& rm -rf /var/lib/apt/lists/*
# Install Node.js 20 (required for Claude Code CLI)
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y nodejs \
&& rm -rf /var/lib/apt/lists/*
# Install Claude Code CLI
RUN npm install -g @anthropic-ai/claude-code
# Create workspace directory
RUN mkdir -p /workspace
# Set working directory
WORKDIR /workspace
# Create a simple healthcheck script
RUN echo '#!/bin/bash\nclaude --version > /dev/null 2>&1' > /healthcheck.sh \
&& chmod +x /healthcheck.sh
# Keep container running (will exec into it)
CMD ["tail", "-f", "/dev/null"]

83
README.md Normal file
View File

@ -0,0 +1,83 @@
# rdev - Remote Developer
Run Claude Code in isolated Kubernetes pods on your k3s cluster.
## Quick Start
```bash
# 1. Set kubeconfig (REQUIRED - this is k3s, not GKE)
export KUBECONFIG=~/.kube/orchard9-k3sf.yaml
# 2. Authenticate Claude locally (if not already)
claude login
# 3. Create credentials secret
./scripts/create-credentials-secret.sh
# 4. Deploy
./scripts/deploy.sh
# 5. Verify
./scripts/verify.sh
```
## Usage
```bash
# Check Claude version
kubectl exec -n rdev claudebox-0 -- claude --version
# Interactive Claude session
kubectl exec -it -n rdev claudebox-0 -- claude "what can you help me with?"
# Run in workspace
kubectl exec -it -n rdev claudebox-0 -- bash
cd /workspace
claude "create a hello world go program"
```
## Architecture
```
k3s cluster
└── rdev namespace
└── claudebox-0 (StatefulSet)
├── Claude Code CLI
├── /workspace (20Gi PVC via Longhorn)
└── /root/.claude (credentials from secret)
```
## Roadmap
- [x] v0.1: Base case - single claudebox pod
- [ ] v0.2: Real workspace mounting (pantheon, aeries)
- [ ] v0.3: Git integration (push/pull)
- [ ] v0.4: Discord bot control
- [ ] v0.5: Streaming output
- [ ] v0.6: Multi-project routing
## Development
```bash
# Build image locally
docker build -t rdev-claudebox:dev .
# Build and push to Artifact Registry
./scripts/build-push.sh v0.1.0
```
## Troubleshooting
```bash
# Check pod status
kubectl get pods -n rdev
# View pod logs
kubectl logs claudebox-0 -n rdev
# Describe pod for events
kubectl describe pod claudebox-0 -n rdev
# Check credentials mount
kubectl exec -n rdev claudebox-0 -- ls -la /root/.claude/
```

View File

@ -0,0 +1,93 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: claudebox
namespace: rdev
labels:
app.kubernetes.io/name: claudebox
app.kubernetes.io/part-of: rdev
spec:
serviceName: claudebox
replicas: 1
selector:
matchLabels:
app: claudebox
template:
metadata:
labels:
app: claudebox
app.kubernetes.io/name: claudebox
app.kubernetes.io/part-of: rdev
spec:
containers:
- name: claudebox
image: us-central1-docker.pkg.dev/orchard9/docker-images/rdev-claudebox:v0.1.0
imagePullPolicy: Always
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2"
memory: "4Gi"
volumeMounts:
# Workspace for projects
- name: workspace
mountPath: /workspace
# Claude credentials (from secret)
- name: claude-credentials
mountPath: /root/.claude
readOnly: true
# Simple liveness check - container is running
livenessProbe:
exec:
command:
- cat
- /healthcheck.sh
initialDelaySeconds: 5
periodSeconds: 60
# Readiness - claude CLI is available
readinessProbe:
exec:
command:
- claude
- --version
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 10
volumes:
- name: workspace
persistentVolumeClaim:
claimName: claudebox-workspace
- name: claude-credentials
secret:
secretName: claude-credentials
defaultMode: 0600
# Pull from Artifact Registry
imagePullSecrets:
- name: gcr-secret
---
# Headless service for StatefulSet
apiVersion: v1
kind: Service
metadata:
name: claudebox
namespace: rdev
labels:
app.kubernetes.io/name: claudebox
app.kubernetes.io/part-of: rdev
spec:
clusterIP: None
selector:
app: claudebox
ports:
- port: 8080
name: http

View File

@ -0,0 +1,13 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: rdev
resources:
- namespace.yaml
- pvc.yaml
- claudebox.yaml
commonLabels:
app.kubernetes.io/managed-by: kustomize
app.kubernetes.io/part-of: rdev

View File

@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: rdev
labels:
app.kubernetes.io/name: rdev
app.kubernetes.io/part-of: rdev

View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: claudebox-workspace
namespace: rdev
labels:
app.kubernetes.io/name: claudebox
app.kubernetes.io/part-of: rdev
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 20Gi

1612
docs/reference.md Normal file

File diff suppressed because it is too large Load Diff

38
scripts/build-push.sh Executable file
View File

@ -0,0 +1,38 @@
#!/bin/bash
# Build and push claudebox image to Artifact Registry
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
# Image configuration
REGISTRY="us-central1-docker.pkg.dev/orchard9/docker-images"
IMAGE_NAME="rdev-claudebox"
VERSION="${1:-latest}"
IMAGE_TAG="$REGISTRY/$IMAGE_NAME:$VERSION"
echo "Building claudebox image..."
echo "Image: $IMAGE_TAG"
echo ""
cd "$PROJECT_ROOT"
# Build the image
docker build -t "$IMAGE_TAG" -t "$REGISTRY/$IMAGE_NAME:latest" .
echo ""
echo "Pushing to Artifact Registry..."
# Push both tags
docker push "$IMAGE_TAG"
docker push "$REGISTRY/$IMAGE_NAME:latest"
echo ""
echo "Done!"
echo ""
echo "Image pushed: $IMAGE_TAG"
echo ""
echo "To deploy, run:"
echo " ./scripts/deploy.sh"

View File

@ -0,0 +1,37 @@
#!/bin/bash
# Create Kubernetes secret from local Claude credentials
# Run this after authenticating with `claude login` locally
set -e
# Ensure kubeconfig is set
if [[ -z "$KUBECONFIG" ]]; then
echo "Error: KUBECONFIG not set"
echo "Run: export KUBECONFIG=~/.kube/orchard9-k3sf.yaml"
exit 1
fi
# Check if Claude credentials exist
CLAUDE_DIR="$HOME/.claude"
if [[ ! -d "$CLAUDE_DIR" ]]; then
echo "Error: Claude credentials not found at $CLAUDE_DIR"
echo "Run 'claude login' first to authenticate"
exit 1
fi
# Create namespace if it doesn't exist
kubectl create namespace rdev --dry-run=client -o yaml | kubectl apply -f -
# Create secret from the .claude directory
echo "Creating claude-credentials secret from $CLAUDE_DIR..."
# We need to create the secret with all files in ~/.claude
kubectl create secret generic claude-credentials \
--namespace=rdev \
--from-file="$CLAUDE_DIR" \
--dry-run=client -o yaml | kubectl apply -f -
echo "Secret created successfully!"
echo ""
echo "Verify with:"
echo " kubectl get secret claude-credentials -n rdev"

72
scripts/deploy.sh Executable file
View File

@ -0,0 +1,72 @@
#!/bin/bash
# Deploy rdev to k3s cluster
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
# Ensure kubeconfig is set
if [[ -z "$KUBECONFIG" ]]; then
echo "Error: KUBECONFIG not set"
echo "Run: export KUBECONFIG=~/.kube/orchard9-k3sf.yaml"
exit 1
fi
echo "Deploying rdev to k3s..."
echo "Using kubeconfig: $KUBECONFIG"
echo ""
# Verify cluster access
echo "Verifying cluster access..."
kubectl cluster-info > /dev/null || {
echo "Error: Cannot connect to cluster"
exit 1
}
# Check if credentials secret exists
if ! kubectl get secret claude-credentials -n rdev > /dev/null 2>&1; then
echo ""
echo "Warning: claude-credentials secret not found!"
echo "Run ./scripts/create-credentials-secret.sh first"
echo ""
read -p "Continue anyway? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
# Check if gcr-secret exists in rdev namespace
if ! kubectl get secret gcr-secret -n rdev > /dev/null 2>&1; then
echo ""
echo "Copying gcr-secret from apps namespace to rdev..."
kubectl get secret gcr-secret -n apps -o yaml | \
sed 's/namespace: apps/namespace: rdev/' | \
kubectl apply -f -
fi
# Apply manifests
echo ""
echo "Applying Kustomize manifests..."
kubectl apply -k "$PROJECT_ROOT/deployments/k8s/base"
echo ""
echo "Waiting for claudebox pod to be ready..."
kubectl wait --for=condition=ready pod -l app=claudebox -n rdev --timeout=120s || {
echo ""
echo "Pod not ready. Check status with:"
echo " kubectl get pods -n rdev"
echo " kubectl describe pod claudebox-0 -n rdev"
echo " kubectl logs claudebox-0 -n rdev"
exit 1
}
echo ""
echo "Deployment complete!"
echo ""
echo "Verify with:"
echo " kubectl exec -n rdev claudebox-0 -- claude --version"
echo ""
echo "Test Claude:"
echo " kubectl exec -it -n rdev claudebox-0 -- claude \"say hello\""

86
scripts/verify.sh Executable file
View File

@ -0,0 +1,86 @@
#!/bin/bash
# Verify rdev deployment
set -e
# Ensure kubeconfig is set
if [[ -z "$KUBECONFIG" ]]; then
echo "Error: KUBECONFIG not set"
echo "Run: export KUBECONFIG=~/.kube/orchard9-k3sf.yaml"
exit 1
fi
echo "=== rdev Deployment Verification ==="
echo ""
# Check namespace
echo "1. Checking namespace..."
if kubectl get namespace rdev > /dev/null 2>&1; then
echo " ✅ Namespace 'rdev' exists"
else
echo " ❌ Namespace 'rdev' not found"
exit 1
fi
# Check PVC
echo ""
echo "2. Checking PVC..."
PVC_STATUS=$(kubectl get pvc claudebox-workspace -n rdev -o jsonpath='{.status.phase}' 2>/dev/null || echo "NotFound")
if [[ "$PVC_STATUS" == "Bound" ]]; then
echo " ✅ PVC 'claudebox-workspace' is Bound"
else
echo " ⚠️ PVC status: $PVC_STATUS"
fi
# Check secret
echo ""
echo "3. Checking credentials secret..."
if kubectl get secret claude-credentials -n rdev > /dev/null 2>&1; then
echo " ✅ Secret 'claude-credentials' exists"
else
echo " ❌ Secret 'claude-credentials' not found"
echo " Run: ./scripts/create-credentials-secret.sh"
fi
# Check pod
echo ""
echo "4. Checking claudebox pod..."
POD_STATUS=$(kubectl get pod claudebox-0 -n rdev -o jsonpath='{.status.phase}' 2>/dev/null || echo "NotFound")
if [[ "$POD_STATUS" == "Running" ]]; then
echo " ✅ Pod 'claudebox-0' is Running"
else
echo " ❌ Pod status: $POD_STATUS"
if [[ "$POD_STATUS" != "NotFound" ]]; then
echo ""
echo " Pod events:"
kubectl describe pod claudebox-0 -n rdev | grep -A 10 "Events:" | head -15
fi
exit 1
fi
# Check Claude CLI
echo ""
echo "5. Checking Claude CLI..."
CLAUDE_VERSION=$(kubectl exec -n rdev claudebox-0 -- claude --version 2>/dev/null || echo "Error")
if [[ "$CLAUDE_VERSION" != "Error" ]]; then
echo " ✅ Claude CLI: $CLAUDE_VERSION"
else
echo " ❌ Claude CLI not responding"
exit 1
fi
# Test Claude (optional)
echo ""
echo "6. Testing Claude response..."
echo " Running: claude \"respond with only the word: working\""
RESPONSE=$(kubectl exec -n rdev claudebox-0 -- claude "respond with only the word: working" --print 2>/dev/null | head -5 || echo "Error")
if [[ "$RESPONSE" != "Error" ]]; then
echo " ✅ Claude responded:"
echo " $RESPONSE"
else
echo " ⚠️ Claude did not respond (may need authentication)"
echo " Check credentials: kubectl exec -n rdev claudebox-0 -- ls -la /root/.claude/"
fi
echo ""
echo "=== Verification Complete ==="