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
This commit is contained in:
Zac Gaetano 2026-04-04 15:09:26 -04:00
parent 39f05fb294
commit 804b1d35b6

View file

@ -1,55 +1,31 @@
#!/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_IP> [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_IP> [TRUENAS_USER] [POOL_NAME]"
@ -58,261 +34,241 @@ validate_input() {
fi
}
test_ssh_connection() {
print_info "Testing SSH connection to $TRUENAS_USER@$TRUENAS_IP..."
# ----------------------------------------------------------------------------
check_prerequisites() {
print_step "Checking prerequisites..."
if ssh -o ConnectTimeout=5 "$TRUENAS_USER@$TRUENAS_IP" "echo 'SSH connection successful'" > /dev/null 2>&1; then
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_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..."
# ----------------------------------------------------------------------------
upload_files() {
print_step "Uploading deployment files..."
# Check if files exist locally
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..."
print_step "Writing .env file on TrueNAS..."
ssh "$TRUENAS_USER@$TRUENAS_IP" << EOF
PROJECT_DIR="$PROJECT_DIR"
# 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"
# 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)
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..."
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
# Wait for services to be ready
echo "Waiting for services to be ready..."
sleep 10
# Show status
echo ' Waiting 15s for services to stabilise...'
sleep 15
docker-compose ps
EOF
"
}
verify_deployment() {
print_info "Verifying deployment..."
# ----------------------------------------------------------------------------
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)'
"
}
# Give services time to stabilize
# ----------------------------------------------------------------------------
verify_deployment() {
print_step "Verifying deployment..."
sleep 5
print_info "Checking service status..."
ssh "$TRUENAS_USER@$TRUENAS_IP" "cd $PROJECT_DIR && docker-compose ps"
print_info "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 "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 ""
echo "Project Directory on TrueNAS:"
echo -e " ${YELLOW}$PROJECT_DIR${NC}"
echo -e " Agents UI: ${YELLOW}http://${TRUENAS_IP}:3000${NC}"
echo -e " Project: ${YELLOW}${PROJECT_DIR}${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 -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 " 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 " 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 "$@"