#!/usr/bin/env bash

set -Eeuo pipefail
export LANG=C
umask 077

EXPECTED_HOSTNAME="sqjcmqtt01"
EXPECTED_IPV4="10.2.203.108"
EXPECTED_OS="openEuler release 24.03 (LTS-SP1)"
EXPECTED_ARCH="x86_64"
EXPECTED_DNF_TX="2"
ASSET_ROOT="/opt/zhct-deploy/emqx01-offline"
Q082_EVIDENCE_ROOT="/root/q082-evidence"
Q083_EVIDENCE_ROOT="/root/q083-evidence"
TARGET_PORT_RE=':(1883|8083|18083|4370|5370|8883|8084)([[:space:]]|$)'
RUN_ID=${1:-}
CONFIRM=${2:-}
Q082_RUN_ID=${3:-}

die() {
  printf 'ERROR: %s\n' "$*" >&2
  exit 1
}

unit_quiet() {
  local unit=$1 enabled active
  enabled=$(systemctl is-enabled "${unit}" 2>/dev/null || true)
  active=$(systemctl is-active "${unit}" 2>/dev/null || true)
  [[ "${enabled}" == "disabled" ]] && [[ "${active}" == "inactive" ]]
}

dir_empty() {
  local directory=$1
  [[ -d "${directory}" ]] || return 1
  [[ -z "$(find "${directory}" -mindepth 1 -print -quit)" ]]
}

[[ "${EUID}" -eq 0 ]] || die "must run as root"
[[ "${RUN_ID}" =~ ^q083-[0-9]{8}T[0-9]{6}[+]0800$ ]] || die "invalid Q083 run id"
[[ "${CONFIRM}" == "--execute-q083-seal" ]] || die "explicit Q083 seal flag is required"
[[ "${Q082_RUN_ID}" =~ ^q082-[0-9]{8}T[0-9]{6}[+]0800$ ]] ||
  die "invalid Q082 evidence run id"
[[ "$(hostname -s)" == "${EXPECTED_HOSTNAME}" ]] || die "hostname mismatch"
[[ "$(uname -m)" == "${EXPECTED_ARCH}" ]] || die "architecture mismatch"
[[ "$(tr -d '\r' </etc/openEuler-release)" == "${EXPECTED_OS}" ]] || die "OS mismatch"
ip -o -4 addr show scope global | awk '{print $4}' | cut -d/ -f1 |
  grep -Fxq "${EXPECTED_IPV4}" || die "expected IPv4 is not configured"
[[ "$(readlink -f "$0")" == "/root/emqx01-q083-seal.sh" ]] ||
  die "Q083 script must run from /root/emqx01-q083-seal.sh"

for command_name in rpm sha256sum systemctl ss find stat flock; do
  command -v "${command_name}" >/dev/null || die "${command_name} not found"
done
for nevra in \
  emqx-5.8.9-1.el8.x86_64 \
  mosquitto-2.0.20-2.oe2403sp1.x86_64 \
  libatomic-12.3.1-67.oe2403sp1.x86_64 \
  cjson-1.7.15-11.oe2403sp1.x86_64 \
  libwebsockets-4.3.3-5.oe2403sp1.x86_64; do
  rpm -q "${nevra}" >/dev/null || die "installed package mismatch: ${nevra}"
done

unit_quiet emqx.service || die "emqx.service is not disabled/inactive"
unit_quiet mosquitto.service || die "mosquitto.service is not disabled/inactive"
[[ -z "$(pgrep -fa 'beam.smp|/usr/lib/emqx|mosquitto' || true)" ]] ||
  die "an EMQX or Mosquitto process is running"
[[ -z "$(ss -lntupH | grep -E "${TARGET_PORT_RE}" || true)" ]] ||
  die "an EMQX target port is occupied"

[[ -d "${ASSET_ROOT}/rpms" ]] || die "Q082 offline RPM directory is missing"
[[ -f "${ASSET_ROOT}/rpms.sha256" ]] || die "Q082 RPM manifest is missing"
[[ "$(find "${ASSET_ROOT}/rpms" -maxdepth 1 -type f -name '*.rpm' | wc -l)" -eq 5 ]] ||
  die "Q082 offline RPM count is not five"
(
  cd "${ASSET_ROOT}/rpms"
  sha256sum -c ../rpms.sha256
) || die "Q082 offline RPM SHA256 verification failed"

Q082_EVIDENCE="${Q082_EVIDENCE_ROOT}/${Q082_RUN_ID}"
[[ -d "${Q082_EVIDENCE}" ]] || die "Q082 evidence directory is missing"
grep -Fq 'EMQX_RPM_SIGNATURE=UNSIGNED_ACCEPTED_BY_PINNED_SHA256' \
  "${Q082_EVIDENCE}/emqx-signature.txt" ||
  die "Q082 EMQX trust-anchor evidence is missing"
[[ "$(grep -Ec 'digests signatures OK$' "${Q082_EVIDENCE}/closure-signatures.txt")" -eq 4 ]] ||
  die "Q082 openEuler RPM signature evidence is incomplete"
grep -Fq 'Transaction Summary' \
  "${Q082_EVIDENCE}/dnf-cache-only-reinstall-assumeno.log" ||
  die "Q082 cache-only transaction summary is missing"
grep -Fq 'Reinstalling:' \
  "${Q082_EVIDENCE}/dnf-cache-only-reinstall-assumeno.log" ||
  die "Q082 cache-only reinstall evidence is missing"
grep -Fq 'Operation aborted.' \
  "${Q082_EVIDENCE}/dnf-cache-only-reinstall-assumeno.log" ||
  die "Q082 assumeno abort evidence is missing"
current_tx=$(dnf history list | awk '$1 ~ /^[0-9]+$/ {print $1; exit}')
[[ "${current_tx:-0}" == "${EXPECTED_DNF_TX}" ]] ||
  die "DNF history changed after Q082"

read -r root_size root_avail < <(df -B1 --output=size,avail / | awk 'NR == 2 {print $1, $2}')
[[ "${root_size}" =~ ^[0-9]+$ && "${root_avail}" =~ ^[0-9]+$ ]] ||
  die "cannot read root filesystem space"
root_avail_pct=$((root_avail * 100 / root_size))
((root_avail_pct >= 20)) || die "root available space is below 20 percent"

for directory in /var/lib/emqx /var/log/emqx /etc/emqx /etc/emqx/certs; do
  [[ -d "${directory}" ]] || die "required EMQX directory is missing: ${directory}"
done
for directory in /var/lib/emqx /var/log/emqx; do
  expected_owner=$(
    rpm -q --dump emqx |
      awk -v path="${directory}" '$1 == path {print $6 ":" $7; exit}'
  )
  [[ -n "${expected_owner}" ]] || die "RPM owner metadata is missing for ${directory}"
  [[ "$(stat -c '%U:%G' "${directory}")" == "${expected_owner}" ]] ||
    die "directory owner mismatch: ${directory}"
  [[ ! -L "${directory}" ]] || die "EMQX default directory must not be a symlink: ${directory}"
done

[[ ! -e "${Q083_EVIDENCE_ROOT}/${RUN_ID}" ]] || die "Q083 evidence run already exists"
install -d -m 0700 "${Q083_EVIDENCE_ROOT}/${RUN_ID}"
EVIDENCE_DIR="${Q083_EVIDENCE_ROOT}/${RUN_ID}"
exec 9>/run/lock/zhct-emqx01-q083.lock
flock -n 9 || die "another EMQX01 Q083 process is running"

{
  printf '# captured_at=%s\n' "$(date --iso-8601=seconds)"
  find /var/lib/emqx /var/log/emqx /etc/emqx/certs \
    -mindepth 1 -printf '%p\t%y\t%U:%G\t%m\t%s\n' | sort
  for path in \
    /etc/emqx/emqx.conf \
    /etc/emqx/acl.conf \
    /etc/zhct/node.env \
    /etc/zhct/secrets \
    /root/emqx01-direct-loopback-pubsub.sh \
    /root/emqx01-q082-cache-only-verify.sh; do
    if [[ -e "${path}" || -L "${path}" ]]; then
      stat -c '%n\t%F\t%U:%G\t%a\t%s' "${path}"
    else
      printf '%s\tABSENT\n' "${path}"
    fi
  done
} >"${EVIDENCE_DIR}/pre-seal-inventory.tsv"

systemctl disable --now emqx.service mosquitto.service
find /var/lib/emqx /var/log/emqx -mindepth 1 -delete
find /etc/emqx/certs -mindepth 1 -delete
rm -f /etc/emqx/emqx.conf /etc/emqx/acl.conf /etc/zhct/node.env
rm -rf /etc/zhct/secrets
rm -f /root/emqx01-direct-loopback-pubsub.sh
rm -f /root/emqx01-q082-cache-only-verify.sh
systemctl daemon-reload

dir_empty /var/lib/emqx || die "/var/lib/emqx is not empty"
dir_empty /var/log/emqx || die "/var/log/emqx is not empty"
dir_empty /etc/emqx/certs || die "/etc/emqx/certs is not empty"
for path in \
  /etc/emqx/emqx.conf \
  /etc/emqx/acl.conf \
  /etc/zhct/node.env \
  /etc/zhct/secrets \
  /root/emqx01-direct-loopback-pubsub.sh \
  /root/emqx01-q082-cache-only-verify.sh; do
  [[ ! -e "${path}" && ! -L "${path}" ]] || die "sealed path remains: ${path}"
done
[[ -z "$(find /var/tmp -maxdepth 1 -name 'emqx-q081-pubsub.*' -print -quit)" ]] ||
  die "Q081 temporary directory remains"
[[ -z "$(find /opt/zhct-deploy -maxdepth 1 -name '.emqx01-q082-*' -print -quit)" ]] ||
  die "Q082 stage directory remains"
unit_quiet emqx.service || die "emqx.service final state is not disabled/inactive"
unit_quiet mosquitto.service || die "mosquitto.service final state is not disabled/inactive"
[[ -z "$(pgrep -fa 'beam.smp|/usr/lib/emqx|mosquitto' || true)" ]] ||
  die "a target process exists after sealing"
[[ -z "$(ss -lntupH | grep -E "${TARGET_PORT_RE}" || true)" ]] ||
  die "a target listener exists after sealing"
for directory in /var/lib/emqx /var/log/emqx; do
  expected_owner=$(
    rpm -q --dump emqx |
      awk -v path="${directory}" '$1 == path {print $6 ":" $7; exit}'
  )
  [[ "$(stat -c '%U:%G' "${directory}")" == "${expected_owner}" ]] ||
    die "directory owner changed: ${directory}"
done

cat >"${EVIDENCE_DIR}/q083-result.env" <<EOF
run_id=${RUN_ID}
q082_run_id=${Q082_RUN_ID}
target_identity=PASS
offline_asset_sha256=PASS
dnf_history=${EXPECTED_DNF_TX}
units_disabled_inactive=PASS
target_processes_zero=PASS
target_listeners_zero=PASS
runtime_state_empty=PASS
template_secrets_absent=PASS
node_config_absent=PASS
default_directory_owner=PASS
root_available_percent=${root_avail_pct}
q083_seal=PASS
EOF
chmod 0600 "${EVIDENCE_DIR}/q083-result.env"

rm -f /root/emqx01-q083-seal.sh
[[ ! -e /root/emqx01-q083-seal.sh ]] || die "Q083 script self-cleanup failed"

printf 'RUN_ID=%s\n' "${RUN_ID}"
printf 'Q082_RUN_ID=%s\n' "${Q082_RUN_ID}"
printf 'DNF_HISTORY_UNCHANGED=%s\n' "${EXPECTED_DNF_TX}"
printf 'ROOT_AVAILABLE_PERCENT=%s\n' "${root_avail_pct}"
printf 'OFFLINE_ASSET_SHA256_PASS\n'
printf 'RUNTIME_STATE_EMPTY\n'
printf 'TEMPLATE_SECRETS_ABSENT\n'
printf 'NODE_CONFIG_ABSENT\n'
printf 'DEFAULT_DIRECTORY_OWNER_PASS\n'
printf 'TARGET_PROCESSES_ZERO\n'
printf 'TARGET_UNITS_DISABLED_INACTIVE\n'
printf 'TARGET_LISTENERS_ZERO\n'
printf 'REMOTE_VALIDATION_SCRIPTS_REMOVED\n'
printf 'Q083_SCRIPT_SELF_REMOVED\n'
printf 'EMQX01_Q083_SEAL_PASS\n'
