#!/usr/bin/env bash

set -Eeuo pipefail
export LC_ALL=C

EXPECTED_HOSTNAME="sqjcnginx01"
EXPECTED_IPV4="10.2.203.102"
EXPECTED_OS="openEuler release 24.03 (LTS-SP1)"
BUNDLE_ROOT="/opt/zhct-deploy"
PACKAGE_ROOT="${BUNDLE_ROOT}/packages"
RPM_ROOT="${PACKAGE_ROOT}/rpms"
STAGE="${PACKAGE_ROOT}/rpms.q040-minimal-stage"
EVIDENCE_DIR="/var/backups/zhct-q040/20260722T203557+0800"
REJECTED_ROOT="${EVIDENCE_DIR}/rejected-assets/rpms-303"
BASELINE_RPM="${EVIDENCE_DIR}/baseline/rpm-all.txt"

ROOT_PACKAGES=(
  autoconf
  automake
  bash
  bzip2
  coreutils
  curl
  diffutils
  file
  findutils
  gawk
  gcc
  gcc-c++
  gettext
  grep
  gzip
  iproute
  iputils
  libmnl-devel
  libnfnetlink-devel
  libnftnl-devel
  libnl3-devel
  libtool
  make
  openssl-devel
  pcre2-devel
  pkgconf-pkg-config
  procps-ng
  python3
  rsync
  sed
  shadow-utils
  systemd
  systemd-devel
  tar
  unzip
  which
  zlib-devel
)

ORCHESTRATOR_RPMS=(
  "${PACKAGE_ROOT}/percona-orchestrator-3.2.6-22.el8.x86_64.rpm"
  "${PACKAGE_ROOT}/percona-orchestrator-cli-3.2.6-22.el8.x86_64.rpm"
  "${PACKAGE_ROOT}/percona-orchestrator-client-3.2.6-22.el8.x86_64.rpm"
)

ORCHESTRATOR_SHA256=(
  "0067ddf9845f31a2f150bbc5714e131676e7c05a87802c8aa3dbc3a0835d0ac2"
  "9814b2f688d3841baa8b99acf52a09eb8046d4b71fc4b19a3fff586dacbade40"
  "d7b4875ec035c3885c88351cd264fcc3f64f1b84780e0682c534d5771831e88a"
)

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

[[ "${EUID}" -eq 0 ]] || die "must run as root"
[[ "$(hostname -s)" == "${EXPECTED_HOSTNAME}" ]] || die "hostname 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"
[[ -d "${RPM_ROOT}" ]] || die "rejected 303-RPM closure is not present"
[[ $(find "${RPM_ROOT}" -maxdepth 1 -type f -name '*.rpm' | wc -l) -eq 303 ]] || \
  die "existing closure does not contain the expected 303 RPMs"
[[ ! -e "${REJECTED_ROOT}" ]] || die "rejected closure evidence already exists"
[[ ! -e "${STAGE}" ]] || die "minimal closure stage already exists"
[[ -f "${BASELINE_RPM}" ]] || die "baseline RPM inventory is missing"
for rpm_file in "${ORCHESTRATOR_RPMS[@]}"; do
  [[ -f "${rpm_file}" ]] || die "Orchestrator RPM missing: ${rpm_file}"
done

umask 077
install -d -m 0700 "$(dirname "${REJECTED_ROOT}")"
mv "${RPM_ROOT}" "${REJECTED_ROOT}"
if [[ -f "${PACKAGE_ROOT}/rpms.sha256" ]]; then
  mv "${PACKAGE_ROOT}/rpms.sha256" "${EVIDENCE_DIR}/rejected-assets/rpms-303.sha256"
fi
printf '%s\n' \
  'Rejected by offline DNF assumeno: explicit full closure would remove protected packages grub2-pc and python3-dnf.' \
  >"${EVIDENCE_DIR}/rejected-assets/reason.txt"
install -d -m 0750 "${STAGE}"

missing_packages=()
for package in "${ROOT_PACKAGES[@]}"; do
  if ! rpm -q "${package}" >/dev/null 2>&1; then
    missing_packages+=("${package}")
  fi
done
printf '%s\n' "${missing_packages[@]}" >"${EVIDENCE_DIR}/minimal-closure-missing-root-packages.txt"

dnf -y install \
  --downloadonly \
  --downloaddir="${STAGE}" \
  --setopt=strict=True \
  --setopt=install_weak_deps=False \
  --setopt=localpkg_gpgcheck=False \
  "${missing_packages[@]}" \
  "${ORCHESTRATOR_RPMS[@]}" \
  >"${EVIDENCE_DIR}/dnf-download-minimal-closure.log" 2>&1

find "${STAGE}" -maxdepth 1 -type f -name 'percona-orchestrator*.rpm' -delete
openeuler_rpm_count=$(find "${STAGE}" -maxdepth 1 -type f -name '*.rpm' | wc -l)
find "${STAGE}" -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z | \
  xargs -0 -r rpm --define '_pkgverify_level signature' -K \
  >"${EVIDENCE_DIR}/minimal-openeuler-rpm-signatures.txt" 2>&1
[[ -s "${EVIDENCE_DIR}/minimal-openeuler-rpm-signatures.txt" ]] || \
  die "no openEuler RPM was downloaded for the minimal closure"
[[ $(wc -l <"${EVIDENCE_DIR}/minimal-openeuler-rpm-signatures.txt") -eq "${openeuler_rpm_count}" ]] || \
  die "openEuler RPM signature output count mismatch"
[[ $(grep -Ec 'signatures OK$' "${EVIDENCE_DIR}/minimal-openeuler-rpm-signatures.txt") -eq "${openeuler_rpm_count}" ]] || \
  die "not every openEuler RPM has a trusted signature"
if grep -Eiq 'BAD|NOT OK|NOKEY|NOTTRUSTED' \
  "${EVIDENCE_DIR}/minimal-openeuler-rpm-signatures.txt"; then
  die "one or more minimal closure RPM signatures are not trusted"
fi

for index in "${!ORCHESTRATOR_RPMS[@]}"; do
  rpm_file=${ORCHESTRATOR_RPMS[$index]}
  actual=$(sha256sum "${rpm_file}" | awk '{print $1}')
  [[ "${actual}" == "${ORCHESTRATOR_SHA256[$index]}" ]] || \
    die "Orchestrator RPM SHA256 mismatch: ${rpm_file}"
  cp -p "${rpm_file}" "${STAGE}/"
done

percona_signature_evidence="${EVIDENCE_DIR}/orchestrator-rpm-signature-verified.txt"
[[ -s "${percona_signature_evidence}" ]] || die "Percona signature evidence is missing"
[[ $(grep -Eci '8507efa5:[[:space:]]+OK' "${percona_signature_evidence}") -eq 3 ]] || \
  die "expected three Percona signatures from key 8507efa5"
if grep -Eiq 'BAD|NOT OK|NOKEY|NOTTRUSTED' "${percona_signature_evidence}"; then
  die "Percona signature evidence contains a failure"
fi

stage_names=$(mktemp)
find "${STAGE}" -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z | \
  xargs -0 -r rpm -qp --qf '%{NAME}\n' | sort -u >"${stage_names}"
for package in "${missing_packages[@]}"; do
  grep -Fxq "${package}" "${stage_names}" || die "missing root package is absent from closure: ${package}"
done
rm -f "${stage_names}"

find "${STAGE}" -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z | \
  xargs -0 -r rpm -qp --qf '%{NAME}\t%{EPOCHNUM}:%{VERSION}-%{RELEASE}\t%{ARCH}\t%{SIZE}\t%{VENDOR}\n' \
  | sort -u >"${EVIDENCE_DIR}/minimal-rpm-nevra.tsv"
find "${STAGE}" -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z | \
  xargs -0 sha256sum >"${EVIDENCE_DIR}/minimal-rpm-sha256-absolute.txt"

mapfile -d '' closure_rpms < <(find "${STAGE}" -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z)
set +e
dnf --disablerepo='*' \
  --setopt=strict=True \
  --setopt=install_weak_deps=False \
  --setopt=localpkg_gpgcheck=False \
  --assumeno install "${closure_rpms[@]}" \
  >"${EVIDENCE_DIR}/dnf-minimal-closure-assumeno.log" 2>&1
dnf_rc=$?
set -e
[[ "${dnf_rc}" -eq 1 ]] || die "unexpected DNF assumeno exit code: ${dnf_rc}"
grep -Eiq 'Transaction Summary|事务摘要' "${EVIDENCE_DIR}/dnf-minimal-closure-assumeno.log" || \
  die "minimal closure did not reach a transaction summary"
if grep -Eiq 'nothing provides|conflicting requests|transaction test error|protected packages|受保护的软件包|冲突的请求' \
  "${EVIDENCE_DIR}/dnf-minimal-closure-assumeno.log"; then
  die "minimal closure has an unresolved dependency, conflict, or protected-package removal"
fi
if grep -Eiq '(^|[[:space:]])(Upgrading|Downgrading|Removing|Reinstalling|Obsoleting)(:|[[:space:]])|^[[:space:]]*(Upgrade|Downgrade|Remove|Reinstall|Obsolete)[[:space:]]+[0-9]+' \
  "${EVIDENCE_DIR}/dnf-minimal-closure-assumeno.log"; then
  die "minimal closure transaction contains an action other than Install"
fi

baseline_sorted=$(mktemp)
current_sorted=$(mktemp)
baseline_nonkey=$(mktemp)
current_nonkey=$(mktemp)
manifest_temp=$(mktemp "${PACKAGE_ROOT}/.rpms.sha256.XXXXXX")
trap 'rm -f "${baseline_sorted}" "${current_sorted}" "${baseline_nonkey}" "${current_nonkey}" "${manifest_temp}"' EXIT
sed '/^#/d;/^[[:space:]]*$/d' "${BASELINE_RPM}" | sort -u >"${baseline_sorted}"
rpm -qa --qf '%{NAME}\t%{EPOCHNUM}:%{VERSION}-%{RELEASE}\t%{ARCH}\n' | sort -u >"${current_sorted}"
diff -u "${baseline_sorted}" "${current_sorted}" \
  >"${EVIDENCE_DIR}/rpm-inventory-after-minimal-closure.diff" || true
grep -v '^gpg-pubkey[[:space:]]' "${baseline_sorted}" >"${baseline_nonkey}"
grep -v '^gpg-pubkey[[:space:]]' "${current_sorted}" >"${current_nonkey}"
cmp -s "${baseline_nonkey}" "${current_nonkey}" || \
  die "non-key RPM inventory changed during Q040"

mv "${STAGE}" "${RPM_ROOT}"
cd "${PACKAGE_ROOT}"
find rpms -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z | \
  xargs -0 sha256sum >"${manifest_temp}"
sha256sum -c "${manifest_temp}" >"${EVIDENCE_DIR}/minimal-rpm-sha256-check.txt"
mv "${manifest_temp}" "${PACKAGE_ROOT}/rpms.sha256"
chmod 0640 "${PACKAGE_ROOT}/rpms.sha256"
chmod -R go-rwx "${RPM_ROOT}"

rpm_count=$(find "${RPM_ROOT}" -maxdepth 1 -type f -name '*.rpm' | wc -l)
rpm_bytes=$(du -sb "${RPM_ROOT}" | awk '{print $1}')
manifest_sha256=$(sha256sum "${PACKAGE_ROOT}/rpms.sha256" | awk '{print $1}')
{
  printf 'captured_at=%s\n' "$(date --iso-8601=seconds)"
  printf 'rejected_rpm_count=303\n'
  printf 'missing_root_package_count=%s\n' "${#missing_packages[@]}"
  printf 'minimal_rpm_count=%s\n' "${rpm_count}"
  printf 'minimal_rpm_bytes=%s\n' "${rpm_bytes}"
  printf 'rpm_manifest_sha256=%s\n' "${manifest_sha256}"
  printf 'rpm_sha256_check=PASS\n'
  printf 'openeuler_signature_check=PASS\n'
  printf 'percona_signature_evidence=PASS_EXISTING\n'
  printf 'non_key_rpm_inventory_check=PASS\n'
  printf 'dnf_offline_dependency_resolution=PASS_ASSUMENO\n'
  printf 'install_execution=NOT_PERFORMED\n'
} >"${EVIDENCE_DIR}/minimal-rpm-closure-result.env"

chmod -R go-rwx "${EVIDENCE_DIR}/rejected-assets"
chmod 0600 "${EVIDENCE_DIR}"/minimal-* \
  "${EVIDENCE_DIR}/dnf-download-minimal-closure.log" \
  "${EVIDENCE_DIR}/dnf-minimal-closure-assumeno.log" \
  "${EVIDENCE_DIR}/rpm-inventory-after-minimal-closure.diff"

printf 'Q040_MINIMAL_RPM_CLOSURE_OK\n'
cat "${EVIDENCE_DIR}/minimal-rpm-closure-result.env"
