Implement zero-friction authentication persistence with MCP user scope
Major breakthrough solving the authentication chicken-and-egg problem: Key Changes: - Copy ~/.claude.json and ~/.claude/ during Docker build for baked-in auth - Add -s user flag to claude mcp add-json for persistent MCP servers - Simplify rebuild logic to prevent unnecessary rebuilds - Update documentation with rebuild instructions Technical Details: - Authentication files placed before USER switch in Dockerfile - MCP configuration now persists across all sessions - Rebuild only occurs when image doesn't exist - Clean separation of build vs runtime concerns Result: Users authenticate once on host, then zero login prompts forever. SMS notifications ready immediately on container start.
This commit is contained in:
parent
d9bf0f4b53
commit
7cd765b756
@ -1,7 +1,7 @@
|
||||
# Copy this file to .env and fill in your credentials
|
||||
# The .env file will be baked into the Docker image during build
|
||||
|
||||
# Required for Claude Code
|
||||
# Required for Claude Code if not using via subscription.
|
||||
ANTHROPIC_API_KEY=your_anthropic_api_key_here
|
||||
|
||||
# Optional: Twilio credentials for SMS notifications
|
||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -26,5 +26,10 @@ data/
|
||||
*.tmp
|
||||
*.temp
|
||||
~*
|
||||
|
||||
# Claude authentication files (copied during build)
|
||||
.claude.json
|
||||
.claude/
|
||||
|
||||
# Environment file with credentials
|
||||
.env
|
||||
|
18
Dockerfile
18
Dockerfile
@ -39,17 +39,30 @@ RUN chmod +x /app/startup.sh
|
||||
# This enables one-time setup - no need for .env in project directories
|
||||
COPY .env /app/.env
|
||||
|
||||
# Set proper ownership
|
||||
# Copy Claude authentication files from host
|
||||
# Note: These must exist - host must have authenticated Claude Code first
|
||||
COPY .claude.json /tmp/.claude.json
|
||||
COPY .claude /tmp/.claude
|
||||
|
||||
# Move auth files to proper location before switching user
|
||||
RUN cp /tmp/.claude.json /home/claude-user/.claude.json && \
|
||||
cp -r /tmp/.claude/* /home/claude-user/.claude/ && \
|
||||
rm -rf /tmp/.claude*
|
||||
|
||||
# Set proper ownership for everything
|
||||
RUN chown -R claude-user:claude-user /app /home/claude-user
|
||||
|
||||
# Switch to non-root user
|
||||
USER claude-user
|
||||
|
||||
# Set HOME immediately after switching user
|
||||
ENV HOME=/home/claude-user
|
||||
|
||||
# Configure MCP server during build if Twilio credentials are provided
|
||||
RUN bash -c 'source /app/.env && \
|
||||
if [ -n "$TWILIO_ACCOUNT_SID" ] && [ -n "$TWILIO_AUTH_TOKEN" ]; then \
|
||||
echo "Configuring Twilio MCP server..." && \
|
||||
/usr/local/bin/claude mcp add-json twilio \
|
||||
/usr/local/bin/claude mcp add-json twilio -s user \
|
||||
"{\"command\":\"npx\",\"args\":[\"-y\",\"@yiyang.1i/sms-mcp-server\"],\"env\":{\"ACCOUNT_SID\":\"$TWILIO_ACCOUNT_SID\",\"AUTH_TOKEN\":\"$TWILIO_AUTH_TOKEN\",\"FROM_NUMBER\":\"$TWILIO_FROM_NUMBER\"}}"; \
|
||||
else \
|
||||
echo "No Twilio credentials found, skipping MCP configuration"; \
|
||||
@ -60,7 +73,6 @@ WORKDIR /workspace
|
||||
|
||||
# Environment variables will be passed from host
|
||||
ENV NODE_ENV=production
|
||||
ENV HOME=/home/claude-user
|
||||
|
||||
# Start both MCP server and Claude Code
|
||||
ENTRYPOINT ["/app/startup.sh"]
|
26
README.md
26
README.md
@ -10,6 +10,15 @@ A Docker container setup for running Claude Code with full autonomous permission
|
||||
- Auto-configures Claude settings for seamless operation
|
||||
- Simple one-command setup and usage
|
||||
|
||||
## Prerequisites
|
||||
|
||||
**Important**: Before building the Docker image, you must authenticate Claude Code on your host system:
|
||||
1. Install Claude Code: `npm install -g @anthropic-ai/claude-code`
|
||||
2. Run `claude` and complete the authentication flow
|
||||
3. Verify authentication files exist: `ls ~/.claude.json ~/.claude/`
|
||||
|
||||
The Docker build will copy your authentication from these locations.
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. **Clone the repository:**
|
||||
@ -157,6 +166,23 @@ Each project gets:
|
||||
- `.claude/CLAUDE.md` - Instructions for Claude behavior
|
||||
- `scratchpad.md` - Project context file
|
||||
|
||||
### Rebuilding the Image
|
||||
|
||||
The Docker image is built only once when you first run `claude-docker`. To force a rebuild:
|
||||
|
||||
```bash
|
||||
# Remove the existing image
|
||||
docker rmi claude-docker:latest
|
||||
|
||||
# Next run of claude-docker will rebuild
|
||||
claude-docker
|
||||
```
|
||||
|
||||
Rebuild when you:
|
||||
- Update your .env file with new credentials
|
||||
- Update the Claude Docker repository
|
||||
- Want to refresh the authentication files
|
||||
|
||||
## Requirements
|
||||
|
||||
- Docker installed and running
|
||||
|
199
scratchpad.md
199
scratchpad.md
@ -1,171 +1,64 @@
|
||||
# Claude Docker Project Scratchpad
|
||||
|
||||
## Project Overview
|
||||
Building a Docker container that runs Claude Code with full autonomous permissions and Twilio SMS notifications upon task completion.
|
||||
Docker container for Claude Code with full autonomous permissions, authentication persistence, and Twilio SMS notifications.
|
||||
|
||||
## What Was Done ✅
|
||||
**Phase 1 - Complete MVP:**
|
||||
- GitHub repository created: https://github.com/VishalJ99/claude-docker
|
||||
## ✅ COMPLETED PHASES
|
||||
|
||||
### Phase 1 - MVP (Complete)
|
||||
- Docker setup with Claude Code + Twilio MCP integration
|
||||
- Wrapper script (`claude-docker.sh`) for easy invocation
|
||||
- Auto .claude directory setup with MCP configuration
|
||||
- Installation script for zshrc alias
|
||||
- SMS notifications via Twilio MCP server
|
||||
- Wrapper script for easy invocation
|
||||
- Auto .claude directory setup
|
||||
- Full autonomous permissions with --dangerously-skip-permissions
|
||||
- Context persistence via scratchpad.md files
|
||||
- Complete documentation and examples
|
||||
- **✅ WORKING** - All startup issues resolved, Docker container launches Claude Code successfully
|
||||
|
||||
## Next Steps 🎯
|
||||
**Phase 2 - Security & Persistence Enhancements:**
|
||||
### Phase 2 - Authentication Persistence (Complete)
|
||||
- **SOLVED**: Authentication files copied during Docker build
|
||||
- **SOLVED**: MCP servers persist with user scope
|
||||
- **SOLVED**: No unnecessary rebuilds
|
||||
- **RESULT**: Zero-friction experience - no login prompts, SMS ready instantly
|
||||
|
||||
### 1. Authentication Persistence (HIGH Priority) - ✅ COMPLETED
|
||||
**Problem:** Need to re-login to Claude Code every time container starts
|
||||
## 🎯 CURRENT FOCUS
|
||||
**Phase 3 - Smart SMS Notifications:**
|
||||
|
||||
**Research Findings:**
|
||||
- Claude Code stores auth tokens in `~/.claude/.credentials.json`
|
||||
- Known issues: #1222 (persistent auth warnings), #1676 (logout after restart)
|
||||
- The devcontainer mounts `/home/node/.claude` for config persistence
|
||||
- But auth tokens are NOT persisted properly even in devcontainer
|
||||
### Next Task: Prompt Engineering for SMS Notifications
|
||||
**Goal:** Configure Claude to automatically send completion SMS to `$TWILIO_TO_NUMBER`
|
||||
|
||||
**Implementation Completed:**
|
||||
1. **Created persistent directory structure:**
|
||||
- Host: `~/.claude-docker/claude-home`
|
||||
- Container: `/home/claude-user/.claude`
|
||||
- Mounted with read/write permissions
|
||||
**Implementation Plan:**
|
||||
1. Update CLAUDE.md template with SMS notification instructions
|
||||
2. Add completion detection logic
|
||||
3. Integrate with existing Twilio MCP server
|
||||
4. Test notification flow
|
||||
|
||||
2. **Updated Docker setup:**
|
||||
- Created non-root user `claude-user` for better security
|
||||
- Set proper ownership and permissions
|
||||
- Added volume mount for Claude home directory
|
||||
## 📚 KEY INSIGHTS FROM AUTHENTICATION JOURNEY
|
||||
|
||||
3. **Enhanced startup script:**
|
||||
- Checks for existing `.credentials.json` on startup
|
||||
- Notifies user if auth exists or login needed
|
||||
- Credentials persist across container restarts
|
||||
### Critical Discovery: ~/.claude.json + MCP Scope
|
||||
- Claude Code requires BOTH `~/.claude.json` (user profile) AND `~/.claude/.credentials.json` (tokens)
|
||||
- MCP servers default to "local" scope (project-specific) - need "user" scope for persistence
|
||||
- Authentication can be baked into Docker image during build
|
||||
- Simple rebuild logic (only if image missing) prevents unnecessary rebuilds
|
||||
|
||||
**Result:** Users now login once and authentication persists forever!
|
||||
### Technical Implementation
|
||||
- Copy auth files during Docker build, not runtime mounting
|
||||
- Use `-s user` flag for MCP persistence across sessions
|
||||
- Files placed at correct locations before user switch in Dockerfile
|
||||
|
||||
### 2. Network Security (High Priority) - PLANNED
|
||||
**Implementation based on devcontainer's init-firewall.sh:**
|
||||
|
||||
**Key Components:**
|
||||
1. **Firewall Script Features:**
|
||||
- Uses iptables with default DROP policy
|
||||
- ipset for managing allowed IP ranges
|
||||
- Dynamic IP resolution for allowed domains
|
||||
- Verification of connectivity post-setup
|
||||
|
||||
2. **Allowed Domains Configuration:**
|
||||
```yaml
|
||||
allowed_domains:
|
||||
- api.anthropic.com # Claude API
|
||||
- api.twilio.com # SMS notifications
|
||||
- github.com # Git operations
|
||||
- raw.githubusercontent.com
|
||||
- registry.npmjs.org # Package management
|
||||
- pypi.org # Python packages
|
||||
|
||||
blocked_paths: # File system restrictions
|
||||
- /etc
|
||||
- /root
|
||||
- ~/.ssh
|
||||
```
|
||||
|
||||
3. **User-Friendly Setup:**
|
||||
- Simple YAML config file for rules
|
||||
- Easy enable/disable of firewall
|
||||
- Logging of blocked attempts
|
||||
- Graceful degradation if firewall fails
|
||||
|
||||
### 3. Shell History Persistence (Medium Priority)
|
||||
- Add persistent bash/zsh history between container sessions
|
||||
- Mount history file to host directory
|
||||
- Implement history management similar to Claude dev container
|
||||
- Ensure commands persist across sessions
|
||||
|
||||
### 4. Additional Persistence Features (Medium Priority)
|
||||
- Persistent npm cache for faster startups
|
||||
## 🔮 FUTURE ENHANCEMENTS
|
||||
- Network security with firewall (iptables + ipset)
|
||||
- Shell history persistence between sessions
|
||||
- Git configuration persistence
|
||||
- Custom shell aliases and environment
|
||||
|
||||
## Direction & Vision
|
||||
**Security-First Autonomous Environment:**
|
||||
- Maintain full Claude autonomy within projects
|
||||
- Add network security layer to prevent unauthorized access
|
||||
- Enhance user experience with persistent shell history
|
||||
- Keep container lightweight and fast
|
||||
- Ensure easy setup and maintenance
|
||||
## 📋 DECISIONS LOG
|
||||
- MCP integration using user scope for persistence
|
||||
- Authentication files baked into Docker image at build time
|
||||
- Single container approach (no Docker Compose)
|
||||
- Simplified rebuild logic (only when image missing)
|
||||
- SMS via `@yiyang.1i/sms-mcp-server` with Auth Token
|
||||
|
||||
## Decisions Log
|
||||
- Using MCP (Model Context Protocol) for Twilio integration instead of direct API
|
||||
- Single container approach (no Docker Compose needed)
|
||||
- API keys via .env file
|
||||
- Context persistence via scratchpad.md files
|
||||
- Simplified settings.json to only include MCP config (no redundant allowedTools)
|
||||
- **NEW:** Adding firewall for network security
|
||||
- **NEW:** Adding shell history persistence like Claude dev container
|
||||
- **NEW (2024-12-06):** Focus on auth persistence first before firewall implementation
|
||||
- **COMPLETED (2024-12-06):** Auth persistence via mounted ~/.claude directory
|
||||
|
||||
## Notes & Context
|
||||
- Repository: https://github.com/VishalJ99/claude-docker
|
||||
- Using --dangerously-skip-permissions flag for full autonomy
|
||||
- Twilio MCP server runs via Claude's MCP config (not as separate process)
|
||||
- Uses @twilio-alpha/mcp package with API Key/Secret authentication
|
||||
- Container auto-removes on exit for clean state
|
||||
- Project directory mounted at /workspace
|
||||
- Need to research Claude dev container's init-firewall.sh implementation
|
||||
- Need to research their history persistence mechanism
|
||||
- **Fixed startup issues:**
|
||||
- Changed executable from `claude-code` to `claude` in startup.sh
|
||||
- Fixed .env parsing to handle comments properly using `set -a`/`source`
|
||||
- Added explicit PATH for npm global binaries
|
||||
- Maintained separation: `claude-docker` (host) vs `claude` (container)
|
||||
- **Current working state:** Container launches successfully, authentication required each session
|
||||
- **Auth Persistence Research (2024-12-06):**
|
||||
- Claude Code has known issues with auth persistence
|
||||
- Tokens stored in temp locations that get cleared
|
||||
- Need to find exact token storage location and persist it
|
||||
|
||||
## MCP Integration Update (2024-12-17)
|
||||
|
||||
### ✅ COMPLETED: Simplified Twilio MCP Integration
|
||||
|
||||
**What Changed:**
|
||||
1. **Switched MCP Server:** From `@twilio-alpha/mcp` (API Key/Secret) to `@yiyang.1i/sms-mcp-server` (Auth Token)
|
||||
2. **Simplified Configuration:** MCP setup now happens during Docker build instead of runtime
|
||||
3. **Removed Complexity:** No more mcp-config.json or environment variable substitution
|
||||
|
||||
**Implementation Details:**
|
||||
1. **Updated .env.example:**
|
||||
- Removed: `TWILIO_API_KEY` and `TWILIO_API_SECRET`
|
||||
- Added: `TWILIO_AUTH_TOKEN`
|
||||
- Kept: `TWILIO_ACCOUNT_SID`, `TWILIO_FROM_NUMBER`, `TWILIO_TO_NUMBER`
|
||||
|
||||
2. **Updated Dockerfile:**
|
||||
- Removed global installation of `@twilio-alpha/mcp`
|
||||
- Added MCP configuration during build using `claude mcp add-json` command
|
||||
- MCP server is configured if Twilio credentials are present in .env
|
||||
|
||||
3. **Simplified startup.sh:**
|
||||
- Removed all MCP configuration logic
|
||||
- Just loads environment variables and starts Claude
|
||||
- Shows Twilio status on startup
|
||||
|
||||
4. **Removed Files:**
|
||||
- `config/mcp-config.json` (no longer needed)
|
||||
- `config/` directory (now empty)
|
||||
|
||||
**Result:**
|
||||
- MCP configuration is baked into the Docker image at build time
|
||||
- No runtime configuration needed
|
||||
- Simpler, more reliable setup
|
||||
- SMS capability available via `twilio__send_text` command
|
||||
|
||||
## Quick References
|
||||
- Install: `./scripts/install.sh`
|
||||
- Usage: `claude-docker` (from any project directory)
|
||||
- Config: `~/.claude-docker/.env`
|
||||
- Repo: https://github.com/VishalJ99/claude-docker
|
||||
- Claude dev container: https://github.com/anthropics/claude-code/tree/main/.devcontainer
|
||||
## 🔗 QUICK REFERENCES
|
||||
- **Install:** `./scripts/install.sh`
|
||||
- **Usage:** `claude-docker` (from any project directory)
|
||||
- **Config:** `~/.claude-docker/.env`
|
||||
- **Force rebuild:** `docker rmi claude-docker:latest`
|
||||
- **SMS command:** `twilio__send_text`
|
||||
- **Repository:** https://github.com/VishalJ99/claude-docker
|
@ -38,28 +38,24 @@ fi
|
||||
NEED_REBUILD=false
|
||||
|
||||
if ! docker images | grep -q "claude-docker"; then
|
||||
echo "Building Claude Docker image with your user permissions..."
|
||||
echo "Building Claude Docker image for first time..."
|
||||
NEED_REBUILD=true
|
||||
elif ! docker image inspect claude-docker:latest | grep -q "USER_UID.*$(id -u)" 2>/dev/null; then
|
||||
echo "Rebuilding Claude Docker image to match your user permissions..."
|
||||
NEED_REBUILD=true
|
||||
elif [ -f "$ENV_FILE" ]; then
|
||||
# Check if .env is newer than the Docker image
|
||||
IMAGE_CREATED=$(docker inspect -f '{{.Created}}' claude-docker:latest 2>/dev/null)
|
||||
if [ -n "$IMAGE_CREATED" ]; then
|
||||
IMAGE_TIMESTAMP=$(date -d "$IMAGE_CREATED" +%s 2>/dev/null || date -j -f "%Y-%m-%dT%H:%M:%S" "${IMAGE_CREATED%%.*}" +%s 2>/dev/null)
|
||||
ENV_TIMESTAMP=$(stat -c %Y "$ENV_FILE" 2>/dev/null || stat -f %m "$ENV_FILE" 2>/dev/null)
|
||||
|
||||
if [ -n "$IMAGE_TIMESTAMP" ] && [ -n "$ENV_TIMESTAMP" ] && [ "$ENV_TIMESTAMP" -gt "$IMAGE_TIMESTAMP" ]; then
|
||||
echo "⚠️ .env file has been updated since last build"
|
||||
echo " Rebuilding to include new credentials..."
|
||||
NEED_REBUILD=true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$NEED_REBUILD" = true ]; then
|
||||
# Copy authentication files to build context
|
||||
if [ -f "$HOME/.claude.json" ]; then
|
||||
cp "$HOME/.claude.json" "$PROJECT_ROOT/.claude.json"
|
||||
fi
|
||||
if [ -d "$HOME/.claude" ]; then
|
||||
cp -r "$HOME/.claude" "$PROJECT_ROOT/.claude"
|
||||
fi
|
||||
|
||||
docker build --build-arg USER_UID=$(id -u) --build-arg USER_GID=$(id -g) -t claude-docker:latest "$PROJECT_ROOT"
|
||||
|
||||
# Clean up copied auth files
|
||||
rm -f "$PROJECT_ROOT/.claude.json"
|
||||
rm -rf "$PROJECT_ROOT/.claude"
|
||||
fi
|
||||
|
||||
# Ensure the claude-home directory exists
|
||||
|
@ -1,59 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Test script to verify Twilio SMS functionality
|
||||
const accountSid = process.env.TWILIO_ACCOUNT_SID;
|
||||
const authToken = process.env.TWILIO_AUTH_TOKEN;
|
||||
const fromNumber = process.env.TWILIO_FROM_NUMBER;
|
||||
const toNumber = process.env.TWILIO_TO_NUMBER;
|
||||
|
||||
console.log('Twilio Test Configuration:');
|
||||
console.log(`Account SID: ${accountSid?.substring(0, 10)}...`);
|
||||
console.log(`Auth Token: ${authToken ? '***' + authToken.substring(authToken.length - 4) : 'Not set'}`);
|
||||
console.log(`From: ${fromNumber}`);
|
||||
console.log(`To: ${toNumber}`);
|
||||
|
||||
// Using Twilio REST API directly
|
||||
const https = require('https');
|
||||
|
||||
const auth = Buffer.from(`${accountSid}:${authToken}`).toString('base64');
|
||||
const data = new URLSearchParams({
|
||||
To: toNumber,
|
||||
From: fromNumber,
|
||||
Body: 'MCP is working! This is a test message from Claude Docker.'
|
||||
});
|
||||
|
||||
const options = {
|
||||
hostname: 'api.twilio.com',
|
||||
port: 443,
|
||||
path: `/2010-04-01/Accounts/${accountSid}/Messages.json`,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Basic ${auth}`,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Content-Length': data.toString().length
|
||||
}
|
||||
};
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
let body = '';
|
||||
res.on('data', (chunk) => body += chunk);
|
||||
res.on('end', () => {
|
||||
if (res.statusCode === 201) {
|
||||
console.log('\n✅ SMS sent successfully!');
|
||||
const response = JSON.parse(body);
|
||||
console.log(`Message SID: ${response.sid}`);
|
||||
console.log(`Status: ${response.status}`);
|
||||
} else {
|
||||
console.error('\n❌ Failed to send SMS');
|
||||
console.error(`Status: ${res.statusCode}`);
|
||||
console.error(`Response: ${body}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (e) => {
|
||||
console.error(`Problem with request: ${e.message}`);
|
||||
});
|
||||
|
||||
req.write(data.toString());
|
||||
req.end();
|
Loading…
Reference in New Issue
Block a user