#!/usr/bin/env bash

set -Eeuo pipefail
export LANG=C

EXPECTED_HOSTNAME="sqjcapp01"
EXPECTED_IPV4="10.2.203.104"
EXPECTED_OS="openEuler release 24.03 (LTS-SP1)"
EXPECTED_ARCH="x86_64"
BACKUP_ROOT="/var/backups/zhct-q050"
TARGET_PORTS_RE=':(8081|18081)([[:space:]]|$)'

umask 077

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

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

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

[[ "${EUID}" -eq 0 ]] || die "must run as root"

ACTUAL_HOSTNAME="$(hostname -s)"
ACTUAL_ARCH="$(uname -m)"
ACTUAL_OS="$(tr -d '\r' </etc/openEuler-release)"

[[ "${ACTUAL_HOSTNAME}" == "${EXPECTED_HOSTNAME}" ]] || \
  die "hostname mismatch: expected ${EXPECTED_HOSTNAME}, got ${ACTUAL_HOSTNAME}"
[[ "${ACTUAL_ARCH}" == "${EXPECTED_ARCH}" ]] || \
  die "architecture mismatch: expected ${EXPECTED_ARCH}, got ${ACTUAL_ARCH}"
[[ "${ACTUAL_OS}" == "${EXPECTED_OS}" ]] || \
  die "OS mismatch: expected ${EXPECTED_OS}, got ${ACTUAL_OS}"
ip -o -4 addr show scope global | awk '{print $4}' | cut -d/ -f1 | \
  grep -Fxq "${EXPECTED_IPV4}" || die "expected IPv4 ${EXPECTED_IPV4} is not configured"

RUN_ID="$(date +%Y%m%dT%H%M%S%z)"
BACKUP_DIR="${BACKUP_ROOT}/${RUN_ID}"
BASELINE_DIR="${BACKUP_DIR}/baseline"
CONFIG_DIR="${BACKUP_DIR}/config"

install -d -m 0700 "${BACKUP_ROOT}" "${BACKUP_DIR}" "${BASELINE_DIR}" "${CONFIG_DIR}"
printf '%s\n' "${RUN_ID}" >"${BACKUP_DIR}/run-id"
printf '%s\n' "${EXPECTED_HOSTNAME}" >"${BACKUP_DIR}/expected-hostname"
printf '%s\n' "${EXPECTED_IPV4}" >"${BACKUP_DIR}/expected-ipv4"

capture identity hostnamectl
capture os-release cat /etc/os-release
capture openeuler-release cat /etc/openEuler-release
capture uname uname -a
capture cpu lscpu
capture memory free -h
capture memory-bytes free -b
capture swap swapon --show --bytes
capture block-devices lsblk -e7 -o NAME,PATH,TYPE,SIZE,FSTYPE,FSVER,MOUNTPOINTS,UUID,PARTUUID
capture filesystems df -hT
capture inodes df -hi
capture pvs pvs --units b --nosuffix
capture vgs vgs --units b --nosuffix
capture lvs lvs -a -o+devices --units b --nosuffix
capture mount findmnt --real

capture ipv4-addresses ip -4 -br addr
capture ipv6-addresses ip -6 -br addr
capture routes ip route show table all
capture rules ip rule show
capture links ip -s link
capture resolv cat /etc/resolv.conf
capture hosts cat /etc/hosts
capture nmcli-general nmcli general status
capture nmcli-devices nmcli -f DEVICE,TYPE,STATE,CONNECTION device status
capture nmcli-connections nmcli -f NAME,UUID,TYPE,DEVICE connection show

capture time timedatectl
capture chrony-tracking chronyc tracking
capture chrony-sources chronyc sources -v
capture_shell public-egress "curl -4 -fsS --connect-timeout 5 --max-time 15 https://ifconfig.me/ip; printf '\\n'"
capture_shell repo-egress "getent ahostsv4 repo.openeuler.org; curl -4 -fsSI --connect-timeout 5 --max-time 15 https://repo.openeuler.org/ | sed -n '1,12p'"

capture listening-ports ss -lntup
capture unix-sockets ss -lxnp
capture sockets-summary ss -s
capture running-services systemctl list-units --type=service --state=running --all --no-pager
capture failed-units systemctl --failed --all --no-pager
capture enabled-units systemctl list-unit-files --state=enabled --no-pager
capture all-service-files systemctl list-unit-files --type=service --no-pager
capture timers systemctl list-timers --all --no-pager
capture root-crontab crontab -l
capture cron-files find /etc/cron.d /etc/cron.daily /etc/cron.hourly /etc/cron.monthly /etc/cron.weekly -maxdepth 2 -printf '%m %u %g %TY-%Tm-%TdT%TH:%TM:%TS %p\n'
capture at-jobs atq

capture rpm-all rpm -qa --qf '%{NAME}\t%{EPOCHNUM}:%{VERSION}-%{RELEASE}\t%{ARCH}\n'
capture_shell rpm-target-residue "rpm -qa | sort | grep -Ei '(^|[-_])(nginx|php|supervisor|mosquitto|redis|mysql|mariadb)([-_.]|$)' || true"
capture_shell process-target-residue "ps -eo pid,ppid,user,group,lstart,cmd --sort=pid | grep -Ei '[n]ginx|[p]hp-fpm|[s]upervisord|[m]osquitto|[r]edis-server|[m]ysqld' || true"
capture_shell command-target-residue "for c in nginx php phpize php-config php-fpm supervisord supervisorctl mosquitto_pub mosquitto_sub redis-cli mysql mysqldump mysqladmin; do command -v \"\$c\" || printf 'ABSENT %s\\n' \"\$c\"; done"
capture_shell directory-target-residue "for p in /etc/nginx /usr/local/nginx /usr/local/php7.3 /usr/local/php7.4 /etc/php /etc/php-fpm.d /etc/supervisor /etc/supervisord.conf /opt/redis-client /opt/mysql-client /workspace /data /opt/zhct-deploy; do if [[ -e \"\$p\" || -L \"\$p\" ]]; then find \"\$p\" -xdev -printf '%m %u %g %s %TY-%Tm-%TdT%TH:%TM:%TS %p\\n'; else printf 'ABSENT %s\\n' \"\$p\"; fi; done"
capture php-socket-residue find /run -maxdepth 3 -type s -name '*php*fpm*.sock' -printf '%m %u %g %p\n'
capture nginx-directory-package rpm -qf /etc/nginx
capture nginx-mime-package rpm -qf /etc/nginx/mime_mailcap.types
capture nginx-mime-sha256 sha256sum /etc/nginx/mime_mailcap.types

capture firewalld-state firewall-cmd --state
capture firewalld-zones firewall-cmd --list-all-zones
capture nft-ruleset nft list ruleset
capture selinux getenforce
capture selinux-config cat /etc/selinux/config
capture sysctl-all sysctl -a
capture limits cat /etc/security/limits.conf
capture limits-files find /etc/security/limits.d -maxdepth 2 -type f -printf '%m %u %g %s %p\n'
capture fips sysctl crypto.fips_enabled
capture dnf-history dnf history list
capture dnf-repolist dnf -v repolist --all
capture dnf-modules dnf module list
capture rpm-keys rpm -qa 'gpg-pubkey*'

BACKUP_PATHS=(
  etc/nginx
  etc/php.ini
  etc/php.d
  etc/php-fpm.conf
  etc/php-fpm.d
  etc/supervisor
  etc/supervisord.conf
  etc/cron.d
  var/spool/cron
  etc/firewalld
  etc/sysctl.conf
  etc/sysctl.d
  etc/security/limits.conf
  etc/security/limits.d
  etc/NetworkManager/system-connections
  etc/systemd/system
  etc/hosts
  etc/resolv.conf
  etc/chrony.conf
)

EXISTING_PATHS=()
for relative_path in "${BACKUP_PATHS[@]}"; do
  if [[ -e "/${relative_path}" || -L "/${relative_path}" ]]; then
    EXISTING_PATHS+=("${relative_path}")
  fi
done

printf '%s\n' "${EXISTING_PATHS[@]}" >"${BACKUP_DIR}/config-paths.txt"
if ((${#EXISTING_PATHS[@]} > 0)); then
  tar --acls --xattrs --numeric-owner -czpf \
    "${CONFIG_DIR}/system-config-before-q050.tar.gz" -C / "${EXISTING_PATHS[@]}"
fi

TARGET_LISTENERS="$(ss -lntupH | grep -E "${TARGET_PORTS_RE}" || true)"
PHP_SOCKETS="$(find /run -maxdepth 3 -type s -name '*php*fpm*.sock' -print 2>/dev/null || true)"
{
  printf 'run_id=%s\n' "${RUN_ID}"
  printf 'backup_dir=%s\n' "${BACKUP_DIR}"
  printf 'hostname=%s\n' "${ACTUAL_HOSTNAME}"
  printf 'ipv4=%s\n' "${EXPECTED_IPV4}"
  printf 'os=%s\n' "${ACTUAL_OS}"
  printf 'arch=%s\n' "${ACTUAL_ARCH}"
  printf 'backup_bytes=%s\n' "$(du -sb "${BACKUP_DIR}" | awk '{print $1}')"
  printf 'target_listener_count=%s\n' "$(printf '%s\n' "${TARGET_LISTENERS}" | sed '/^$/d' | wc -l)"
  printf 'php_socket_count=%s\n' "$(printf '%s\n' "${PHP_SOCKETS}" | sed '/^$/d' | wc -l)"
  printf 'target_listener_check=%s\n' "$([[ -z "${TARGET_LISTENERS}" && -z "${PHP_SOCKETS}" ]] && printf PASS || printf FAIL)"
  printf 'target_service_installation=NOT_PERFORMED\n'
  printf 'target_service_start=NOT_PERFORMED\n'
  printf 'application_release=NOT_PERFORMED\n'
  printf 'business_cron_enable=NOT_PERFORMED\n'
} >"${BACKUP_DIR}/result.env"

inventory_temp=$(mktemp "${BACKUP_DIR}/.file-inventory.XXXXXX")
find "${BACKUP_DIR}" -xdev \
  ! -name file-inventory.tsv \
  ! -name sha256sums.txt \
  ! -name "$(basename "${inventory_temp}")" \
  -printf '%m\t%u\t%g\t%s\t%TY-%Tm-%TdT%TH:%TM:%TS\t%p\n' \
  | sort >"${inventory_temp}"
mv "${inventory_temp}" "${BACKUP_DIR}/file-inventory.tsv"
find "${BACKUP_DIR}" -xdev -type f ! -name sha256sums.txt -print0 \
  | sort -z | xargs -0 sha256sum >"${BACKUP_DIR}/sha256sums.txt"
chmod -R go-rwx "${BACKUP_DIR}"

[[ -z "${TARGET_LISTENERS}" ]] || die "App target TCP port is already occupied; evidence preserved at ${BACKUP_DIR}"
[[ -z "${PHP_SOCKETS}" ]] || die "PHP-FPM socket residue exists; evidence preserved at ${BACKUP_DIR}"

printf 'Q050_BASELINE_BACKUP_OK\n'
printf 'RUN_ID=%s\n' "${RUN_ID}"
printf 'BACKUP_DIR=%s\n' "${BACKUP_DIR}"
printf 'TARGET_LISTENER_CHECK=PASS\n'
