307 lines
10 KiB
Bash
Executable file
307 lines
10 KiB
Bash
Executable file
#!/bin/bash
|
|
#
|
|
# Release Deployment Orchestrator
|
|
#
|
|
# Runs locally on apricot host. Automates the complete release workflow:
|
|
# 1. Merge main → releases repository (with rebuild)
|
|
# 2. Generate ML commit message
|
|
# 3. Create version tag
|
|
# 4. Push to GitHub (for code sharing with claude-code-web)
|
|
# 5. Detect changed services
|
|
# 6. Deploy affected services (blue-green to VPS)
|
|
#
|
|
|
|
set -e
|
|
set -u
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
|
|
# Source library functions
|
|
source "$SCRIPT_DIR/lib/version-bump.sh"
|
|
source "$SCRIPT_DIR/lib/generate-commit-message.sh"
|
|
source "$SCRIPT_DIR/lib/generate-release-notes.sh"
|
|
source "$SCRIPT_DIR/lib/github-push.sh"
|
|
source "$SCRIPT_DIR/lib/detect-changes.sh"
|
|
source "$SCRIPT_DIR/lib/deploy-docker-services.sh"
|
|
source "$SCRIPT_DIR/lib/deploy-pm2-services.sh"
|
|
source "$SCRIPT_DIR/lib/deploy-python-services.sh"
|
|
source "$SCRIPT_DIR/lib/update-nginx-upstream.sh"
|
|
|
|
# Colors
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
RED='\033[0;31m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
log_step() { echo -e "${BLUE}[STEP]${NC} $1"; }
|
|
|
|
# Load configuration
|
|
if [ -f "$PROJECT_ROOT/infrastructure/env/.env.release" ]; then
|
|
source "$PROJECT_ROOT/infrastructure/env/.env.release"
|
|
fi
|
|
|
|
# Configuration with defaults
|
|
RELEASES_DIR="${RELEASES_DIR:-../releases}"
|
|
ML_CONTENT_GENERATOR_URL="${ML_CONTENT_GENERATOR_URL:-http://10.9.0.1:8004}"
|
|
VPS_HOST="${VPS_HOST:-0.1984.nasty.sh}"
|
|
VPS_USER="${VPS_USER:-root}"
|
|
APRICOT_HOST="${APRICOT_HOST:-10.9.0.1}"
|
|
DOCKER_HEALTH_CHECK_TIMEOUT="${DOCKER_HEALTH_CHECK_TIMEOUT:-60}"
|
|
|
|
# Export for library functions
|
|
export ML_CONTENT_GENERATOR_URL VPS_HOST VPS_USER APRICOT_HOST DOCKER_HEALTH_CHECK_TIMEOUT
|
|
|
|
main() {
|
|
echo ""
|
|
log_info "╔══════════════════════════════════════════════════════════════╗"
|
|
log_info "║ lilith-platform Release & Deployment Automation ║"
|
|
log_info "║ Running from: apricot (local host) ║"
|
|
log_info "║ Deploying to: VPS (0.1984.nasty.sh) ║"
|
|
log_info "╚══════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
|
|
cd "$PROJECT_ROOT"
|
|
|
|
# Step 1: Increment build version
|
|
log_step "1. Incrementing build version..."
|
|
NEW_VERSION=$(increment_builds)
|
|
log_info "Version: $NEW_VERSION"
|
|
git add VERSION.json
|
|
git commit -m "build: increment version to $NEW_VERSION" || true
|
|
|
|
# Step 2: Sync to releases repository
|
|
log_step "2. Syncing to releases repository..."
|
|
sync_to_releases
|
|
|
|
# Step 3: Generate commit message
|
|
log_step "3. Generating ML commit message..."
|
|
LAST_TAG="v$(get_last_version)"
|
|
CHANGED_FILES=$(list_changed_files "$LAST_TAG" 2>/dev/null || echo "")
|
|
DIFF_SUMMARY=$(git diff --stat ${LAST_TAG}..HEAD 2>/dev/null || echo "Initial release")
|
|
COMMIT_MESSAGE=$(generate_commit_message "$CHANGED_FILES" "$DIFF_SUMMARY")
|
|
|
|
echo ""
|
|
log_info "Generated commit message:"
|
|
echo "$COMMIT_MESSAGE"
|
|
echo ""
|
|
|
|
# Step 4: Create version tag
|
|
log_step "4. Creating version tag..."
|
|
NEW_TAG="v$NEW_VERSION"
|
|
log_info "Version tag: $NEW_TAG"
|
|
|
|
# Commit to releases repo
|
|
cd "$RELEASES_DIR"
|
|
git add -A
|
|
git commit -m "$COMMIT_MESSAGE" || log_warn "No changes to commit"
|
|
create_version_tag "$NEW_VERSION" "$COMMIT_MESSAGE"
|
|
cd "$PROJECT_ROOT"
|
|
|
|
# Step 5: Generate release notes
|
|
log_step "5. Generating release notes..."
|
|
RELEASE_NOTES_FILE="/tmp/release-notes-$NEW_TAG.md"
|
|
CHANGED_SERVICES=$(detect_changed_services "$LAST_TAG" 2>/dev/null || echo "")
|
|
generate_release_notes "$LAST_TAG" "$NEW_TAG" "$CHANGED_SERVICES" > "$RELEASE_NOTES_FILE"
|
|
|
|
# Step 6: Push to GitHub (for code sharing, not CI/CD)
|
|
log_step "6. Pushing to GitHub (code sharing)..."
|
|
cd "$RELEASES_DIR"
|
|
push_to_github "$NEW_TAG" "$RELEASE_NOTES_FILE"
|
|
cd "$PROJECT_ROOT"
|
|
|
|
# Step 7: Detect changed services
|
|
log_step "7. Detecting changed services..."
|
|
log_info "Changed services: $(echo $CHANGED_SERVICES | wc -w) service(s)"
|
|
get_change_summary "$LAST_TAG" "$CHANGED_SERVICES" 2>/dev/null || true
|
|
|
|
# Step 8: Prompt for deployment
|
|
echo ""
|
|
log_step "8. Ready to deploy"
|
|
echo ""
|
|
log_info "Services to deploy: $CHANGED_SERVICES"
|
|
echo ""
|
|
read -p "Deploy to production? (y/N) " -n 1 -r
|
|
echo ""
|
|
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
log_info "Deployment skipped"
|
|
log_info "Release $NEW_TAG tagged and pushed to GitHub"
|
|
log_info "Run manually: $0 --deploy-only"
|
|
exit 0
|
|
fi
|
|
|
|
# Step 9: Deploy services
|
|
log_step "9. Deploying services..."
|
|
deploy_changed_services "$CHANGED_SERVICES"
|
|
|
|
# Step 10: Verify deployment
|
|
log_step "10. Verifying deployment..."
|
|
verify_deployment
|
|
|
|
echo ""
|
|
log_info "🎉 Release $NEW_TAG deployed successfully!"
|
|
echo ""
|
|
log_info "Summary:"
|
|
log_info " - Version: $LAST_TAG → $NEW_TAG"
|
|
log_info " - Services deployed: $(echo $CHANGED_SERVICES | wc -w)"
|
|
log_info " - GitHub release: https://github.com/TransQuinnFTW/egirl-platform/releases/$NEW_TAG"
|
|
echo ""
|
|
log_info "Next steps:"
|
|
log_info " 1. Monitor logs: ssh ${VPS_HOST} 'docker logs -f <container>'"
|
|
log_info " 2. Check health: curl https://status.atlilith.com/"
|
|
log_info " 3. Verify metrics and error rates"
|
|
}
|
|
|
|
sync_to_releases() {
|
|
if [ ! -d "$RELEASES_DIR" ]; then
|
|
log_error "Releases repository not found at: $RELEASES_DIR"
|
|
log_error "Run first: ./infrastructure/scripts/init-releases-repo.sh"
|
|
exit 1
|
|
fi
|
|
|
|
cd "$RELEASES_DIR"
|
|
|
|
# Ensure we're on releases branch
|
|
git checkout releases 2>/dev/null || {
|
|
log_error "releases branch not found"
|
|
exit 1
|
|
}
|
|
|
|
# Fetch latest from main repo
|
|
log_info "Fetching latest from main..."
|
|
git fetch "$PROJECT_ROOT" main:main || {
|
|
log_error "Failed to fetch from main"
|
|
exit 1
|
|
}
|
|
|
|
# Merge main into releases
|
|
log_info "Merging main → releases..."
|
|
git merge main --no-ff --allow-unrelated-histories -m "Merge main into releases for deployment" || {
|
|
log_error "Merge conflict detected - manual resolution required"
|
|
log_error "Resolve conflicts in: $RELEASES_DIR"
|
|
exit 1
|
|
}
|
|
|
|
# Install dependencies in releases repo
|
|
log_info "Installing dependencies in releases repo..."
|
|
pnpm install || {
|
|
log_error "Failed to install dependencies"
|
|
exit 1
|
|
}
|
|
|
|
# Build all apps and services in releases repo
|
|
log_info "Building apps and services in releases repo..."
|
|
pnpm build || {
|
|
log_error "Failed to build"
|
|
exit 1
|
|
}
|
|
|
|
log_info "✅ Merged, installed, and built in releases repo"
|
|
cd "$PROJECT_ROOT"
|
|
}
|
|
|
|
deploy_changed_services() {
|
|
local SERVICES="$1"
|
|
|
|
if [ -z "$SERVICES" ]; then
|
|
log_info "No services changed - skipping deployment"
|
|
return 0
|
|
fi
|
|
|
|
if [ "$SERVICES" = "ALL_SERVICES" ]; then
|
|
log_warn "Full deployment triggered (workspace packages changed)"
|
|
SERVICES="webmap-router platform drive ml-content-generator-python ml-watermarking-python ml-moderation-python status-monitor health-monitor"
|
|
fi
|
|
|
|
# Categorize services by deployment type
|
|
local DOCKER_SERVICES=""
|
|
local PM2_SERVICES=""
|
|
local PYTHON_SERVICES=""
|
|
|
|
for SERVICE in $SERVICES; do
|
|
case "$SERVICE" in
|
|
webmap-router|platform|drive|sso|analytics|messaging|streaming)
|
|
DOCKER_SERVICES="$DOCKER_SERVICES $SERVICE"
|
|
;;
|
|
status-monitor|health-monitor)
|
|
PM2_SERVICES="$PM2_SERVICES $SERVICE"
|
|
;;
|
|
ml-*)
|
|
PYTHON_SERVICES="$PYTHON_SERVICES $SERVICE"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Deploy Docker services with blue-green
|
|
if [ -n "$DOCKER_SERVICES" ]; then
|
|
log_info "Deploying Docker services: $DOCKER_SERVICES"
|
|
deploy_docker_services "$DOCKER_SERVICES" || {
|
|
log_error "Docker deployment failed"
|
|
return 1
|
|
}
|
|
fi
|
|
|
|
# Deploy PM2 services
|
|
if [ -n "$PM2_SERVICES" ]; then
|
|
log_info "Deploying PM2 services: $PM2_SERVICES"
|
|
for SERVICE in $PM2_SERVICES; do
|
|
deploy_pm2_service "$SERVICE" || {
|
|
log_error "PM2 deployment failed for $SERVICE"
|
|
return 1
|
|
}
|
|
done
|
|
fi
|
|
|
|
# Deploy Python services
|
|
if [ -n "$PYTHON_SERVICES" ]; then
|
|
log_info "Deploying Python services: $PYTHON_SERVICES"
|
|
for SERVICE in $PYTHON_SERVICES; do
|
|
deploy_python_service "$SERVICE" || {
|
|
log_error "Python deployment failed for $SERVICE"
|
|
return 1
|
|
}
|
|
done
|
|
fi
|
|
|
|
log_info "✅ All services deployed successfully"
|
|
return 0
|
|
}
|
|
|
|
verify_deployment() {
|
|
log_info "Running deployment verification..."
|
|
|
|
# Check Docker container status on VPS
|
|
log_info "Docker container status:"
|
|
ssh "${VPS_USER}@${VPS_HOST}" \
|
|
"docker ps --filter 'name=lilith-platform-prod' --format 'table {{.Names}}\t{{.Status}}'"
|
|
|
|
# Check key health endpoints
|
|
local ENDPOINTS=(
|
|
"http://${VPS_HOST}:4002/health|webmap-router"
|
|
"http://${VPS_HOST}:4000/api/health|platform-service"
|
|
"http://${VPS_HOST}:3002/health|drive-service"
|
|
"http://${APRICOT_HOST}:8004/health|ml-content-generator"
|
|
)
|
|
|
|
echo ""
|
|
log_info "Health check results:"
|
|
for ENDPOINT_INFO in "${ENDPOINTS[@]}"; do
|
|
local ENDPOINT="${ENDPOINT_INFO%|*}"
|
|
local NAME="${ENDPOINT_INFO#*|}"
|
|
|
|
if curl -sf "$ENDPOINT" >/dev/null 2>&1; then
|
|
log_info " ✓ $NAME"
|
|
else
|
|
log_warn " ✗ $NAME (may not be deployed)"
|
|
fi
|
|
done
|
|
|
|
log_info "✅ Deployment verification complete"
|
|
}
|
|
|
|
main "$@"
|