#!/bin/bash
# cwp83fix.sh - repair a CWP panel left broken by the PHP 8.3 migration.
# Safe to re-run: every step checks first and only acts when needed.
#   sh cwp83fix.sh          diagnose + fix
#   sh cwp83fix.sh --check  diagnose only, change nothing
# Log: /var/log/cwp/cwp83fix.log

# Re-exec under full bash when started as `sh cwp83fix.sh` (POSIX mode).
if [ -z "$BASH_VERSION" ] || shopt -oq posix 2>/dev/null; then exec /bin/bash "$0" "$@"; fi

CHECK=0; [ "$1" = "--check" ] && CHECK=1
PHP=/usr/local/cwp/php71/bin/php
HTDOCS=/usr/local/cwpsrv/htdocs
SERVICES=/usr/local/cwpsrv/var/services
ZIP_BASE=http://static.cdn-cwp.com/files/cwp/el7
RPM_VER=8.3.36-1
WORK=/root/cwp83fix
mkdir -p /var/log/cwp "$WORK"
exec > >(tee -a /var/log/cwp/cwp83fix.log) 2>&1
echo "=== $(date) cwp83fix start (check-only=$CHECK)"

EL=$(rpm -E %rhel)
code() { curl -sk -m 20 -o /dev/null -w '%{http_code}' "$1"; }
panel() { echo "admin=$(code https://127.0.0.1:2031/login/index.php) user=$(code https://127.0.0.1:2083/)"; }
restart_panel() { systemctl restart cwpsrv-phpfpm cwp-phpfpm cwpsrv; sleep 4; }
CHANGED=0
PM=$(command -v dnf || command -v yum)
HOTFIX=""; [ "$EL" = "8" ] && HOTFIX="--setopt=appstream.module_hotfixes=1"
echo "EL=$EL rpm=$(rpm -q cwpphp) panel: $(panel)"

# --- 1. pcre2 in /usr/local/lib (built by the PHP selector/switcher) ----------
# Built without JIT it breaks cwpphp 8.3 / PHP 8.5 ("Unable to start pcre
# module"); deleted, it breaks alt PHPs linked to it ("unrecognised
# compile-time option bit(s)"). Fix for both: rebuild 10.39 with JIT.
need_pcre=0
for b in "$PHP" /opt/alt/php*/usr/bin/php; do
    [ -x "$b" ] || continue
    if "$b" -v 2>&1 | grep -q 'Unable to start pcre module\|unrecognised compile-time option'; then
        echo "pcre2 problem in: $b"; need_pcre=1
    fi
done
echo "pcre2 rebuild needed: $need_pcre"
if [ "$need_pcre" = 1 ] && [ "$CHECK" = 0 ]; then
    cd /usr/local/src
    if [ ! -d pcre2-10.39 ]; then
        wget -q -O pcre2.zip http://static.cdn-cwp.com/files/php/addons/pcre2-10.39.zip && unzip -oq pcre2.zip
    fi
    cd pcre2-10.39 || { echo "ERROR: no pcre2 source"; exit 1; }
    make distclean >/dev/null 2>&1
    ./configure --enable-jit >/dev/null && make -j"$(nproc)" >/dev/null && make install >/dev/null && ldconfig \
        || { echo "ERROR: pcre2 build failed"; exit 1; }
    echo "pcre2 rebuilt with JIT"; CHANGED=1
    for s in $(systemctl list-units --no-legend --state=running 'php-fpm*' | awk '{print $1}'); do systemctl restart "$s"; done
fi

# --- 2. cwpphp runtime must be 8.3 ------------------------------------------
# EL8: libzip lives only in the disabled AppStream php module streams, so
# dnf needs module_hotfixes to see it.
CUR=$("$PHP" -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;' 2>/dev/null)
echo "cwpphp version: ${CUR:-broken}"
if [ "$CUR" != "8.3" ] && [ "$CHECK" = 0 ]; then
    rpm -q epel-release >/dev/null || $PM -y install epel-release
    # EL8/EL9 cwpphp needs these runtime libs from EPEL (not pulled automatically).
    [ "$EL" != 7 ] && $PM -y install libmcrypt libc-client >/dev/null 2>&1
    $PM -y $HOTFIX install \
        "http://repo.centos-webpanel.com/repo/${EL}/beta/cwpphp-${RPM_VER}.x86_64.rpm" || { echo "ERROR: RPM install failed"; exit 1; }
    [ -f /usr/local/cwp/php71/php.d/intl.ini ] && mv -f /usr/local/cwp/php71/php.d/intl.ini /usr/local/cwp/php71/php.d/intl.ini.disabled
    CUR=$("$PHP" -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;' 2>/dev/null)
    CHANGED=1
    [ "$CUR" = "8.3" ] || { echo "ERROR: php still '$CUR' after RPM install"; exit 1; }
fi

# --- 3. panel code must be the 8.3-encoded build ----------------------------
# Old (7.2) code on the 8.3 loader fails `php -l` with an ionCube message.
need_code=0
for f in "$HTDOCS/admin/index.php" "$HTDOCS/admin/login/index.php" "$SERVICES/users/login/index.php"; do
    "$PHP" -l "$f" 2>&1 | grep -qi 'ioncube\|encoded' && { echo "old encoding: $f"; need_code=1; }
done
echo "code update needed: $need_code"
if [ "$need_code" = 1 ] && [ "$CHECK" = 0 ]; then
    # Single-connection download from the CDN (static.cdn-cwp.com). One stream
    # per server keeps the mass rollout gentle on the infrastructure; a parallel
    # range download would multiply load across thousands of servers. Retry on
    # failure, then verify the archive is intact. A cache-buster avoids a stale
    # edge-cached copy after the zip is refreshed at the origin.
    fetch() { # $1 = zip name
        local U="$ZIP_BASE/$1?cb=$(date +%s)" sz
        echo "downloading $1 from CDN"
        curl -fL --connect-timeout 30 -m 1800 --retry 5 --retry-delay 10 --retry-connrefused \
            -o "$WORK/$1" "$U" || { echo "ERROR: download of $1 failed"; return 1; }
        sz=$(stat -c%s "$WORK/$1" 2>/dev/null || echo 0)
        [ "$sz" -ge 1000000 ] || { echo "ERROR: $1 too small ($sz bytes) - not on CDN?"; return 1; }
        unzip -tq "$WORK/$1" >/dev/null || { echo "ERROR: $1 is corrupt"; return 1; }
    }
    for z in cwp-el7-php-83.zip cwp-services-php-83.zip; do
        if [ -f "$WORK/$z" ] && unzip -tq "$WORK/$z" >/dev/null 2>&1; then echo "$z already downloaded"; else fetch "$z" || exit 1; fi
    done
    echo "backing up current code to $WORK/pre-fix-code.tgz"
    tar -C /usr/local/cwpsrv -czf "$WORK/pre-fix-code.tgz" htdocs/admin htdocs/resources var/services \
        --exclude=var/services/roundcube/logs --exclude=var/services/roundcube/temp
    chattr -i -R "$HTDOCS/admin" 2>/dev/null
    (cd "$HTDOCS" && unzip -oq "$WORK/cwp-el7-php-83.zip")
    chattr +i -R "$HTDOCS/admin" 2>/dev/null
    (cd "$SERVICES" && unzip -oq "$WORK/cwp-services-php-83.zip")
    echo "new code extracted"; CHANGED=1
fi

# --- 4. restart and verify --------------------------------------------------
if [ "$CHECK" = 0 ]; then
    [ "$CHANGED" = 1 ] && restart_panel
    R=$(panel); echo "panel after fix: $R"
    if [ "$R" = "admin=200 user=200" ]; then
        touch /usr/local/cwp/.conf/.cwpphp83.done
        echo "=== OK: panel works on PHP $CUR"
    else
        echo "=== NOT OK: send /var/log/cwp/cwp83fix.log and: tail -50 /usr/local/cwpsrv/logs/error_log"
        exit 1
    fi
fi
