#!/usr/bin/env bash

set -Eeuo pipefail
umask 077
export LANG=C

EXPECTED_HOSTNAME="sqjcapp01"
EXPECTED_IPV4="10.2.203.104"
EXPECTED_OS="openEuler release 24.03 (LTS-SP1)"
EXPECTED_ARCH="x86_64"
EXPECTED_SEAL_WRAPPER_SHA256="45171865992b8d8bf94735a889b3f69eefe65707187cf3acb97683bbd69b9e02"
EXPECTED_POST_CLONE_WRAPPER_SHA256="ca5fa0a113739bcbe3ff51b2815bb50723cd8b1cf33dfb6d3f3f172caf2dabc6"
EXPECTED_ROLE_ACTION_SHA256="e3eae16aa5c87f43b56495fc71de378a947578cabe18b507e8fed01ac1f32025"
EXPECTED_RUNTIME_SHA256="89e5149c516af624dd56b3ef8aaa389a4f2447a68dfe989978c61c459d859162"
EXPECTED_APP_HOOKS_SHA256="03c6b13a0def5bf3954e069581b6f9fbe0f81f24285ba2319b6279c0406d70aa"
EXPECTED_ROLE_ENV_SHA256="ef700d329e2767e1ee1c9733fb26e1d5e256bab3ed023bceb9e5d01a7f5df46f"
EXPECTED_UNITS_SHA256="d43e179c108f6db551b452256f867d6d4f6fc9e031c1d4def2f343966af8d684"
EXPECTED_NODES_SHA256="83e6abb26b4af6d8a8dc74082acac73009d0d60446bd641f65b8b4e9c21e6afc"

BUNDLE_ROOT="/opt/zhct-deploy"
SEAL_SCRIPT="${BUNDLE_ROOT}/common/scripts/seal-template.sh"
POST_CLONE_SCRIPT="${BUNDLE_ROOT}/common/scripts/post-clone.sh"
VERIFY_SCRIPT="${BUNDLE_ROOT}/common/scripts/verify-role.sh"
EVIDENCE_ROOT="/var/backups/zhct-q053"
RUN_ID=${1:-}
MODE=${2:-}
EVIDENCE_DIR="${EVIDENCE_ROOT}/${RUN_ID}"
FINAL_RESULT="${EVIDENCE_DIR}/result.env"
FINAL_INDEX="${EVIDENCE_DIR}/evidence-index.sha256"
FINAL_ARCHIVE="/root/sanquan-app01-q053-evidence-${RUN_ID}.tar.gz"
SENSITIVE_SCAN_TMP="${EVIDENCE_ROOT}/.${RUN_ID}.sensitive-scan.tmp"
MIN_FREE_BYTES=$((8 * 1024 * 1024 * 1024))
TARGET_PORT_RE=':(8081|18081)([[:space:]]|$)'
SEAL_ATTEMPTED=0
SEAL_COMPLETED=0

TARGET_UNITS=(
  nginx.service
  php-fpm73.service
  php-fpm74.service
  supervisor.service
  crond.service
)

TARGET_PROCESSES=(
  nginx
  php-fpm
  supervisord
  mysqld
  mysqld_safe
  redis-server
  mosquitto
)

FORMAL_BINARIES=(
  /usr/local/nginx/sbin/nginx
  /usr/local/php7.3/bin/php
  /usr/local/php7.3/sbin/php-fpm
  /usr/local/php7.4/bin/php
  /usr/local/php7.4/sbin/php-fpm
  /usr/local/bin/supervisord
  /usr/local/bin/mosquitto_pub
  /usr/local/bin/mosquitto_sub
  /usr/local/lib/libmosquitto.so.1
  /opt/mysql-client/5.6.51/bin/mysqldump
  /opt/redis-client/5.0.14/bin/redis-cli
)

FORMAL_UNIT_FILES=(
  /etc/systemd/system/nginx.service
  /etc/systemd/system/php-fpm73.service
  /etc/systemd/system/php-fpm74.service
  /etc/systemd/system/supervisor.service
  /usr/lib/systemd/system/crond.service
)

FRAMEWORK_FILES=(
  "${SEAL_SCRIPT}"
  "${POST_CLONE_SCRIPT}"
  "${BUNDLE_ROOT}/common/bin/role-action.sh"
  "${BUNDLE_ROOT}/common/lib/runtime.sh"
  "${BUNDLE_ROOT}/roles/app/hooks.sh"
  "${BUNDLE_ROOT}/roles/app/role.env"
  "${BUNDLE_ROOT}/roles/app/manifests/systemd-units.tsv"
  "${BUNDLE_ROOT}/roles/app/manifests/nodes.tsv"
)

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

on_exit() {
  local rc=$?
  set +e
  rm -f "${SENSITIVE_SCAN_TMP}"
  if [[ "${rc}" -ne 0 && -d "${EVIDENCE_DIR}" ]]; then
    {
      printf 'failed_at=%s\n' "$(date --iso-8601=seconds)"
      printf 'exit_code=%s\n' "${rc}"
      printf 'seal_attempted=%s\n' "${SEAL_ATTEMPTED}"
      printf 'seal_completed=%s\n' "${SEAL_COMPLETED}"
      printf 'services_must_remain_quiescent=YES\n'
      printf 'automatic_rerun=FORBIDDEN\n'
      printf 'automatic_identity_restore=FORBIDDEN\n'
    } >"${EVIDENCE_DIR}/failure.env"
    chmod 0600 "${EVIDENCE_DIR}/failure.env"
  fi
  trap - EXIT
  exit "${rc}"
}
trap on_exit EXIT

capture() {
  local name=$1
  shift
  {
    printf '# captured_at=%s\n' "$(date --iso-8601=seconds)"
    printf '# command='
    printf '%q ' "$@"
    printf '\n'
    "$@"
  } >"${EVIDENCE_DIR}/${name}.txt" 2>&1
}

capture_shell() {
  local name=$1
  local command=$2
  {
    printf '# captured_at=%s\n' "$(date --iso-8601=seconds)"
    printf '# command=%s\n' "${command}"
    bash -Eeuo pipefail -c "${command}"
  } >"${EVIDENCE_DIR}/${name}.txt" 2>&1
}

assert_file_sha256() {
  local path=$1
  local expected=$2
  [[ -f "${path}" && ! -L "${path}" ]] || die "required regular file is missing or unsafe: ${path}"
  [[ "$(sha256sum "${path}" | awk '{print $1}')" == "${expected}" ]] || \
    die "SHA256 mismatch: ${path}"
}

assert_snapshot_unchanged() {
  local before=$1
  local after=$2
  local label=$3
  cmp -s <(sed -n '3,$p' "${before}") <(sed -n '3,$p' "${after}") || \
    die "${label} changed during seal"
}

assert_units_quiescent() {
  local output=$1
  : >"${output}"
  local unit enabled active substate
  for unit in "${TARGET_UNITS[@]}"; do
    enabled=$(systemctl is-enabled "${unit}" 2>/dev/null || true)
    active=$(systemctl show "${unit}" -p ActiveState --value)
    substate=$(systemctl show "${unit}" -p SubState --value)
    printf '%s\t%s\t%s\t%s\n' "${unit}" "${enabled}" "${active}" "${substate}" >>"${output}"
    [[ "${enabled}" == "disabled" ]] || die "unit is not disabled: ${unit}"
    [[ "${active}" == "inactive" ]] || die "unit is not inactive: ${unit}"
    [[ "${substate}" == "dead" ]] || die "unit is not dead: ${unit}"
  done
}

assert_no_target_processes() {
  local output=$1
  : >"${output}"
  local process
  for process in "${TARGET_PROCESSES[@]}"; do
    pgrep -a -x "${process}" >>"${output}" 2>&1 || true
  done
  [[ ! -s "${output}" ]] || die "target process remains"
}

assert_no_target_listeners() {
  local output=$1
  ss -lntupH >"${output}"
  ! grep -Eq "${TARGET_PORT_RE}" "${output}" || die "target listener remains"
}

assert_application_root_absent() {
  local output=$1
  local state=ABSENT
  [[ ! -e /workspace/wwwroot/zhct_sanquan && ! -L /workspace/wwwroot/zhct_sanquan ]] || \
    state=PRESENT
  printf '/workspace/wwwroot/zhct_sanquan\t%s\n' "${state}" >"${output}"
  [[ "${state}" == "ABSENT" ]] || die "formal application root is present"
}

assert_runtime_identity_absent() {
  local output=$1
  : >"${output}"
  local path state
  for path in /etc/zhct/node.env /etc/cron.d/zhct-sanquan /etc/zhct/secrets; do
    state=ABSENT
    [[ ! -e "${path}" && ! -L "${path}" ]] || state=PRESENT
    printf '%s\t%s\n' "${path}" "${state}" >>"${output}"
    [[ "${state}" == "ABSENT" ]] || die "runtime identity/config is already present: ${path}"
  done
}

assert_space_gate() {
  local output=$1
  local available total percent
  available=$(df -PB1 / | awk 'NR == 2 {print $4}')
  total=$(df -PB1 / | awk 'NR == 2 {print $2}')
  percent=$((available * 100 / total))
  {
    printf 'root_available_bytes=%s\n' "${available}"
    printf 'root_total_bytes=%s\n' "${total}"
    printf 'root_available_percent=%s\n' "${percent}"
    printf 'minimum_available_bytes=%s\n' "${MIN_FREE_BYTES}"
    printf 'minimum_available_percent=20\n'
  } >"${output}"
  [[ "${available}" -ge "${MIN_FREE_BYTES}" ]] || die "root free bytes below 8GiB gate"
  [[ "${percent}" -ge 20 ]] || die "root free percentage below 20% gate"
}

assert_preseal_identity_present() {
  local output=$1
  [[ -f /etc/machine-id && ! -L /etc/machine-id && -s /etc/machine-id ]] || \
    die "machine-id is missing, unsafe or already empty before seal"
  find /etc/ssh -maxdepth 1 -type f -name 'ssh_host_*_key*' -printf '%f\n' | sort >"${output}"
  [[ "$(wc -l <"${output}")" -eq 6 ]] || die "expected six SSH host key files before seal"
  if grep -Ev '^(ssh_host_(ecdsa|ed25519|rsa)_key)(\.pub)?$' "${output}" | grep -q .; then
    die "unexpected SSH host key type would survive the canonical seal"
  fi
}

assert_clone_identity_removed() {
  local output=$1
  local machine_size key_count
  [[ -f /etc/machine-id && ! -L /etc/machine-id ]] || die "/etc/machine-id is missing or unsafe"
  machine_size=$(stat -c '%s' /etc/machine-id)
  [[ "${machine_size}" -eq 0 ]] || die "/etc/machine-id is not empty"
  [[ ! -e /var/lib/dbus/machine-id && ! -L /var/lib/dbus/machine-id ]] || \
    die "DBus machine-id remains"
  key_count=$(find /etc/ssh -maxdepth 1 -type f -name 'ssh_host_*_key*' -print | wc -l)
  [[ "${key_count}" -eq 0 ]] || die "SSH host key remains"
  {
    printf 'etc_machine_id_bytes=%s\n' "${machine_size}"
    printf 'dbus_machine_id=ABSENT\n'
    printf 'ssh_host_key_file_count=%s\n' "${key_count}"
  } >"${output}"
}

prepare_empty_seal_directories() {
  local output=$1
  : >"${output}"
  local path
  for path in /var/run/supervisor /var/lib/supervisor; do
    if [[ ! -e "${path}" && ! -L "${path}" ]]; then
      install -d -m 0700 -o root -g root "${path}"
      printf '%s\tCREATED_EMPTY_FOR_CANONICAL_SEAL\n' "${path}" >>"${output}"
    else
      [[ -d "${path}" && ! -L "${path}" ]] || die "unsafe seal directory: ${path}"
      printf '%s\tPRESENT\n' "${path}" >>"${output}"
    fi
  done
  for path in /var/log/supervisor /workspace/wwwroot /workspace/wwwroot/log; do
    [[ -d "${path}" && ! -L "${path}" ]] || die "required seal directory is missing or unsafe: ${path}"
    printf '%s\tPRESENT\n' "${path}" >>"${output}"
  done
}

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

for command in awk bash chmod cmp cut date df dnf env find grep hostname install ip pgrep \
  sed sha256sum sort ssh-keygen ss stat systemctl tar tr uname wc xargs; do
  command -v "${command}" >/dev/null 2>&1 || die "required command is missing: ${command}"
done

[[ ! -e "${EVIDENCE_DIR}" && ! -L "${EVIDENCE_DIR}" ]] || die "Q053 evidence run exists"
[[ ! -e "${FINAL_ARCHIVE}" && ! -L "${FINAL_ARCHIVE}" ]] || die "Q053 archive exists"
install -d -m 0700 "${EVIDENCE_ROOT}" "${EVIDENCE_DIR}"
install -m 0600 -o root -g root "$0" "${EVIDENCE_DIR}/q053-seal-app01.sh"

assert_file_sha256 "${SEAL_SCRIPT}" "${EXPECTED_SEAL_WRAPPER_SHA256}"
assert_file_sha256 "${POST_CLONE_SCRIPT}" "${EXPECTED_POST_CLONE_WRAPPER_SHA256}"
assert_file_sha256 "${BUNDLE_ROOT}/common/bin/role-action.sh" "${EXPECTED_ROLE_ACTION_SHA256}"
assert_file_sha256 "${BUNDLE_ROOT}/common/lib/runtime.sh" "${EXPECTED_RUNTIME_SHA256}"
assert_file_sha256 "${BUNDLE_ROOT}/roles/app/hooks.sh" "${EXPECTED_APP_HOOKS_SHA256}"
assert_file_sha256 "${BUNDLE_ROOT}/roles/app/role.env" "${EXPECTED_ROLE_ENV_SHA256}"
assert_file_sha256 "${BUNDLE_ROOT}/roles/app/manifests/systemd-units.tsv" "${EXPECTED_UNITS_SHA256}"
assert_file_sha256 "${BUNDLE_ROOT}/roles/app/manifests/nodes.tsv" "${EXPECTED_NODES_SHA256}"
capture framework-sha256-before sha256sum "${FRAMEWORK_FILES[@]}"
capture_shell framework-bash-syntax \
  "for file in '${SEAL_SCRIPT}' '${POST_CLONE_SCRIPT}' '${BUNDLE_ROOT}/common/bin/role-action.sh' '${BUNDLE_ROOT}/common/lib/runtime.sh' '${BUNDLE_ROOT}/roles/app/hooks.sh'; do bash -n \"\${file}\"; done"

capture role-verify-before bash -c \
  "ulimit -n 65536; exec '${VERIFY_SCRIPT}' --role app --bundle-root '${BUNDLE_ROOT}'"
capture hostname-before hostnamectl
capture ipv4-before ip -4 -br addr
capture routes-before ip route show table all
capture dnf-history-before dnf history list
capture_shell dnf-history-ids-before "dnf history list | awk '\$1 ~ /^[0-9]+$/ {print \$1}'"
capture filesystems-before df -hT
capture inodes-before df -hi
capture binaries-before sha256sum "${FORMAL_BINARIES[@]}"
capture unit-files-before sha256sum "${FORMAL_UNIT_FILES[@]}"
assert_units_quiescent "${EVIDENCE_DIR}/units-before.tsv"
assert_no_target_processes "${EVIDENCE_DIR}/target-processes-before.txt"
assert_no_target_listeners "${EVIDENCE_DIR}/listeners-before.txt"
assert_application_root_absent "${EVIDENCE_DIR}/application-root-before.tsv"
assert_runtime_identity_absent "${EVIDENCE_DIR}/runtime-identity-before.tsv"
assert_space_gate "${EVIDENCE_DIR}/space-before.env"
assert_preseal_identity_present "${EVIDENCE_DIR}/ssh-host-key-files-before.txt"
find /etc/ssh -maxdepth 1 -type f -name 'ssh_host_*.pub' -print0 | \
  sort -z | xargs -0 -r -n 1 ssh-keygen -lf >"${EVIDENCE_DIR}/ssh-host-public-fingerprints-before.txt"
prepare_empty_seal_directories "${EVIDENCE_DIR}/seal-directories-before.tsv"

capture seal-dry-run bash "${SEAL_SCRIPT}" --role app --bundle-root "${BUNDLE_ROOT}" --dry-run
capture post-clone-dry-run-before env RUN_ID="${RUN_ID}-dry-before" \
  bash "${POST_CLONE_SCRIPT}" --role app --node app01 \
  --expected-ip "${EXPECTED_IPV4}" --bundle-root "${BUNDLE_ROOT}" --dry-run

SEAL_ATTEMPTED=1
capture seal-live env RUN_ID="${RUN_ID}" \
  bash "${SEAL_SCRIPT}" --role app --bundle-root "${BUNDLE_ROOT}" --confirm-seal
SEAL_COMPLETED=1

assert_units_quiescent "${EVIDENCE_DIR}/units-after.tsv"
assert_no_target_processes "${EVIDENCE_DIR}/target-processes-after.txt"
assert_no_target_listeners "${EVIDENCE_DIR}/listeners-after.txt"
assert_application_root_absent "${EVIDENCE_DIR}/application-root-after.tsv"
assert_runtime_identity_absent "${EVIDENCE_DIR}/runtime-identity-after.tsv"
assert_clone_identity_removed "${EVIDENCE_DIR}/clone-identity-after.env"
assert_space_gate "${EVIDENCE_DIR}/space-after.env"

capture ipv4-after ip -4 -br addr
capture routes-after ip route show table all
capture dnf-history-after dnf history list
capture_shell dnf-history-ids-after "dnf history list | awk '\$1 ~ /^[0-9]+$/ {print \$1}'"
capture filesystems-after df -hT
capture inodes-after df -hi
capture binaries-after sha256sum "${FORMAL_BINARIES[@]}"
capture unit-files-after sha256sum "${FORMAL_UNIT_FILES[@]}"
capture framework-sha256-after sha256sum "${FRAMEWORK_FILES[@]}"

assert_snapshot_unchanged "${EVIDENCE_DIR}/ipv4-before.txt" "${EVIDENCE_DIR}/ipv4-after.txt" "IPv4"
assert_snapshot_unchanged "${EVIDENCE_DIR}/routes-before.txt" "${EVIDENCE_DIR}/routes-after.txt" "routes"
assert_snapshot_unchanged "${EVIDENCE_DIR}/dnf-history-ids-before.txt" \
  "${EVIDENCE_DIR}/dnf-history-ids-after.txt" "DNF history"
assert_snapshot_unchanged "${EVIDENCE_DIR}/binaries-before.txt" \
  "${EVIDENCE_DIR}/binaries-after.txt" "application binaries"
assert_snapshot_unchanged "${EVIDENCE_DIR}/unit-files-before.txt" \
  "${EVIDENCE_DIR}/unit-files-after.txt" "unit files"
assert_snapshot_unchanged "${EVIDENCE_DIR}/framework-sha256-before.txt" \
  "${EVIDENCE_DIR}/framework-sha256-after.txt" "framework"

capture post-clone-dry-run-after env RUN_ID="${RUN_ID}-dry-after" \
  bash "${POST_CLONE_SCRIPT}" --role app --node app01 \
  --expected-ip "${EXPECTED_IPV4}" --bundle-root "${BUNDLE_ROOT}" --dry-run

find "${EVIDENCE_DIR}" -type f \( -name '*.pem' -o -name '*.key' -o -name 'ssh_host_*' \) -print \
  >"${EVIDENCE_DIR}/forbidden-private-files.txt"
[[ ! -s "${EVIDENCE_DIR}/forbidden-private-files.txt" ]] || die "private key-like evidence found"
if grep -RInE \
  '(BEGIN[[:space:]]+(RSA|EC|OPENSSH)?[[:space:]]*PRIVATE KEY|AKIA[0-9A-Z]{16}|(password|passwd|token|cookie|secret)[[:space:]]*[:=][[:space:]]*[^_[:space:]]{6,})' \
  "${EVIDENCE_DIR}" --exclude='q053-seal-app01.sh' >"${SENSITIVE_SCAN_TMP}"; then
  mv "${SENSITIVE_SCAN_TMP}" "${EVIDENCE_DIR}/sensitive-scan.txt"
  die "sensitive-looking content found"
fi
rm -f "${SENSITIVE_SCAN_TMP}"
printf 'PASS\n' >"${EVIDENCE_DIR}/sensitive-scan.txt"

{
  printf 'run_id=%s\n' "${RUN_ID}"
  printf 'completed_at=%s\n' "$(date --iso-8601=seconds)"
  printf 'q051_deployment_gate=PASS_WITH_EVIDENCE_EXCEPTION\n'
  printf 'q052_offline_rebuild_gate=USER_WAIVED\n'
  printf 'target_identity=PASS\n'
  printf 'framework_integrity=PASS\n'
  printf 'role_verify_before=PASS\n'
  printf 'preseal_quiescence=PASS\n'
  printf 'seal_dry_run=PASS\n'
  printf 'post_clone_dry_run=PASS\n'
  printf 'live_seal=PASS\n'
  printf 'clone_identity_removed=PASS\n'
  printf 'formal_application_root=ABSENT\n'
  printf 'target_listener_count=0\n'
  printf 'target_process_count=0\n'
  printf 'machine_id_bytes=0\n'
  printf 'ssh_host_key_file_count=0\n'
  printf 'service_activation=NOT_PERFORMED\n'
  printf 'post_clone_execution=NOT_PERFORMED\n'
  printf 'business_release=NOT_PERFORMED\n'
  printf 'template_state=SEALED_AWAITING_CUSTOMER_APPLICATION\n'
  printf 'guest_reboot_or_reconnect=FORBIDDEN\n'
  printf 'next_task=Q110_CUSTOMER_TEMPLATE_APPLICATION\n'
  printf 'final_check=PASS\n'
} >"${FINAL_RESULT}"
chmod 0600 "${FINAL_RESULT}"

find "${EVIDENCE_DIR}" -type l -print >"${EVIDENCE_DIR}/symlinks.txt"
[[ ! -s "${EVIDENCE_DIR}/symlinks.txt" ]] || die "evidence contains a symbolic link"
(
  cd "${EVIDENCE_DIR}"
  find . -type f ! -name 'evidence-index.sha256' -print0 | \
    sort -z | xargs -0 sha256sum >"$(basename "${FINAL_INDEX}")"
)
chmod 0600 "${FINAL_INDEX}"
(
  cd "${EVIDENCE_ROOT}"
  tar -czf "${FINAL_ARCHIVE}" "${RUN_ID}"
)
sha256sum "${FINAL_ARCHIVE}" >"${FINAL_ARCHIVE}.sha256"
chmod 0600 "${FINAL_ARCHIVE}" "${FINAL_ARCHIVE}.sha256"

printf 'Q053_APP01_TEMPLATE_SEAL_OK\n'
printf 'run_id=%s\n' "${RUN_ID}"
printf 'evidence=%s\n' "${EVIDENCE_DIR}"
printf 'archive=%s\n' "${FINAL_ARCHIVE}"
printf 'archive_sha256=%s\n' "$(awk '{print $1}' "${FINAL_ARCHIVE}.sha256")"
printf 'template_state=SEALED_AWAITING_CUSTOMER_APPLICATION\n'
printf 'DO_NOT_REBOOT_RECONNECT_OR_RUN_MORE_COMMANDS\n'
