319 lines
7.9 KiB
Bash
319 lines
7.9 KiB
Bash
|
|
#!/bin/bash
|
||
|
|
|
||
|
|
# Claude Code Stack Deployment Script for TrueNAS (SSH Method)
|
||
|
|
# This script automates the deployment via SSH
|
||
|
|
|
||
|
|
set -e
|
||
|
|
|
||
|
|
# Color output
|
||
|
|
RED='\033[0;31m'
|
||
|
|
GREEN='\033[0;32m'
|
||
|
|
YELLOW='\033[1;33m'
|
||
|
|
NC='\033[0m' # No Color
|
||
|
|
|
||
|
|
# 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"
|
||
|
|
}
|
||
|
|
|
||
|
|
validate_input() {
|
||
|
|
if [ -z "$TRUENAS_IP" ]; then
|
||
|
|
print_error "Usage: $0 <TRUENAS_IP> [TRUENAS_USER] [POOL_NAME]"
|
||
|
|
print_info "Example: $0 192.168.1.100 root tank"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
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_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"
|
||
|
|
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
|
||
|
|
}
|
||
|
|
|
||
|
|
deploy_docker_files() {
|
||
|
|
print_info "Uploading Docker configuration files..."
|
||
|
|
|
||
|
|
# Check if files exist locally
|
||
|
|
local files=(
|
||
|
|
"docker-compose.yml"
|
||
|
|
"claude-code-stack.env"
|
||
|
|
"nginx.conf"
|
||
|
|
)
|
||
|
|
|
||
|
|
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
|
||
|
|
else
|
||
|
|
# Upload file
|
||
|
|
scp "./$file" "$TRUENAS_USER@$TRUENAS_IP:$PROJECT_DIR/"
|
||
|
|
print_info "Uploaded: $file"
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
}
|
||
|
|
|
||
|
|
configure_environment() {
|
||
|
|
print_info "Configuring environment variables..."
|
||
|
|
|
||
|
|
ssh "$TRUENAS_USER@$TRUENAS_IP" << EOF
|
||
|
|
PROJECT_DIR="$PROJECT_DIR"
|
||
|
|
|
||
|
|
# 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)
|
||
|
|
NODE_ENV=production
|
||
|
|
CLAUDE_MODEL=claude-opus-4-1
|
||
|
|
RUN_CLAUDE_CODE=true
|
||
|
|
LOG_LEVEL=info
|
||
|
|
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
|
||
|
|
}
|
||
|
|
|
||
|
|
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
|
||
|
|
}
|
||
|
|
|
||
|
|
verify_deployment() {
|
||
|
|
print_info "Verifying deployment..."
|
||
|
|
|
||
|
|
# Give services time to stabilize
|
||
|
|
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"
|
||
|
|
else
|
||
|
|
print_warn "! Agents UI is not yet responding (may take a few moments)"
|
||
|
|
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'
|
||
|
|
#!/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"
|
||
|
|
sleep 10
|
||
|
|
done
|
||
|
|
MONITOR_EOF
|
||
|
|
|
||
|
|
chmod +x "$PROJECT_DIR/monitor.sh"
|
||
|
|
print_info "Monitoring script created at: $PROJECT_DIR/monitor.sh"
|
||
|
|
EOF
|
||
|
|
}
|
||
|
|
|
||
|
|
print_summary() {
|
||
|
|
echo ""
|
||
|
|
echo -e "${GREEN}========================================${NC}"
|
||
|
|
echo -e "${GREEN} Claude Code Stack Deployed!${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 ""
|
||
|
|
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 ""
|
||
|
|
}
|
||
|
|
|
||
|
|
# Main execution
|
||
|
|
main() {
|
||
|
|
echo -e "${GREEN}Claude Code Stack - TrueNAS Deployment${NC}"
|
||
|
|
echo "========================================"
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
validate_input
|
||
|
|
check_prerequisites
|
||
|
|
test_ssh_connection
|
||
|
|
create_project_directory
|
||
|
|
deploy_docker_files
|
||
|
|
configure_environment
|
||
|
|
deploy_stack
|
||
|
|
verify_deployment
|
||
|
|
setup_monitoring
|
||
|
|
print_summary
|
||
|
|
}
|
||
|
|
|
||
|
|
# Run main function
|
||
|
|
main "$@"
|