#!/bin/bash # SSH Deploy script for HR AI Backend # Usage: ./scripts/deploy.sh [environment] [image_tag] set -e # Configuration ENVIRONMENT="${1:-production}" IMAGE_TAG="${2:-latest}" REGISTRY_ID="${YANDEX_REGISTRY_ID:-your-registry-id}" IMAGE_NAME="hr-ai-backend" FULL_IMAGE_NAME="cr.yandex/${REGISTRY_ID}/${IMAGE_NAME}:${IMAGE_TAG}" # Server configuration (set these as environment variables) SERVER_HOST="${DEPLOY_HOST:-your-server.com}" SERVER_USER="${DEPLOY_USER:-deploy}" SERVER_PORT="${DEPLOY_PORT:-22}" DEPLOY_PATH="${DEPLOY_PATH:-/opt/hr-ai-backend}" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color echo -e "${YELLOW}Deploying HR AI Backend to ${ENVIRONMENT} environment${NC}" # Check if required environment variables are set missing_vars="" if [ -z "$SERVER_HOST" ] || [ "$SERVER_HOST" = "your-server.com" ]; then missing_vars="$missing_vars DEPLOY_HOST" fi if [ -z "$SERVER_USER" ] || [ "$SERVER_USER" = "deploy" ]; then missing_vars="$missing_vars DEPLOY_USER" fi if [ -z "$REGISTRY_ID" ] || [ "$REGISTRY_ID" = "your-registry-id" ]; then missing_vars="$missing_vars YANDEX_REGISTRY_ID" fi if [ -n "$missing_vars" ]; then echo -e "${RED}Error: Required environment variables are not set:${NC}" for var in $missing_vars; do echo " - $var" done echo "" echo "Example configuration:" echo "export DEPLOY_HOST=your-server.example.com" echo "export DEPLOY_USER=deploy" echo "export YANDEX_REGISTRY_ID=crp1234567890abcdef" echo "export DEPLOY_PATH=/opt/hr-ai-backend # optional" echo "export DEPLOY_PORT=22 # optional" exit 1 fi # Test SSH connection echo -e "${BLUE}Testing SSH connection to ${SERVER_USER}@${SERVER_HOST}:${SERVER_PORT}...${NC}" if ! ssh -p "${SERVER_PORT}" -o ConnectTimeout=10 -o StrictHostKeyChecking=no "${SERVER_USER}@${SERVER_HOST}" "echo 'SSH connection successful'"; then echo -e "${RED}Error: Cannot connect to server via SSH${NC}" echo "Please check your SSH key configuration and server details" exit 1 fi # Create deployment directory structure on server echo -e "${BLUE}Creating deployment directories on server...${NC}" ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" " sudo mkdir -p ${DEPLOY_PATH}/{config,logs,data,agent_commands} sudo mkdir -p ${DEPLOY_PATH}/data/{postgres,redis,uploads,caddy_data,caddy_config} sudo mkdir -p ${DEPLOY_PATH}/logs/{caddy} sudo chown -R ${SERVER_USER}:${SERVER_USER} ${DEPLOY_PATH} " # Copy configuration files to server echo -e "${BLUE}Copying configuration files to server...${NC}" scp -P "${SERVER_PORT}" docker-compose.yml "${SERVER_USER}@${SERVER_HOST}:${DEPLOY_PATH}/" scp -P "${SERVER_PORT}" Caddyfile "${SERVER_USER}@${SERVER_HOST}:${DEPLOY_PATH}/" # Create frontend environment file echo -e "${BLUE}Creating frontend environment configuration...${NC}" ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" " cat > ${DEPLOY_PATH}/.env.local << 'EOF' # Frontend Environment Configuration NODE_ENV=production # API URL (adjust based on your setup) NEXT_PUBLIC_API_URL=https://\${SERVER_HOST}/api REACT_APP_API_URL=https://\${SERVER_HOST}/api VUE_APP_API_URL=https://\${SERVER_HOST}/api # LiveKit Configuration for frontend NEXT_PUBLIC_LIVEKIT_URL=ws://\${SERVER_HOST}/rtc REACT_APP_LIVEKIT_URL=ws://\${SERVER_HOST}/rtc VUE_APP_LIVEKIT_URL=ws://\${SERVER_HOST}/rtc # For localhost development (no HTTPS) # NEXT_PUBLIC_API_URL=http://\${SERVER_HOST}/api # REACT_APP_API_URL=http://\${SERVER_HOST}/api # VUE_APP_API_URL=http://\${SERVER_HOST}/api # NEXT_PUBLIC_LIVEKIT_URL=ws://\${SERVER_HOST}/rtc # REACT_APP_LIVEKIT_URL=ws://\${SERVER_HOST}/rtc # VUE_APP_LIVEKIT_URL=ws://\${SERVER_HOST}/rtc # Add your frontend-specific environment variables here EOF " # Create production environment file on server echo -e "${BLUE}Creating production environment configuration...${NC}" ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" " cat > ${DEPLOY_PATH}/.env << 'EOF' # Production Environment Configuration DATABASE_URL=postgresql+asyncpg://hr_user:hr_password@postgres:5432/hr_ai REDIS_CACHE_URL=redis REDIS_CACHE_PORT=6379 REDIS_CACHE_DB=0 # LiveKit Configuration LIVEKIT_URL=ws://livekit:7880 LIVEKIT_API_KEY=devkey LIVEKIT_API_SECRET=devkey_secret_32chars_minimum_length # Caddy Domain Configuration (set your domain for automatic HTTPS) DOMAIN=${SERVER_HOST:-localhost} # App Configuration APP_ENV=production DEBUG=false # Add your production API keys here: # OPENAI_API_KEY=your-openai-api-key # DEEPGRAM_API_KEY=your-deepgram-api-key # CARTESIA_API_KEY=your-cartesia-api-key # ELEVENLABS_API_KEY=your-elevenlabs-api-key # S3 Storage Configuration (optional) # S3_ENDPOINT_URL=https://s3.storage.selcloud.ru # S3_ACCESS_KEY_ID=your_s3_access_key # S3_SECRET_ACCESS_KEY=your_s3_secret_key # S3_BUCKET_NAME=your-bucket-name # S3_REGION=ru-1 # Milvus Vector Database Configuration (optional) # MILVUS_URI=http://milvus:19530 # MILVUS_COLLECTION=hr_candidate_profiles EOF " # Create production docker compose override echo -e "${BLUE}Creating production docker compose configuration...${NC}" ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" " cat > ${DEPLOY_PATH}/docker-compose.prod.yml << 'EOF' services: backend: image: ${FULL_IMAGE_NAME} env_file: - .env restart: unless-stopped volumes: - ./agent_commands:/tmp/agent_commands - ./data/uploads:/app/uploads - ./logs:/app/logs postgres: restart: unless-stopped volumes: - ./data/postgres:/var/lib/postgresql/data redis: restart: unless-stopped volumes: - ./data/redis:/data livekit: restart: unless-stopped ports: - \"3478:3478/udp\" caddy: env_file: - .env restart: unless-stopped volumes: - ./data/caddy_data:/data - ./data/caddy_config:/config - ./logs/caddy:/var/log/caddy frontend: restart: unless-stopped EOF " # Pull latest image and deploy echo -e "${BLUE}Pulling latest image and starting services...${NC}" ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" " cd ${DEPLOY_PATH} # Configure Docker for Yandex Cloud Registry echo 'Configuring Docker for Yandex Cloud Registry...' # Completely reset Docker config to fix credential helper issues echo 'Resetting Docker configuration...' mkdir -p ~/.docker cat > ~/.docker/config.json << 'DOCKER_CONFIG' { \"auths\": {}, \"HttpHeaders\": { \"User-Agent\": \"Docker-Client/20.10.0 (linux)\" } } DOCKER_CONFIG # Install yc CLI if not found if ! command -v yc &> /dev/null; then echo 'Installing Yandex Cloud CLI...' curl -sSL https://storage.yandexcloud.net/yandexcloud-yc/install.sh | bash source ~/.bashrc || source ~/.bash_profile || export PATH=\"\$HOME/yandex-cloud/bin:\$PATH\" fi # Use manual login instead of yc configure-docker if command -v yc &> /dev/null; then echo 'Getting Yandex Cloud token and logging in manually...' YC_TOKEN=\$(yc iam create-token 2>/dev/null) if [ ! -z \"\$YC_TOKEN\" ]; then echo \"\$YC_TOKEN\" | docker login --username oauth --password-stdin cr.yandex echo 'Docker login successful' else echo 'Error: Could not get YC token. Please run: yc init' echo 'You need to authenticate yc CLI first on the server' exit 1 fi else echo 'Error: yc CLI installation failed' exit 1 fi echo 'Current Docker config:' cat ~/.docker/config.json # Pull only our custom images from Yandex Registry first echo 'Pulling custom images from Yandex Registry...' docker pull ${FULL_IMAGE_NAME} || echo 'Failed to pull backend image' docker pull cr.yandex/crp9p5rtbnbop36duusi/hr-ai-frontend:latest || echo 'Failed to pull frontend image' # Reset Docker config to default for pulling public images echo 'Resetting Docker config for public images...' cat > ~/.docker/config.json << 'DOCKER_CONFIG' { \"auths\": {} } DOCKER_CONFIG # Stop old containers echo 'Stopping existing services...' docker compose -f docker-compose.yml -f docker-compose.prod.yml down --remove-orphans # Start new containers echo 'Starting services with new image...' docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d # Wait for services to start echo 'Waiting for services to start...' sleep 10 # Run database migrations echo 'Running database migrations...' docker compose -f docker-compose.yml -f docker-compose.prod.yml exec -T backend uv run alembic upgrade head || echo 'Migration failed or already up to date' # Show service status echo 'Service status:' docker compose -f docker-compose.yml -f docker-compose.prod.yml ps " # Health check echo -e "${BLUE}Performing health check...${NC}" sleep 20 if ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" "curl -f http://localhost/health" >/dev/null 2>&1; then echo -e "${GREEN}✓ Deployment successful! Service is healthy.${NC}" else echo -e "${YELLOW}⚠ Service deployed but health check failed. Check logs:${NC}" echo "ssh -p ${SERVER_PORT} ${SERVER_USER}@${SERVER_HOST} 'cd ${DEPLOY_PATH} && docker compose logs backend caddy'" fi echo -e "${GREEN}Deployment completed!${NC}" echo "" echo "Service URLs:" if [ "\$DOMAIN" != "localhost" ]; then echo " Main site: https://${SERVER_HOST}" echo " API: https://${SERVER_HOST}/api" echo " LiveKit: https://${SERVER_HOST}/livekit" else echo " Main site: http://${SERVER_HOST}" echo " API: http://${SERVER_HOST}/api" echo " LiveKit: http://${SERVER_HOST}/livekit" fi echo "" echo "Useful commands:" echo " Check logs: ssh ${SERVER_USER}@${SERVER_HOST} 'cd ${DEPLOY_PATH} && docker compose logs -f'" echo " Service status: ssh ${SERVER_USER}@${SERVER_HOST} 'cd ${DEPLOY_PATH} && docker compose ps'" echo " Restart: ssh ${SERVER_USER}@${SERVER_HOST} 'cd ${DEPLOY_PATH} && docker compose restart'"