#!/bin/bash
################################################################################
# Script Name: version/3.0.2/UPDATE.sh
# Description: Retire the shared fallback certificate, and unstick a server
#              whose recorded version stopped matching its binary.
# Company: hitechcloud.org
# Copyright (c) hitechcloud.org
################################################################################
#
# Two things, and the first one is the one that can stop a listener.
#
# Ten vhost templates named /etc/nginx/ssl/cert.crt as the certificate to fall
# back on when a site has none of its own. That file came from a pair committed
# to the configuration repository: self-signed, CN=openpanel.com, expired in
# February 2026, and with its private key in the repository - so it was shared
# by every server that had it and by everyone who had ever cloned. 3.0.2 points
# those templates at /etc/hitechcloud/native/ssl/_default/ instead, which is
# generated per host. This makes sure that directory is populated before a
# template that needs it is rendered, because nginx does not start a TLS
# listener whose certificate file is missing.
#
# The second is the update loop. A run killed before its last step leaves new
# binaries and the version number the server had before, so the panel offers
# the same update again and it dies in the same place; a server was found
# reporting 2.1.17 while running a 3.0.1 binary. 3.0.2 removes both causes, but
# a server already in that state arrives here still mismatched, and it is worth
# saying so out loud rather than leaving it in a log.
#
# Safe to run twice and safe to run out of order.
################################################################################

set -uo pipefail

GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
ok()   { echo -e "[3.0.2] ${GREEN}✓${NC} $*"; }
warn() { echo -e "[3.0.2] ${YELLOW}!${NC} $*" >&2; }

[[ $EUID -eq 0 ]] || { echo "[3.0.2] This update must run as root." >&2; exit 1; }

DEFAULT_DIR="/etc/hitechcloud/native/ssl/_default"

# 1. The per-host fallback certificate the templates now point at.
#
# This mirrors native_ensure_default_cert rather than calling it, because a
# server part-way through an interrupted update may have a CLI that predates
# that function. The awkward parts are the same ones it handles: `hostname -f`
# fails on cloud images with no resolvable FQDN, an empty CN is refused, and
# X.509 caps a CN at 64 characters while cloud FQDNs routinely exceed it.
if [[ -s "${DEFAULT_DIR}/fullchain.pem" && -s "${DEFAULT_DIR}/privkey.pem" ]] \
   && openssl x509 -checkend 2592000 -noout -in "${DEFAULT_DIR}/fullchain.pem" >/dev/null 2>&1; then
    ok "The per-host fallback certificate is present and current"
else
    mkdir -p "$DEFAULT_DIR"
    fqdn=$(hostname -f 2>/dev/null) || fqdn=""
    [[ -n "$fqdn" ]] || fqdn=$(hostname 2>/dev/null) || fqdn=""
    fqdn=$(printf '%s' "$fqdn" | tr -cd 'A-Za-z0-9.-')
    [[ -n "$fqdn" ]] || fqdn="hitechcloud.local"
    cn="$fqdn"
    if (( ${#cn} > 64 )); then
        cn=$(hostname -s 2>/dev/null | tr -cd 'A-Za-z0-9.-')
        [[ -n "$cn" && ${#cn} -le 64 ]] || cn="hitechcloud.local"
    fi
    if err=$(openssl req -x509 -nodes -newkey rsa:2048 -days 3650 \
             -keyout "${DEFAULT_DIR}/privkey.pem" -out "${DEFAULT_DIR}/fullchain.pem" \
             -subj "/CN=${cn}/O=HiTechCloud Panel" \
             -addext "subjectAltName=DNS:${fqdn},DNS:${cn},IP:127.0.0.1" 2>&1); then
        chmod 600 "${DEFAULT_DIR}/privkey.pem"
        chmod 644 "${DEFAULT_DIR}/fullchain.pem"
        ok "Per-host fallback certificate created (CN=${cn})"
    else
        warn "Could not create the fallback certificate in ${DEFAULT_DIR}:"
        echo "$err" | tail -n 3 | sed 's/^/      /' >&2
    fi
fi

# 2. Any vhost still naming the shared pair, repointed.
#
# grep -rl into an array rather than a pipeline into a loop: under pipefail a
# pipeline whose reader stops early reports the writer's SIGPIPE as failure.
repointed=0
if [[ -d /etc/hitechcloud/nginx ]]; then
    mapfile -t stale < <(grep -rlE '/etc/(nginx|apache2|litespeed)/ssl/cert\.(crt|key)' \
                         /etc/hitechcloud/nginx 2>/dev/null)
    for f in ${stale[@]+"${stale[@]}"}; do
        [[ -f "$f" ]] || continue
        sed -i -E "s#/etc/(nginx|apache2|litespeed)/ssl/cert\.crt#${DEFAULT_DIR}/fullchain.pem#g; \
                   s#/etc/(nginx|apache2|litespeed)/ssl/cert\.key#${DEFAULT_DIR}/privkey.pem#g" "$f"
        repointed=$(( repointed + 1 ))
    done
fi
if (( repointed > 0 )); then
    ok "${repointed} vhost file(s) moved off the shared certificate"
    if command -v nginx >/dev/null 2>&1 && nginx -t >/dev/null 2>&1; then
        systemctl reload nginx >/dev/null 2>&1 || true
    else
        warn "nginx -t does not pass after repointing - not reloading. Run 'nginx -t' to see why."
    fi
else
    ok "No vhost referenced the shared certificate"
fi

# 3. The shared pair itself, removed wherever it landed. Its private key is
#    public, so leaving it on disk leaves something that can be pointed at.
for stale_cert in /etc/hitechcloud/nginx/certs/cert.crt /etc/hitechcloud/nginx/certs/cert.key; do
    if [[ -f "$stale_cert" ]]; then
        rm -f "$stale_cert"
        ok "Removed $(basename "$stale_cert") - its private key was published"
    fi
done

# 4. A recorded version that disagrees with the binary. Reported, not
#    corrected: the gap is exactly the set of migrations that may not have run,
#    and stamping over it would skip them for good. update.sh runs them.
recorded=$(cat /etc/hitechcloud/version 2>/dev/null | tr -d '[:space:]')
running=$(/usr/local/bin/hitechcloud version 2>/dev/null | head -1 | tr -d '[:space:]')
if [[ -n "$recorded" && -n "$running" && "$recorded" != "$running" ]]; then
    warn "The recorded version (${recorded}) is not the version running (${running})."
    warn "  An update was stopped before its last step, which is where the version is written."
    warn "  3.0.2 removes both causes: the panel no longer caps a full update at ten minutes,"
    warn "  and the updater no longer walks every version number between the two."
    warn "  Run 'hitechcloudcli update --force' once more and it will finish and record itself."
else
    ok "The recorded version matches the running binary"
fi

ok "3.0.2 update complete"
exit 0
