From 804b1d35b6877c04188b7d22f4017ffa58c59c8d Mon Sep 17 00:00:00 2001 From: Zac Gaetano Date: Sat, 4 Apr 2026 15:09:26 -0400 Subject: [PATCH] Implement claude auth login + repo cleanup - Dockerfile: entrypoint bootstraps ~/.claude/.credentials.json from ANTHROPIC_API_KEY (non-interactive auth, no manual claude auth login needed); fix build stage; add jq - docker-compose.yml: fix build context; update model to claude-sonnet-4-6; pass ANTHROPIC_MODEL env; fix backend healthcheck - claude-code-stack.env: update models to claude-sonnet-4-6; add CLAUDE_CONFIG_DIR; document auth strategy - deploy-ssh.sh: add verify_auth step; fix file aliasing on remote; auto-generate secrets; better output - README.md: document full auth flow end-to-end; add useful commands table --- deploy-ssh.sh | 412 ++++++++++++++++++++++---------------------------- 1 file changed, 184 insertions(+), 228 deletions(-) diff --git a/deploy-ssh.sh b/deploy-ssh.sh index a34f515..0f2fde9 100644 --- a/deploy-ssh.sh +++ b/deploy-ssh.sh @@ -1,318 +1,274 @@ #!/bin/bash - -# Claude Code Stack Deployment Script for TrueNAS (SSH Method) -# This script automates the deployment via SSH - +# ============================================================================ +# Claude Code Stack - TrueNAS SSH Deployment Script +# Usage: ./deploy-ssh.sh [TRUENAS_USER] [POOL_NAME] +# Example: ./deploy-ssh.sh 192.168.1.100 root tank +# ============================================================================ set -e # Color output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' -NC='\033[0m' # No Color +BLUE='\033[0;34m' +NC='\033[0m' # Configuration TRUENAS_IP="${1:-}" TRUENAS_USER="${2:-root}" POOL_NAME="${3:-tank}" PROJECT_DIR="/mnt/${POOL_NAME}/docker/claude-code-stack" -DOCKER_COMPOSE_VERSION="3.8" # Functions -print_info() { - echo -e "${GREEN}[INFO]${NC} $1" -} - -print_warn() { - echo -e "${YELLOW}[WARN]${NC} $1" -} - -print_error() { - echo -e "${RED}[ERROR]${NC} $1" -} - -check_prerequisites() { - print_info "Checking prerequisites..." - - # Check if ssh is available - if ! command -v ssh &> /dev/null; then - print_error "SSH client is not installed" - exit 1 - fi - - # Check if ANTHROPIC_API_KEY is set - if [ -z "$ANTHROPIC_API_KEY" ]; then - print_warn "ANTHROPIC_API_KEY environment variable not set" - read -p "Enter your Anthropic API Key: " -r ANTHROPIC_API_KEY - export ANTHROPIC_API_KEY - fi - - print_info "Prerequisites check passed" -} +print_info() { echo -e "${GREEN}[INFO]${NC} $1"; } +print_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +print_error() { echo -e "${RED}[ERROR]${NC} $1"; } +print_step() { echo -e "${BLUE}[STEP]${NC} $1"; } +# ---------------------------------------------------------------------------- validate_input() { if [ -z "$TRUENAS_IP" ]; then print_error "Usage: $0 [TRUENAS_USER] [POOL_NAME]" - print_info "Example: $0 192.168.1.100 root tank" + print_info "Example: $0 192.168.1.100 root tank" exit 1 fi } +# ---------------------------------------------------------------------------- +check_prerequisites() { + print_step "Checking prerequisites..." + + if ! command -v ssh &>/dev/null; then + print_error "SSH client not found. Please install it." + exit 1 + fi + + if ! command -v scp &>/dev/null; then + print_error "scp not found. Please install openssh-client." + exit 1 + fi + + # Prompt for API key if not already set + if [ -z "$ANTHROPIC_API_KEY" ]; then + print_warn "ANTHROPIC_API_KEY is not set in your environment." + echo -n " Enter your Anthropic API key (sk-ant-...): " + read -r ANTHROPIC_API_KEY + export ANTHROPIC_API_KEY + fi + + if [ -z "$ANTHROPIC_API_KEY" ]; then + print_error "ANTHROPIC_API_KEY is required. Obtain one at https://console.anthropic.com/" + exit 1 + fi + + print_info "Prerequisites OK" +} + +# ---------------------------------------------------------------------------- test_ssh_connection() { - print_info "Testing SSH connection to $TRUENAS_USER@$TRUENAS_IP..." - - if ssh -o ConnectTimeout=5 "$TRUENAS_USER@$TRUENAS_IP" "echo 'SSH connection successful'" > /dev/null 2>&1; then + print_step "Testing SSH connection to ${TRUENAS_USER}@${TRUENAS_IP}..." + if ssh -o ConnectTimeout=8 -o BatchMode=yes \ + "${TRUENAS_USER}@${TRUENAS_IP}" "echo 'SSH OK'" >/dev/null 2>&1; then print_info "SSH connection successful" else - print_error "Failed to connect to TrueNAS via SSH" - print_info "Please verify:" - print_info " - TRUENAS_IP is correct: $TRUENAS_IP" - print_info " - SSH is enabled on TrueNAS" - print_info " - You have the correct credentials" + print_error "Cannot connect to ${TRUENAS_USER}@${TRUENAS_IP} via SSH." + print_info "Check: TrueNAS IP, SSH enabled, SSH keys / credentials configured." exit 1 fi } +# ---------------------------------------------------------------------------- create_project_directory() { - print_info "Creating project directory on TrueNAS..." - - ssh "$TRUENAS_USER@$TRUENAS_IP" << 'EOF' -POOL_NAME="$1" -PROJECT_DIR="/mnt/${POOL_NAME}/docker/claude-code-stack" - -# Create directory -mkdir -p "$PROJECT_DIR" -chmod 755 "$PROJECT_DIR" - -# Verify -if [ -d "$PROJECT_DIR" ]; then - echo "Directory created: $PROJECT_DIR" -else - echo "Failed to create directory" - exit 1 -fi -EOF + print_step "Creating project directory on TrueNAS..." + ssh "${TRUENAS_USER}@${TRUENAS_IP}" " + mkdir -p '${PROJECT_DIR}' + chmod 755 '${PROJECT_DIR}' + echo 'Directory ready: ${PROJECT_DIR}' + " } -deploy_docker_files() { - print_info "Uploading Docker configuration files..." - - # Check if files exist locally +# ---------------------------------------------------------------------------- +upload_files() { + print_step "Uploading deployment files..." + local files=( - "docker-compose.yml" + "claude-agents-ui-Dockerfile" + "claude-code-stack-docker-compose.yml" "claude-code-stack.env" "nginx.conf" + "deploy-mcp.py" + "CHECKLIST.md" + "QUICKSTART.md" + "DEPLOYMENT_GUIDE.md" ) - - for file in "${files[@]}"; do - if [ ! -f "./$file" ]; then - print_warn "Local file not found: $file" - print_info "Creating minimal $file on remote..." - - case $file in - "docker-compose.yml") - # Create minimal compose file on remote - ssh "$TRUENAS_USER@$TRUENAS_IP" "cat > ${PROJECT_DIR}/docker-compose.yml << 'COMPOSE_EOF' -version: '3.8' -services: - agents-ui: - image: node:20-alpine - container_name: claude-agents-ui - restart: unless-stopped - ports: - - \"3000:3000\" - environment: - ANTHROPIC_API_KEY: \${ANTHROPIC_API_KEY} - volumes: - - claude-config:/root/.claude - - workspace:/workspace - networks: - - claude-stack -volumes: - claude-config: - driver: local - workspace: - driver: local - -networks: - claude-stack: - driver: bridge -COMPOSE_EOF" - ;; - "claude-code-stack.env") - ssh "$TRUENAS_USER@$TRUENAS_IP" "cat > ${PROJECT_DIR}/.env << 'ENV_EOF' -ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} -POSTGRES_PASSWORD=changeMe123! -SESSION_SECRET=$(openssl rand -base64 32) -NODE_ENV=production -ENV_EOF" - ;; - "nginx.conf") - ssh "$TRUENAS_USER@$TRUENAS_IP" "touch ${PROJECT_DIR}/nginx.conf" - ;; - esac + for f in "${files[@]}"; do + if [ -f "./${f}" ]; then + scp "./${f}" "${TRUENAS_USER}@${TRUENAS_IP}:${PROJECT_DIR}/" && \ + print_info " Uploaded: ${f}" else - # Upload file - scp "./$file" "$TRUENAS_USER@$TRUENAS_IP:$PROJECT_DIR/" - print_info "Uploaded: $file" + print_warn " Skipping (not found locally): ${f}" fi done + + # Rename docker-compose to the standard name docker-compose expects + ssh "${TRUENAS_USER}@${TRUENAS_IP}" " + cd '${PROJECT_DIR}' + [ -f claude-code-stack-docker-compose.yml ] && \ + cp claude-code-stack-docker-compose.yml docker-compose.yml + [ -f claude-agents-ui-Dockerfile ] && \ + cp claude-agents-ui-Dockerfile Dockerfile + " + print_info " Aliased docker-compose.yml and Dockerfile" } +# ---------------------------------------------------------------------------- configure_environment() { - print_info "Configuring environment variables..." - - ssh "$TRUENAS_USER@$TRUENAS_IP" << EOF -PROJECT_DIR="$PROJECT_DIR" + print_step "Writing .env file on TrueNAS..." -# Create .env file if it doesn't exist -if [ ! -f "$PROJECT_DIR/.env" ]; then - cat > "$PROJECT_DIR/.env" << 'ENV_EOF' -ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY -POSTGRES_PASSWORD=$(openssl rand -base64 16) -SESSION_SECRET=$(openssl rand -base64 32) + # Generate random secrets server-side + ssh "${TRUENAS_USER}@${TRUENAS_IP}" bash -s -- \ + "${ANTHROPIC_API_KEY}" "${PROJECT_DIR}" << 'REMOTE_EOF' +API_KEY="$1" +PROJECT_DIR="$2" + +POSTGRES_PASS=$(openssl rand -base64 16 | tr -d '/+=' | head -c 20) +SESSION_SEC=$(openssl rand -base64 32) + +cat > "${PROJECT_DIR}/.env" << ENV_EOF +# Auto-generated by deploy-ssh.sh on $(date) +ANTHROPIC_API_KEY=${API_KEY} +CLAUDE_MODEL=claude-sonnet-4-6 NODE_ENV=production -CLAUDE_MODEL=claude-opus-4-1 -RUN_CLAUDE_CODE=true +POSTGRES_USER=claude +POSTGRES_PASSWORD=${POSTGRES_PASS} +POSTGRES_DB=claude_agents +REDIS_HOST=redis +REDIS_PORT=6379 +SESSION_SECRET=${SESSION_SEC} +CORS_ORIGIN=* LOG_LEVEL=info +WORKSPACE_DIR=/workspace +CLAUDE_CONFIG_DIR=/root/.claude ENV_EOF - print_info "Created .env file" -else - # Update ANTHROPIC_API_KEY in existing .env - sed -i.bak "s/ANTHROPIC_API_KEY=.*/ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY/" "$PROJECT_DIR/.env" - print_info "Updated ANTHROPIC_API_KEY in .env" -fi -chmod 600 "$PROJECT_DIR/.env" -EOF +chmod 600 "${PROJECT_DIR}/.env" +echo " .env written with auto-generated secrets" +REMOTE_EOF } +# ---------------------------------------------------------------------------- deploy_stack() { - print_info "Deploying Docker stack..." - - ssh "$TRUENAS_USER@$TRUENAS_IP" << EOF -cd $PROJECT_DIR - -# Pull latest images -echo "Pulling Docker images..." -docker-compose pull - -# Build (if Dockerfile present) -if [ -f "Dockerfile" ]; then - echo "Building custom image..." - docker-compose build --no-cache -fi - -# Deploy -echo "Starting services..." -docker-compose up -d - -# Wait for services to be ready -echo "Waiting for services to be ready..." -sleep 10 - -# Show status -docker-compose ps -EOF + print_step "Pulling images and deploying stack..." + ssh "${TRUENAS_USER}@${TRUENAS_IP}" " + cd '${PROJECT_DIR}' + echo ' Pulling base images...' + docker-compose pull --quiet 2>/dev/null || true + echo ' Building custom images...' + docker-compose build --pull + echo ' Starting services...' + docker-compose up -d + echo ' Waiting 15s for services to stabilise...' + sleep 15 + docker-compose ps + " } +# ---------------------------------------------------------------------------- +verify_auth() { + print_step "Verifying Claude auth inside agents-ui container..." + ssh "${TRUENAS_USER}@${TRUENAS_IP}" " + cd '${PROJECT_DIR}' + echo '--- Claude auth status ---' + docker-compose exec -T agents-ui claude auth status 2>/dev/null || \ + echo ' (auth check not available yet - check logs if UI fails)' + " +} + +# ---------------------------------------------------------------------------- verify_deployment() { - print_info "Verifying deployment..." - - # Give services time to stabilize + print_step "Verifying deployment..." sleep 5 - - print_info "Checking service status..." - ssh "$TRUENAS_USER@$TRUENAS_IP" "cd $PROJECT_DIR && docker-compose ps" - - print_info "Testing Agents UI..." - if curl -s -f "http://$TRUENAS_IP:3000" > /dev/null 2>&1; then - print_info "✓ Agents UI is responsive" + + print_info "Service status:" + ssh "${TRUENAS_USER}@${TRUENAS_IP}" "cd '${PROJECT_DIR}' && docker-compose ps" + + print_info "Testing Agents UI (port 3000)..." + if curl -s -f --max-time 10 "http://${TRUENAS_IP}:3000" >/dev/null 2>&1; then + print_info " ✓ Agents UI is responding" else - print_warn "! Agents UI is not yet responding (may take a few moments)" + print_warn " ! Agents UI not yet responding — may still be starting up." + print_info " Check logs: ssh ${TRUENAS_USER}@${TRUENAS_IP} \"cd ${PROJECT_DIR} && docker-compose logs -f agents-ui\"" fi } +# ---------------------------------------------------------------------------- setup_monitoring() { - print_info "Setting up basic monitoring..." - - ssh "$TRUENAS_USER@$TRUENAS_IP" << EOF -PROJECT_DIR="$PROJECT_DIR" - -# Create monitoring script -cat > "$PROJECT_DIR/monitor.sh" << 'MONITOR_EOF' + print_step "Installing monitoring script..." + ssh "${TRUENAS_USER}@${TRUENAS_IP}" " + cat > '${PROJECT_DIR}/monitor.sh' << 'MON_EOF' #!/bin/bash - while true; do clear - echo "=== Claude Code Stack Status ===" - echo "Time: \$(date)" - echo "" - docker-compose ps - echo "" - echo "Resource Usage:" - docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}" - echo "" - echo "Press Ctrl+C to exit" + echo '=== Claude Code Stack Status ===' + echo \"Time: \$(date)\" + echo '' + docker-compose -f '${PROJECT_DIR}/docker-compose.yml' ps + echo '' + echo 'Resource Usage:' + docker stats --no-stream --format 'table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}' + echo '' + echo 'Press Ctrl+C to exit' sleep 10 done -MONITOR_EOF - -chmod +x "$PROJECT_DIR/monitor.sh" -print_info "Monitoring script created at: $PROJECT_DIR/monitor.sh" -EOF +MON_EOF + chmod +x '${PROJECT_DIR}/monitor.sh' + " + print_info " Monitoring script: ${PROJECT_DIR}/monitor.sh" } +# ---------------------------------------------------------------------------- print_summary() { echo "" - echo -e "${GREEN}========================================${NC}" - echo -e "${GREEN} Claude Code Stack Deployed!${NC}" - echo -e "${GREEN}========================================${NC}" + echo -e "${GREEN}============================================${NC}" + echo -e "${GREEN} Claude Code Stack — Deployment Complete!${NC}" + echo -e "${GREEN}============================================${NC}" echo "" - echo "Access the Agents UI at:" - echo -e " ${YELLOW}http://$TRUENAS_IP:3000${NC}" + echo -e " Agents UI: ${YELLOW}http://${TRUENAS_IP}:3000${NC}" + echo -e " Project: ${YELLOW}${PROJECT_DIR}${NC}" echo "" - echo "Project Directory on TrueNAS:" - echo -e " ${YELLOW}$PROJECT_DIR${NC}" + echo " Useful commands:" + echo -e " Logs: ${YELLOW}ssh ${TRUENAS_USER}@${TRUENAS_IP} \"cd ${PROJECT_DIR} && docker-compose logs -f\"${NC}" + echo -e " Status: ${YELLOW}ssh ${TRUENAS_USER}@${TRUENAS_IP} \"cd ${PROJECT_DIR} && docker-compose ps\"${NC}" + echo -e " Stop: ${YELLOW}ssh ${TRUENAS_USER}@${TRUENAS_IP} \"cd ${PROJECT_DIR} && docker-compose down\"${NC}" + echo -e " Auth: ${YELLOW}ssh ${TRUENAS_USER}@${TRUENAS_IP} \"cd ${PROJECT_DIR} && docker-compose exec agents-ui claude auth status\"${NC}" + echo -e " Monitor: ${YELLOW}ssh ${TRUENAS_USER}@${TRUENAS_IP} \"${PROJECT_DIR}/monitor.sh\"${NC}" echo "" - echo "Useful commands:" - echo " SSH into TrueNAS:" - echo -e " ${YELLOW}ssh $TRUENAS_USER@$TRUENAS_IP${NC}" - echo "" - echo " View logs:" - echo -e " ${YELLOW}cd $PROJECT_DIR && docker-compose logs -f${NC}" - echo "" - echo " Stop services:" - echo -e " ${YELLOW}cd $PROJECT_DIR && docker-compose down${NC}" - echo "" - echo " Monitor:" - echo -e " ${YELLOW}cd $PROJECT_DIR && ./monitor.sh${NC}" - echo "" - echo "Next steps:" - echo " 1. Configure your API keys in the UI or .env" - echo " 2. Set up agents via the web interface" - echo " 3. Test with a simple agent execution" + echo " Next steps:" + echo " 1. Open http://${TRUENAS_IP}:3000" + echo " 2. Create your first agent in the UI" + echo " 3. Run a test prompt to verify Claude Code is working" echo "" } -# Main execution +# ============================================================================ +# Main +# ============================================================================ main() { - echo -e "${GREEN}Claude Code Stack - TrueNAS Deployment${NC}" - echo "========================================" + echo -e "${GREEN}Claude Code Stack — TrueNAS SSH Deployment${NC}" + echo "============================================" echo "" - validate_input check_prerequisites test_ssh_connection create_project_directory - deploy_docker_files + upload_files configure_environment deploy_stack + verify_auth verify_deployment setup_monitoring print_summary } -# Run main function main "$@"