#!/bin/bash
################################################################################
# Script Name: version/3.9.1/UPDATE.sh
# Description: Apply version 3.9.1:
#              - a plan that withholds a feature now refuses the route, not
#                just the menu entry
#              - an emptied feature set granted the default list instead of
#                nothing
#              - a library that will not load stops the command instead of
#                taking the other branch
#              - embedded assets revalidate, so a deploy cannot pair new HTML
#                with an old script
#              Each check below asks whether the binaries on THIS server
#              carry the fix, because every one of these faults looked like a
#              working panel from the outside.
# 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.9.1] ${GREEN}✓${NC} $*"; }
warn() { echo -e "[3.9.1] ${YELLOW}!${NC} $*" >&2; }
FAILURES=0
bad()  { echo -e "[3.9.1] ${RED}✗${NC} $*" >&2; FAILURES=$((FAILURES + 1)); }
[[ $EUID -eq 0 ]] || { echo "[3.9.1] This update must run as root." >&2; exit 1; }

VERSION="3.9.1"
BIN=/usr/local/bin
DRY_RUN="${HITECHCLOUD_UPDATE_DRY_RUN:-0}"

if [[ "$DRY_RUN" == 1 ]]; then
    echo "[3.9.1] would: record version ${VERSION} in /usr/local/hitechcloud/version and /etc/hitechcloud/version"
else
    mkdir -p /usr/local/hitechcloud /etc/hitechcloud 2>/dev/null || true
    echo "$VERSION" > /usr/local/hitechcloud/version 2>/dev/null || true
    echo "$VERSION" > /etc/hitechcloud/version 2>/dev/null || true
fi

engine_bin() {
    local c
    for c in "$BIN/hitechcloud-rustpanel" "$BIN/hitechcloudpanel-engine"; do
        [[ -x "$c" ]] && { echo "$c"; return 0; }
    done
    return 1
}

################################################################################
# 1. Does a withheld feature actually refuse the route?
#
# This is the release's reason. Until now a plan that did not include mail hid
# the Emails menu and served /emails to anyone who typed the URL - measured on
# a real server: /emails, /mysql, /files, /ftp, /backups, /ssl and /domains all
# answered 200 with their feature withheld. The nav was decoration.
#
# The check replays a request into the engine on a throwaway socket with the
# identity as plain headers, which is how this is measured without a session:
# once with no features, once with the feature. A 403 then a 200 is the fix; two
# 200s mean this server is still serving pages the plan does not include.
################################################################################
gate_refuses() {
    local bin sock pid waited code_without code_with
    bin=$(engine_bin) || { warn "the panel's engine is not installed - the route gate cannot be checked"; return 0; }
    sock="$(mktemp -u /run/hitechcloud-391-XXXXXX.sock)"
    "$bin" serve --socket "$sock" >/tmp/.hitechcloud-391-gate.$$ 2>&1 &
    pid=$!
    waited=0
    while [[ $waited -lt 100 ]]; do
        [[ -S "$sock" ]] && break
        kill -0 "$pid" 2>/dev/null || break
        sleep 0.1
        waited=$((waited + 1))
    done
    if [[ ! -S "$sock" ]]; then
        bad "the engine did not start, so the route gate could not be checked"
        head -5 /tmp/.hitechcloud-391-gate.$$ 2>/dev/null | sed 's/^/    /' >&2
        kill "$pid" 2>/dev/null; wait "$pid" 2>/dev/null
        rm -f "$sock" /tmp/.hitechcloud-391-gate.$$
        return 1
    fi

    probe() {
        curl -s -o /dev/null -w '%{http_code}' --max-time 10 --unix-socket "$sock" \
            -H "X-Hitech-User-Id: 2" -H "X-Hitech-Username: gatecheck" \
            -H "X-Hitech-Context: gatecheck" -H "X-Hitech-Plan-Id: 1" \
            -H "X-Hitech-Via: session" -H "X-Hitech-Client-Ip: 127.0.0.1" \
            -H "X-Hitech-Locale: en" -H "X-Hitech-Features: $1" \
            "http://localhost/emails" 2>/dev/null
    }
    code_without=$(probe "account")
    code_with=$(probe "emails,account")

    kill "$pid" 2>/dev/null; wait "$pid" 2>/dev/null
    rm -f "$sock" /tmp/.hitechcloud-391-gate.$$

    if [[ "$code_without" == "403" ]]; then
        ok "a withheld feature refuses its route (/emails answered ${code_without} without it, ${code_with} with it)"
        return 0
    fi
    bad "/emails answered ${code_without} with the emails feature withheld - it should be 403."
    warn "    This server is still serving pages the plan does not include: the menu"
    warn "    hides them, the URL does not. Its panel predates 3.9.1."
    return 1
}

if [[ "$DRY_RUN" == 1 ]]; then
    echo "[3.9.1] would: start the engine on a throwaway socket and check /emails is refused without the feature"
else
    gate_refuses
fi

################################################################################
# 2. Is the library guard on disk?
#
# 3.9.1's other half: a library that will not load now stops the command. The
# guard lives in a file, lib/loader.sh, and the tree under
# /usr/local/hitechcloudcli is what the update copies into place - so a server
# whose copy did not arrive has the old behaviour and nothing says so. On the
# demo that silence made the update run the container path on a native machine,
# skip the native half of the upgrade, and never invalidate the cache, while
# reporting success.
################################################################################
CLI_TREE=/usr/local/hitechcloudcli
if [[ "$DRY_RUN" == 1 ]]; then
    echo "[3.9.1] would: check ${CLI_TREE}/lib/loader.sh is present and defines its guard"
elif [[ ! -d "$CLI_TREE" ]]; then
    warn "${CLI_TREE} does not exist - the shell CLI is not installed here"
elif [[ ! -r "$CLI_TREE/lib/loader.sh" ]]; then
    bad "${CLI_TREE}/lib/loader.sh is missing, so a library that fails to load is still silent."
    warn "    Re-run the update, or copy the tree from the release bundle. A flat"
    warn "    ${CLI_TREE} with no lib/ directory is the shape that caused this."
elif ! grep -q "cli_require_lib" "$CLI_TREE/lib/loader.sh" 2>/dev/null; then
    bad "${CLI_TREE}/lib/loader.sh is there but defines no cli_require_lib - the copy is older than 3.9.1."
else
    ok "the library guard is installed (lib/loader.sh defines cli_require_lib)"
fi

################################################################################
# 3. Do embedded assets revalidate?
#
# max-age on an unversioned asset URL let a browser pair an old script with new
# HTML for five minutes after a deploy - not a cosmetic fault: buttons did
# nothing and no form saved. Asked over the loopback so it does not depend on
# the firewall or on DNS; a panel that is still restarting is a warning, not a
# failure, because this runs during an update.
################################################################################
if [[ "$DRY_RUN" == 1 ]]; then
    echo "[3.9.1] would: ask the panel for an embedded asset and require Cache-Control: no-cache"
else
    PORT=$(grep -oE '^[[:space:]]*port[[:space:]]*=[[:space:]]*[0-9]+' /etc/hitechcloud/hitechcloud/conf/hitechcloud.config 2>/dev/null | grep -oE '[0-9]+$' | head -1)
    PORT="${PORT:-2083}"
    HEADERS=$(curl -s -o /dev/null -D - -k --max-time 10 "https://127.0.0.1:${PORT}/static/js/panel-chrome.js" 2>/dev/null | tr -d '\r')
    if [[ -z "$HEADERS" ]]; then
        warn "the panel did not answer on ${PORT}, so the asset headers were not checked (it may still be restarting)"
    elif grep -qi '^cache-control:.*no-cache' <<< "$HEADERS"; then
        ok "embedded assets revalidate, so a deploy cannot pair new HTML with an old script"
    elif grep -qi '^cache-control:.*max-age=' <<< "$HEADERS"; then
        bad "embedded assets still carry a max-age: $(grep -i '^cache-control:' <<< "$HEADERS")"
        warn "    For that long after each deploy a browser can hold an old script"
        warn "    against new HTML, and the panel looks broken to whoever hits it."
    else
        warn "no Cache-Control on the asset - the panel answered, but not with a header this can judge"
    fi
fi

if [[ $FAILURES -gt 0 ]]; then
    echo -e "[3.9.1] ${RED}${FAILURES} check(s) failed${NC} - the update is applied, but read the lines above." >&2
    exit 1
fi
ok "3.9.1 applied"
