#!/usr/bin/env bash
set -Eeuo pipefail

TASK="REDIS01_Q131_SELINUX_LABEL_REPAIR_AND_VERIFY"
EXPECTED_HOST="sqjcredis01"
EXPECTED_IP="10.2.203.112"
FIXED_ENTRY="/root/redis-q131-q132-v2"
FIXED_ENTRY_SHA256="d229ade533bbe22f66f174bbcfccf685b470f00c466a5286649710be14794f5e"
DIAG_LOG="/var/backups/zhct-q131-startup-diagnosis/q131-startup-diagnosis-20260730T033626Z/operator.log"
DIAG_LOG_SHA256="213c7809c1de591c1a250946df8ec1c2ce6a82059d9ca1010aa6b07d29a32a62"
RUN_ID="q131-selinux-repair-$(date -u +%Y%m%dT%H%M%SZ)"
EVIDENCE_DIR="/var/backups/zhct-q131-selinux-repair/${RUN_ID}"
AUDIT_LOG="${EVIDENCE_DIR}/operator.log"
RESULT_FILE="${EVIDENCE_DIR}/result.env"

FCONTEXT_REGEXES=(
  '/opt/redis/5\.0\.14/bin(/.*)?'
  '/opt/redis/7\.2\.14/bin(/.*)?'
  '/etc/redis(/.*)?'
  '/data/redis5(/.*)?'
  '/data/redis7(/.*)?'
  '/var/log/redis5(/.*)?'
  '/var/log/redis7(/.*)?'
  '/run/redis5(/.*)?'
  '/run/redis7(/.*)?'
)
FCONTEXT_TYPES=(
  redis_exec_t
  redis_exec_t
  redis_conf_t
  redis_var_lib_t
  redis_var_lib_t
  redis_log_t
  redis_log_t
  redis_var_run_t
  redis_var_run_t
)
RESTORE_PATHS=(
  /opt/redis/5.0.14/bin
  /opt/redis/7.2.14/bin
  /etc/redis
  /data/redis5
  /data/redis7
  /var/log/redis5
  /var/log/redis7
)

timestamp() {
  date '+%Y-%m-%dT%H:%M:%S%z'
}

if test "${1:-}" = "--static-validate"; then
  test "${#FCONTEXT_REGEXES[@]}" -eq "${#FCONTEXT_TYPES[@]}"
  echo "Q131_SELINUX_LABEL_REPAIR_STATIC_VALIDATE=PASS"
  echo "TARGET_HOST=${EXPECTED_HOST}"
  echo "TARGET_IP=${EXPECTED_IP}"
  echo "REDIS_UNITS=REDIS5_REDIS7_ONLY"
  echo "AUXILIARY_UNITS=DISABLED_INACTIVE"
  echo "VIP=DISABLED"
  echo "SECRET_VALUES=NOT_OUTPUT_NOT_HASHED"
  exit 0
fi

printf 'BEGIN|%s|%s\n' "$TASK" "$(timestamp)"

fail_before_write() {
  echo "ERROR: $*"
  printf 'END|%s|RC=1|PERSISTENT_WRITE=ZERO\n' "$TASK"
  exit 1
}

test "$(id -u)" -eq 0 || fail_before_write "root is required"
actual_host="$(hostname -s)"
test "$actual_host" = "$EXPECTED_HOST" ||
  fail_before_write "hostname mismatch: actual=${actual_host} expected=${EXPECTED_HOST}"
ip -4 -o addr show scope global | awk '{print $4}' | cut -d/ -f1 |
  grep -Fxq "$EXPECTED_IP" ||
  fail_before_write "expected IPv4 is absent: ${EXPECTED_IP}"

echo "Q131_SELINUX_REPAIR_TARGET_IDENTITY=PASS|host=${actual_host}|ip=${EXPECTED_IP}"

for command_name in semanage restorecon matchpathcon getenforce systemctl ss runuser sha256sum; do
  command -v "$command_name" >/dev/null 2>&1 ||
    fail_before_write "required command is missing: ${command_name}"
done

test "$(getenforce)" = "Enforcing" ||
  fail_before_write "SELinux is not Enforcing"
test -f "$FIXED_ENTRY" && test ! -L "$FIXED_ENTRY" ||
  fail_before_write "fixed Q131 entry is absent or unsafe"
printf '%s  %s\n' "$FIXED_ENTRY_SHA256" "$FIXED_ENTRY" | sha256sum -c - ||
  fail_before_write "fixed Q131 entry checksum mismatch"
test -f "$DIAG_LOG" && test ! -L "$DIAG_LOG" ||
  fail_before_write "startup diagnosis log is absent or unsafe"
printf '%s  %s\n' "$DIAG_LOG_SHA256" "$DIAG_LOG" | sha256sum -c - ||
  fail_before_write "startup diagnosis receipt checksum mismatch"

grep -Fq 'status=203/EXEC' "$DIAG_LOG" ||
  fail_before_write "diagnosis does not contain systemd 203/EXEC"
grep -Fq 'REDIS5_VERSION_RC=0' "$DIAG_LOG" ||
  fail_before_write "Redis5 direct execution probe did not pass"
grep -Fq 'REDIS7_VERSION_RC=0' "$DIAG_LOG" ||
  fail_before_write "Redis7 direct execution probe did not pass"
grep -Fq 'unconfined_u:object_r:user_tmp_t:s0 /opt/redis/5.0.14/bin/redis-server' "$DIAG_LOG" ||
  fail_before_write "Redis5 user_tmp_t evidence is absent"
grep -Fq 'unconfined_u:object_r:user_tmp_t:s0 /opt/redis/7.2.14/bin/redis-server' "$DIAG_LOG" ||
  fail_before_write "Redis7 user_tmp_t evidence is absent"

for path in /opt/redis/5.0.14/bin/redis-server /opt/redis/7.2.14/bin/redis-server; do
  test -f "$path" && test ! -L "$path" ||
    fail_before_write "Redis executable is absent or unsafe: ${path}"
  current_context="$(stat -c '%C' "$path")"
  case "$current_context" in
    *:user_tmp_t:*) ;;
    *) fail_before_write "unexpected pre-repair executable context: ${path} ${current_context}" ;;
  esac
done

for path in /etc/redis/redis5.conf /etc/redis/redis7.conf; do
  test -f "$path" && test ! -L "$path" ||
    fail_before_write "Redis config is absent or unsafe: ${path}"
  test "$(stat -c '%U:%G:%a' "$path")" = "root:redis:640" ||
    fail_before_write "Redis config metadata mismatch: ${path}"
  if grep -Eq '__[A-Z0-9_]+__' "$path"; then
    fail_before_write "unresolved placeholder in Redis config: ${path}"
  fi
done

for secret in /etc/zhct/secrets/redis5.password /etc/zhct/secrets/redis7.password; do
  test -f "$secret" && test ! -L "$secret" && test -s "$secret" ||
    fail_before_write "secret metadata gate failed: ${secret}"
  test "$(stat -c '%U:%G:%a' "$secret")" = "root:root:600" ||
    fail_before_write "secret metadata mismatch: ${secret}"
done
echo "Q131_SECRET_METADATA_GATE=PASS|values=NOT_OUTPUT_NOT_HASHED"

for unit in sentinel5.service sentinel7.service orchestrator.service; do
  test "$(systemctl is-enabled "$unit" 2>/dev/null || true)" = "disabled" ||
    fail_before_write "auxiliary unit is not disabled: ${unit}"
  test "$(systemctl is-active "$unit" 2>/dev/null || true)" = "inactive" ||
    fail_before_write "auxiliary unit is not inactive: ${unit}"
done

policy_fcontexts="$(semanage fcontext -l 2>&1)"
for required_type in redis_exec_t redis_conf_t redis_var_lib_t redis_log_t redis_var_run_t; do
  grep -Fq ":${required_type}:" <<<"$policy_fcontexts" ||
    fail_before_write "required SELinux type is unavailable: ${required_type}"
done
port_table="$(semanage port -l 2>&1)"
grep -E '^redis_port_t[[:space:]]+tcp[[:space:]].*(^|[[:space:],])6379([[:space:],]|$)' \
  <<<"$port_table" >/dev/null ||
  fail_before_write "tcp/6379 is not assigned to redis_port_t"

port_6387_owner="$(
  grep -E '(^|[[:space:],])6387([[:space:],]|$)' <<<"$port_table" |
    awk '{print $1}' | sort -u | paste -sd ',' - || true
)"
case "$port_6387_owner" in
  "") PORT_6387_ACTION="ADD" ;;
  redis_port_t) PORT_6387_ACTION="KEEP" ;;
  *) fail_before_write "tcp/6387 belongs to another SELinux port type: ${port_6387_owner}" ;;
esac

local_fcontexts="$(semanage fcontext -C -l 2>&1 || true)"
for regex in "${FCONTEXT_REGEXES[@]}"; do
  if grep -Fq -- "$regex" <<<"$local_fcontexts"; then
    fail_before_write "pre-existing local fcontext rule conflicts with controlled repair: ${regex}"
  fi
done

for unit in redis5.service redis7.service; do
  test "$(systemctl is-enabled "$unit" 2>/dev/null || true)" = "enabled" ||
    fail_before_write "expected failed activation unit is not enabled: ${unit}"
  test "$(systemctl is-active "$unit" 2>/dev/null || true)" = "failed" ||
    fail_before_write "expected failed activation unit is not failed: ${unit}"
done
if ss -lntupH |
  grep -E ':(6379|6387|26379|26387|3000|10008)([[:space:]]|$)' >/dev/null; then
  fail_before_write "target listener exists before repair"
fi

echo "Q131_SELINUX_REPAIR_PREWRITE_GATE=PASS"

install -d -o root -g root -m 0700 "$EVIDENCE_DIR"
exec 3>&1 4>&2
exec >"$AUDIT_LOG" 2>&1

mutation_started=0
success=0
port_added=0
created_rule_count=0

write_result() {
  local state="$1"
  local detail="$2"
  local temp_result
  temp_result="$(mktemp "${EVIDENCE_DIR}/.result.XXXXXX")"
  {
    printf 'state=%s\n' "$state"
    printf 'detail=%s\n' "$detail"
    printf 'run_id=%s\n' "$RUN_ID"
    printf 'target_host=%s\n' "$EXPECTED_HOST"
    printf 'target_ip=%s\n' "$EXPECTED_IP"
    printf 'redis_units=redis5,redis7\n'
    printf 'auxiliary_units=disabled_inactive\n'
    printf 'vip=disabled\n'
    printf 'secret_values=not_output_not_hashed\n'
  } >"$temp_result"
  install -o root -g root -m 0600 "$temp_result" "$RESULT_FILE"
  rm -f -- "$temp_result"
}

rollback_repair() {
  set +e
  echo "=== AUTOMATIC SAFE ROLLBACK ==="
  systemctl disable --now redis5.service redis7.service
  systemctl disable --now sentinel5.service sentinel7.service orchestrator.service
  systemctl reset-failed redis5.service redis7.service
  if test "$port_added" -eq 1; then
    semanage port -d -p tcp 6387
  fi
  local i
  for ((i=created_rule_count-1; i>=0; i--)); do
    semanage fcontext -d "${FCONTEXT_REGEXES[$i]}"
  done
  restorecon -RF "${RESTORE_PATHS[@]}"
  chcon -R -t user_tmp_t /opt/redis/5.0.14/bin /opt/redis/7.2.14/bin
  echo "Q131_SELINUX_REPAIR_ROLLBACK=SAFE_QUIET_STATE"
  set -e
}

finalize() {
  local rc=$?
  trap - EXIT
  if test "$rc" -ne 0 && test "$mutation_started" -eq 1 && test "$success" -eq 0; then
    rollback_repair
    write_result FAIL "repair_or_verify_rc_${rc}"
  fi
  printf 'END|%s|RC=%s|AUDIT_LOG=%s\n' "$TASK" "$rc" "$AUDIT_LOG"
  if test "$rc" -eq 0; then
    printf 'Q131_SELINUX_LABEL_REPAIR_AND_VERIFY=PASS_ROLE_ACTIVATED_LOCAL_ONLY\n'
  else
    printf 'Q131_SELINUX_LABEL_REPAIR_AND_VERIFY=FAIL_ROLLED_BACK_SAFE_QUIET\n'
  fi
  printf 'SECRET_VALUES=NOT_OUTPUT_NOT_HASHED\n'
  printf 'END|%s|RC=%s|AUDIT_LOG=%s\n' "$TASK" "$rc" "$AUDIT_LOG" >&3
  if test "$rc" -eq 0; then
    printf 'Q131_SELINUX_LABEL_REPAIR_AND_VERIFY=PASS_ROLE_ACTIVATED_LOCAL_ONLY\n' >&3
  else
    printf 'Q131_SELINUX_LABEL_REPAIR_AND_VERIFY=FAIL_ROLLED_BACK_SAFE_QUIET\n' >&3
  fi
  printf 'SECRET_VALUES=NOT_OUTPUT_NOT_HASHED\n' >&3
}
trap finalize EXIT

echo "AUDIT_BEGIN|${TASK}|$(timestamp)"
echo "RUN_ID=${RUN_ID}"
echo "ROOT_CAUSE=SELINUX_USER_TMP_T_CAUSING_SYSTEMD_203_EXEC"
echo "=== PRE-REPAIR CONTEXTS ==="
ls -Zd "${RESTORE_PATHS[@]}"
echo "PORT_6387_ACTION=${PORT_6387_ACTION}"
echo "SECRET_VALUES=NOT_OUTPUT_NOT_HASHED"

mutation_started=1
for ((i=0; i<${#FCONTEXT_REGEXES[@]}; i++)); do
  semanage fcontext -a -t "${FCONTEXT_TYPES[$i]}" "${FCONTEXT_REGEXES[$i]}"
  created_rule_count=$((created_rule_count + 1))
done
if test "$PORT_6387_ACTION" = "ADD"; then
  semanage port -a -t redis_port_t -p tcp 6387
  port_added=1
fi

restorecon -RFv "${RESTORE_PATHS[@]}"

echo "=== POST-REPAIR CONTEXTS ==="
ls -Zd "${RESTORE_PATHS[@]}"
for path in /opt/redis/5.0.14/bin/redis-server /opt/redis/7.2.14/bin/redis-server; do
  case "$(stat -c '%C' "$path")" in
    *:redis_exec_t:*) ;;
    *) echo "ERROR: redis_exec_t was not applied: ${path}"; exit 1 ;;
  esac
done

systemctl reset-failed redis5.service redis7.service
systemctl enable --now redis5.service redis7.service
systemctl disable --now sentinel5.service sentinel7.service orchestrator.service

"$FIXED_ENTRY" q131 verify --run-id "${RUN_ID}-verify"
"$FIXED_ENTRY" q131 archive --source-run-id "${RUN_ID}-verify"

test "$(systemctl is-enabled redis5.service)" = "enabled"
test "$(systemctl is-active redis5.service)" = "active"
test "$(systemctl is-enabled redis7.service)" = "enabled"
test "$(systemctl is-active redis7.service)" = "active"
for unit in sentinel5.service sentinel7.service orchestrator.service; do
  test "$(systemctl is-enabled "$unit" 2>/dev/null || true)" = "disabled"
  test "$(systemctl is-active "$unit" 2>/dev/null || true)" = "inactive"
done

echo "=== FINAL UNIT AND LISTENER STATE ==="
for unit in redis5.service redis7.service sentinel5.service sentinel7.service orchestrator.service; do
  printf 'UNIT|%s|enabled=%s|active=%s\n' \
    "$unit" \
    "$(systemctl is-enabled "$unit" 2>/dev/null || true)" \
    "$(systemctl is-active "$unit" 2>/dev/null || true)"
done
ss -lntupH |
  grep -E ':(6379|6387|26379|26387|3000|10008)([[:space:]]|$)' || true

write_result PASS ROLE_ACTIVATED_LOCAL_ONLY
success=1
echo "Q131_SELINUX_LABEL_REPAIR_AND_VERIFY=PASS_ROLE_ACTIVATED_LOCAL_ONLY"
echo "Q132=LOCKED_NOT_EXECUTED"
echo "SENTINEL_HA=NOT_CONFIGURED"
echo "VIP=DISABLED"
echo "SECRET_VALUES=NOT_OUTPUT_NOT_HASHED"
