#!/usr/bin/env bash

set -Eeuo pipefail
export LANG=C
umask 077

EXPECTED_HOSTNAME="sqjcDB01"
EXPECTED_IPV4="10.2.208.106"
EXPECTED_OS="openEuler release 24.03 (LTS-SP1)"
EXPECTED_ARCH="x86_64"
BACKUP_ROOT="/var/backups/zhct-q060"
BUNDLE_ROOT="/opt/zhct-deploy"
PACKAGE_ROOT="${BUNDLE_ROOT}/packages"
STAGE="${PACKAGE_ROOT}/.q060-download-stage"
MYSQL56="mysql-5.6.51-linux-glibc2.12-x86_64.tar.gz"
MYSQL80="mysql-8.0.46-linux-glibc2.28-x86_64.tar.xz"
MYSQL56_URL="https://cdn.mysql.com/archives/mysql-5.6/${MYSQL56}"
MYSQL80_URL="https://cdn.mysql.com/Downloads/MySQL-8.0/${MYSQL80}"
MYSQL56_KEY_URL="https://repo.mysql.com/RPM-GPG-KEY-mysql"
MYSQL80_KEY_URL="https://repo.mysql.com/RPM-GPG-KEY-mysql-2025"
MYSQL56_FPR="A4A9406876FCBD3C456770C88C718D3B5072E1F5"
MYSQL80_FPR="BCA43417C3B485DD128EC6D4B7B3B788A8D3785C"
MIN_FREE_BYTES=$((13 * 1024 * 1024 * 1024))

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

require_command() {
  command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}

download_once() {
  local url="$1"
  local destination="$2"
  local part="${destination}.part"
  [[ ! -e "${destination}" && ! -e "${part}" ]] || \
    die "refuse to overwrite an existing download: ${destination}"
  curl --fail --show-error --location \
    --proto '=https' --tlsv1.2 \
    --connect-timeout 15 --max-time 7200 \
    --retry 2 --retry-delay 3 --retry-all-errors \
    --output "${part}" "${url}"
  [[ -s "${part}" ]] || die "download is empty: ${url}"
  mv "${part}" "${destination}"
}

key_fingerprint() {
  local key_file="$1"
  GNUPGHOME="${GNUPG_HOME}" gpg --batch --with-colons \
    --import-options show-only --import "${key_file}" |
    awk -F: '$1 == "fpr" {print $10; exit}'
}

verify_signature() {
  local archive="$1"
  local signature="$2"
  local expected_fingerprint="$3"
  local status_file="$4"
  set +e
  GNUPGHOME="${GNUPG_HOME}" gpg --batch --status-fd 1 \
    --verify "${signature}" "${archive}" >"${status_file}" 2>&1
  local rc=$?
  set -e
  [[ "${rc}" -eq 0 ]] || die "GPG verification failed: ${archive}"
  grep -Eq "^[[]GNUPG:[]] VALIDSIG ${expected_fingerprint} " "${status_file}" || \
    die "GPG signature did not use the pinned fingerprint: ${archive}"
  grep -Eq '^[[]GNUPG:[]] GOODSIG ' "${status_file}" || \
    die "GPG did not report a good signature: ${archive}"
}

assert_archive_layout() {
  local archive="$1"
  local top="$2"
  local members="$3"
  python3 - "${archive}" "${top}" <<'PY'
import pathlib
import posixpath
import sys
import tarfile

archive = pathlib.Path(sys.argv[1])
top = sys.argv[2]
with tarfile.open(archive, "r:*") as tf:
    members = tf.getmembers()
    if not members:
        raise SystemExit(f"empty archive: {archive}")
    for member in members:
        path = pathlib.PurePosixPath(member.name)
        if path.is_absolute() or ".." in path.parts or not path.parts or path.parts[0] != top:
            raise SystemExit(f"unsafe or unexpected archive member: {member.name}")
        if member.issym() or member.islnk():
            target = pathlib.PurePosixPath(member.linkname)
            if target.is_absolute():
                raise SystemExit(f"unsafe archive link: {member.name} -> {member.linkname}")
            base = path.parent if member.issym() else pathlib.PurePosixPath()
            resolved = pathlib.PurePosixPath(posixpath.normpath(str(base / target)))
            if not resolved.parts or resolved.parts[0] != top:
                raise SystemExit(f"archive link escapes top directory: {member.name} -> {member.linkname}")
PY
  tar -tf "${archive}" >"${members}"
  [[ -s "${members}" ]] || die "archive has no members: ${archive}"
  awk -v top="${top}/" '
    substr($0, 1, 1) == "/" || $0 ~ /(^|\/)\.\.(\/|$)/ || index($0, top) != 1 {bad=1}
    END {exit bad}
  ' "${members}" || die "archive contains an unsafe or unexpected path: ${archive}"
  for required in bin/mysqld bin/mysql bin/mysqladmin bin/mysqldump; do
    grep -Fxq "${top}/${required}" "${members}" || \
      die "required archive member is missing: ${top}/${required}"
  done
}

[[ "${EUID}" -eq 0 ]] || die "must run as root"
[[ $# -eq 1 ]] || die "usage: $0 <q060-baseline-run-id>"
RUN_ID="$1"
[[ "${RUN_ID}" =~ ^[0-9]{8}T[0-9]{6}[+-][0-9]{4}$ ]] || die "invalid run id"
EVIDENCE_DIR="${BACKUP_ROOT}/${RUN_ID}"
RESULT_FILE="${EVIDENCE_DIR}/result.env"

for command_name in curl gpg sha256sum tar findmnt awk grep stat python3; do
  require_command "${command_name}"
done
[[ "$(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"
[[ -s "${RESULT_FILE}" ]] || die "Q-060 baseline result is missing"
grep -Fxq 'baseline_result=PASS' "${RESULT_FILE}" || die "baseline gate is not PASS"
grep -Fxq 'install_execution=NOT_PERFORMED' "${RESULT_FILE}" || \
  die "baseline does not prove installation is unperformed"
[[ "$(findmnt -bn -o AVAIL /)" -ge "${MIN_FREE_BYTES}" ]] || \
  die "less than 13 GiB is available before download"
[[ ! -e "${STAGE}" ]] || die "download stage already exists: ${STAGE}"
for final_asset in "${MYSQL56}" "${MYSQL56}.asc" "${MYSQL80}" "${MYSQL80}.asc"; do
  [[ ! -e "${PACKAGE_ROOT}/${final_asset}" ]] || \
    die "final asset already exists; use the verification path instead: ${final_asset}"
done

install -d -m 0700 "${STAGE}"
GNUPG_HOME="${STAGE}/gnupg"
install -d -m 0700 "${GNUPG_HOME}"

download_once "${MYSQL56_URL}" "${STAGE}/${MYSQL56}"
download_once "${MYSQL56_URL}.asc" "${STAGE}/${MYSQL56}.asc"
download_once "${MYSQL80_URL}" "${STAGE}/${MYSQL80}"
download_once "${MYSQL80_URL}.asc" "${STAGE}/${MYSQL80}.asc"
download_once "${MYSQL56_KEY_URL}" "${STAGE}/mysql56-release-key.asc"
download_once "${MYSQL80_KEY_URL}" "${STAGE}/mysql80-release-key.asc"

[[ "$(key_fingerprint "${STAGE}/mysql56-release-key.asc")" == "${MYSQL56_FPR}" ]] || \
  die "MySQL 5.6 release key fingerprint mismatch"
[[ "$(key_fingerprint "${STAGE}/mysql80-release-key.asc")" == "${MYSQL80_FPR}" ]] || \
  die "MySQL 8.0 release key fingerprint mismatch"
GNUPGHOME="${GNUPG_HOME}" gpg --batch --import \
  "${STAGE}/mysql56-release-key.asc" "${STAGE}/mysql80-release-key.asc" \
  >"${EVIDENCE_DIR}/mysql-gpg-key-import.txt" 2>&1
install -m 0600 "${STAGE}/mysql56-release-key.asc" \
  "${EVIDENCE_DIR}/mysql56-release-key.asc"
install -m 0600 "${STAGE}/mysql80-release-key.asc" \
  "${EVIDENCE_DIR}/mysql80-release-key.asc"
sha256sum \
  "${EVIDENCE_DIR}/mysql56-release-key.asc" \
  "${EVIDENCE_DIR}/mysql80-release-key.asc" \
  >"${EVIDENCE_DIR}/mysql-release-keys.sha256"

verify_signature "${STAGE}/${MYSQL56}" "${STAGE}/${MYSQL56}.asc" \
  "${MYSQL56_FPR}" "${EVIDENCE_DIR}/mysql56-gpg-status.txt"
verify_signature "${STAGE}/${MYSQL80}" "${STAGE}/${MYSQL80}.asc" \
  "${MYSQL80_FPR}" "${EVIDENCE_DIR}/mysql80-gpg-status.txt"
assert_archive_layout "${STAGE}/${MYSQL56}" \
  "mysql-5.6.51-linux-glibc2.12-x86_64" \
  "${EVIDENCE_DIR}/mysql56-archive-members.txt"
assert_archive_layout "${STAGE}/${MYSQL80}" \
  "mysql-8.0.46-linux-glibc2.28-x86_64" \
  "${EVIDENCE_DIR}/mysql80-archive-members.txt"

(
  cd "${STAGE}"
  sha256sum "${MYSQL56}" "${MYSQL56}.asc" "${MYSQL80}" "${MYSQL80}.asc"
) >"${EVIDENCE_DIR}/mysql-source-assets.sha256"
{
  printf 'artifact\tbytes\tsha256\tgpg_fingerprint\tstatus\n'
  printf '%s\t%s\t%s\t%s\tLOCKED\n' \
    "${MYSQL56}" "$(stat -c %s "${STAGE}/${MYSQL56}")" \
    "$(sha256sum "${STAGE}/${MYSQL56}" | awk '{print $1}')" "${MYSQL56_FPR}"
  printf '%s\t%s\t%s\t%s\tLOCKED\n' \
    "${MYSQL80}" "$(stat -c %s "${STAGE}/${MYSQL80}")" \
    "$(sha256sum "${STAGE}/${MYSQL80}" | awk '{print $1}')" "${MYSQL80_FPR}"
} >"${EVIDENCE_DIR}/mysql-source-assets.tsv"

rm -rf "${GNUPG_HOME}"
rm -f "${STAGE}/mysql56-release-key.asc" "${STAGE}/mysql80-release-key.asc"
for final_asset in "${MYSQL56}" "${MYSQL56}.asc" "${MYSQL80}" "${MYSQL80}.asc"; do
  mv "${STAGE}/${final_asset}" "${PACKAGE_ROOT}/${final_asset}"
done
rmdir "${STAGE}"
chmod 0640 \
  "${PACKAGE_ROOT}/${MYSQL56}" "${PACKAGE_ROOT}/${MYSQL56}.asc" \
  "${PACKAGE_ROOT}/${MYSQL80}" "${PACKAGE_ROOT}/${MYSQL80}.asc"

{
  printf 'captured_at=%s\n' "$(date --iso-8601=seconds)"
  printf 'source_asset_download=PASS\n'
  printf 'mysql56_gpg=PASS\n'
  printf 'mysql80_gpg=PASS\n'
  printf 'archive_layout=PASS\n'
  printf 'manifest_update=PENDING_LOCAL_EVIDENCE_REVIEW\n'
  printf 'install_execution=NOT_PERFORMED\n'
  printf 'post_clone_execution=NOT_PERFORMED\n'
} >"${EVIDENCE_DIR}/source-assets-result.env"
chmod 0600 "${EVIDENCE_DIR}"/mysql-* "${EVIDENCE_DIR}/source-assets-result.env"

printf 'Q060_SOURCE_ASSETS_LOCKED_OK\n'
cat "${EVIDENCE_DIR}/source-assets-result.env"
