#!/usr/bin/env bash

set -Eeuo pipefail
export LANG=C
umask 077

SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
NODES_PAYLOAD="${SCRIPT_DIR}/payload/cluster-nodes.tsv"
SEEDS_PAYLOAD="${SCRIPT_DIR}/payload/static-seeds.txt"
WS_HELPER="${SCRIPT_DIR}/payload/ws-mqtt-smoke.py"
EVIDENCE_ROOT=/root/q240-emqx-evidence
ROLLBACK_ROOT=/root/q240-emqx-rollback
COOKIE_FILE=/etc/zhct/secrets/emqx.cluster-cookie
Q200_FORMAL_RECEIPT=/root/q200-formal-pass.receipt
EXECUTE_UNLOCKED=1
TARGET_PORT_RE=':(1883|8083|18083|4370|5370)([[:space:]]|$)'

usage() {
  cat <<'USAGE'
Usage:
  q240-emqx-cluster.sh --static-validate
  q240-emqx-cluster.sh emqx01|emqx02|emqx03 preflight --expected-cookie-sha256 SHA256
  q240-emqx-cluster.sh emqx01|emqx02|emqx03 execute --expected-cookie-sha256 SHA256 --confirm TOKEN
  q240-emqx-cluster.sh emqx01|emqx02|emqx03 verify --expected-cookie-sha256 SHA256
  q240-emqx-cluster.sh emqx01|emqx02|emqx03 rollback --backup-id ID --confirm TOKEN
  q240-emqx-cluster.sh emqx01|emqx02|emqx03 archive
USAGE
}

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

sha256_file() {
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$1" | awk '{print $1}'
  else
    shasum -a 256 "$1" | awk '{print $1}'
  fi
}

verify_manifest() {
  local expected path actual
  while read -r expected path; do
    path=${path#\*}
    [[ "${expected}" =~ ^[0-9a-f]{64}$ ]] || die "invalid SHA256SUMS entry"
    [[ "${path}" != /* && "${path}" != *'..'* ]] || die "unsafe manifest path"
    [[ -f "${SCRIPT_DIR}/${path}" ]] || die "missing manifest file: ${path}"
    actual=$(sha256_file "${SCRIPT_DIR}/${path}")
    [[ "${actual}" == "${expected}" ]] || die "manifest mismatch: ${path}"
    printf '%s: OK\n' "${path}"
  done <"${SCRIPT_DIR}/SHA256SUMS"
}

static_validate() {
  verify_manifest
  bash -n "${SCRIPT_DIR}/q240-emqx-cluster.sh"
  [[ "$(awk 'END {print NR-1}' "${NODES_PAYLOAD}")" == 3 ]] ||
    die "cluster node contract must contain three nodes"
  [[ "$(LC_ALL=C sort -u "${SEEDS_PAYLOAD}" | wc -l | tr -d ' ')" == 3 ]] ||
    die "static seeds must contain three unique nodes"
  awk -F'\t' 'NR == 1 {next} NF != 4 ||
    $1 !~ /^emqx0[1-3]$/ || $4 != "emqx@" $2 {exit 1}' "${NODES_PAYLOAD}" ||
    die "invalid node contract"
  while IFS=$'\t' read -r key ip host node_name; do
    [[ "${key}" == node_key ]] && continue
    grep -Fxq "${node_name}" "${SEEDS_PAYLOAD}" || die "node missing from seeds"
  done <"${NODES_PAYLOAD}"
  python3 -c 'import ast,sys; ast.parse(open(sys.argv[1], encoding="utf-8").read())' \
    "${WS_HELPER}"
  grep -Fq 'EXECUTE_UNLOCKED=1' "${SCRIPT_DIR}/q240-emqx-cluster.sh" ||
    die "execute unlock marker is missing"
  grep -Fq 'Q200_FORMAL_PASS=PASS' "${SCRIPT_DIR}/q240-emqx-cluster.sh" ||
    die "Q200 formal receipt gate is missing"
  printf 'PACKAGE_CONTAINS_SECRET=NO\n'
  printf 'REMOTE_EXECUTION=NOT_PERFORMED\n'
  printf 'Q240_EXECUTE_DEPENDENCY_LOCK=CLOSED\n'
  printf 'Q240_EMQX_PACKAGE_STATIC_VALIDATE_PASS\n'
}

if [[ "${1:-}" == --static-validate ]]; then
  [[ $# -eq 1 ]] || die "--static-validate takes no other arguments"
  static_validate
  exit 0
fi

NODE_KEY=${1:-}
ACTION=${2:-}
shift $(( $# >= 2 ? 2 : $# ))
EXPECTED_COOKIE_SHA=
BACKUP_ID=
CONFIRM=
while (($#)); do
  case "$1" in
    --expected-cookie-sha256) EXPECTED_COOKIE_SHA=${2:-}; shift 2 ;;
    --backup-id) BACKUP_ID=${2:-}; shift 2 ;;
    --confirm) CONFIRM=${2:-}; shift 2 ;;
    -h|--help) usage; exit 0 ;;
    *) die "unknown argument: $1" ;;
  esac
done

case "${NODE_KEY}" in
  emqx01)
    EXPECTED_HOST=sqjcmqtt01
    EXPECTED_IP=10.2.203.108
    EXPECTED_NODE='emqx@10.2.203.108'
    ROUTE_PEER=10.2.203.109
    JOIN_TARGET=
    ;;
  emqx02)
    EXPECTED_HOST=sqjcmqtt02
    EXPECTED_IP=10.2.203.109
    EXPECTED_NODE='emqx@10.2.203.109'
    ROUTE_PEER=10.2.203.110
    JOIN_TARGET='emqx@10.2.203.108'
    ;;
  emqx03)
    EXPECTED_HOST=sqjcmqtt03
    EXPECTED_IP=10.2.203.110
    EXPECTED_NODE='emqx@10.2.203.110'
    ROUTE_PEER=10.2.203.108
    JOIN_TARGET='emqx@10.2.203.108'
    ;;
  *) usage >&2; die "node must be emqx01, emqx02 or emqx03" ;;
esac
case "${ACTION}" in
  preflight|execute|verify|rollback|archive) ;;
  *) usage >&2; die "unsupported action" ;;
esac

if [[ "${ACTION}" == execute && "${EXECUTE_UNLOCKED}" -ne 1 ]]; then
  printf 'TARGET_HOST=%s\nTARGET_IP=%s\nNODE_KEY=%s\nACTION=execute\n' \
    "${EXPECTED_HOST}" "${EXPECTED_IP}" "${NODE_KEY}"
  printf 'REMOTE_MUTATION=ZERO\nQ240_EXECUTE_DEPENDENCY_LOCK=CLOSED\n'
  die "execute locked: Q141-Q143 and Q200 are not formally PASS"
fi

[[ "${EUID}" -eq 0 ]] || die "remote actions require root"
RUN_ID="q240-${NODE_KEY}-${ACTION}-$(date +%Y%m%dT%H%M%S%z)"
EVIDENCE_DIR="${EVIDENCE_ROOT}/${NODE_KEY}/${RUN_ID}"
install -d -m 0700 "${EVIDENCE_DIR}"
install -m 0600 "${SCRIPT_DIR}/SHA256SUMS" "${EVIDENCE_DIR}/PACKAGE_SHA256SUMS"
exec > >(tee -a "${EVIDENCE_DIR}/stdout.log") \
  2> >(tee -a "${EVIDENCE_DIR}/stderr.log" >&2)
exec 9>/run/lock/zhct-q240-emqx.lock
flock -n 9 || die "another Q-240 EMQX action is running"

finish() {
  local rc=$?
  set +e
  printf 'END_TIME=%s\nCOMMAND_RC=%s\n' "$(date '+%Y-%m-%dT%H:%M:%S%z')" "${rc}"
  [[ "${rc}" -eq 0 ]] ||
    printf 'ROLLBACK_STATUS=NOT_AUTOMATIC; preserve evidence and use explicit rollback if join began\n'
  exit "${rc}"
}
trap finish EXIT

printf 'START_TIME=%s\n' "$(date '+%Y-%m-%dT%H:%M:%S%z')"
printf 'TARGET_HOST=%s\nTARGET_IP=%s\nNODE_KEY=%s\nEXPECTED_NODE=%s\nACTION=%s\n' \
  "${EXPECTED_HOST}" "${EXPECTED_IP}" "${NODE_KEY}" "${EXPECTED_NODE}" "${ACTION}"
printf 'ARGV='
printf '%q ' "$0" "${NODE_KEY}" "${ACTION}"
[[ -n "${EXPECTED_COOKIE_SHA}" ]] &&
  printf '%q ' --expected-cookie-sha256 "${EXPECTED_COOKIE_SHA}"
[[ -n "${BACKUP_ID}" ]] && printf '%q ' --backup-id "${BACKUP_ID}"
[[ -n "${CONFIRM}" ]] && printf '%q ' --confirm "${CONFIRM}"
printf '\nEVIDENCE_DIR=%s\n' "${EVIDENCE_DIR}"

require_cookie_sha() {
  [[ "${EXPECTED_COOKIE_SHA}" =~ ^[0-9a-f]{64}$ ]] ||
    die "expected cookie SHA256 must be 64 lowercase hexadecimal characters"
}

assert_identity() {
  [[ "$(hostname -s)" == "${EXPECTED_HOST}" ]] || die "hostname mismatch"
  ip -o -4 addr show scope global | awk '{print $4}' | cut -d/ -f1 |
    grep -Fxq "${EXPECTED_IP}" || die "expected IPv4 is not configured"
  [[ -f /etc/zhct/node.env ]] || die "node.env missing"
  grep -Fxq 'ROLE=emqx' /etc/zhct/node.env || die "role mismatch"
  grep -Fxq "NODE_KEY=${NODE_KEY}" /etc/zhct/node.env || die "node key mismatch"
  grep -Fxq "NODE_IP=${EXPECTED_IP}" /etc/zhct/node.env || die "node IP mismatch"
  grep -Fxq "NODE_NAME=${EXPECTED_HOST}" /etc/zhct/node.env || die "hostname identity mismatch"
  rpm -q emqx-5.8.9-1.el8.x86_64
  printf 'HOSTNAME_IP_NODE_ENV=PASS\nEMQX_VERSION=5.8.9\n'
}

assert_cookie_metadata() {
  require_cookie_sha
  [[ -s "${COOKIE_FILE}" ]] || die "cookie file missing or empty"
  [[ "$(stat -c '%U:%G' "${COOKIE_FILE}")" == root:root ]] ||
    die "cookie owner mismatch"
  [[ "$(stat -c '%a' "${COOKIE_FILE}")" == 600 ]] || die "cookie mode mismatch"
  local actual
  actual=$(sha256_file "${COOKIE_FILE}")
  [[ "${actual}" == "${EXPECTED_COOKIE_SHA}" ]] || die "cookie SHA256 mismatch"
  printf 'COOKIE_PRESENT=PASS\n'
  printf 'COOKIE_OWNER=root:root\nCOOKIE_MODE=0600\n'
  printf 'COOKIE_SIZE=%s\nCOOKIE_SHA256=%s\n' \
    "$(stat -c '%s' "${COOKIE_FILE}")" "${actual}"
  printf 'COOKIE_VALUE_OUTPUT=PROHIBITED\n'
}

assert_q200_formal_receipt() {
  [[ -f "${Q200_FORMAL_RECEIPT}" ]] || die "Q200 formal receipt missing"
  [[ "$(stat -c '%U:%G:%a' "${Q200_FORMAL_RECEIPT}")" == root:root:600 ]] ||
    die "Q200 formal receipt owner/mode mismatch"
  local marker
  for marker in \
    'Q200_FORMAL_PASS=PASS' \
    'Q200_GLOBAL_DEPENDENCIES_COMPLETE=PASS' \
    'Q200_EMQX_MINIMUM_NETWORK_MATRIX=PASS' \
    'Q200_EMQX01_NODE_GATE_PASS' \
    'Q200_EMQX02_NODE_GATE_PASS' \
    'Q200_EMQX03_NODE_GATE_PASS'; do
    grep -Fxq "${marker}" "${Q200_FORMAL_RECEIPT}" ||
      die "Q200 formal receipt marker missing: ${marker}"
  done
  printf 'Q200_FORMAL_RECEIPT_OWNER_MODE=root:root:600\n'
  printf 'Q200_FORMAL_RECEIPT_SHA256=%s\n' \
    "$(sha256_file "${Q200_FORMAL_RECEIPT}")"
  printf 'Q200_FORMAL_RECEIPT_CONTENT_GATE=PASS\n'
}

assert_static_config() {
  [[ -f /etc/emqx/emqx.conf ]] || die "EMQX config missing"
  grep -Fq "name = \"${EXPECTED_NODE}\"" /etc/emqx/emqx.conf ||
    die "node.name mismatch"
  grep -Fq 'discovery_strategy = static' /etc/emqx/emqx.conf ||
    die "static discovery missing"
  grep -Fq 'seeds = ["emqx@10.2.203.108", "emqx@10.2.203.109", "emqx@10.2.203.110"]' \
    /etc/emqx/emqx.conf || die "static seeds mismatch"
  ! grep -Fq '__EMQX_CLUSTER_COOKIE__' /etc/emqx/emqx.conf ||
    die "cookie placeholder remains"
  printf 'NODE_NAME_UNIQUE=%s\n' "${EXPECTED_NODE}"
  printf 'STATIC_SEEDS_EXACT=PASS\n'
}

tcp_check() {
  local ip=$1 port=$2
  timeout 4 bash -c "exec 3<>/dev/tcp/${ip}/${port}"
  printf 'TCP_REACHABLE=%s:%s\n' "${ip}" "${port}"
}

assert_runtime() {
  emqx check_config
  printf 'CHECK_CONFIG_RC=0\n'
  [[ "$(systemctl is-enabled emqx.service)" == enabled ]] ||
    die "emqx.service is not enabled"
  systemctl is-active --quiet emqx.service || die "emqx.service is not active"
  [[ "$(systemctl is-active mosquitto.service 2>/dev/null || true)" == inactive ]] ||
    die "mosquitto.service must remain inactive"
  pgrep -x beam.smp >/dev/null || die "beam.smp process missing"
  ! pgrep -x mosquitto >/dev/null || die "mosquitto daemon must remain absent"
  local port
  for port in 1883 8083 18083 4370 5370; do
    ss -lntH | grep -Eq ":${port}([[:space:]]|$)" ||
      die "listener missing: ${port}"
  done
  printf 'LISTENERS_BEGIN\n'
  ss -lntupH | grep -E "${TARGET_PORT_RE}"
  printf 'LISTENERS_END\n'
  tcp_check "${ROUTE_PEER}" 4370
  tcp_check "${ROUTE_PEER}" 5370
}

cluster_status_verify() {
  local status count
  status=$(emqx ctl cluster status)
  printf 'CLUSTER_STATUS_BEGIN\n%s\nCLUSTER_STATUS_END\n' "${status}"
  count=$(printf '%s\n' "${status}" |
    grep -oE 'emqx@10[.]2[.]203[.](108|109|110)' | sort -u | wc -l | tr -d ' ')
  [[ "${count}" == 3 ]] || die "running member count is not three"
  printf '%s\n' "${status}" | grep -Eq 'stopped_nodes[[:space:]]*=>[[:space:]]*\\[\\]' ||
    die "stopped member exists"
  while IFS= read -r seed; do
    printf '%s\n' "${status}" | grep -Fq "${seed}" || die "member missing: ${seed}"
  done <"${SEEDS_PAYLOAD}"
  printf 'THREE_RUNNING_MEMBERS=PASS\nSTOPPED_MEMBERS=ZERO\n'
}

pubsub_pair() {
  local scheme=$1 sub_endpoint=$2 pub_endpoint=$3 label=$4
  local topic payload output pid received
  topic="sanquan/q240/${NODE_KEY}/${label}/$(date +%s)-$$"
  payload="Q240_${NODE_KEY}_${label}_$(openssl rand -hex 12)"
  output=$(mktemp)
  if [[ "${scheme}" == tcp ]]; then
    timeout 18 mosquitto_sub -h "${sub_endpoint}" -p 1883 \
      -t "${topic}" -C 1 -W 15 >"${output}" &
    pid=$!
    sleep 1
    mosquitto_pub -h "${pub_endpoint}" -p 1883 -t "${topic}" -m "${payload}"
  else
    timeout 20 python3 "${WS_HELPER}" \
      --subscriber-host "${sub_endpoint}" \
      --publisher-host "${pub_endpoint}" \
      --topic "${topic}" --payload "${payload}" >"${output}"
    received=$(<"${output}")
    rm -f "${output}"
    printf '%s\n' "${received}"
    grep -Fxq 'WS_MQTT_PUBSUB_MATCH=PASS' <<<"${received}" ||
      die "${label} WebSocket pub/sub mismatch"
    printf '%s_TOPIC=%s\n%s_MATCH=PASS\n' \
      "${label}" "${topic}" "${label}"
    return
  fi
  wait "${pid}"
  received=$(<"${output}")
  rm -f "${output}"
  [[ "${received}" == "${payload}" ]] || die "${label} pub/sub mismatch"
  printf '%s_TOPIC=%s\n%s_SENT=%s\n%s_RECEIVED=%s\n%s_MATCH=PASS\n' \
    "${label}" "${topic}" "${label}" "${payload}" "${label}" "${received}" "${label}"
}

preflight() {
  verify_manifest
  for cmd in emqx rpm systemctl ss openssl timeout mosquitto_pub mosquitto_sub python3; do
    command -v "${cmd}" >/dev/null || die "required command missing: ${cmd}"
  done
  assert_identity
  assert_cookie_metadata
  assert_static_config
  assert_runtime
  printf 'Q141_INDEPENDENT_QUIET_GATE_REQUIRED=YES\n'
  printf 'Q141_Q142_Q143_FORMAL_PASS_REQUIRED=YES\n'
  printf 'Q200_FORMAL_PASS_REQUIRED=YES\n'
  printf 'Q240_REMOTE_CHANGE=NOT_PERFORMED\n'
  printf 'Q240_EMQX_%s_PREFLIGHT_PASS\n' "${NODE_KEY^^}"
}

verify_node() {
  assert_identity
  assert_cookie_metadata
  assert_static_config
  assert_runtime
  printf 'NODE_STATUS_BEGIN\n'
  emqx ctl status
  printf 'NODE_STATUS_END\n'
  cluster_status_verify
  pubsub_pair tcp 127.0.0.1 127.0.0.1 LOCAL_TCP
  pubsub_pair websocket 127.0.0.1 127.0.0.1 LOCAL_WS
  pubsub_pair tcp "${ROUTE_PEER}" 127.0.0.1 CROSS_NODE_TCP
  pubsub_pair websocket "${ROUTE_PEER}" 127.0.0.1 CROSS_NODE_WS
  printf 'JOURNAL_ERRORS_BEGIN\n'
  journalctl -u emqx.service -p err -n 80 --no-pager |
    sed -E '/cookie|secret|password|token/Id' || true
  printf 'JOURNAL_ERRORS_END\nJOURNAL_SECRET_FILTER_APPLIED=YES\n'
  printf 'DIRECT_NODE_BYPASS_NEGATIVE_TEST=REQUIRES_EXTERNAL_NON_ALLOWED_SOURCE\n'
  printf 'MQTT_VIP_ALLOWED_PATH=REQUIRES_Q250_Q251_EXTERNAL_EVIDENCE\n'
  printf 'FORMAL_Q240_CLUSTER_ACCEPTANCE=NOT_CLAIMED_BY_SINGLE_NODE_PACKAGE\n'
  printf 'Q240_EMQX_%s_NODE_CLUSTER_VERIFY_PASS\n' "${NODE_KEY^^}"
}

case "${ACTION}" in
  preflight)
    preflight
    ;;
  execute)
    [[ "${EXECUTE_UNLOCKED}" -eq 1 ]] ||
      die "execute locked: Q141-Q143 and Q200 are not formally PASS"
    assert_q200_formal_receipt
    [[ "${CONFIRM}" == "q240-${NODE_KEY}-execute" ]] ||
      die "execute requires --confirm q240-${NODE_KEY}-execute"
    preflight
    BACKUP_ID="${RUN_ID}"
    backup="${ROLLBACK_ROOT}/${NODE_KEY}/${BACKUP_ID}"
    install -d -m 0700 "${backup}"
    printf 'PRE_ENABLED=%q\nPRE_ACTIVE=%q\n' \
      "$(systemctl is-enabled emqx.service 2>/dev/null || true)" \
      "$(systemctl is-active emqx.service 2>/dev/null || true)" \
      >"${backup}/state.env"
    chmod 0600 "${backup}/state.env"
    emqx ctl cluster status >"${backup}/cluster-status.before"
    chmod 0600 "${backup}/cluster-status.before"
    printf 'BACKUP_ID=%s\n' "${BACKUP_ID}"
    join_performed=0
    if [[ -n "${JOIN_TARGET}" ]]; then
      current_status=$(emqx ctl cluster status)
      if printf '%s\n' "${current_status}" | grep -Fq "${JOIN_TARGET}"; then
        printf 'JOIN_TARGET=%s\nJOIN_ACTION=ALREADY_MEMBER_SKIP\n' "${JOIN_TARGET}"
      else
        emqx ctl cluster join "${JOIN_TARGET}"
        join_performed=1
        printf 'JOIN_TARGET=%s\nJOIN_ACTION=JOIN_EXECUTED\n' "${JOIN_TARGET}"
      fi
    else
      printf 'JOIN_TARGET=NONE_COORDINATOR_NODE\nJOIN_ACTION=COORDINATOR_READY\n'
    fi
    printf 'JOIN_PERFORMED=%q\n' "${join_performed}" >>"${backup}/state.env"
    assert_identity
    assert_cookie_metadata
    assert_static_config
    assert_runtime
    printf 'CLUSTER_STATUS_OBSERVATION_BEGIN\n'
    emqx ctl cluster status
    printf 'CLUSTER_STATUS_OBSERVATION_END\n'
    printf 'FORMAL_Q240_CLUSTER_ACCEPTANCE=NOT_YET_VERIFIED\n'
    printf 'Q240_EMQX_%s_EXECUTE_NODE_PASS\n' "${NODE_KEY^^}"
    ;;
  verify)
    verify_node
    ;;
  rollback)
    [[ -n "${BACKUP_ID}" ]] || die "rollback requires --backup-id"
    [[ "${BACKUP_ID}" =~ ^q240-${NODE_KEY}-execute-[0-9T+:-]+$ ]] ||
      die "invalid backup id"
    [[ "${CONFIRM}" == "q240-${NODE_KEY}-rollback" ]] ||
      die "rollback requires --confirm q240-${NODE_KEY}-rollback"
    backup="${ROLLBACK_ROOT}/${NODE_KEY}/${BACKUP_ID}"
    [[ -f "${backup}/state.env" ]] || die "rollback state missing"
    # shellcheck disable=SC1090
    source "${backup}/state.env"
    if [[ "${JOIN_PERFORMED:-0}" == 1 ]]; then
      emqx ctl cluster leave || true
      printf 'ROLLBACK_MEMBERSHIP=LOCAL_JOIN_REVERSED\n'
    else
      printf 'ROLLBACK_MEMBERSHIP=PREEXISTING_MEMBERSHIP_UNCHANGED\n'
    fi
    if [[ "${PRE_ACTIVE}" == active ]]; then
      systemctl start emqx.service
    else
      systemctl stop emqx.service
    fi
    if [[ "${PRE_ENABLED}" == enabled ]]; then
      systemctl enable emqx.service
    else
      systemctl disable emqx.service
    fi
    printf 'ROLLBACK_STATUS=LOCAL_NODE_LEFT_CLUSTER_AND_UNIT_STATE_RESTORED\n'
    printf 'Q240_EMQX_%s_ROLLBACK_PASS\n' "${NODE_KEY^^}"
    ;;
  archive)
    {
      printf 'TIME=%s\nTARGET_HOST=%s\nTARGET_IP=%s\nNODE_KEY=%s\n' \
        "$(date '+%Y-%m-%dT%H:%M:%S%z')" "${EXPECTED_HOST}" "${EXPECTED_IP}" "${NODE_KEY}"
      printf 'ACTION=archive\nCOMMAND_RC=0\nJUDGMENT=NODE_EVIDENCE_ARCHIVE_CREATED\n'
      printf 'ROLLBACK_STATUS=NOT_APPLICABLE_READ_ONLY_ARCHIVE\n'
    } >"${EVIDENCE_DIR}/archive-receipt.txt"
    chmod 0600 "${EVIDENCE_DIR}/archive-receipt.txt"
    archive="/root/q240-emqx-${NODE_KEY}-evidence-$(date +%Y%m%dT%H%M%S%z).tar.gz"
    tar -czf "${archive}" -C "${EVIDENCE_ROOT}" "${NODE_KEY}"
    chmod 0600 "${archive}"
    printf 'ARCHIVE_PATH=%s\nARCHIVE_SHA256=%s\nARCHIVE_MODE=0600\n' \
      "${archive}" "$(sha256_file "${archive}")"
    printf 'Q240_EMQX_%s_ARCHIVE_PASS\n' "${NODE_KEY^^}"
    ;;
esac

trap - EXIT
printf 'END_TIME=%s\nCOMMAND_RC=0\n' "$(date '+%Y-%m-%dT%H:%M:%S%z')"
