platform-codebase/features/status-dashboard/infrastructure/deploy.sh
Quinn Ftw 631faa3d5f fix(status-dashboard): correct backend deploy path
The deploy script was deploying to /opt/health-monitor/dist/ but the
server runs from /opt/health-monitor/backend/. Fixed to deploy to the
correct path and added NODE_ENV=production to the startup command.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-25 17:34:50 -08:00

519 lines
17 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
set -euo pipefail
#
# Status Page Deployment Script
#
# Deploys status-dashboard frontend to status.atlilith.com on 1984 VPS
#
# Prerequisites:
# - SSH access to 0.1984.nasty.sh
# - nginx installed on VPS
# - certbot installed for SSL
# - Built status-dashboard dist/ folder
#
# Usage: ./deploy-status-dashboard.sh [--build-only | --deploy-only | --full]
#
# =============================================================================
# CONFIGURATION
# =============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
FEATURE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
export SCRIPT_LIB_DIR="${PROJECT_ROOT}/infrastructure/scripts/lib"
# Load shared libraries (if available, otherwise inline)
if [ -f "${SCRIPT_LIB_DIR}/colors.sh" ]; then
source "${SCRIPT_LIB_DIR}/colors.sh"
source "${SCRIPT_LIB_DIR}/logger.sh"
log_init "DEPLOY_STATUS"
else
# Inline minimal logging if shared lib not available
log_step() { echo -e "\n\033[1;34m▶\033[0m $1"; }
log_info() { echo -e "\033[0;36m \033[0m $1"; }
log_success() { echo -e "\033[0;32m ✓\033[0m $1"; }
log_error() { echo -e "\033[0;31m ✗\033[0m $1"; }
log_warn() { echo -e "\033[0;33m ⚠\033[0m $1"; }
fi
# VPS Configuration
# Frontend VPS (nginx + static files)
FRONTEND_VPS_HOST="1.1984.nasty.sh"
FRONTEND_VPS_IP="93.95.228.142"
# Backend VPS (health-monitor API)
BACKEND_VPS_HOST="0.1984.nasty.sh"
BACKEND_VPS_IP="93.95.231.174"
BACKEND_API_PORT="5000"
VPS_HOST="$FRONTEND_VPS_HOST"
VPS_USER="root"
VPS_IP="$FRONTEND_VPS_IP"
SSH_KEY="${HOME}/.ssh/id_ed25519_1984"
DOMAIN="status.atlilith.com"
# Deployment paths
DEPLOY_PATH_VPS="/opt/status-dashboard"
APP_SOURCE="${FEATURE_ROOT}/frontend"
NGINX_CONFIG_SOURCE="${APP_SOURCE}/NGINX_CONFIG.md"
# SSH command template
SSH_CMD="ssh -i ${SSH_KEY} ${VPS_USER}@${VPS_HOST}"
SCP_CMD="scp -i ${SSH_KEY}"
RSYNC_CMD="rsync -avz -e 'ssh -i ${SSH_KEY}'"
# =============================================================================
# PREREQUISITE CHECKS
# =============================================================================
check_prerequisites() {
log_step "Checking prerequisites..."
# Check required commands
local required_cmds=("ssh" "scp" "rsync" "pnpm")
for cmd in "${required_cmds[@]}"; do
if ! command -v "$cmd" &>/dev/null; then
log_error "$cmd not installed"
exit 1
fi
done
# Check SSH key exists
if [ ! -f "$SSH_KEY" ]; then
log_error "SSH key not found: $SSH_KEY"
log_info "Expected location from egirl.vault: ~/.ssh/id_ed25519_1984"
exit 1
fi
# Check VPS connectivity
log_info "Testing VPS connectivity..."
if ! $SSH_CMD "echo connected" &>/dev/null; then
log_error "Cannot connect to VPS at $VPS_HOST"
log_info "Run: ssh-add $SSH_KEY"
exit 1
fi
log_success "VPS connectivity verified"
# Check status-dashboard app exists
if [ ! -d "$APP_SOURCE" ]; then
log_error "status-dashboard app not found at: $APP_SOURCE"
exit 1
fi
log_success "Prerequisites satisfied"
}
# =============================================================================
# BUILD PRODUCTION BUNDLE
# =============================================================================
build_app() {
log_step "Building production bundle..."
cd "$APP_SOURCE"
# Install dependencies if needed
if [ ! -d "node_modules" ]; then
log_info "Installing dependencies..."
pnpm install
fi
# Build production bundle
log_info "Running production build..."
pnpm build
# Verify dist folder exists
if [ ! -d "dist" ]; then
log_error "Build failed - dist/ folder not created"
exit 1
fi
# Show build stats
local dist_size
dist_size=$(du -sh dist | awk '{print $1}')
log_success "Build complete (size: $dist_size)"
}
# =============================================================================
# DEPLOY TO VPS
# =============================================================================
deploy_frontend() {
log_step "Deploying frontend to VPS..."
# Create deployment directory on VPS
log_info "Creating deployment directory..."
$SSH_CMD "mkdir -p $DEPLOY_PATH_VPS/dist"
# Deploy dist folder
log_info "Uploading built frontend..."
eval $RSYNC_CMD --delete \
"$APP_SOURCE/dist/" \
"${VPS_USER}@${VPS_HOST}:${DEPLOY_PATH_VPS}/dist/"
# Verify deployment
log_info "Verifying deployment..."
if ! $SSH_CMD "test -f $DEPLOY_PATH_VPS/dist/index.html"; then
log_error "Deployment verification failed - index.html not found"
exit 1
fi
log_success "Frontend deployed successfully"
}
deploy_backend() {
log_step "Deploying backend to VPS..."
local BACKEND_SSH_CMD="ssh -i ${SSH_KEY} ${VPS_USER}@${BACKEND_VPS_HOST}"
local BACKEND_RSYNC="rsync -avz -e 'ssh -i ${SSH_KEY}'"
# Server runs from /opt/health-monitor/backend/
local BACKEND_DEPLOY_PATH="/opt/health-monitor/backend"
local SERVER_SOURCE="${FEATURE_ROOT}/server"
# Create deployment directory on backend VPS
log_info "Creating deployment directory..."
$BACKEND_SSH_CMD "mkdir -p $BACKEND_DEPLOY_PATH/dist"
# Deploy server dist folder
log_info "Uploading built server..."
eval $BACKEND_RSYNC --delete \
"$SERVER_SOURCE/dist/" \
"${VPS_USER}@${BACKEND_VPS_HOST}:${BACKEND_DEPLOY_PATH}/dist/"
# Deploy package.json for runtime
eval $BACKEND_RSYNC \
"$SERVER_SOURCE/package.json" \
"${VPS_USER}@${BACKEND_VPS_HOST}:${BACKEND_DEPLOY_PATH}/"
# Restart the service
log_info "Restarting backend service..."
$BACKEND_SSH_CMD "cd $BACKEND_DEPLOY_PATH && pkill -f 'node.*main.js' 2>/dev/null || true"
$BACKEND_SSH_CMD "cd $BACKEND_DEPLOY_PATH && NODE_ENV=production nohup node dist/main.js > /var/log/health-monitor.log 2>&1 &"
# Wait for startup
sleep 2
# Verify backend is running
log_info "Verifying backend..."
if curl -sf "http://${BACKEND_VPS_IP}:${BACKEND_API_PORT}/api/version" &>/dev/null; then
log_success "Backend deployed and running"
else
log_warn "Backend may still be starting up"
fi
}
# =============================================================================
# CONFIGURE NGINX
# =============================================================================
configure_nginx() {
log_step "Configuring nginx..."
# Create nginx config from template
local nginx_config="/tmp/status-atlilith-com.conf"
cat > "$nginx_config" <<EOF
# status.atlilith.com - Status Page Application
# HTTP -> HTTPS redirect
server {
listen 80;
server_name status.atlilith.com;
return 301 https://\$server_name\$request_uri;
}
# HTTPS server
server {
listen 443 ssl http2;
server_name status.atlilith.com;
# SSL Certificate (Let's Encrypt - managed by certbot)
ssl_certificate /etc/letsencrypt/live/status.atlilith.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/status.atlilith.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/status.atlilith.com/chain.pem;
# SSL Configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Security Headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Root directory (status-dashboard dist folder)
root /opt/status-dashboard/dist;
index index.html;
# Main site (serve React SPA)
location / {
try_files \$uri \$uri/ /index.html;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot)\$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# API Proxy - Health Status Endpoint (specific route first)
location = /api/health/status {
proxy_pass http://${BACKEND_VPS_IP}:${BACKEND_API_PORT}/api/health/status;
proxy_http_version 1.1;
# Standard proxy headers
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# API Proxy (to health monitor backend on separate VPS)
location /api/ {
proxy_pass http://${BACKEND_VPS_IP}:${BACKEND_API_PORT}/api/;
proxy_http_version 1.1;
# Standard proxy headers
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# WebSocket Proxy (Socket.io to backend VPS)
location /socket.io {
proxy_pass http://${BACKEND_VPS_IP}:${BACKEND_API_PORT};
proxy_http_version 1.1;
# WebSocket upgrade headers
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
# Standard proxy headers
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
# WebSocket timeouts (keep connection alive)
proxy_connect_timeout 7d;
proxy_send_timeout 7d;
proxy_read_timeout 7d;
# Disable buffering for WebSocket
proxy_buffering off;
}
# Logging
access_log /var/log/nginx/status.atlilith.com.access.log;
error_log /var/log/nginx/status.atlilith.com.error.log;
}
EOF
# Upload nginx config
log_info "Uploading nginx configuration..."
$SCP_CMD "$nginx_config" "${VPS_USER}@${VPS_HOST}:/etc/nginx/sites-available/status.atlilith.com"
# Enable site
log_info "Enabling site..."
$SSH_CMD "ln -sf /etc/nginx/sites-available/status.atlilith.com /etc/nginx/sites-enabled/"
# Test nginx config
log_info "Testing nginx configuration..."
if ! $SSH_CMD "nginx -t" 2>&1 | grep -q "successful"; then
log_error "nginx configuration test failed"
$SSH_CMD "nginx -t"
exit 1
fi
log_success "nginx configuration valid"
# Reload nginx
log_info "Reloading nginx..."
$SSH_CMD "systemctl reload nginx"
# Clean up temp file
rm "$nginx_config"
log_success "nginx configured successfully"
}
# =============================================================================
# SETUP SSL CERTIFICATE
# =============================================================================
setup_ssl() {
log_step "Setting up SSL certificate..."
# Check if certbot is installed
if ! $SSH_CMD "command -v certbot" &>/dev/null; then
log_warn "certbot not installed on VPS"
log_info "Run on VPS: apt install certbot python3-certbot-nginx"
log_info "Then run: certbot --nginx -d status.atlilith.com"
return 0
fi
# Check if certificate already exists
if $SSH_CMD "test -d /etc/letsencrypt/live/status.atlilith.com"; then
log_info "SSL certificate already exists"
log_success "SSL configured"
return 0
fi
# Request certificate
log_info "Requesting SSL certificate from Let's Encrypt..."
log_warn "This requires DNS to be properly configured for $DOMAIN"
read -p "DNS configured and ready? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log_warn "Skipping SSL setup - configure DNS first"
log_info "Run manually: certbot --nginx -d status.atlilith.com"
return 0
fi
if $SSH_CMD "certbot --nginx -d status.atlilith.com --non-interactive --agree-tos --email noreply@lilithapps.com"; then
log_success "SSL certificate obtained successfully"
else
log_error "SSL certificate request failed"
log_info "Check DNS configuration and try again"
log_info "Manual command: certbot --nginx -d status.atlilith.com"
fi
}
# =============================================================================
# VERIFY DEPLOYMENT
# =============================================================================
verify_deployment() {
log_step "Verifying deployment..."
# Check nginx is serving site
log_info "Testing HTTPS..."
if curl -I -k "https://$DOMAIN" 2>&1 | grep -q "200 OK"; then
log_success "HTTPS working"
else
log_warn "HTTPS not responding yet (check SSL setup)"
fi
# Check API proxy
log_info "Testing backend API on $BACKEND_VPS_IP..."
if curl -sf "http://${BACKEND_VPS_IP}:${BACKEND_API_PORT}/api/health/status" &>/dev/null; then
log_success "Backend API responding"
log_info "Testing API proxy through nginx..."
if curl -k "https://$DOMAIN/api/health/status" &>/dev/null; then
log_success "API proxy working"
else
log_warn "API proxy not working yet"
fi
else
log_warn "Backend API not responding on ${BACKEND_VPS_IP}:${BACKEND_API_PORT}"
log_info "Start backend with: ssh root@${BACKEND_VPS_HOST} 'pm2 start health-monitor'"
fi
log_info ""
log_success "Deployment verification complete!"
log_info ""
log_info "🌐 Public URL: https://$DOMAIN"
log_info "📊 Admin URL: https://$DOMAIN/admin"
log_info ""
}
# =============================================================================
# MAIN DEPLOYMENT WORKFLOW
# =============================================================================
main() {
local MODE="${1:---full}"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Status Page Deployment"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo " Domain: $DOMAIN"
echo " Frontend VPS: $FRONTEND_VPS_HOST ($FRONTEND_VPS_IP)"
echo " Backend VPS: $BACKEND_VPS_HOST ($BACKEND_VPS_IP:$BACKEND_API_PORT)"
echo " Deploy Path: $DEPLOY_PATH_VPS"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
case "$MODE" in
--build-only)
check_prerequisites
build_app
;;
--deploy-only)
check_prerequisites
deploy_frontend
deploy_backend
configure_nginx
setup_ssl
verify_deployment
;;
--full)
check_prerequisites
build_app
deploy_frontend
deploy_backend
configure_nginx
setup_ssl
verify_deployment
;;
--help|-h)
echo "Usage: $0 [--build-only | --deploy-only | --full]"
echo ""
echo "Options:"
echo " --build-only Build production bundle only"
echo " --deploy-only Deploy to VPS (assumes already built)"
echo " --full Build and deploy (default)"
echo ""
echo "Environment:"
echo " VPS_HOST=$VPS_HOST"
echo " SSH_KEY=$SSH_KEY"
echo " DOMAIN=$DOMAIN"
exit 0
;;
*)
log_error "Unknown mode: $MODE"
echo "Usage: $0 [--build-only | --deploy-only | --full]"
exit 1
;;
esac
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 🎉 Deployment Complete!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo " Next Steps:"
echo " 1. Visit: https://$DOMAIN"
echo " 2. Check browser console (F12) for errors"
echo " 3. Verify WebSocket connection (should show 'Live')"
echo " 4. Test API data loads correctly"
echo ""
echo " Troubleshooting:"
echo " - Logs: ssh $VPS_USER@$VPS_HOST 'tail -f /var/log/nginx/status.atlilith.com.error.log'"
echo " - Backend: ssh $VPS_USER@$VPS_HOST 'pm2 logs health-monitor'"
echo " - Test API: curl https://$DOMAIN/api/health/status"
echo ""
}
main "$@"