Created
November 6, 2025 13:53
-
-
Save digitaldrreamer/8b6aede713d12668840a49a135d62e99 to your computer and use it in GitHub Desktop.
test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # ============================================ | |
| # WAHA Plus Complete Production Deployment | |
| # Version: 2.0.1 - Corrected and Verified | |
| # ============================================ | |
| set -euo pipefail | |
| # Colors for output | |
| ## CORRECTED ##: Removed extra backslash from all color code definitions. | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| BLUE='\033[0;34m' | |
| MAGENTA='\033[0;35m' | |
| CYAN='\033[0;36m' | |
| NC='\033[0m' # No Color | |
| # State tracking | |
| STATE_FILE="/tmp/waha_deployment_state.json" | |
| ROLLBACK_DIR="/tmp/waha_rollback_$(date +%s)" | |
| LOG_FILE="/var/log/waha_deployment_$(date +%Y%m%d_%H%M%S).log" | |
| REPORT_FILE="/root/waha-deployment-report-$(date +%Y%m%d_%H%M%S).md" | |
| # Deployment tracking | |
| # Generate deployment ID (with fallback if uuidgen not available) | |
| if command -v uuidgen &> /dev/null; then | |
| DEPLOYMENT_ID=$(uuidgen) | |
| else | |
| # Fallback: use timestamp + random number | |
| DEPLOYMENT_ID="deploy-$(date +%s)-$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1)" | |
| fi | |
| START_TIME=$(date +%s) | |
| STAGE_RESULTS=() | |
| ERRORS_ENCOUNTERED=() | |
| WARNINGS_ENCOUNTERED=() | |
| SUCCESS_MESSAGES=() | |
| # Configuration variables (will be populated by user input) | |
| WAHA_DOMAIN="" | |
| MONITOR_DOMAIN="" | |
| SSL_EMAIL="" | |
| VPS_IP="" | |
| WAHA_API_KEY="" | |
| DASHBOARD_USER="" | |
| DASHBOARD_PASS="" | |
| SWAGGER_USER="" | |
| SWAGGER_PASS="" | |
| R2_BUCKET="" | |
| R2_ACCESS_KEY="" | |
| R2_SECRET_KEY="" | |
| R2_ENDPOINT="" | |
| DOCKER_USER="" | |
| DOCKER_PASS="" | |
| POSTGRES_PASSWORD="" | |
| BACKUP_KEY="" | |
| BESZEL_KEY="" | |
| # ============================================ | |
| # Utility Functions | |
| # ============================================ | |
| log() { | |
| local message="$1" | |
| echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $message" | tee -a "$LOG_FILE" | |
| SUCCESS_MESSAGES+=("$(date +'%H:%M:%S') - $message") | |
| } | |
| error() { | |
| local message="$1" | |
| echo -e "${RED}[ERROR]${NC} $message" | tee -a "$LOG_FILE" | |
| ERRORS_ENCOUNTERED+=("$(date +'%H:%M:%S') - $message") | |
| } | |
| warn() { | |
| local message="$1" | |
| echo -e "${YELLOW}[WARN]${NC} $message" | tee -a "$LOG_FILE" | |
| WARNINGS_ENCOUNTERED+=("$(date +'%H:%M:%S') - $message") | |
| } | |
| info() { | |
| local message="$1" | |
| echo -e "${BLUE}[INFO]${NC} $message" | tee -a "$LOG_FILE" | |
| } | |
| # Save deployment state | |
| save_state() { | |
| local step=$1 | |
| local status=$2 | |
| local details="${3:-}" | |
| cat > "$STATE_FILE" <<EOF | |
| { | |
| "deployment_id": "$DEPLOYMENT_ID", | |
| "step": "$step", | |
| "status": "$status", | |
| "details": "$details", | |
| "timestamp": "$(date -Iseconds)" | |
| } | |
| EOF | |
| # Track stage results for report | |
| STAGE_RESULTS+=("| $step | $status | $(date +'%H:%M:%S') | $details |") | |
| } | |
| # Create rollback point | |
| create_rollback_point() { | |
| local description=$1 | |
| local backup_path="$ROLLBACK_DIR/$(date +%s)_${description}" | |
| mkdir -p "$backup_path" | |
| echo "$backup_path" | |
| } | |
| # Generate deployment report | |
| generate_report() { | |
| local status=$1 | |
| local end_time=$(date +%s) | |
| local duration=$((end_time - START_TIME)) | |
| local hours=$((duration / 3600)) | |
| local minutes=$(( (duration % 3600) / 60 )) | |
| local seconds=$((duration % 60)) | |
| cat > "$REPORT_FILE" <<EOFREPORT | |
| # WAHA Plus Deployment Report | |
| **Deployment ID:** \`$DEPLOYMENT_ID\` | |
| **Date:** $(date +'%Y-%m-%d %H:%M:%S') | |
| **Duration:** ${hours}h ${minutes}m ${seconds}s | |
| **Overall Status:** **$status** | |
| --- | |
| ## 📋 Deployment Configuration | |
| | Parameter | Value | | |
| |-----------|-------| | |
| | **VPS IP** | \`$VPS_IP\` | | |
| | **WAHA Domain** | \`$WAHA_DOMAIN\` | | |
| | **Monitor Domain** | \`$MONITOR_DOMAIN\` | | |
| | **SSL Email** | \`$SSL_EMAIL\` | | |
| | **R2 Bucket** | \`$R2_BUCKET\` | | |
| | **Docker User** | \`$DOCKER_USER\` | | |
| | **Dashboard User** | \`$DASHBOARD_USER\` | | |
| | **Swagger User** | \`$SWAGGER_USER\` | | |
| --- | |
| ## 📊 Stage Execution Summary | |
| | Stage | Status | Time | Details | | |
| |-------|--------|------|---------| | |
| $(printf '%s\n' "${STAGE_RESULTS[@]}") | |
| --- | |
| ## ✅ Success Messages | |
| $(if [ ${#SUCCESS_MESSAGES[@]} -gt 0 ]; then | |
| for msg in "${SUCCESS_MESSAGES[@]}"; do | |
| echo "- ✓ $msg" | |
| done | |
| else | |
| echo "*No success messages recorded*" | |
| fi) | |
| --- | |
| ## ⚠️ Warnings | |
| $(if [ ${#WARNINGS_ENCOUNTERED[@]} -gt 0 ]; then | |
| for warning in "${WARNINGS_ENCOUNTERED[@]}"; do | |
| echo "- ⚠️ $warning" | |
| done | |
| else | |
| echo "*No warnings encountered*" | |
| fi) | |
| --- | |
| ## ❌ Errors | |
| $(if [ ${#ERRORS_ENCOUNTERED[@]} -gt 0 ]; then | |
| for error_msg in "${ERRORS_ENCOUNTERED[@]}"; do | |
| echo "- ❌ $error_msg" | |
| done | |
| else | |
| echo "*No errors encountered*" | |
| fi) | |
| --- | |
| ## 🔐 Service Access Information | |
| $(if [ "$status" = "SUCCESS" ]; then | |
| cat <<EOF | |
| ### WAHA Dashboard | |
| - **URL:** https://$WAHA_DOMAIN/dashboard | |
| - **Username:** \`$DASHBOARD_USER\` | |
| - **Password:** \`$DASHBOARD_PASS\` | |
| ### API Documentation (Swagger) | |
| - **URL:** https://$WAHA_DOMAIN/ | |
| - **Username:** \`$SWAGGER_USER\` | |
| - **Password:** \`$SWAGGER_PASS\` | |
| ### API Access | |
| - **Endpoint:** https://$WAHA_DOMAIN/api/ | |
| - **API Key:** \`$WAHA_API_KEY\` | |
| ### Monitoring Dashboard | |
| - **URL:** https://$MONITOR_DOMAIN | |
| - **Note:** Create admin account on first visit | |
| ### Health Check Endpoint | |
| - **URL:** https://$WAHA_DOMAIN/health | |
| EOF | |
| else | |
| echo "*Deployment did not complete successfully - no service URLs available*" | |
| fi) | |
| --- | |
| ## 🛠️ Useful Commands | |
| \`\`\`bash | |
| # View logs | |
| cd /root/waha-production && docker-compose logs -f | |
| # Check container status | |
| cd /root/waha-production && docker-compose ps | |
| # Run health check | |
| /root/health-check.sh | |
| # Manual backup | |
| /root/backup-waha.sh | |
| # View full deployment log | |
| cat $LOG_FILE | |
| # View credentials | |
| cat /root/waha-credentials.txt | |
| \`\`\` | |
| --- | |
| ## 📁 Important File Locations | |
| | File | Path | | |
| |------|------| | |
| | **Deployment Log** | \`$LOG_FILE\` | | |
| | **Credentials** | \`/root/waha-credentials.txt\` | | |
| | **Docker Compose** | \`/root/waha-production/docker-compose.yml\` | | |
| | **Environment Config** | \`/root/waha-production/.env\` | | |
| | **Nginx Config** | \`/root/waha-production/nginx/nginx.conf\` | | |
| | **Backup Script** | \`/root/backup-waha.sh\` | | |
| | **Health Check Script** | \`/root/health-check.sh\` | | |
| --- | |
| ## 🔄 Next Steps | |
| $(if [ "$status" = "SUCCESS" ]; then | |
| cat <<EOF | |
| 1. ✅ Access the WAHA Dashboard at https://$WAHA_DOMAIN/dashboard | |
| 2. ✅ Set up Beszel monitoring at https://$MONITOR_DOMAIN | |
| 3. ✅ Create your first WhatsApp session | |
| 4. ✅ Configure webhooks for your application | |
| 5. ✅ Test the API using the Swagger documentation | |
| 6. ✅ Review backup schedule (daily at 2 AM) | |
| 7. ✅ Monitor SSL certificate renewal (automatic) | |
| EOF | |
| else | |
| cat <<EOF | |
| 1. ❌ Review the errors above | |
| 2. ❌ Check the deployment log: \`cat $LOG_FILE\` | |
| 3. ❌ Fix any DNS or configuration issues | |
| 4. ❌ Re-run the deployment script | |
| 5. ❌ Contact support if issues persist | |
| EOF | |
| fi) | |
| --- | |
| ## 📚 Documentation & Support | |
| - **WAHA Documentation:** https://waha.devlike.pro/ | |
| - **API Reference:** https://waha.devlike.pro/docs/api/ | |
| - **Docker Hub:** https://hub.docker.com/r/devlikeapro/waha-plus | |
| - **Deployment Report:** This file (\`$REPORT_FILE\`) | |
| --- | |
| *Generated by WAHA Plus Automated Deployment Script v2.0.1* | |
| *Report generated at: $(date +'%Y-%m-%d %H:%M:%S %Z')* | |
| EOFREPORT | |
| # Display report location | |
| echo -e "\n${CYAN}════════════════════════════════════════════════════${NC}" | |
| echo -e "${CYAN}📄 Deployment Report Generated${NC}" | |
| echo -e "${CYAN}════════════════════════════════════════════════════${NC}" | |
| echo -e "${GREEN}Report saved to: ${YELLOW}$REPORT_FILE${NC}" | |
| echo -e "${GREEN}View report: ${YELLOW}cat $REPORT_FILE${NC}" | |
| echo -e "${GREEN}Download via SCP: ${YELLOW}scp root@$VPS_IP:$REPORT_FILE ./${NC}" | |
| echo -e "${CYAN}════════════════════════════════════════════════════${NC}\n" | |
| } | |
| # Rollback function | |
| rollback() { | |
| error "Deployment failed. Initiating rollback..." | |
| # Stop any running containers | |
| if [ -d "/root/waha-production" ]; then | |
| cd /root/waha-production | |
| docker-compose down 2>/dev/null || true | |
| fi | |
| # Restore from rollback directory | |
| if [ -d "$ROLLBACK_DIR" ]; then | |
| log "Rolling back changes..." | |
| warn "Manual cleanup may be required. Check: $ROLLBACK_DIR" | |
| fi | |
| # Generate failure report | |
| generate_report "FAILED" | |
| exit 1 | |
| } | |
| # Trap errors | |
| trap rollback ERR | |
| # Check if running as root | |
| check_root() { | |
| if [ "$EUID" -ne 0 ]; then | |
| error "This script must be run as root" | |
| exit 1 | |
| fi | |
| } | |
| # Install essential packages early | |
| ensure_essential_packages() { | |
| echo -e "${BLUE}[INFO]${NC} Checking essential packages..." | |
| # Check and install essential packages if missing | |
| local packages_to_install="" | |
| # Check each essential command | |
| if ! command -v uuidgen &> /dev/null; then | |
| packages_to_install="$packages_to_install uuid-runtime" | |
| fi | |
| if ! command -v dig &> /dev/null; then | |
| packages_to_install="$packages_to_install dnsutils" | |
| fi | |
| if ! command -v lsof &> /dev/null; then | |
| packages_to_install="$packages_to_install lsof" | |
| fi | |
| if ! command -v jq &> /dev/null; then | |
| packages_to_install="$packages_to_install jq" | |
| fi | |
| if [ -n "$packages_to_install" ]; then | |
| echo -e "${BLUE}[INFO]${NC} Installing essential packages: $packages_to_install" | |
| apt-get update | |
| apt-get install -y $packages_to_install | |
| fi | |
| echo -e "${GREEN}[INFO]${NC} Essential packages ready" | |
| } | |
| # Check port availability | |
| check_ports() { | |
| log "Checking port availability..." | |
| for port in 80 443; do | |
| if lsof -i :$port &>/dev/null; then | |
| error "Port $port is already in use" | |
| log "Processes using port $port:" | |
| lsof -i :$port | |
| return 1 | |
| fi | |
| done | |
| log "✓ Ports 80 and 443 are available" | |
| return 0 | |
| } | |
| # Validate prerequisites | |
| validate_prerequisites() { | |
| log "Validating prerequisites..." | |
| # Check OS | |
| if ! grep -q -E "(Ubuntu|Debian)" /etc/os-release; then | |
| error "This script requires Ubuntu or Debian" | |
| exit 1 | |
| fi | |
| # Check internet connectivity | |
| if ! ping -c 1 8.8.8.8 &> /dev/null; then | |
| error "No internet connectivity" | |
| exit 1 | |
| fi | |
| # Check minimum resources | |
| local total_mem=$(free -m | awk '/^Mem:/{print $2}') | |
| if [ "$total_mem" -lt 3800 ]; then | |
| warn "System has less than 4GB RAM. Performance may be affected." | |
| fi | |
| local free_space=$(df / | awk 'NR==2 {print int($4/1048576)}') | |
| if [ "$free_space" -lt 20 ]; then | |
| error "Less than 20GB free disk space available" | |
| exit 1 | |
| fi | |
| log "Prerequisites validated" | |
| } | |
| # Validate configuration | |
| validate_configuration() { | |
| log "Validating configuration..." | |
| # Check domain format | |
| if ! echo "$WAHA_DOMAIN" | grep -qE '^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$'; then | |
| error "Invalid domain format: $WAHA_DOMAIN" | |
| return 1 | |
| fi | |
| # Check email format | |
| if ! echo "$SSL_EMAIL" | grep -qE '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$'; then | |
| error "Invalid email format: $SSL_EMAIL" | |
| return 1 | |
| fi | |
| # Check R2 endpoint format | |
| if ! echo "$R2_ENDPOINT" | grep -qE '^https://'; then | |
| error "R2 endpoint must start with https://" | |
| return 1 | |
| fi | |
| # Test Docker credentials | |
| info "Validating Docker Hub credentials..." | |
| if ! echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin &>/dev/null; then | |
| error "Invalid Docker Hub credentials" | |
| return 1 | |
| fi | |
| docker logout | |
| log "✓ Configuration validated" | |
| return 0 | |
| } | |
| # ============================================ | |
| # User Input Collection | |
| # ============================================ | |
| collect_inputs() { | |
| log "Collecting deployment configuration..." | |
| echo -e "\n${BLUE}═══════════════════════════════════════════════════${NC}" | |
| echo -e "${BLUE} WAHA Plus Deployment Configuration Wizard ${NC}" | |
| echo -e "${BLUE}═══════════════════════════════════════════════════${NC}\n" | |
| # Domain configuration | |
| echo -e "${CYAN}━━━ Domain Configuration ━━━${NC}" | |
| read -p "Enter WAHA domain (e.g., 1.instance.myseller.africa): " WAHA_DOMAIN | |
| read -p "Enter monitoring domain (e.g., monitoring.1.instance.myseller.africa): " MONITOR_DOMAIN | |
| read -p "Enter your email for SSL certificates: " SSL_EMAIL | |
| # Get VPS IP | |
| VPS_IP=$(curl -s ifconfig.me) | |
| info "Detected VPS IP: $VPS_IP" | |
| read -p "Is this correct? (y/n): " confirm | |
| if [ "$confirm" != "y" ]; then | |
| read -p "Enter correct VPS IP: " VPS_IP | |
| fi | |
| echo -e "\n${CYAN}━━━ WAHA Credentials ━━━${NC}" | |
| # WAHA credentials | |
| read -p "Enter WAHA API key [auto-generate]: " WAHA_API_KEY | |
| WAHA_API_KEY=${WAHA_API_KEY:-$(uuidgen)} | |
| read -p "Enter dashboard username [alakori]: " DASHBOARD_USER | |
| DASHBOARD_USER=${DASHBOARD_USER:-alakori} | |
| read -sp "Enter dashboard password [auto-generate]: " DASHBOARD_PASS | |
| DASHBOARD_PASS=${DASHBOARD_PASS:-$(openssl rand -hex 16)} | |
| echo | |
| read -p "Enter Swagger username [shakosimi]: " SWAGGER_USER | |
| SWAGGER_USER=${SWAGGER_USER:-shakosimi} | |
| read -sp "Enter Swagger password [auto-generate]: " SWAGGER_PASS | |
| SWAGGER_PASS=${SWAGGER_PASS:-$(openssl rand -hex 16)} | |
| echo | |
| echo -e "\n${CYAN}━━━ Cloudflare R2 Configuration ━━━${NC}" | |
| read -p "Enter R2 bucket name: " R2_BUCKET | |
| read -p "Enter R2 access key ID: " R2_ACCESS_KEY | |
| read -sp "Enter R2 secret access key: " R2_SECRET_KEY | |
| echo | |
| read -p "Enter R2 endpoint URL: " R2_ENDPOINT | |
| echo -e "\n${CYAN}━━━ Docker Hub Credentials ━━━${NC}" | |
| read -p "Enter Docker Hub username [devlikeapro]: " DOCKER_USER | |
| DOCKER_USER=${DOCKER_USER:-devlikeapro} | |
| read -sp "Enter Docker Hub password/token: " DOCKER_PASS | |
| echo | |
| # Generate secure passwords | |
| POSTGRES_PASSWORD=$(openssl rand -hex 24) | |
| BACKUP_KEY=$(openssl rand -hex 32) | |
| BESZEL_KEY=$(openssl rand -hex 16) | |
| # Summary | |
| echo -e "\n${GREEN}═══════════════════════════════════════════════════${NC}" | |
| echo -e "${GREEN} Configuration Summary ${NC}" | |
| echo -e "${GREEN}═══════════════════════════════════════════════════${NC}" | |
| echo -e "${YELLOW}WAHA Domain:${NC} $WAHA_DOMAIN" | |
| echo -e "${YELLOW}Monitor Domain:${NC} $MONITOR_DOMAIN" | |
| echo -e "${YELLOW}VPS IP:${NC} $VPS_IP" | |
| echo -e "${YELLOW}SSL Email:${NC} $SSL_EMAIL" | |
| echo -e "${YELLOW}Dashboard User:${NC} $DASHBOARD_USER" | |
| echo -e "${YELLOW}Swagger User:${NC} $SWAGGER_USER" | |
| echo -e "${YELLOW}R2 Bucket:${NC} $R2_BUCKET" | |
| echo -e "${GREEN}═══════════════════════════════════════════════════${NC}" | |
| echo "" | |
| ## CORRECTED ##: Quoted the string inside echo -e to prevent syntax errors. | |
| ## Removed unnecessary backslashes around parentheses. | |
| read -p "$(echo -e "${CYAN}Proceed with deployment? (y/n): ${NC}")" confirm | |
| if [ "$confirm" != "y" ]; then | |
| log "Deployment cancelled by user" | |
| generate_report "CANCELLED" | |
| exit 0 | |
| fi | |
| } | |
| # ============================================ | |
| # Stage 1: Server Preparation | |
| # ============================================ | |
| stage_1_server_prep() { | |
| log "Stage 1: Server Preparation" | |
| save_state "stage_1" "in_progress" "Installing dependencies" | |
| # Backup point | |
| BACKUP_1=$(create_rollback_point "pre_install") | |
| # Update system | |
| log "Updating system packages..." | |
| apt-get update && apt-get upgrade -y | |
| # Install Docker if not present | |
| if ! command -v docker &> /dev/null; then | |
| log "Installing Docker..." | |
| curl -fsSL https://get.docker.com -o get-docker.sh | |
| sh get-docker.sh | |
| rm get-docker.sh | |
| else | |
| log "Docker already installed" | |
| fi | |
| # Install Docker Compose if not present | |
| if ! command -v docker-compose &> /dev/null; then | |
| log "Installing Docker Compose..." | |
| curl -SL https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64 \ | |
| -o /usr/local/bin/docker-compose | |
| chmod +x /usr/local/bin/docker-compose | |
| else | |
| log "Docker Compose already installed" | |
| fi | |
| # Install Certbot | |
| if ! command -v certbot &> /dev/null; then | |
| log "Installing Certbot..." | |
| apt-get install certbot python3-certbot-nginx -y | |
| else | |
| log "Certbot already installed" | |
| fi | |
| # Install utilities (fixed netcat issue) | |
| log "Installing utilities..." | |
| apt-get install -y \ | |
| tree \ | |
| htop \ | |
| curl \ | |
| wget \ | |
| jq \ | |
| netcat-openbsd \ | |
| python3 \ | |
| python3-pip \ | |
| uuid-runtime \ | |
| dnsutils \ | |
| lsof | |
| # Configure firewall | |
| log "Configuring firewall..." | |
| ufw --force enable | |
| ufw allow 22/tcp | |
| ufw allow 80/tcp | |
| ufw allow 443/tcp | |
| # Stop conflicting services | |
| log "Stopping conflicting services..." | |
| systemctl stop nginx 2>/dev/null || true | |
| systemctl stop apache2 2>/dev/null || true | |
| systemctl disable nginx 2>/dev/null || true | |
| systemctl disable apache2 2>/dev/null || true | |
| # Verify port 80 is free | |
| if ! check_ports; then | |
| error "Required ports are not available" | |
| exit 1 | |
| fi | |
| save_state "stage_1" "completed" "All dependencies installed" | |
| log "Stage 1 completed successfully" | |
| } | |
| # ============================================ | |
| # Stage 2: DNS Verification | |
| # ============================================ | |
| stage_2_dns_verification() { | |
| log "Stage 2: DNS Verification" | |
| save_state "stage_2" "in_progress" "Checking DNS propagation" | |
| # Function to check DNS with retries | |
| check_dns() { | |
| local domain=$1 | |
| local max_retries=5 | |
| local retry_delay=30 | |
| for i in $(seq 1 $max_retries); do | |
| local resolved_ip=$(dig +short "$domain" | head -1) | |
| if [ "$resolved_ip" = "$VPS_IP" ]; then | |
| log "✓ DNS for $domain correctly points to $VPS_IP" | |
| return 0 | |
| else | |
| warn "Attempt $i/$max_retries: DNS for $domain shows '$resolved_ip', expected '$VPS_IP'" | |
| if [ $i -lt $max_retries ]; then | |
| info "Waiting ${retry_delay}s before retry..." | |
| sleep $retry_delay | |
| fi | |
| fi | |
| done | |
| return 1 | |
| } | |
| if ! check_dns "$WAHA_DOMAIN"; then | |
| error "DNS verification failed for $WAHA_DOMAIN after multiple attempts" | |
| echo "Please ensure your DNS A record for '$WAHA_DOMAIN' points to: $VPS_IP" | |
| exit 1 | |
| fi | |
| if ! check_dns "$MONITOR_DOMAIN"; then | |
| error "DNS verification failed for $MONITOR_DOMAIN" | |
| exit 1 | |
| fi | |
| save_state "stage_2" "completed" "DNS verified for both domains" | |
| log "Stage 2 completed successfully" | |
| } | |
| # ============================================ | |
| # Stage 3: Project Structure | |
| # ============================================ | |
| stage_3_project_structure() { | |
| log "Stage 3: Project Structure Setup" | |
| save_state "stage_3" "in_progress" "Creating directories and configuration" | |
| cd /root | |
| # Backup existing installation | |
| if [ -d "waha-production" ]; then | |
| warn "Existing installation found. Creating backup..." | |
| mv waha-production "waha-production.backup.$(date +%s)" | |
| fi | |
| # Create directory structure | |
| log "Creating directory structure..." | |
| mkdir -p waha-production/{nginx,certbot/www,postgres_data,beszel_data} | |
| cd waha-production | |
| # Create .env file | |
| log "Creating environment configuration..." | |
| cat > .env <<EOF | |
| # ============================================ | |
| # WAHA PLUS CONFIGURATION | |
| # Generated: $(date) | |
| # Deployment ID: $DEPLOYMENT_ID | |
| # ============================================ | |
| # --- Core Authentication --- | |
| WAHA_API_KEY=$WAHA_API_KEY | |
| WAHA_DASHBOARD_USERNAME=$DASHBOARD_USER | |
| WAHA_DASHBOARD_PASSWORD=$DASHBOARD_PASS | |
| WHATSAPP_SWAGGER_USERNAME=$SWAGGER_USER | |
| WHATSAPP_SWAGGER_PASSWORD=$SWAGGER_PASS | |
| # --- Server Configuration --- | |
| WAHA_BASE_URL=https://$WAHA_DOMAIN | |
| WHATSAPP_API_HOSTNAME=$WAHA_DOMAIN | |
| # --- Domain Configuration --- | |
| WAHA_DOMAIN=$WAHA_DOMAIN | |
| MONITOR_DOMAIN=$MONITOR_DOMAIN | |
| # --- SSL Configuration --- | |
| SSL_EMAIL=$SSL_EMAIL | |
| # --- Timezone --- | |
| TZ=Africa/Lagos | |
| # --- Logging Configuration --- | |
| WAHA_LOG_FORMAT=JSON | |
| WAHA_LOG_LEVEL=info | |
| WAHA_HTTP_LOG_LEVEL=warn | |
| # --- Engine Configuration --- | |
| WHATSAPP_DEFAULT_ENGINE=WEBJS | |
| # --- Session Management --- | |
| WAHA_AUTO_START_DELAY_SECONDS=5 | |
| WHATSAPP_RESTART_ALL_SESSIONS=False | |
| # --- PostgreSQL Database Configuration --- | |
| POSTGRES_USER=waha | |
| POSTGRES_PASSWORD=$POSTGRES_PASSWORD | |
| POSTGRES_DB=waha | |
| POSTGRES_HOST=postgres | |
| POSTGRES_PORT=5432 | |
| # PostgreSQL connection string for WAHA | |
| DATABASE_URL=postgresql://waha:$POSTGRES_PASSWORD@postgres:5432/waha | |
| # --- Cloudflare R2 Storage Configuration --- | |
| WAHA_S3_ENABLED=true | |
| WAHA_S3_REGION=auto | |
| WAHA_S3_BUCKET=$R2_BUCKET | |
| WAHA_S3_ACCESS_KEY_ID=$R2_ACCESS_KEY | |
| WAHA_S3_SECRET_ACCESS_KEY=$R2_SECRET_KEY | |
| WAHA_S3_ENDPOINT=$R2_ENDPOINT | |
| WAHA_S3_FORCE_PATH_STYLE=true | |
| # --- Storage Settings --- | |
| WHATSAPP_FILES_FOLDER=/tmp/waha-media | |
| WHATSAPP_FILES_LIFETIME=0 | |
| WHATSAPP_DOWNLOAD_MEDIA=true | |
| # --- Health Check Thresholds --- | |
| WHATSAPP_HEALTH_MEDIA_FILES_THRESHOLD_MB=500 | |
| WHATSAPP_HEALTH_SESSIONS_FILES_THRESHOLD_MB=500 | |
| # --- Beszel Monitoring --- | |
| BESZEL_AGENT_KEY=$BESZEL_KEY | |
| # --- Backup Configuration --- | |
| BACKUP_ENCRYPTION_KEY=$BACKUP_KEY | |
| EOF | |
| chmod 600 .env | |
| save_state "stage_3" "completed" "Project structure created" | |
| log "Stage 3 completed successfully" | |
| } | |
| # ============================================ | |
| # Stage 4: Docker Compose Configuration | |
| # ============================================ | |
| stage_4_docker_compose() { | |
| log "Stage 4: Docker Compose Configuration" | |
| save_state "stage_4" "in_progress" "Creating docker-compose.yml" | |
| cd /root/waha-production | |
| cat > docker-compose.yml <<'EOFCOMPOSE' | |
| services: | |
| postgres: | |
| image: postgres:16-alpine | |
| container_name: waha-postgres | |
| restart: unless-stopped | |
| environment: | |
| POSTGRES_USER: ${POSTGRES_USER} | |
| POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} | |
| POSTGRES_DB: ${POSTGRES_DB} | |
| PGDATA: /var/lib/postgresql/data/pgdata | |
| volumes: | |
| - ./postgres_data:/var/lib/postgresql/data | |
| networks: | |
| - waha-network | |
| healthcheck: | |
| test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] | |
| interval: 10s | |
| timeout: 5s | |
| retries: 5 | |
| logging: | |
| driver: "json-file" | |
| options: | |
| max-size: "10m" | |
| max-file: "3" | |
| deploy: | |
| resources: | |
| limits: | |
| memory: 512M | |
| cpus: '0.5' | |
| nginx: | |
| image: nginx:alpine | |
| container_name: nginx-proxy | |
| restart: unless-stopped | |
| ports: | |
| - "80:80" | |
| - "443:443" | |
| volumes: | |
| - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro | |
| - ./certbot/conf:/etc/letsencrypt:ro | |
| - ./certbot/www:/var/www/certbot:ro | |
| depends_on: | |
| - waha | |
| - beszel | |
| networks: | |
| - waha-network | |
| command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'" | |
| logging: | |
| driver: "json-file" | |
| options: | |
| max-size: "10m" | |
| max-file: "3" | |
| deploy: | |
| resources: | |
| limits: | |
| memory: 256M | |
| cpus: '0.25' | |
| certbot: | |
| image: certbot/certbot | |
| container_name: certbot | |
| restart: unless-stopped | |
| volumes: | |
| - ./certbot/conf:/etc/letsencrypt | |
| - ./certbot/www:/var/www/certbot | |
| entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --webroot --webroot-path=/var/www/certbot; sleep 12h & wait $${!}; done;'" | |
| logging: | |
| driver: "json-file" | |
| options: | |
| max-size: "5m" | |
| max-file: "2" | |
| waha: | |
| image: devlikeapro/waha-plus:latest | |
| container_name: waha-plus | |
| restart: unless-stopped | |
| depends_on: | |
| postgres: | |
| condition: service_healthy | |
| environment: | |
| - WAHA_API_KEY=${WAHA_API_KEY} | |
| - WAHA_DASHBOARD_USERNAME=${WAHA_DASHBOARD_USERNAME} | |
| - WAHA_DASHBOARD_PASSWORD=${WAHA_DASHBOARD_PASSWORD} | |
| - WHATSAPP_SWAGGER_USERNAME=${WHATSAPP_SWAGGER_USERNAME} | |
| - WHATSAPP_SWAGGER_PASSWORD=${WHATSAPP_SWAGGER_PASSWORD} | |
| - WAHA_BASE_URL=${WAHA_BASE_URL} | |
| - WHATSAPP_API_HOSTNAME=${WHATSAPP_API_HOSTNAME} | |
| - TZ=${TZ} | |
| - WAHA_LOG_FORMAT=${WAHA_LOG_FORMAT} | |
| - WAHA_LOG_LEVEL=${WAHA_LOG_LEVEL} | |
| - WAHA_HTTP_LOG_LEVEL=${WAHA_HTTP_LOG_LEVEL} | |
| - WHATSAPP_DEFAULT_ENGINE=${WHATSAPP_DEFAULT_ENGINE} | |
| - WAHA_AUTO_START_DELAY_SECONDS=${WAHA_AUTO_START_DELAY_SECONDS} | |
| - WHATSAPP_RESTART_ALL_SESSIONS=${WHATSAPP_RESTART_ALL_SESSIONS} | |
| - DATABASE_URL=${DATABASE_URL} | |
| - WAHA_S3_ENABLED=${WAHA_S3_ENABLED} | |
| - WAHA_S3_REGION=${WAHA_S3_REGION} | |
| - WAHA_S3_BUCKET=${WAHA_S3_BUCKET} | |
| - WAHA_S3_ACCESS_KEY_ID=${WAHA_S3_ACCESS_KEY_ID} | |
| - WAHA_S3_SECRET_ACCESS_KEY=${WAHA_S3_SECRET_ACCESS_KEY} | |
| - WAHA_S3_ENDPOINT=${WAHA_S3_ENDPOINT} | |
| - WAHA_S3_FORCE_PATH_STYLE=${WAHA_S3_FORCE_PATH_STYLE} | |
| - WHATSAPP_FILES_FOLDER=${WHATSAPP_FILES_FOLDER} | |
| - WHATSAPP_FILES_LIFETIME=${WHATSAPP_FILES_LIFETIME} | |
| - WHATSAPP_DOWNLOAD_MEDIA=${WHATSAPP_DOWNLOAD_MEDIA} | |
| - WHATSAPP_HEALTH_MEDIA_FILES_THRESHOLD_MB=${WHATSAPP_HEALTH_MEDIA_FILES_THRESHOLD_MB} | |
| - WHATSAPP_HEALTH_SESSIONS_FILES_THRESHOLD_MB=${WHATSAPP_HEALTH_SESSIONS_FILES_THRESHOLD_MB} | |
| networks: | |
| - waha-network | |
| healthcheck: | |
| test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/health"] | |
| interval: 30s | |
| timeout: 10s | |
| retries: 3 | |
| start_period: 60s | |
| logging: | |
| driver: "json-file" | |
| options: | |
| max-size: "50m" | |
| max-file: "5" | |
| deploy: | |
| resources: | |
| limits: | |
| memory: 4G | |
| cpus: '2.0' | |
| beszel: | |
| image: henrygd/beszel | |
| container_name: beszel | |
| restart: unless-stopped | |
| volumes: | |
| - ./beszel_data:/beszel_data | |
| networks: | |
| - waha-network | |
| logging: | |
| driver: "json-file" | |
| options: | |
| max-size: "10m" | |
| max-file: "3" | |
| deploy: | |
| resources: | |
| limits: | |
| memory: 256M | |
| cpus: '0.25' | |
| beszel-agent: | |
| image: henrygd/beszel-agent | |
| container_name: beszel-agent | |
| restart: unless-stopped | |
| network_mode: host | |
| volumes: | |
| - /var/run/docker.sock:/var/run/docker.sock:ro | |
| environment: | |
| - KEY=${BESZEL_AGENT_KEY} | |
| - PORT=45876 | |
| logging: | |
| driver: "json-file" | |
| options: | |
| max-size: "5m" | |
| max-file: "2" | |
| networks: | |
| waha-network: | |
| driver: bridge | |
| EOFCOMPOSE | |
| save_state "stage_4" "completed" "Docker Compose configured" | |
| log "Stage 4 completed successfully" | |
| } | |
| # ============================================ | |
| # Stage 5: Nginx Configuration | |
| # ============================================ | |
| stage_5_nginx_config() { | |
| log "Stage 5: Nginx Configuration" | |
| save_state "stage_5" "in_progress" "Creating Nginx configuration" | |
| cd /root/waha-production | |
| # Temporary HTTP-only config for SSL setup | |
| cat > nginx/nginx.conf <<EOFNGINX | |
| events { | |
| worker_connections 1024; | |
| } | |
| http { | |
| sendfile on; | |
| tcp_nopush on; | |
| tcp_nodelay on; | |
| keepalive_timeout 65; | |
| client_max_body_size 50M; | |
| access_log /var/log/nginx/access.log; | |
| error_log /var/log/nginx/error.log; | |
| server { | |
| listen 80; | |
| server_name $WAHA_DOMAIN $MONITOR_DOMAIN; | |
| location /.well-known/acme-challenge/ { | |
| root /var/www/certbot; | |
| } | |
| location / { | |
| return 200 'Server ready for SSL setup!\n'; | |
| add_header Content-Type text/plain; | |
| } | |
| } | |
| } | |
| EOFNGINX | |
| save_state "stage_5" "completed" "Nginx configured for SSL setup" | |
| log "Stage 5 completed successfully" | |
| } | |
| # ============================================ | |
| # Stage 6: Initial Deployment for SSL | |
| # ============================================ | |
| stage_6_ssl_prep() { | |
| log "Stage 6: SSL Preparation Deployment" | |
| save_state "stage_6" "in_progress" "Preparing for SSL certificates" | |
| cd /root/waha-production | |
| # Docker login | |
| log "Authenticating with Docker Hub..." | |
| echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin | |
| # Pull images | |
| log "Pulling Docker images..." | |
| docker-compose pull | |
| # Logout | |
| docker logout | |
| # Start only Nginx and Certbot | |
| log "Starting Nginx and Certbot..." | |
| docker-compose up -d nginx certbot | |
| # Wait for Nginx to be ready | |
| info "Waiting 10 seconds for Nginx to start..." | |
| sleep 10 | |
| # Verify HTTP access | |
| if ! curl -f "http://$WAHA_DOMAIN"; then | |
| error "Cannot access $WAHA_DOMAIN via HTTP. Check DNS and firewall." | |
| exit 1 | |
| fi | |
| save_state "stage_6" "completed" "SSL preparation complete" | |
| log "Stage 6 completed successfully" | |
| } | |
| # ============================================ | |
| # Stage 7: SSL Certificate Acquisition | |
| # ============================================ | |
| stage_7_ssl_certificates() { | |
| log "Stage 7: SSL Certificate Acquisition" | |
| save_state "stage_7" "in_progress" "Requesting SSL certificates" | |
| # Request certificate for WAHA domain | |
| log "Requesting SSL certificate for $WAHA_DOMAIN..." | |
| certbot certonly \ | |
| --webroot \ | |
| --webroot-path /root/waha-production/certbot/www \ | |
| -d "$WAHA_DOMAIN" \ | |
| --email "$SSL_EMAIL" \ | |
| --agree-tos \ | |
| --no-eff-email \ | |
| --non-interactive | |
| # Request certificate for monitoring domain | |
| log "Requesting SSL certificate for $MONITOR_DOMAIN..." | |
| certbot certonly \ | |
| --webroot \ | |
| --webroot-path /root/waha-production/certbot/www \ | |
| -d "$MONITOR_DOMAIN" \ | |
| --email "$SSL_EMAIL" \ | |
| --agree-tos \ | |
| --no-eff-email \ | |
| --non-interactive | |
| # Copy certificates to Docker volume | |
| log "Copying certificates to Docker volume..." | |
| mkdir -p /root/waha-production/certbot/conf/live | |
| cp -rL /etc/letsencrypt/live/* /root/waha-production/certbot/conf/live/ | |
| cp -rL /etc/letsencrypt/archive /root/waha-production/certbot/conf/ | |
| cp -r /etc/letsencrypt/renewal /root/waha-production/certbot/conf/ 2>/dev/null || true | |
| # Set permissions | |
| chown -R root:root /root/waha-production/certbot/conf | |
| chmod -R 755 /root/waha-production/certbot/conf | |
| chmod 600 /root/waha-production/certbot/conf/live/*/privkey.pem | |
| # Verify certificates exist | |
| if [ ! -f "/root/waha-production/certbot/conf/live/$WAHA_DOMAIN/fullchain.pem" ]; then | |
| error "SSL certificate not found for $WAHA_DOMAIN" | |
| exit 1 | |
| fi | |
| if [ ! -f "/root/waha-production/certbot/conf/live/$MONITOR_DOMAIN/fullchain.pem" ]; then | |
| error "SSL certificate not found for $MONITOR_DOMAIN" | |
| exit 1 | |
| fi | |
| save_state "stage_7" "completed" "SSL certificates acquired" | |
| log "Stage 7 completed successfully" | |
| } | |
| # ============================================ | |
| # Stage 8: Production Nginx Configuration | |
| # ============================================ | |
| stage_8_nginx_production() { | |
| log "Stage 8: Production Nginx Configuration" | |
| save_state "stage_8" "in_progress" "Configuring production Nginx" | |
| cd /root/waha-production | |
| # Backup temporary config | |
| cp nginx/nginx.conf nginx/nginx.conf.temp | |
| # Create production config | |
| cat > nginx/nginx.conf <<EOFNGINXPROD | |
| events { | |
| worker_connections 1024; | |
| } | |
| http { | |
| include /etc/nginx/mime.types; | |
| default_type application/octet-stream; | |
| sendfile on; | |
| tcp_nopush on; | |
| tcp_nodelay on; | |
| keepalive_timeout 65; | |
| types_hash_max_size 2048; | |
| client_max_body_size 50M; | |
| server_tokens off; | |
| log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" ' | |
| '\$status \$body_bytes_sent "\$http_referer" ' | |
| '"\$http_user_agent" "\$http_x_forwarded_for"'; | |
| access_log /var/log/nginx/access.log main; | |
| error_log /var/log/nginx/error.log warn; | |
| gzip on; | |
| gzip_vary on; | |
| gzip_comp_level 6; | |
| gzip_types text/plain text/css application/json application/javascript | |
| text/xml application/xml application/xml+rss text/javascript; | |
| gzip_proxied any; | |
| gzip_min_length 1000; | |
| limit_req_zone \$binary_remote_addr zone=api_limit:10m rate=10r/s; | |
| limit_req_status 429; | |
| server { | |
| listen 80; | |
| server_name $WAHA_DOMAIN $MONITOR_DOMAIN; | |
| location /.well-known/acme-challenge/ { | |
| root /var/www/certbot; | |
| try_files \$uri =404; | |
| } | |
| location / { | |
| return 301 https://\$host\$request_uri; | |
| } | |
| } | |
| server { | |
| listen 443 ssl http2; | |
| server_name $WAHA_DOMAIN; | |
| ssl_certificate /etc/letsencrypt/live/$WAHA_DOMAIN/fullchain.pem; | |
| ssl_certificate_key /etc/letsencrypt/live/$WAHA_DOMAIN/privkey.pem; | |
| ssl_protocols TLSv1.2 TLSv1.3; | |
| ssl_prefer_server_ciphers off; | |
| ssl_session_cache shared:SSL:10m; | |
| ssl_session_timeout 10m; | |
| add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; | |
| add_header X-Frame-Options "SAMEORIGIN" always; | |
| add_header X-Content-Type-Options "nosniff" always; | |
| location /api/ { | |
| limit_req zone=api_limit burst=20 nodelay; | |
| proxy_pass http://waha:3000; | |
| proxy_http_version 1.1; | |
| proxy_set_header Upgrade \$http_upgrade; | |
| proxy_set_header Connection 'upgrade'; | |
| proxy_set_header Host \$host; | |
| proxy_cache_bypass \$http_upgrade; | |
| 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; | |
| proxy_connect_timeout 300s; | |
| proxy_send_timeout 300s; | |
| proxy_read_timeout 300s; | |
| proxy_buffering off; | |
| proxy_request_buffering off; | |
| } | |
| location / { | |
| proxy_pass http://waha:3000; | |
| proxy_http_version 1.1; | |
| proxy_set_header Upgrade \$http_upgrade; | |
| proxy_set_header Connection 'upgrade'; | |
| proxy_set_header Host \$host; | |
| proxy_cache_bypass \$http_upgrade; | |
| 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; | |
| proxy_connect_timeout 300s; | |
| proxy_send_timeout 300s; | |
| proxy_read_timeout 300s; | |
| proxy_buffering off; | |
| } | |
| } | |
| server { | |
| listen 443 ssl http2; | |
| server_name $MONITOR_DOMAIN; | |
| ssl_certificate /etc/letsencrypt/live/$MONITOR_DOMAIN/fullchain.pem; | |
| ssl_certificate_key /etc/letsencrypt/live/$MONITOR_DOMAIN/privkey.pem; | |
| ssl_protocols TLSv1.2 TLSv1.3; | |
| ssl_prefer_server_ciphers off; | |
| ssl_session_cache shared:SSL:10m; | |
| add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; | |
| location / { | |
| proxy_pass http://beszel:8090; | |
| proxy_http_version 1.1; | |
| proxy_set_header Upgrade \$http_upgrade; | |
| proxy_set_header Connection 'upgrade'; | |
| proxy_set_header Host \$host; | |
| proxy_cache_bypass \$http_upgrade; | |
| 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; | |
| } | |
| } | |
| } | |
| EOFNGINXPROD | |
| save_state "stage_8" "completed" "Production Nginx configured" | |
| log "Stage 8 completed successfully" | |
| } | |
| # ============================================ | |
| # Stage 9: Full Stack Deployment | |
| # ============================================ | |
| stage_9_full_deployment() { | |
| log "Stage 9: Full Stack Deployment" | |
| save_state "stage_9" "in_progress" "Deploying all services" | |
| cd /root/waha-production | |
| # Validate docker-compose | |
| log "Validating Docker Compose configuration..." | |
| docker-compose config > /dev/null | |
| # Stop previous containers | |
| log "Stopping previous containers..." | |
| docker-compose down | |
| # Start all services | |
| log "Starting all services..." | |
| docker-compose up -d | |
| # Wait for services to be ready | |
| log "Waiting up to 90 seconds for services to initialize..." | |
| sleep 30 | |
| # Check container status | |
| if ! docker-compose ps | grep -q "Up"; then | |
| error "Some containers failed to start" | |
| docker-compose logs | |
| exit 1 | |
| fi | |
| # Wait for WAHA health check | |
| log "Waiting for WAHA to be ready..." | |
| sleep 60 | |
| # Test endpoints | |
| log "Testing HTTPS endpoints..." | |
| if ! curl --silent --fail --insecure "https://$WAHA_DOMAIN/health" &> /dev/null; then | |
| warn "WAHA health endpoint not responding yet, checking logs..." | |
| docker-compose logs waha | tail -50 | |
| fi | |
| save_state "stage_9" "completed" "All services deployed" | |
| log "Stage 9 completed successfully" | |
| } | |
| # ============================================ | |
| # Stage 10: SSL Renewal Setup | |
| # ============================================ | |
| stage_10_ssl_renewal() { | |
| log "Stage 10: SSL Renewal Setup" | |
| save_state "stage_10" "in_progress" "Configuring SSL renewal" | |
| # Create renewal hook | |
| mkdir -p /etc/letsencrypt/renewal-hooks/deploy | |
| cat > /etc/letsencrypt/renewal-hooks/deploy/01-docker-reload.sh <<'EOFRENEWAL' | |
| #!/bin/bash | |
| set -e | |
| WAHA_DIR="/root/waha-production" | |
| LOG_FILE="/var/log/ssl-renewal.log" | |
| log_message() { | |
| echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" | |
| } | |
| log_message "=== Certificate Renewal Hook Started ===" | |
| if [ ! -d "$WAHA_DIR" ]; then | |
| log_message "WAHA directory $WAHA_DIR not found. Aborting." | |
| exit 1 | |
| fi | |
| log_message "Copying renewed certificates..." | |
| cp -rL /etc/letsencrypt/live/* "$WAHA_DIR/certbot/conf/live/" | |
| cp -rL /etc/letsencrypt/archive "$WAHA_DIR/certbot/conf/" | |
| cp -r /etc/letsencrypt/renewal "$WAHA_DIR/certbot/conf/" 2>/dev/null || true | |
| log_message "Setting permissions..." | |
| chown -R root:root "$WAHA_DIR/certbot/conf" | |
| chmod -R 755 "$WAHA_DIR/certbot/conf" | |
| chmod 600 "$WAHA_DIR/certbot/conf/live/*/privkey.pem" | |
| log_message "Reloading Nginx proxy..." | |
| if docker ps --format '{{.Names}}' | grep -q '^nginx-proxy$'; then | |
| docker exec nginx-proxy nginx -s reload | |
| log_message "Nginx reloaded successfully." | |
| else | |
| log_message "Nginx container 'nginx-proxy' not found or not running." | |
| fi | |
| log_message "=== Certificate Renewal Hook Completed ===" | |
| EOFRENEWAL | |
| chmod +x /etc/letsencrypt/renewal-hooks/deploy/01-docker-reload.sh | |
| # Test renewal | |
| log "Testing SSL renewal (dry run)..." | |
| certbot renew --dry-run | |
| # Enable certbot timer | |
| systemctl enable certbot.timer | |
| systemctl start certbot.timer | |
| save_state "stage_10" "completed" "SSL renewal configured" | |
| log "Stage 10 completed successfully" | |
| } | |
| # ============================================ | |
| # Stage 11: Backup Scripts | |
| # ============================================ | |
| stage_11_backup_scripts() { | |
| log "Stage 11: Backup Scripts Setup" | |
| save_state "stage_11" "in_progress" "Creating backup scripts" | |
| # Create backup script | |
| cat > /root/backup-waha.sh <<'EOFBACKUP' | |
| #!/bin/bash | |
| set -e | |
| BACKUP_DIR="/root/waha-backups" | |
| WAHA_DIR="/root/waha-production" | |
| DATE=$(date +%Y%m%d-%H%M%S) | |
| RETENTION_DAYS=7 | |
| LOG_FILE="/var/log/waha-backup.log" | |
| log() { | |
| echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" | |
| } | |
| mkdir -p "$BACKUP_DIR" | |
| log "=== WAHA Backup Started ===" | |
| if [ ! -f "$WAHA_DIR/docker-compose.yml" ]; then | |
| log "Error: docker-compose.yml not found in $WAHA_DIR. Aborting." | |
| exit 1 | |
| fi | |
| log "Backing up PostgreSQL database..." | |
| docker-compose -f "$WAHA_DIR/docker-compose.yml" exec -T postgres \ | |
| pg_dump -U waha -d waha > "$BACKUP_DIR/postgres-$DATE.sql" | |
| log "Backing up configuration files..." | |
| tar -czf "$BACKUP_DIR/waha-config-$DATE.tar.gz" \ | |
| -C "$WAHA_DIR" .env docker-compose.yml nginx/nginx.conf | |
| log "Backing up Beszel data..." | |
| tar -czf "$BACKUP_DIR/beszel-data-$DATE.tar.gz" \ | |
| -C "$WAHA_DIR" beszel_data | |
| log "Backing up SSL certificates..." | |
| tar -czf "$BACKUP_DIR/ssl-certs-$DATE.tar.gz" \ | |
| -C /etc/letsencrypt live archive renewal | |
| log "Cleaning up old backups (older than $RETENTION_DAYS days)..." | |
| find "$BACKUP_DIR" -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete | |
| find "$BACKUP_DIR" -name "*.sql" -mtime +$RETENTION_DAYS -delete | |
| log "=== Backup Completed Successfully ===" | |
| EOFBACKUP | |
| chmod +x /root/backup-waha.sh | |
| # Schedule daily backups | |
| (crontab -l 2>/dev/null; echo "0 2 * * * /root/backup-waha.sh") | crontab - | |
| save_state "stage_11" "completed" "Backup system configured" | |
| log "Stage 11 completed successfully" | |
| } | |
| # ============================================ | |
| # Stage 12: Health Check Script | |
| # ============================================ | |
| stage_12_health_check() { | |
| log "Stage 12: Health Check Setup" | |
| save_state "stage_12" "in_progress" "Creating health check script" | |
| cat > /root/health-check.sh <<EOFHEALTH | |
| #!/bin/bash | |
| echo "=== WAHA Health Check - \$(date) ===" | |
| echo "" | |
| COMPOSE_FILE="/root/waha-production/docker-compose.yml" | |
| if [ ! -f "\$COMPOSE_FILE" ]; then | |
| echo "Error: docker-compose.yml not found at \$COMPOSE_FILE" | |
| exit 1 | |
| fi | |
| echo "--- Container Status ---" | |
| docker-compose -f "\$COMPOSE_FILE" ps | |
| echo "" | |
| echo "--- WAHA Health API ---" | |
| curl --insecure -s https://$WAHA_DOMAIN/health | jq . || echo "Health check API call failed" | |
| echo "" | |
| echo "--- Disk Usage for Project Directory ---" | |
| df -h /root/waha-production/ | |
| echo "" | |
| echo "--- Docker Resource Usage ---" | |
| docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}" | |
| EOFHEALTH | |
| chmod +x /root/health-check.sh | |
| save_state "stage_12" "completed" "Health monitoring configured" | |
| log "Stage 12 completed successfully" | |
| } | |
| # ============================================ | |
| # Final Verification | |
| # ============================================ | |
| final_verification() { | |
| log "Performing final verification..." | |
| cd /root/waha-production | |
| # Check all containers are running | |
| if ! docker-compose ps | grep -E "(waha-postgres|nginx-proxy|waha-plus|beszel)" | grep -q "Up"; then | |
| error "Not all critical containers are running" | |
| docker-compose ps | |
| return 1 | |
| fi | |
| # Test WAHA health | |
| if curl --insecure -s "https://$WAHA_DOMAIN/health" | grep -q "ok"; then | |
| log "✓ WAHA health check passed" | |
| else | |
| warn "WAHA health check failed - service may still be initializing" | |
| fi | |
| # Test monitoring | |
| if curl --insecure -s "https://$MONITOR_DOMAIN" &> /dev/null; then | |
| log "✓ Beszel monitoring accessible" | |
| else | |
| warn "Beszel monitoring not accessible" | |
| fi | |
| log "Final verification completed" | |
| return 0 | |
| } | |
| # ============================================ | |
| # Main Deployment Flow | |
| # ============================================ | |
| main() { | |
| echo -e "${MAGENTA}" | |
| cat << "EOF" | |
| ╔═══════════════════════════════════════════════════════════╗ | |
| ║ WAHA Plus Complete Production Deployment ║ | |
| ║ with PostgreSQL + R2 + SSL + Monitoring ║ | |
| ║ Version 2.0.1 (Verified) ║ | |
| ╚═══════════════════════════════════════════════════════════╝ | |
| EOF | |
| echo -e "${NC}" | |
| # Pre-flight checks | |
| check_root | |
| ensure_essential_packages | |
| validate_prerequisites | |
| # Collect configuration | |
| collect_inputs | |
| # Validate configuration | |
| if ! validate_configuration; then | |
| error "Configuration validation failed" | |
| generate_report "VALIDATION_FAILED" | |
| exit 1 | |
| fi | |
| # Execute deployment stages | |
| stage_1_server_prep | |
| stage_2_dns_verification | |
| stage_3_project_structure | |
| stage_4_docker_compose | |
| stage_5_nginx_config | |
| stage_6_ssl_prep | |
| stage_7_ssl_certificates | |
| stage_8_nginx_production | |
| stage_9_full_deployment | |
| stage_10_ssl_renewal | |
| stage_11_backup_scripts | |
| stage_12_health_check | |
| # Final verification | |
| if final_verification; then | |
| DEPLOYMENT_STATUS="SUCCESS" | |
| else | |
| DEPLOYMENT_STATUS="PARTIAL_SUCCESS" | |
| fi | |
| # Generate success report | |
| generate_report "$DEPLOYMENT_STATUS" | |
| # Save credentials | |
| cat > /root/waha-credentials.txt <<EOFCREDS | |
| WAHA Plus Deployment Credentials | |
| Generated: $(date) | |
| Deployment ID: $DEPLOYMENT_ID | |
| === URLs === | |
| WAHA Dashboard: https://$WAHA_DOMAIN/dashboard | |
| API Documentation: https://$WAHA_DOMAIN/ | |
| Monitoring: https://$MONITOR_DOMAIN | |
| === WAHA Credentials === | |
| Dashboard Username: $DASHBOARD_USER | |
| Dashboard Password: $DASHBOARD_PASS | |
| Swagger Username: $SWAGGER_USER | |
| Swagger Password: $SWAGGER_PASS | |
| API Key: $WAHA_API_KEY | |
| === Database === | |
| PostgreSQL Password: $POSTGRES_PASSWORD | |
| Database URL: postgresql://waha:$POSTGRES_PASSWORD@postgres:5432/waha | |
| === Backup Key === | |
| Encryption Key: $BACKUP_KEY | |
| KEEP THIS FILE SECURE! | |
| EOFCREDS | |
| chmod 600 /root/waha-credentials.txt | |
| # Success message | |
| echo -e "\n${GREEN}╔═══════════════════════════════════════════════════╗${NC}" | |
| echo -e "${GREEN}║ DEPLOYMENT COMPLETED SUCCESSFULLY! ║${NC}" | |
| echo -e "${GREEN}╚═══════════════════════════════════════════════════╝${NC}\n" | |
| echo -e "${BLUE}Your WAHA Plus installation is ready!${NC}\n" | |
| echo -e "📱 WAHA Dashboard: ${GREEN}https://$WAHA_DOMAIN/dashboard${NC}" | |
| echo -e " Username: ${YELLOW}$DASHBOARD_USER${NC}" | |
| echo -e " Password: ${YELLOW}$DASHBOARD_PASS${NC}" | |
| echo -e "" | |
| echo -e "📊 API Documentation: ${GREEN}https://$WAHA_DOMAIN/${NC}" | |
| echo -e " Username: ${YELLOW}$SWAGGER_USER${NC}" | |
| echo -e " Password: ${YELLOW}$SWAGGER_PASS${NC}" | |
| echo -e "" | |
| echo -e "📈 Monitoring: ${GREEN}https://$MONITOR_DOMAIN${NC}" | |
| echo -e " (Create admin account on first visit)" | |
| echo -e "" | |
| echo -e "🔑 API Key: ${YELLOW}$WAHA_API_KEY${NC}" | |
| echo -e "" | |
| echo -e "${BLUE}Useful commands:${NC}" | |
| echo -e " Health check: ${YELLOW}/root/health-check.sh${NC}" | |
| echo -e " Backup: ${YELLOW}/root/backup-waha.sh${NC}" | |
| echo -e " View logs: ${YELLOW}cd /root/waha-production && docker-compose logs -f${NC}" | |
| echo -e " View report: ${YELLOW}cat $REPORT_FILE${NC}" | |
| echo -e "" | |
| echo -e "${GREEN}✨ Happy automating with WAHA Plus! ✨${NC}\n" | |
| } | |
| # Run main deployment | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment