#!/bin/bash
################################################################################
# Script Name: version/3.9.0/UPDATE.sh
# Description: Apply version 3.9.0:
#              - the panel was slow because one CLI command executed the panel
#                binary thirty times for a value that cannot change
#              - the pages carried the same inline JavaScript on every request,
#                and a site row wrote out six icons in full
#              - a subdomain's DNS records were invisible, then uneditable,
#                then missing from the zone picker
#              - phpMyAdmin on RHEL: its own cache directory, its cookie key,
#                and a configuration storage it offered but never created
#              The checks below are what this release exists for: the module
#              list has to be fast, because a server that kept the old CLI
#              still waits thirteen seconds for /sites and nothing else would
#              say so.
# 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.0] ${GREEN}✓${NC} $*"; }
warn() { echo -e "[3.9.0] ${YELLOW}!${NC} $*" >&2; }
FAILURES=0
bad()  { echo -e "[3.9.0] ${RED}✗${NC} $*" >&2; FAILURES=$((FAILURES + 1)); }
[[ $EUID -eq 0 ]] || { echo "[3.9.0] This update must run as root." >&2; exit 1; }

VERSION="3.9.0"
BIN=/usr/local/bin
DRY_RUN="${HITECHCLOUD_UPDATE_DRY_RUN:-0}"
run() { if [[ "$DRY_RUN" == 1 ]]; then echo "[3.9.0] would: $*"; else "$@"; fi; }

# A dry run writes nothing, including this - see version/README.md.
if [[ "$DRY_RUN" == 1 ]]; then
    echo "[3.9.0] 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

################################################################################
# 1. Is the module list fast?
#
# This is the release's reason. `native-module list --json` took thirteen
# seconds on a twenty-core server: it filled in each module's `licensed` field
# through the edition, and asking the edition executes the panel binary -
# eighty megabytes of Go, about four tenths of a second - so thirty modules
# meant thirty executions of it, one after another. The panel runs that command
# to render /sites, cached for under a minute, so every customer navigation a
# minute apart waited for all of it.
#
# Measuring is the only honest check. The command's output is unchanged, so
# nothing in it distinguishes the fixed CLI from the old one, and a server whose
# CLI the update failed to replace would look repaired while still costing the
# customer thirteen seconds a page.
#
# Two seconds is the threshold: it measured 0.67s after the fix and 13.0s
# before, so there is no honest way to land between them by accident.
################################################################################
if [[ "$DRY_RUN" == 1 ]]; then
    echo "[3.9.0] would: time 'native-module list --json' and require it under 2s"
elif [[ -x "$BIN/hitechcloudcli" ]]; then
    start=$(date +%s%N)
    if "$BIN/hitechcloudcli" native-module list --json >/dev/null 2>&1; then
        elapsed_ms=$(( ($(date +%s%N) - start) / 1000000 ))
        if [[ $elapsed_ms -lt 2000 ]]; then
            ok "the module list answers in ${elapsed_ms}ms, so /sites no longer waits on it"
        else
            bad "the module list took ${elapsed_ms}ms - it should be under 2000ms."
            warn "    This server is still running a CLI from before 3.9.0: /sites will"
            warn "    take about thirteen seconds on every request a minute apart."
            warn "    Install the 3.9.0 CLI from the mirror and run this script again."
        fi
    else
        warn "the module list did not answer, so its speed could not be measured"
    fi
else
    warn "$BIN/hitechcloudcli is missing"
fi

################################################################################
# 2. Can the engine start?
#
# Same check 3.8.0 introduced, and for the same reason: a sidecar that dies
# leaves every converted route with the Go handler, which looks exactly like a
# working panel. `routes` builds nothing; `serve` on a throwaway socket builds
# the whole router, which is the only thing that would catch a duplicate
# pattern. 3.9.0 adds routes to both halves, so it is asked again here.
################################################################################
# 3.8.0 looked for $BIN/hitechcloudpanel-engine. The installer installs the
# binary under its other published name, hitechcloud-rustpanel - the release
# ships both names for the same bytes - so on every real server that check
# found nothing, warned, and returned 0. Its headline check had never run.
# This one takes every name the binary is published under and uses the first
# that is there.
engine_starts() {
    local name="$1" bin="" candidate sock
    shift
    for candidate in "$@"; do
        [[ -x "$candidate" ]] && { bin="$candidate"; break; }
    done
    [[ -n "$bin" ]] || { warn "$name is not installed under any of its names ($*) - nothing to check"; return 0; }

    if ! "$bin" routes >/dev/null 2>&1; then
        bad "$name cannot even list its routes - the binary is broken"
        return 1
    fi
    local listed
    listed=$("$bin" routes 2>/dev/null | wc -l)

    sock="$(mktemp -u /run/hitechcloud-update-XXXXXX.sock)"
    "$bin" serve --socket "$sock" >/tmp/.hitechcloud-engine-check.$$ 2>&1 &
    local pid=$!
    local 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" ]] && kill -0 "$pid" 2>/dev/null; then
        ok "$name starts and serves $listed routes"
        kill "$pid" 2>/dev/null
        wait "$pid" 2>/dev/null
        rm -f "$sock" /tmp/.hitechcloud-engine-check.$$
        return 0
    fi

    bad "$name lists $listed routes but dies when it tries to serve them:"
    head -5 /tmp/.hitechcloud-engine-check.$$ 2>/dev/null | sed 's/^/    /' >&2
    kill "$pid" 2>/dev/null
    wait "$pid" 2>/dev/null
    rm -f "$sock" /tmp/.hitechcloud-engine-check.$$
    return 1
}

if [[ "$DRY_RUN" == 1 ]]; then
    echo "[3.9.0] would: start each engine on a throwaway socket and check it serves"
else
    engine_starts "the panel's engine" "$BIN/hitechcloud-rustpanel" "$BIN/hitechcloudpanel-engine"
    engine_starts "the admin engine"   "$BIN/hitechcloud-rustadmin"  "$BIN/hitechcloudadmin-engine"
fi

################################################################################
# 3. Clean up after the migration replay.
#
# A server that walked the old migrations - which any `--force` update did,
# because a version it could not read was taken for "nothing installed" - got
# the shell-completion block appended to /root/.bashrc once per version walked.
# Six copies were found on one server, all naming
# /usr/local/admin/scripts/aliases.txt, a path from a layout this product left
# behind years ago. Every login then printed six awk errors before the prompt.
#
# The block is not written by anything current, so the repair is to remove the
# copies that name the dead path. A block naming a file that exists is left
# alone: an operator may have written it.
################################################################################
dedupe_completion_block() {
    local rc="$1" dead="/usr/local/admin/scripts/aliases.txt"
    [[ -f "$rc" ]] || return 0
    grep -q "$dead" "$rc" 2>/dev/null || return 0
    [[ -e "$dead" ]] && { warn "$dead exists, so $rc is left alone"; return 0; }
    local copies
    copies=$(grep -c "^# hitechcloudcli aliases$" "$rc" 2>/dev/null || echo 0)
    if [[ "$DRY_RUN" == 1 ]]; then
        echo "[3.9.0] would: remove $copies completion block(s) naming a missing file from $rc"
        return 0
    fi
    cp -a "$rc" "${rc}.before-3.9.0" 2>/dev/null || true
    # From each block's comment line through the `complete -W` line that ends
    # it. A range per occurrence, so all the copies go.
    sed -i '/^# hitechcloudcli aliases$/,/^    complete -W /d' "$rc"
    if grep -q "$dead" "$rc" 2>/dev/null; then
        warn "$rc still names $dead - left as it is, kept a copy at ${rc}.before-3.9.0"
    else
        ok "removed $copies stale shell-completion block(s) from $rc (kept ${rc}.before-3.9.0)"
    fi
}
dedupe_completion_block /root/.bashrc

################################################################################
# 4. phpMyAdmin's configuration, if it is installed.
#
# 3.9.0 stops writing that file world-readable: it carries blowfish_secret,
# which is the key phpMyAdmin encrypts its authentication cookies with, so any
# account on the machine could read it and forge one. A reinstall of the
# configuration is what applies the new mode, and it is only offered here -
# rewriting a working phpMyAdmin unasked is not this script's business.
################################################################################
if [[ -x "$BIN/hitechcloudcli" ]] && "$BIN/hitechcloudcli" native-phpmyadmin status >/dev/null 2>&1; then
    cfg=$("$BIN/hitechcloudcli" native-phpmyadmin status 2>/dev/null | awk '/^  Config/{print $2}')
    if [[ -n "$cfg" && -f "$cfg" ]]; then
        mode=$(stat -c%a "$cfg" 2>/dev/null)
        if [[ "$mode" == "640" ]]; then
            ok "phpMyAdmin's configuration is $mode, so its cookie key is not world-readable"
        else
            warn "phpMyAdmin's configuration is mode $mode and holds its cookie-encryption key."
            warn "    Run: hitechcloudcli native-phpmyadmin install"
            warn "    That rewrites it 0640 root:<web group> and keeps everybody signed in."
        fi
    fi
fi

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