#!/bin/bash
################################################################################
# Script Name: version/3.6.0/UPDATE.sh
# Description: Apply version 3.6.0 - the Rust migration release:
#              - hitechcloud-rustpanel and hitechcloud-rustadmin, the Rust
#                twins of the two panels, are REQUIRED from this version:
#                each panel starts its twin and hands the ported routes to it
#                over a root-only unix socket (RUSTPANEL=off / RUSTADMIN=off
#                in the panel's environment file switch a twin off)
#              - hitechcloud-rustcli carries most of the CLI; the scripts it
#                replaced are removed from /usr/local/hitechcloudcli
#              - every twin is checked (routes / ported) before the panels
#                are restarted, and both panels are health-checked afterwards;
#                on failure the binaries this update touched are put back
# Company/Brand: HiTechCloud by Pho Tue SoftWare Solutions JSC
################################################################################

set -uo pipefail
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
ok()   { echo -e "[3.6.0] ${GREEN}✓${NC} $*"; }
warn() { echo -e "[3.6.0] ${YELLOW}!${NC} $*" >&2; }
bad()  { echo -e "[3.6.0] ${RED}✗${NC} $*" >&2; }
[[ $EUID -eq 0 ]] || { echo "[3.6.0] This update must run as root." >&2; exit 1; }

VERSION="3.6.0"
SRC="${HITECHCLOUD_SRC:-/opt/hitechcloud}"
CLI_DIR="/usr/local/hitechcloudcli"
BIN=/usr/local/bin
ROLLBACK="/var/lib/hitechcloud/rollback/${VERSION}"
MIRROR="${HITECHCLOUD_MIRROR:-https://mirrors.hitechcloud.org}"
DRY_RUN="${HITECHCLOUD_UPDATE_DRY_RUN:-0}"
run() { if [[ "$DRY_RUN" == 1 ]]; then echo "[3.6.0] would: $*"; else "$@"; fi; }

arch=$(uname -m); case "$arch" in x86_64) arch=amd64 ;; aarch64) arch=arm64 ;; esac

mkdir -p /usr/local/hitechcloud /etc/hitechcloud "$ROLLBACK" 2>/dev/null || true
echo "$VERSION" > /usr/local/hitechcloud/version 2>/dev/null || true
echo "$VERSION" > /etc/hitechcloud/version 2>/dev/null || true

# 0. What this update may have to put back: the binaries as they are now
#    (the regular updater has already installed this release's panels;
#    update.sh keeps the previous ones as <binary>.prev since 3.6.0).
for b in hitechcloud hitechadmin hitechcloud-rustcli hitechcloud-rustpanel hitechcloud-rustadmin; do
    [[ -x "${BIN}/${b}" ]] && cp -a "${BIN}/${b}" "${ROLLBACK}/${b}" 2>/dev/null
done

# 1. The CLI tree. This release deletes the scripts hitechcloud-rustcli
#    replaced, so the copy is synced with --delete: a deleted script left
#    behind would shadow the Rust command for the dispatcher. Only the
#    tracked tree is mirrored; nothing outside the checkout is touched.
if [[ -d "${SRC}/hitechcloudcli" && -f "${SRC}/hitechcloudcli/hitechcloudcli" ]]; then
    run rsync -a --delete --exclude '.git' --exclude 'rustcli/target' "${SRC}/hitechcloudcli/" "${CLI_DIR}/" \
        && ok "CLI tree synced from ${SRC}/hitechcloudcli (retired scripts removed)" \
        || warn "the CLI tree could not be synced; retired scripts may still be present under ${CLI_DIR}"
    find "$CLI_DIR" -name '*.sh' -exec chmod +x {} \; 2>/dev/null || true
    [[ -f "${CLI_DIR}/hitechcloudcli" ]] && run install -m 755 "${CLI_DIR}/hitechcloudcli" "${BIN}/hitechcloudcli"
    [[ -f "${CLI_DIR}/native/jail.sh" ]] && run install -m 755 "${CLI_DIR}/native/jail.sh" "${BIN}/hitechcloud-jail"
else
    warn "no checkout at ${SRC}/hitechcloudcli; the CLI tree is left as the updater copied it"
fi

# fetch <binary>: from the checkout's bin/ or encoded-<arch>/, else the mirror,
# else the GitHub release. ELF + architecture + SHA256SUMS checked before use.
fetch() {
    local b="$1" tmp candidate
    for candidate in "${SRC}/bin/${b}" "${SRC}/hitechcloudpanel/encoded-${arch}/${b}"; do
        if [[ -x "$candidate" ]] && head -c 4 "$candidate" | grep -q $'\x7fELF'; then
            run install -m 755 "$candidate" "${BIN}/${b}" && { ok "${b} installed from the checkout"; return 0; }
        fi
    done
    tmp=$(mktemp "/tmp/${b}.XXXXXX")
    if curl -4fsSL --retry 3 --connect-timeout 20 "${MIRROR}/releases/${VERSION}/${b}-linux-${arch}" -o "$tmp" 2>/dev/null \
       || curl -fsSL --retry 3 --connect-timeout 20 "https://github.com/hitechcloud-vietnam/HiTechCloud-Panel/releases/download/v${VERSION}/${b}-linux-${arch}" -o "$tmp" 2>/dev/null; then
        if ! head -c 4 "$tmp" | grep -q $'\x7fELF'; then rm -f "$tmp"; bad "${b}: the download is not an executable"; return 1; fi
        local sums want got
        sums=$(mktemp)
        if curl -4fsSL --retry 2 --connect-timeout 20 "${MIRROR}/releases/${VERSION}/SHA256SUMS" -o "$sums" 2>/dev/null; then
            want=$(awk -v a="${b}-linux-${arch}" '$2 == a || $2 == "*"a { print $1; exit }' "$sums")
            got=$(sha256sum "$tmp" | awk '{print $1}')
            if [[ -n "$want" && "$want" != "$got" ]]; then rm -f "$tmp" "$sums"; bad "${b}: checksum mismatch against SHA256SUMS"; return 1; fi
        fi
        rm -f "$sums"
        run install -m 755 "$tmp" "${BIN}/${b}"; rm -f "$tmp"
        ok "${b} ${VERSION} installed from the release"
        return 0
    fi
    rm -f "$tmp"
    bad "${b} could not be downloaded from ${MIRROR} or GitHub"
    return 1
}

# 2. hitechcloud-rustcli: required since 3.4.0; this release's scripts call
#    commands only the 3.6.0 binary has (admin, domains-add, user-*...).
if ! { [[ -x "${BIN}/hitechcloud-rustcli" ]] && "${BIN}/hitechcloud-rustcli" ported 2>/dev/null | tr ' ' '\n' | grep -qx domains-add; }; then
    fetch hitechcloud-rustcli || warn "hitechcloud-rustcli is outdated; every command it carries is unavailable until the mirror is reachable (rerun: hitechcloudcli update)"
fi

# 3. The panels' Rust twins: required from 3.6.0. A twin that is present but
#    older than this release (no `routes` output / no page routes) is refreshed.
twin_ok() { [[ -x "${BIN}/$1" ]] && "${BIN}/$1" routes 2>/dev/null | grep -q ' page'; }
twins_failed=0
for twin in hitechcloud-rustpanel hitechcloud-rustadmin; do
    if twin_ok "$twin"; then ok "${twin} answers its route table"; continue; fi
    if fetch "$twin" && twin_ok "$twin"; then ok "${twin} answers its route table"; else bad "${twin} is missing or does not answer; the panel serves every route itself until it is installed"; twins_failed=1; fi
done

# 4. Templates and the things every release refreshes.
if [[ -f /etc/hitechcloud/native/native.conf && -d "${SRC}/hitechcloudpanel/configuration-native" ]]; then
    run cp -a "${SRC}/hitechcloudpanel/configuration-native/." /etc/hitechcloud/native/templates/ 2>/dev/null && ok "Templates refreshed"
fi

# 5. Restart both panels and make sure they answer. The panel's own
#    /healthz (2083) and the admin's login page (2087) are the probes; the
#    ports come from the configuration when they were changed.
panel_port=$(hitechcloudcli port 2>/dev/null | tr -dc '0-9'); [[ -n "$panel_port" ]] || panel_port=2083
admin_port=$(hitechcloudcli admin port 2>/dev/null | tr -dc '0-9'); [[ -n "$admin_port" ]] || admin_port=2087
probe() { # probe <port> <path>
    for _ in $(seq 1 30); do
        curl -ksf --max-time 3 "https://127.0.0.1:$1$2" >/dev/null 2>&1 && return 0
        curl -sf  --max-time 3 "http://127.0.0.1:$1$2"  >/dev/null 2>&1 && return 0
        sleep 1
    done
    return 1
}
if [[ "$DRY_RUN" == 1 ]]; then echo "[3.6.0] would: restart hitechcloud hitechadmin and probe ${panel_port}/healthz and ${admin_port}/login"; exit 0; fi

systemctl daemon-reload
systemctl restart hitechcloud >/dev/null 2>&1 || true
systemctl is-enabled hitechadmin >/dev/null 2>&1 && systemctl restart hitechadmin >/dev/null 2>&1

healthy=1
probe "$panel_port" /healthz && ok "hitechcloud answers on ${panel_port}" || { bad "hitechcloud does not answer on ${panel_port}"; healthy=0; }
if systemctl is-enabled hitechadmin >/dev/null 2>&1; then
    probe "$admin_port" /login && ok "hitechadmin answers on ${admin_port}" || { bad "hitechadmin does not answer on ${admin_port}"; healthy=0; }
fi

if (( healthy == 0 )); then
    # Put back what this update changed, then what the regular updater
    # changed (<binary>.prev, kept by hitechcloudcli update since 3.6.0).
    bad "rolling back the binaries this update touched"
    for b in hitechcloud-rustcli hitechcloud-rustpanel hitechcloud-rustadmin; do
        [[ -f "${ROLLBACK}/${b}" ]] && install -m 755 "${ROLLBACK}/${b}" "${BIN}/${b}"
    done
    for b in hitechcloud hitechadmin; do
        [[ -f "${BIN}/${b}.prev" ]] && install -m 755 "${BIN}/${b}.prev" "${BIN}/${b}" && warn "${b}: previous build restored"
    done
    systemctl restart hitechcloud >/dev/null 2>&1 || true
    systemctl is-enabled hitechadmin >/dev/null 2>&1 && systemctl restart hitechadmin >/dev/null 2>&1
    probe "$panel_port" /healthz && warn "hitechcloud is back on the previous build" || bad "hitechcloud still does not answer: journalctl -u hitechcloud"
    echo "[3.6.0] The update was rolled back. Fix the cause and rerun: hitechcloudcli update" >&2
    exit 1
fi

if (( twins_failed )); then
    warn "Version ${VERSION} applied without one of the Rust twins; rerun 'hitechcloudcli update' once the mirror is reachable"
    exit 0
fi
ok "Version ${VERSION} applied. Rust twins: $(${BIN}/hitechcloud-rustpanel routes | wc -l) panel routes, $(${BIN}/hitechcloud-rustadmin routes | wc -l) admin routes; CLI: $(${BIN}/hitechcloud-rustcli ported | wc -w) commands in Rust."
