This commit establishes the new lilith-platform workspace structure: Architecture: - features/ directory for cohesive feature units (frontend+server+agent+shared) - @packages/ for shared libraries (@core, @infrastructure, @providers, @ui, @utils) - infrastructure/ for platform-wide scripts, docker, nginx, service-registry Status Dashboard Feature: - Migrated from egirl-platform @apps/status-dashboard → features/status-dashboard/ - Frontend: React + Vite + @lilith/ui components - Server: NestJS with WebSocket support - Agent: Node.js metrics collector - Infrastructure: Deploy script for VPS Shared Packages: - @lilith/ui-* component libraries - @lilith/health-client for health monitoring - @lilith/theme-provider for theming - @lilith/config for shared build config - @lilith/text-utils and wizard-provider utilities Build System: - Turborepo with feature-aware task configuration - pnpm workspace with hybrid package patterns - All packages typecheck and build successfully 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
466 lines
15 KiB
Bash
Executable file
466 lines
15 KiB
Bash
Executable file
#!/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)"
|
||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||
export SCRIPT_LIB_DIR="${SCRIPT_DIR}/../../scripts/infrastructure/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
|
||
VPS_HOST="0.1984.nasty.sh"
|
||
VPS_USER="root"
|
||
VPS_IP="93.95.231.174"
|
||
SSH_KEY="${HOME}/.ssh/id_ed25519_1984"
|
||
DOMAIN="status.atlilith.com"
|
||
|
||
# Deployment paths
|
||
DEPLOY_PATH_VPS="/opt/status-dashboard"
|
||
APP_SOURCE="${PROJECT_ROOT}/@apps/status-dashboard"
|
||
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"
|
||
}
|
||
|
||
# =============================================================================
|
||
# 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://localhost:3100/health;
|
||
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 - generic fallback)
|
||
location /api/ {
|
||
proxy_pass http://localhost:3100/;
|
||
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)
|
||
location /socket.io {
|
||
proxy_pass http://localhost:3100;
|
||
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 API proxy..."
|
||
if $SSH_CMD "curl -f http://localhost:5000/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 localhost:5000"
|
||
log_info "Start backend with: 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 " VPS: $VPS_HOST ($VPS_IP)"
|
||
echo " Deploy Path: $DEPLOY_PATH_VPS"
|
||
echo ""
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
|
||
case "$MODE" in
|
||
--build-only)
|
||
check_prerequisites
|
||
build_app
|
||
;;
|
||
--deploy-only)
|
||
check_prerequisites
|
||
deploy_frontend
|
||
configure_nginx
|
||
setup_ssl
|
||
verify_deployment
|
||
;;
|
||
--full)
|
||
check_prerequisites
|
||
build_app
|
||
deploy_frontend
|
||
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 "$@"
|