#!/bin/bash # ============================================================================ # 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' 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" # 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"; } 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" 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_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 "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_step "Creating project directory on TrueNAS..." ssh "${TRUENAS_USER}@${TRUENAS_IP}" " mkdir -p '${PROJECT_DIR}' chmod 755 '${PROJECT_DIR}' echo 'Directory ready: ${PROJECT_DIR}' " } # ---------------------------------------------------------------------------- upload_files() { print_step "Uploading deployment files..." local files=( "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 f in "${files[@]}"; do if [ -f "./${f}" ]; then scp "./${f}" "${TRUENAS_USER}@${TRUENAS_IP}:${PROJECT_DIR}/" && \ print_info " Uploaded: ${f}" else 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_step "Writing .env file on TrueNAS..." # 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 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 chmod 600 "${PROJECT_DIR}/.env" echo " .env written with auto-generated secrets" REMOTE_EOF } # ---------------------------------------------------------------------------- deploy_stack() { 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_step "Verifying deployment..." sleep 5 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 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_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 -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 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 — Deployment Complete!${NC}" echo -e "${GREEN}============================================${NC}" echo "" echo -e " Agents UI: ${YELLOW}http://${TRUENAS_IP}:3000${NC}" echo -e " Project: ${YELLOW}${PROJECT_DIR}${NC}" echo "" 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 " 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 # ============================================================================ main() { echo -e "${GREEN}Claude Code Stack — TrueNAS SSH Deployment${NC}" echo "============================================" echo "" validate_input check_prerequisites test_ssh_connection create_project_directory upload_files configure_environment deploy_stack verify_auth verify_deployment setup_monitoring print_summary } main "$@"