MOON
Server: Apache
System: Linux vps.erhabenn.com.br 3.10.0-1160.119.1.el7.tuxcare.els2.x86_64 #1 SMP Mon Jul 15 12:09:18 UTC 2024 x86_64
User: machen (1008)
PHP: 8.2.31
Disabled: NONE
Upload Files
File: //usr/tmp/lck_tmp/install-backend.sh
#!/bin/bash

# =========================================================================
#  Ladang Cuan Kreator AI Backend - PM2 Auto Installer
# =========================================================================
# This script automates:
#  1. Dynamic OS Detection (Ubuntu, Debian, CentOS, RHEL, Rocky, AlmaLinux)
#  2. System Dependency Installation & Upgrades
#  3. Production Node.js Environment Setup (v18.x)
#  4. PM2 Global Installation & Setup
#  5. App Deployment & Production Dependency Extraction
#  6. SSL Self-Signed Certificate Generation
#  7. Port 1607 Firewall Auto-Configuration (UFW / Firewall-cmd)
#  8. PM2 Service Startup & Daemon Initialization
# =========================================================================

set -e # Exit on error

# --- Configuration ---
APP_NAME="lck-backend"
INSTALL_DIR="/opt/$APP_NAME"
# Prioritize command line argument, then environment variable, then default port 1607
PORT=${1:-${PORT:-1607}}
DISABLE_HTTPS=${2:-${DISABLE_HTTPS:-false}}
NODE_VERSION="18"

# Colors for outstanding terminal aesthetics
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color

echo -e "${CYAN}=================================================================${NC}"
echo -e "${PURPLE}     Ladang Cuan Kreator AI Backend - Automated PM2 Deployer     ${NC}"
echo -e "${CYAN}=================================================================${NC}"

# 1. Verify Root Privileges
if [ "$EUID" -ne 0 ]; then 
  echo -e "${RED}Error: Please run as root or with sudo privileges.${NC}"
  exit 1
fi

# Repair /tmp permissions and recreate, but if /tmp is a read-only filesystem (common on cPanel loopback mounts),
# redirect all system temporary writes to a safe, writable fallback folder (/var/tmp/lck_tmp or /root/lck_tmp).
echo -e "Configuring writable system temporary environment..."
LCK_TMP="/var/tmp/lck_tmp"
mkdir -p "$LCK_TMP" 2>/dev/null || LCK_TMP="/root/lck_tmp"
mkdir -p "$LCK_TMP" || true
chmod 777 "$LCK_TMP" || true

export TMPDIR="$LCK_TMP"
export TEMP="$LCK_TMP"
export TMP="$LCK_TMP"
echo -e "Temporary directory redirected to: ${GREEN}$TMPDIR${NC}"

# If /var/tmp is also read-only or symlinked to /tmp, override the RPM transaction path
# by writing a custom %_tmppath macro in the root user's personal .rpmmacros file.
if [ "$EUID" -eq 0 ]; then
    echo -e "Configuring RPM macro overrides to use writable temp path..."
    mkdir -p /root/lck_tmp || true
    chmod 777 /root/lck_tmp || true
    echo "%_tmppath /root/lck_tmp" > /root/.rpmmacros
    echo -e "RPM temporary path redirected to: ${GREEN}/root/lck_tmp${NC}"
fi

# Check if Node.js and PM2 are already present and fully operational
SKIP_RUNTIME_SETUP=false
if command -v node >/dev/null 2>&1 && command -v pm2 >/dev/null 2>&1; then
    SKIP_RUNTIME_SETUP=true
    echo -e "${GREEN}Node.js ($(node -v)) and PM2 ($(pm2 -v 2>/dev/null || echo 'active')) are already installed.${NC}"
    echo -e "${GREEN}Skipping system runtime/dependencies setup to complete updates in seconds!${NC}"
    
    # Resolve PM2 command path dynamically for safe-path execution
    PM2_CMD="pm2"
    if [ -x "/usr/bin/pm2" ] && ( /usr/bin/pm2 -v >/dev/null 2>&1 ); then
        PM2_CMD="/usr/bin/pm2"
    elif [ -x "/usr/local/bin/pm2" ] && ( /usr/local/bin/pm2 -v >/dev/null 2>&1 ); then
        PM2_CMD="/usr/local/bin/pm2"
    fi
fi

if [ "$SKIP_RUNTIME_SETUP" = false ]; then

# 2. Dynamic OS Detection & Capability Assessment
echo -e "${CYAN}[1/8] Assessing Server OS & System Libraries...${NC}"

# Detect GLIBC Version to determine Node.js compatibility scientifically
GLIBC_VER=$(ldd --version 2>/dev/null | head -n1 | grep -oE '[0-9]+\.[0-9]+' | head -n1 || echo "2.17")
echo -e "Detected System GLIBC Version: ${GREEN}$GLIBC_VER${NC}"

if [ -f /etc/os-release ]; then
    . /etc/os-release
    OS_ID=$ID
    OS_NAME=$NAME
    OS_VERSION=$VERSION_ID
else
    echo -e "${YELLOW}Warning: /etc/os-release not found. Checking standard release files...${NC}"
    if [ -f /etc/cloudlinux-release ]; then
        OS_ID="cloudlinux"
        OS_NAME="CloudLinux"
        OS_VERSION=$(cat /etc/cloudlinux-release | grep -oE '[0-9]+\.[0-9]+' | head -n1)
    elif [ -f /etc/centos-release ]; then
        OS_ID="centos"
        OS_NAME="CentOS"
        OS_VERSION=$(cat /etc/centos-release | grep -oE '[0-9]+\.[0-9]+' | head -n1)
    elif [ -f /etc/redhat-release ]; then
        OS_ID="rhel"
        OS_NAME="RedHat"
        OS_VERSION=$(cat /etc/redhat-release | grep -oE '[0-9]+\.[0-9]+' | head -n1)
    else
        OS_ID="generic"
        OS_NAME="Generic Linux"
        OS_VERSION="unknown"
    fi
fi
echo -e "Detected OS: ${GREEN}$OS_NAME ($OS_ID, Version: $OS_VERSION)${NC}"

# Scientifically determine max supported Node.js version
# Node 18+ requires glibc 2.28+ (modern systems)
# Node 16+ requires glibc 2.17+ (EL7 / CloudLinux 7 / CentOS 7)
# Node 10-14 can run on glibc 2.12 (EL6 / CloudLinux 6 / CentOS 6)
if [ "$(echo -e "$GLIBC_VER\n2.28" | sort -V | head -n1)" = "2.28" ]; then
    NODE_VERSION="18"
    echo -e "System supports modern Node.js. Target version: ${GREEN}v18${NC}"
elif [ "$(echo -e "$GLIBC_VER\n2.17" | sort -V | head -n1)" = "2.17" ]; then
    NODE_VERSION="16"
    echo -e "GLIBC 2.17 limitation detected. Target version: ${GREEN}v16${NC}"
else
    NODE_VERSION="10"
    echo -e "GLIBC 2.12 limitation detected (CentOS 6 / CloudLinux 6). Target version: ${GREEN}v10${NC}"
fi

# Determine Package Manager and commands
if [[ "$OS_ID" == "ubuntu" || "$OS_ID" == "debian" || "$OS_ID" == "raspbian" ]]; then
    PKG_UPDATE="apt-get update -y"
    PKG_INSTALL="apt-get install -y"
    FIREWALL="ufw"
elif [[ "$OS_ID" == "centos" || "$OS_ID" == "rhel" || "$OS_ID" == "rocky" || "$OS_ID" == "almalinux" || "$OS_ID" == "cloudlinux" ]]; then
    if command -v dnf >/dev/null 2>&1; then
        PKG_MANAGER="dnf"
    else
        PKG_MANAGER="yum"
    fi
    PKG_UPDATE="$PKG_MANAGER makecache --setopt=skip_if_unavailable=true"
    PKG_INSTALL="$PKG_MANAGER install -y --skip-broken --setopt=skip_if_unavailable=true"
    FIREWALL="firewalld"
else
    echo -e "${RED}Unsupported OS: $OS_ID. Please install Node.js and PM2 manually.${NC}"
    exit 1
fi

# 3. System Update & Base System Dependency Installation
echo -e "${CYAN}[2/8] Installing vital system build dependencies...${NC}"
$PKG_UPDATE
if [[ "$OS_ID" == "ubuntu" || "$OS_ID" == "debian" ]]; then
    $PKG_INSTALL curl wget tar build-essential openssl unzip
else
    $PKG_INSTALL curl wget tar gcc-c++ make openssl-devel unzip
fi

# 4. Install Node.js Environment
if command -v node >/dev/null 2>&1 && node -v >/dev/null 2>&1 && command -v npm >/dev/null 2>&1; then
    CURRENT_NODE_VER=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
    echo -e "Node.js and npm are already installed (Node: v$(node -v), npm: v$(npm -v 2>/dev/null || echo "unknown"))."
    if [ "$CURRENT_NODE_VER" -lt "$NODE_VERSION" ]; then
        echo -e "${YELLOW}Warning: Node version is too old. Upgrading to Node.js v${NODE_VERSION}...${NC}"
        INSTALL_NODE=true
    else
        # If we are forced to v16 on EL7 or v10 on EL6, reinstall to satisfy glibc constraints
        if [[ "$NODE_VERSION" == "16" && "$CURRENT_NODE_VER" -gt 16 ]]; then
            echo -e "${YELLOW}Warning: Node.js v${CURRENT_NODE_VER} detected on EL7. Reinstalling Node.js v16...${NC}"
            INSTALL_NODE=true
        elif [[ "$NODE_VERSION" == "10" && "$CURRENT_NODE_VER" -gt 10 ]]; then
            echo -e "${YELLOW}Warning: Node.js v${CURRENT_NODE_VER} detected on EL6. Reinstalling Node.js v10...${NC}"
            INSTALL_NODE=true
        else
            INSTALL_NODE=false
        fi
    fi
else
    echo -e "${YELLOW}Node.js/npm not found or is incomplete. Proceeding with clean engine installation...${NC}"
    INSTALL_NODE=true
fi

if [ "$INSTALL_NODE" = true ]; then
    echo -e "${CYAN}[3/8] Setting up Node.js v${NODE_VERSION}.x...${NC}"
    
    # Resolve the exact pre-compiled tarball version based on capability detection
    NODE_TAR_VER="18.20.2"
    if [ "$NODE_VERSION" = "16" ]; then
        NODE_TAR_VER="16.20.2"
    elif [ "$NODE_VERSION" = "10" ]; then
        NODE_TAR_VER="10.24.1"
    fi
    
    echo -e "${YELLOW}Universal Linux Deployment: Installing pre-compiled Node.js v${NODE_TAR_VER} x64 binary...${NC}"
    
    # Purge old Nodesource repositories and local conflicting package manager files
    echo -e "Purging conflicting node/npm packages and caching..."
    if command -v apt-get >/dev/null 2>&1; then
        apt-get remove -y nodejs npm >/dev/null 2>&1 || true
        apt-get autoremove -y >/dev/null 2>&1 || true
    elif command -v yum >/dev/null 2>&1; then
        rm -f /etc/yum.repos.d/nodesource-*.repo || true
        yum clean all >/dev/null 2>&1 || true
        yum remove -y nodejs npm >/dev/null 2>&1 || true
    fi
    
    # Force delete existing system links to ensure clean rewrite
    rm -f /usr/bin/node /usr/bin/npm /usr/bin/npx /usr/bin/pm2 /usr/local/bin/pm2 || true
    
    NODE_TAR="node-v${NODE_TAR_VER}-linux-x64.tar.gz"
    echo -e "Downloading Node.js tarball..."
    if wget -q --no-check-certificate "https://nodejs.org/dist/v${NODE_TAR_VER}/$NODE_TAR"; then
        echo -e "Download complete via wget."
    elif curl -k -sL -O "https://nodejs.org/dist/v${NODE_TAR_VER}/$NODE_TAR"; then
        echo -e "Download complete via curl."
    else
        echo -e "${RED}Error: Failed to download Node.js tarball using wget or curl.${NC}"
        exit 1
    fi
    
    if [ -f "$NODE_TAR" ]; then
        echo -e "Extracting Node.js v${NODE_TAR_VER} to /usr/local..."
        tar -xzf "$NODE_TAR" -C /usr/local --strip-components=1
        rm -f "$NODE_TAR"
        
        # Create standard system absolute symlinks directly pointing to absolute library files
        # This completely avoids writing/modifying /usr/local/bin/npm which may be write-protected (chattr/selinux) by the OS
        ln -sf /usr/local/bin/node /usr/bin/node
        ln -sf /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/bin/npm
        ln -sf /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/bin/npx
        
        # Ensure all binary links and modules are executable
        chmod +x /usr/local/bin/node /usr/local/bin/npm /usr/local/bin/npx || true
        chmod +x /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/lib/node_modules/npm/bin/npx-cli.js || true
        
        echo -e "${GREEN}Node.js v${NODE_TAR_VER} pre-compiled binary installed successfully!${NC}"
    else
        echo -e "${RED}Error: Node.js tarball not found after download.${NC}"
        exit 1
    fi
fi

# 5. Install & Setup PM2 Globally
echo -e "${CYAN}[4/8] Installing & Setting up PM2 Process Manager globally...${NC}"

# Detect if PM2 is broken (circular symlinks, missing, or throws errors)
PM2_BROKEN=false
if command -v pm2 >/dev/null 2>&1; then
    # Use a subshell to safely check pm2 -v without exiting under set -e
    if ! (pm2 -v >/dev/null 2>&1); then
        echo -e "${YELLOW}Warning: Existing pm2 installation is broken or has circular symbolic links.${NC}"
        PM2_BROKEN=true
    fi
else
    # If command -v pm2 fails, but the files still exist (e.g. broken symlink not in PATH or failing resolve)
    if [ -L "/usr/bin/pm2" ] || [ -L "/usr/local/bin/pm2" ]; then
        echo -e "${YELLOW}Warning: Broken or circular pm2 symbolic links detected in system directories.${NC}"
        PM2_BROKEN=true
    fi
fi

# Clean up broken links if detected
if [ "$PM2_BROKEN" = true ]; then
    echo -e "Cleaning up broken/circular pm2 symbolic links..."
    rm -f /usr/bin/pm2 /usr/local/bin/pm2 || true
fi

if ! command -v pm2 >/dev/null 2>&1 || [ "$INSTALL_NODE" = true ] || [ "$PM2_BROKEN" = true ]; then
    # Force clean up before npm install to prevent npm linking errors
    rm -f /usr/bin/pm2 /usr/local/bin/pm2 || true
    echo -e "Installing PM2 globally via npm..."
    
    # Dynamically select PM2 version based on the active Node.js version
    PM2_INSTALL_TARGET="pm2"
    CURRENT_NODE_MAJOR=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
    if [ "$CURRENT_NODE_MAJOR" -lt 18 ]; then
        PM2_INSTALL_TARGET="pm2@5.3.0"
        echo -e "${YELLOW}Warning: Active Node.js major version v${CURRENT_NODE_MAJOR} is less than v18.${NC}"
        echo -e "Targeting compatible PM2 version: ${GREEN}${PM2_INSTALL_TARGET}${NC}"
    fi
    
    # Set a custom global prefix inside the root home folder to avoid permissions/lchown errors in /usr/local/bin
    echo -e "Configuring secure npm global prefix in /root/.npm-global to bypass system blocks..."
    mkdir -p /root/.npm-global || true
    npm config set prefix '/root/.npm-global' || true
    export PATH="/root/.npm-global/bin:$PATH"
    
    echo -e "Attempting global PM2 installation (will fallback gracefully to local PM2 on failure)..."
    if npm install -g "$PM2_INSTALL_TARGET" --unsafe-perm=true --allow-root; then
        echo -e "${GREEN}PM2 installed/updated successfully globally.${NC}"
    else
        echo -e "${YELLOW}Warning: Global PM2 installation failed due to system permissions or lchown blocks.${NC}"
        echo -e "${YELLOW}Graceful fallback: We will install and run PM2 locally inside the project folder.${NC}"
    fi
else
    # Safely get PM2 version in a subshell to avoid set -e crashing if something unexpected happens
    PM2_CURRENT_VERSION=$(pm2 -v 2>/dev/null || echo "unknown")
    echo -e "PM2 is already installed and working (v$PM2_CURRENT_VERSION). Skipping reinstall..."
fi

# Ensure PM2 is in system PATH with absolute, non-circular symlinks
echo -e "Verifying global PM2 executable path and resolving symlinks..."

# Find the real, physical PM2 CLI script in global node_modules
NPM_PREFIX=$(npm prefix -g 2>/dev/null || echo "/usr/local")
REAL_PM2=""

if [ -f "$NPM_PREFIX/lib/node_modules/pm2/bin/pm2" ]; then
    REAL_PM2="$NPM_PREFIX/lib/node_modules/pm2/bin/pm2"
elif [ -f "/usr/local/lib/node_modules/pm2/bin/pm2" ]; then
    REAL_PM2="/usr/local/lib/node_modules/pm2/bin/pm2"
elif [ -f "/usr/lib/node_modules/pm2/bin/pm2" ]; then
    REAL_PM2="/usr/lib/node_modules/pm2/bin/pm2"
fi

if [ -n "$REAL_PM2" ]; then
    echo -e "Found physical PM2 CLI entrypoint at: ${GREEN}$REAL_PM2${NC}"
    # Delete symlinks first to prevent link-over-link issues
    rm -f /usr/bin/pm2 /usr/local/bin/pm2 || true
    ln -sf "$REAL_PM2" /usr/bin/pm2 || true
    ln -sf "$REAL_PM2" /usr/local/bin/pm2 || true
    echo -e "Created direct, non-circular symlinks for /usr/bin/pm2 and /usr/local/bin/pm2."
else
    # Defensive fallback if npm global structure is non-standard
    echo -e "${YELLOW}Warning: Physical pm2 entrypoint not found in standard node_modules. Using path-based linking...${NC}"
    
    PM2_PATH=""
    # Safely find a working pm2 binary (must be a regular file or a valid symlink, not a loop)
    if [ -f "/usr/local/bin/pm2" ] && [ ! -L "/usr/local/bin/pm2" ]; then
        PM2_PATH="/usr/local/bin/pm2"
    elif [ -f "/usr/bin/pm2" ] && [ ! -L "/usr/bin/pm2" ]; then
        PM2_PATH="/usr/bin/pm2"
    else
        # Ask npm where its global bin directory is
        NPM_BIN_DIR=$(npm bin -g 2>/dev/null || npm prefix -g 2>/dev/null || echo "")
        if [ -n "$NPM_BIN_DIR" ]; then
            if [ -d "$NPM_BIN_DIR/bin" ] && [ -f "$NPM_BIN_DIR/bin/pm2" ]; then
                PM2_PATH="$NPM_BIN_DIR/bin/pm2"
            elif [ -f "$NPM_BIN_DIR/pm2" ]; then
                PM2_PATH="$NPM_BIN_DIR/pm2"
            fi
        fi
    fi
    
    if [ -n "$PM2_PATH" ]; then
        # Ensure we do not link to self
        if [ "$PM2_PATH" != "/usr/bin/pm2" ]; then
            rm -f /usr/bin/pm2 || true
            ln -sf "$PM2_PATH" /usr/bin/pm2 || true
        fi
        if [ "$PM2_PATH" != "/usr/local/bin/pm2" ]; then
            rm -f /usr/local/bin/pm2 || true
            ln -sf "$PM2_PATH" /usr/local/bin/pm2 || true
        fi
    else
        echo -e "${RED}Error: pm2 binary could not be located. PM2 setup may fail.${NC}"
    fi
fi

# Clear bash command lookup path cache
hash -r || true

# Define PM2 command path dynamically, prioritizing absolute system executable paths
PM2_CMD="pm2"
if [ -x "/usr/bin/pm2" ] && ( /usr/bin/pm2 -v >/dev/null 2>&1 ); then
    PM2_CMD="/usr/bin/pm2"
elif [ -x "/usr/local/bin/pm2" ] && ( /usr/local/bin/pm2 -v >/dev/null 2>&1 ); then
    PM2_CMD="/usr/local/bin/pm2"
fi
echo -e "Resolved PM2 execution command to: ${GREEN}$PM2_CMD${NC}"

fi # End of SKIP_RUNTIME_SETUP skip



# 6. Extract Application Files and Install Production Dependencies
echo -e "${CYAN}[5/8] Creating installation directory at $INSTALL_DIR...${NC}"
mkdir -p "$INSTALL_DIR"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ZIP_FILE="$SCRIPT_DIR/backend.zip"

if [ -f "$ZIP_FILE" ]; then
    # Calculate and log file size and MD5 checksum
    FILE_SIZE=$(wc -c < "$ZIP_FILE" | tr -d '[:space:]' 2>/dev/null || echo "unknown")
    if command -v md5sum >/dev/null 2>&1; then
        FILE_MD5=$(md5sum "$ZIP_FILE" | awk '{print $1}')
    elif command -v md5 >/dev/null 2>&1; then
        FILE_MD5=$(md5 -q "$ZIP_FILE")
    else
        FILE_MD5="unknown"
    fi
    echo -e "Staged backend.zip verification: Size = ${FILE_SIZE} bytes, MD5 = ${FILE_MD5}"

    # Verify Zip file integrity before extracting to prevent hangs and corrupt installs
    echo -e "Testing archive integrity..."
    if unzip -tq "$ZIP_FILE" >/dev/null 2>&1; then
        echo -e "${GREEN}Staged zip archive is valid.${NC}"
    else
        echo -e "${RED}Critical Error: backend.zip is corrupted or not a valid zip archive!${NC}"
        echo -e "${YELLOW}Analyzing file headers to diagnose download failure...${NC}"
        # Read the first few chars to detect HTML/error pages
        FIRST_CHARS=$(head -c 100 "$ZIP_FILE" 2>/dev/null || true)
        echo -e "File signature preview: \"$FIRST_CHARS\""
        if [[ "$FIRST_CHARS" == *"<!"* || "$FIRST_CHARS" == *"<html"* || "$FIRST_CHARS" == *"Request Rejected"* ]]; then
            echo -e "${RED}WAF / Firewall detected! The download URL returned an HTML block page instead of the actual zip. Check connection/credentials.${NC}"
        fi
        exit 1
    fi

    echo -e "Existing config/session detection..."
    if [ -f "$INSTALL_DIR/config.json" ]; then
        echo -e "Backing up existing ${GREEN}config.json${NC}..."
        cp "$INSTALL_DIR/config.json" "$LCK_TMP/config.json.bak"
    fi
    if [ -f "$INSTALL_DIR/cookie.json" ]; then
        echo -e "Backing up existing ${GREEN}cookie.json${NC}..."
        cp "$INSTALL_DIR/cookie.json" "$LCK_TMP/cookie.json.bak"
    fi
    if [ -f "$INSTALL_DIR/history.json" ]; then
        echo -e "Backing up existing ${GREEN}history.json${NC}..."
        cp "$INSTALL_DIR/history.json" "$LCK_TMP/history.json.bak"
    fi

    echo -e "Extracting deployment archive backend.zip into $INSTALL_DIR..."
    unzip -o "$ZIP_FILE" -d "$INSTALL_DIR"

    echo -e "Restoring backup configurations..."
    if [ -f "$LCK_TMP/config.json.bak" ]; then
        echo -e "Merging backup config.json with package configuration..."
        node -e "
            const fs = require('fs');
            const backupPath = '$LCK_TMP/config.json.bak';
            const currentPath = '$INSTALL_DIR/config.json';
            let current = {};
            try { if (fs.existsSync(currentPath)) current = JSON.parse(fs.readFileSync(currentPath, 'utf8')); } catch(e) {}
            let backup = {};
            try { if (fs.existsSync(backupPath)) backup = JSON.parse(fs.readFileSync(backupPath, 'utf8')); } catch(e) {}
            const merged = { ...current, ...backup };
            fs.writeFileSync(currentPath, JSON.stringify(merged, null, 2), 'utf8');
        " 2>/dev/null || mv "$LCK_TMP/config.json.bak" "$INSTALL_DIR/config.json"
    fi
    if [ -f "$LCK_TMP/cookie.json.bak" ]; then
        mv "$LCK_TMP/cookie.json.bak" "$INSTALL_DIR/cookie.json"
    fi
    if [ -f "$LCK_TMP/history.json.bak" ]; then
        mv "$LCK_TMP/history.json.bak" "$INSTALL_DIR/history.json"
    fi
    
    # Update or create config.json with correct port and disableHttps
    if [ ! -f "$INSTALL_DIR/config.json" ]; then
        echo "{\"port\": $PORT}" > "$INSTALL_DIR/config.json"
    fi
    
    if [ "$DISABLE_HTTPS" = "true" ] || [ "$DISABLE_HTTPS" = "--disable-https" ]; then
        echo -e "Configuring backend config to run with ${YELLOW}HTTPS Disabled${NC}..."
        node -e "
            const fs = require('fs');
            const path = '$INSTALL_DIR/config.json';
            const data = fs.existsSync(path) ? JSON.parse(fs.readFileSync(path, 'utf8')) : {};
            data.port = parseInt('$PORT');
            data.disableHttps = true;
            fs.writeFileSync(path, JSON.stringify(data, null, 2), 'utf8');
        " 2>/dev/null || {
            sed -i 's/"port": [0-9]*/"port": '$PORT'/g' "$INSTALL_DIR/config.json"
            if grep -q "disableHttps" "$INSTALL_DIR/config.json"; then
                sed -i 's/"disableHttps": [a-z]*/"disableHttps": true/g' "$INSTALL_DIR/config.json"
            else
                sed -i 's/}/  ,\"disableHttps\": true\n}/g' "$INSTALL_DIR/config.json"
            fi
        }
    else
        node -e "
            const fs = require('fs');
            const path = '$INSTALL_DIR/config.json';
            const data = fs.existsSync(path) ? JSON.parse(fs.readFileSync(path, 'utf8')) : {};
            data.port = parseInt('$PORT');
            fs.writeFileSync(path, JSON.stringify(data, null, 2), 'utf8');
        " 2>/dev/null || {
            sed -i 's/"port": [0-9]*/"port": '$PORT'/g' "$INSTALL_DIR/config.json"
        }
    fi
    
    if [ -d "$INSTALL_DIR/node_modules" ] && [ -d "$INSTALL_DIR/node_modules/node-pty" ]; then
        echo -e "${GREEN}node_modules directory and node-pty already exist. Skipping npm install...${NC}"
    else
        echo -e "Installing/updating production packages (npm install --production)..."
        cd "$INSTALL_DIR"
        npm install --production || {
            echo -e "${YELLOW}Warning: Native module compilation failed. Retrying without node-pty...${NC}"
            node -e "
                const fs = require('fs');
                const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
                if (pkg.dependencies && pkg.dependencies['node-pty']) {
                    delete pkg.dependencies['node-pty'];
                    fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2), 'utf8');
                    console.log('Removed node-pty dependency from package.json');
                }
            " 2>/dev/null || true
            npm install --production
        }
    fi

    # Ensure PM2 is installed. If not available globally, install it locally.
    HAS_GLOBAL_PM2=false
    if [ -x "/usr/bin/pm2" ] && ( /usr/bin/pm2 -v >/dev/null 2>&1 ); then
        HAS_GLOBAL_PM2=true
    elif [ -x "/usr/local/bin/pm2" ] && ( /usr/local/bin/pm2 -v >/dev/null 2>&1 ); then
        HAS_GLOBAL_PM2=true
    fi

    if [ "$HAS_GLOBAL_PM2" = false ]; then
        echo -e "${YELLOW}Global PM2 not found or broken. Ensuring local PM2 is installed inside $INSTALL_DIR...${NC}"
        cd "$INSTALL_DIR"
        
        # Determine PM2 version to install locally
        PM2_INSTALL_TARGET="pm2"
        CURRENT_NODE_MAJOR=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
        if [ "$CURRENT_NODE_MAJOR" -lt 18 ]; then
            PM2_INSTALL_TARGET="pm2@5.3.0"
        fi
        
        if [ ! -d "$INSTALL_DIR/node_modules/pm2" ]; then
            echo -e "Installing local compatible PM2 ($PM2_INSTALL_TARGET)..."
            npm install "$PM2_INSTALL_TARGET" --no-save --unsafe-perm=true --allow-root || true
        fi
        
        # Create local PM2 wrapper script to prevent quoting-expansion issues in bash
        echo -e "Creating local PM2 wrapper script..."
        cat << EOF > "$INSTALL_DIR/pm2-local.sh"
#!/bin/bash
node "$INSTALL_DIR/node_modules/pm2/bin/pm2" "\$@"
EOF
        chmod +x "$INSTALL_DIR/pm2-local.sh" || true
        PM2_CMD="$INSTALL_DIR/pm2-local.sh"
        echo -e "PM2 execution command redirected to local wrapper: ${GREEN}$PM2_CMD${NC}"
    fi
else
    echo -e "${RED}Critical Error: backend.zip not found in $SCRIPT_DIR.${NC}"
    echo -e "Ensure that backend.zip and this script are located in the same directory."
    exit 1
fi

# 7. Generate Production SSL Self-Signed Certificates
echo -e "${CYAN}[6/8] Generating Self-Signed SSL Certificates...${NC}"
if [ ! -f "$INSTALL_DIR/cert.pem" ]; then
    openssl req -x509 -newkey rsa:4096 -keyout "$INSTALL_DIR/key.pem" -out "$INSTALL_DIR/cert.pem" -days 365 -nodes -subj "/C=ID/ST=Jakarta/L=Jakarta/O=MagicPhotos/OU=AI/CN=localhost"
    echo -e "SSL Certificates generated."
else
    echo -e "SSL Certificates already exist. Skipping..."
fi

# 8. Firewall Configuration
echo -e "${CYAN}[7/8] Configuring Firewall to open port $PORT...${NC}"
HAS_FIREWALL=false

# 1. ConfigServer Firewall (CSF)
if command -v csf >/dev/null 2>&1; then
    echo -e "CSF (ConfigServer Firewall) detected. Appending port $PORT..."
    if [ -f /etc/csf/csf.conf ]; then
        # Check if port is already allowed
        if ! grep -q "$PORT" /etc/csf/csf.conf; then
            sed -i "s/TCP_IN = \"/TCP_IN = \"$PORT,/g" /etc/csf/csf.conf
            sed -i "s/TCP_OUT = \"/TCP_OUT = \"$PORT,/g" /etc/csf/csf.conf
            echo -e "Port $PORT added to CSF TCP_IN and TCP_OUT successfully."
        else
            echo -e "Port $PORT is already allowed in CSF configuration."
        fi
        csf -r || true
        echo -e "CSF firewall restarted."
        HAS_FIREWALL=true
    fi
fi

# 2. UFW Firewall
if [[ "$FIREWALL" == "ufw" ]]; then
    if command -v ufw >/dev/null 2>&1; then
        ufw allow $PORT/tcp || true
        echo -e "UFW firewall port $PORT opened."
        HAS_FIREWALL=true
    fi
# 3. Firewalld
elif [[ "$FIREWALL" == "firewalld" ]]; then
    if command -v firewall-cmd >/dev/null 2>&1; then
        firewall-cmd --permanent --add-port=$PORT/tcp || true
        firewall-cmd --reload || true
        echo -e "Firewalld port $PORT opened."
        HAS_FIREWALL=true
    fi
fi

# 4. Raw iptables rules (Always run if iptables command is available to ensure packet acceptance)
if command -v iptables >/dev/null 2>&1; then
    echo -e "iptables detected. Ensuring port $PORT is allowed..."
    # Check if the rule already exists to prevent duplicate rules
    if ! iptables -C INPUT -p tcp --dport $PORT -j ACCEPT >/dev/null 2>&1; then
        iptables -I INPUT -p tcp --dport $PORT -j ACCEPT
        echo -e "Port $PORT rule inserted into iptables."
    else
        echo -e "Port $PORT rule already exists in iptables."
    fi
    
    # Save the rule to make it persistent on CentOS/RHEL/CloudLinux
    if [ -f /etc/redhat-release ] || [ -f /etc/centos-release ] || [ -f /etc/os-release ]; then
        if command -v service >/dev/null 2>&1; then
            service iptables save >/dev/null 2>&1 || true
        elif [ -f /sbin/iptables-save ]; then
            /sbin/iptables-save > /etc/sysconfig/iptables >/dev/null 2>&1 || true
        fi
    fi
    HAS_FIREWALL=true
fi

if [ "$HAS_FIREWALL" = false ]; then
    echo -e "${YELLOW}No standard firewall manager or iptables detected. Ensure port $PORT is open manually.${NC}"
fi

# 9. Register & Start Application under PM2 Process Manager
echo -e "${CYAN}[8/8] Deploying process under PM2 Control Panel...${NC}"
cd "$INSTALL_DIR"

# Stop existing PM2 process if already running to prevent duplicates
"$PM2_CMD" delete "$APP_NAME" >/dev/null 2>&1 || true

# Forcefully terminate any duplicate or zombie process occupying the port (excluding ourselves and our parent shell)
echo -e "Ensuring port $PORT is free from any duplicate/zombie processes..."

# Find all processes holding the port using lsof, ss, or netstat
PIDS_ON_PORT=""
if command -v lsof >/dev/null 2>&1; then
    PIDS_ON_PORT=$(lsof -t -i:$PORT 2>/dev/null || true)
else
    # Fallback to ss or netstat
    PIDS_ON_PORT=$(ss -lptn "sport = :$PORT" 2>/dev/null | grep -oP 'pid=\K[0-9]+' || netstat -nlp 2>/dev/null | grep ":$PORT " | awk '{print $7}' | cut -d'/' -f1 || true)
fi

# Filter out our own PID ($$) and our parent PID ($PPID) to prevent suicide
PID_TO_KILL=""
for pid in $PIDS_ON_PORT; do
    clean_pid=$(echo "$pid" | grep -oE '[0-9]+' || true)
    if [ -n "$clean_pid" ]; then
        if [ "$clean_pid" -ne "$$" ] && [ "$clean_pid" -ne "$PPID" ]; then
            PID_TO_KILL="$PID_TO_KILL $clean_pid"
        fi
    fi
done

if [ -n "$PID_TO_KILL" ]; then
    echo -e "Found duplicate processes on port $PORT (PIDs: $PID_TO_KILL). Terminating..."
    kill -9 $PID_TO_KILL >/dev/null 2>&1 || true
else
    echo -e "Port $PORT is clean. No duplicate processes found."
fi

# Start server under PM2 with exponential backoff delay on crash/restart loops
PORT="$PORT" "$PM2_CMD" start server.js --name "$APP_NAME" --max-memory-restart 500M --exp-backoff-restart-delay 1000

# Save PM2 process list to persist across reboots
"$PM2_CMD" save

# Setup PM2 Startup script
echo -e "Generating PM2 system-level startup config..."
"$PM2_CMD" startup systemd | tail -n 1 | bash || true

# Configure systemd to automatically restart the PM2 daemon if it gets killed
echo -e "Patching PM2 systemd service to auto-restart when killed..."
for service in /etc/systemd/system/pm2-*.service; do
    if [ -f "$service" ]; then
        if ! grep -q "Restart=" "$service"; then
            sed -i '/\[Service\]/a Restart=always\nRestartSec=10' "$service"
            echo -e "Service $(basename $service) configured to Restart=always when killed."
            systemctl daemon-reload || true
            service_name=$(basename "$service" .service)
            systemctl restart "$service_name" || true
        fi
    fi
done

echo -e "${CYAN}=================================================================${NC}"
echo -e "${GREEN}             PM2 BACKEND DEPLOYED SUCCESSFULLY!                  ${NC}"
echo -e "${CYAN}=================================================================${NC}"
echo -e "Process Name:   ${GREEN}$APP_NAME${NC}"
echo -e "Install Path:   ${GREEN}$INSTALL_DIR${NC}"
echo -e "Server Port:    ${GREEN}$PORT${NC}"
echo -e ""
echo -e "Manage your application using the following PM2 commands:"
echo -e "  - View Live Logs:      ${GREEN}pm2 logs $APP_NAME${NC}"
echo -e "  - View Process Status: ${GREEN}pm2 status${NC}"
echo -e "  - Restart Service:     ${GREEN}pm2 restart $APP_NAME${NC}"
echo -e "  - Stop Service:        ${GREEN}pm2 stop $APP_NAME${NC}"
echo -e "${CYAN}=================================================================${NC}"