#!/bin/bash
################################################################################
# Script Name: version/3.8.0/UPDATE.sh
# Description: Apply version 3.8.0:
#              - the panel's engine could not start at all, on any stack, and
#                the Go fallback hid it completely
#              - a reseller reached four routes the panel answers 403 for,
#                one of which saves another account's web-server configuration
#              - routes the engine swallowed from Go's own mux
#              - fifty of sixty-one CLI commands answer without the native-
#                prefix; the old names still work
#              The check below is the one this release exists for: it asks the
#              sidecar whether it can start. Nothing else does - a sidecar that
#              dies leaves every route with the Go handler, which looks exactly
#              like a working panel.
# 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.8.0] ${GREEN}✓${NC} $*"; }
warn() { echo -e "[3.8.0] ${YELLOW}!${NC} $*" >&2; }
FAILURES=0
bad()  { echo -e "[3.8.0] ${RED}✗${NC} $*" >&2; FAILURES=$((FAILURES + 1)); }
[[ $EUID -eq 0 ]] || { echo "[3.8.0] This update must run as root." >&2; exit 1; }

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

# A dry run writes nothing, including this - see version/README.md.
if [[ "$DRY_RUN" == 1 ]]; then
    echo "[3.8.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. Can the engine start?
#
# Until this release it could not, and nothing said so. Building its router
# panicked - duplicate patterns on every stack, and four more faults in the
# native half - so `supervise` restarted a dying process for as long as the
# panel ran. The hand-over is a fast path and never a dependency, by design: a
# dead sidecar leaves `Handles` false, the Go handler answers, and the panel
# behaves exactly as it did before the engine existed. No 500, no alert, no
# page missing.
#
# So the check is `routes`, which builds nothing, and then `serve` on a throwaway
# socket, which builds the whole router and is the only thing that would have
# caught it. Both engines, because they failed for different reasons.
################################################################################
engine_starts() {
    local name="$1" bin="$2" sock
    [[ -x "$bin" ]] || { warn "$name is not installed at $bin - 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 first, then indent, then stderr. Written the other way round -
    # `sed … >&2 | head -5` - sed's stdout is empty, so head prints nothing
    # and the whole of the engine's output goes to stderr unbounded: the
    # opposite of both intentions at once.
    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.8.0] would: start each engine on a throwaway socket and check it serves"
else
    engine_starts "the panel's engine" "$BIN/hitechcloudpanel-engine"
    engine_starts "the admin engine"   "$BIN/hitechcloudadmin-engine"
fi

################################################################################
# 2. The CLI's short names.
#
# Fifty commands answer without the native- prefix now. The old names still
# work, so nothing on this server had to change - but if the short surface does
# not answer, the binary is older than this release and the rest of it is not
# here either.
#
# The probe is `ftp add --help`, and the shape of it is the point. A bare
# `<alias> --help` proves nothing: the dispatcher's own listing filters by
# substring, and every one of the fifty aliases is a substring of the command
# it resolves to (`vhost` of `native-vhost`, `mail` of `email-manage`), so an
# older binary prints the same lines for the same query. A *noun and verb*
# cannot be answered that way - an older dispatcher takes `ftp` for a command,
# finds no `ftp.sh`, and exits 127 - so requiring exit 0 and the resolved name
# in the output is a question only a binary with the unified surface can
# answer. And `--help` runs nothing: it is printed from the COMMANDS table.
################################################################################
if [[ -x "$BIN/hitechcloudcli" ]]; then
    probe=$("$BIN/hitechcloudcli" ftp add --help 2>/dev/null) && [[ "$probe" == *ftp-add* ]] \
        && ok "the CLI answers a noun and a verb, so the unified surface is in it" \
        || warn "the CLI does not resolve 'ftp add' to ftp-add - it may predate 3.8.0"
else
    warn "$BIN/hitechcloudcli is missing"
fi

################################################################################
# 3. Whose mail a session may see.
#
# 3.8.0 stops a reseller reaching four routes the panel answers 403 for, and the
# mail pages narrowing to their own accounts. Both live in the admin binary and
# its engine; there is nothing on disk to repair, so this only reports what is
# installed.
################################################################################
if [[ -x "$BIN/hitechcloudadmin-engine" ]]; then
    ok "the admin engine is installed - the reseller-boundary fix is in it and in the Go server"
fi

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