#!/usr/bin/env bash
set -Eeuo pipefail
set +x
umask 077

readonly LABEL='Q102_NGINX01_OFFLINE_SEMANAGE_INSTALL'
readonly EXPECTED_HOST='sqjcnginx01'
readonly EXPECTED_IP='10.2.203.102'
readonly EXPECTED_OS='openEuler release 24.03 (LTS-SP1)'
readonly BUNDLE='/root/q102-nginx01-semanage-rpms.tar.gz'
readonly BUNDLE_SHA256='14093392a62cf648fc18cb46864ddcff533f2cfd1003fa35cb94d646a4175c63'
readonly MANIFEST_SHA256='1a7fb47a34d1c91b8fc01720e965955a7bf163d94de361614be3ff863ec7aee7'
readonly KEY_SHA256='006e79d37c10e74c24df6d07c4efc4176515cec009daa5ed493b06f5b6ef39c1'
readonly KEY_FINGERPRINT='8AA16BF9F2CA5244010DCA963B477C60B675600B'
readonly SCRIPT_PATH="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)/$(basename -- "${BASH_SOURCE[0]}")"

RUN_ID="q102-nginx01-semanage-$(date -u +%Y%m%dT%H%M%SZ)"
AUDIT_DIR="/var/backups/zhct-q102-offline-semanage/${RUN_ID}"
AUDIT_LOG="${AUDIT_DIR}/operator.log"
WORK=''
RPMDB=''
GNUPGHOME_TMP=''
DNF_HISTORY_BEFORE='NOT_CAPTURED'
DNF_HISTORY_AFTER='NOT_CAPTURED'
package_install_started=0
success=0
static_mode=0

finish() {
    local rc=$?
    set +e

    [[ -z "$WORK" || ! -d "$WORK" || "$WORK" != /var/tmp/q102-nginx01-semanage.* ]] || rm -rf -- "$WORK"
    [[ -z "$RPMDB" || ! -d "$RPMDB" || "$RPMDB" != /var/tmp/q102-nginx01-rpmdb.* ]] || rm -rf -- "$RPMDB"
    [[ -z "$GNUPGHOME_TMP" || ! -d "$GNUPGHOME_TMP" || "$GNUPGHOME_TMP" != /var/tmp/q102-nginx01-gnupg.* ]] || rm -rf -- "$GNUPGHOME_TMP"

    if [[ "$static_mode" -eq 1 ]]; then
        printf '%s\n' \
            'Q102_NGINX01_OFFLINE_SEMANAGE_STATIC_VALIDATE_PASS' \
            "END|${LABEL}|RC=${rc}|AUDIT_LOG=NOT_CREATED_STATIC_VALIDATE"
        exit "$rc"
    fi

    if [[ "$rc" -eq 0 && "$success" -eq 1 ]]; then
        printf '%s\n' 'Q102_NGINX01_OFFLINE_SEMANAGE_INSTALL_PASS'
    else
        if [[ "$package_install_started" -eq 0 ]]; then
            printf '%s\n' 'PACKAGE_TRANSACTION=NOT_STARTED'
        else
            printf '%s\n' \
                'PACKAGE_TRANSACTION=STARTED_REVIEW_DNF_HISTORY' \
                'INSTALLED_SUPPORT_PACKAGES=RETAINED_NO_AUTOMATIC_UNINSTALL'
            rpm -q checkpolicy policycoreutils-python-utils python3-IPy \
                python3-audit python3-libselinux python3-libsemanage \
                python3-policycoreutils python3-setools 2>&1 || true
        fi
        printf '%s\n' 'Q102_NGINX01_OFFLINE_SEMANAGE_INSTALL_FAIL_STOP'
    fi

    printf '%s\n' \
        "DNF_HISTORY_BEFORE=${DNF_HISTORY_BEFORE}" \
        "DNF_HISTORY_AFTER=${DNF_HISTORY_AFTER}" \
        'SELINUX_RULE_MUTATION=ZERO' \
        'SERVICE_MUTATION=ZERO' \
        'FIREWALL_MUTATION=ZERO' \
        'SENTINEL_ACTIVATION=NOT_PERFORMED' \
        "END|${LABEL}|RC=${rc}|AUDIT_LOG=${AUDIT_LOG}"
    exit "$rc"
}
trap finish EXIT

static_validate() {
    bash -n "$SCRIPT_PATH"
    grep -Fq "readonly EXPECTED_HOST='sqjcnginx01'" "$SCRIPT_PATH"
    grep -Fq "readonly EXPECTED_IP='10.2.203.102'" "$SCRIPT_PATH"
    grep -Fq "readonly BUNDLE_SHA256='${BUNDLE_SHA256}'" "$SCRIPT_PATH"
    grep -Fq -- "--disablerepo='*'" "$SCRIPT_PATH"
    grep -Fq -- '--assumeno install "${rpms[@]}"' "$SCRIPT_PATH"
    grep -Fq -- '-y install "${rpms[@]}"' "$SCRIPT_PATH"

    if grep -En \
        '^[[:space:]]*(systemctl[[:space:]]+(start|stop|restart|reload|enable|disable)|firewall-cmd|semanage[[:space:]]+(fcontext|port)[[:space:]]+(-a|-m|-d)|chcon|restorecon|setenforce|ip[[:space:]]+addr[[:space:]]+(add|del))' \
        "$SCRIPT_PATH"; then
        return 1
    fi

    printf '%s\n' \
        'STATIC_TARGET=sqjcnginx01/10.2.203.102' \
        'STATIC_OFFLINE_RPM_COUNT=8' \
        'STATIC_DNF_PLAN=INSTALL_ONLY' \
        'STATIC_SELINUX_RULE_WRITE=FORBIDDEN' \
        'STATIC_SERVICE_AND_FIREWALL_WRITE=FORBIDDEN'
}

printf 'BEGIN|%s|%s\n' "$LABEL" "$(date '+%Y-%m-%dT%H:%M:%S%z')"

if [[ "${1:-}" == '--static-validate' ]]; then
    static_mode=1
    static_validate
    exit 0
fi

[[ $# -eq 0 ]] || { printf '%s\n' 'ERROR: arguments are not accepted' >&2; exit 1; }
[[ "$(id -u)" -eq 0 ]] || { printf '%s\n' 'ERROR: root is required' >&2; exit 1; }
actual_host="$(hostname -s)"
[[ "$actual_host" == "$EXPECTED_HOST" ]] || {
    printf 'ERROR: hostname mismatch: %s\n' "$actual_host" >&2
    exit 1
}
ip -o -4 addr show scope global |
    awk '{split($4,a,"/"); print a[1]}' |
    grep -Fxq "$EXPECTED_IP" || {
        printf '%s\n' 'ERROR: expected IP is absent' >&2
        exit 1
    }
grep -Fq "$EXPECTED_OS" /etc/openEuler-release || {
    printf '%s\n' 'ERROR: openEuler release mismatch' >&2
    exit 1
}
[[ "$(uname -m)" == 'x86_64' ]] || {
    printf '%s\n' 'ERROR: architecture mismatch' >&2
    exit 1
}
printf 'Q102_NGINX01_OFFLINE_SEMANAGE_IDENTITY_PASS|host=%s|ip=%s|os=%s\n' \
    "$actual_host" "$EXPECTED_IP" "$EXPECTED_OS"

for command_name in tar gzip sha256sum rpm dnf gpg ip awk grep stat \
                    install tee mktemp systemctl ss; do
    command -v "$command_name" >/dev/null 2>&1 || {
        printf 'ERROR: required command is missing: %s\n' "$command_name" >&2
        exit 1
    }
done

[[ -f "$BUNDLE" && ! -L "$BUNDLE" ]]
[[ "$(stat -c '%U:%G' "$BUNDLE")" == 'root:root' ]]
printf '%s  %s\n' "$BUNDLE_SHA256" "$BUNDLE" | sha256sum -c -

for unit in redis5.service redis7.service sentinel5.service sentinel7.service keepalived.service; do
    [[ "$(systemctl is-active "$unit" 2>/dev/null || true)" != active ]]
done

orchestrator_enabled_before="$(systemctl is-enabled orchestrator.service 2>/dev/null || true)"
orchestrator_active_before="$(systemctl is-active orchestrator.service 2>/dev/null || true)"
orchestrator_pid_before="$(systemctl show orchestrator.service -p MainPID --value)"

if tar -tzf "$BUNDLE" | grep -Eq '(^/|(^|/)\.\.(/|$))'; then
    printf '%s\n' 'ERROR: unsafe archive member' >&2
    exit 1
fi

WORK="$(mktemp -d /var/tmp/q102-nginx01-semanage.XXXXXX)"
tar --no-same-owner -xzf "$BUNDLE" -C "$WORK"
ROOT="$WORK/q132-semanage-offline-rpms"
[[ -d "$ROOT" && ! -L "$ROOT" ]]
[[ "$(find "$ROOT" -mindepth 1 -type l -print -quit | wc -l)" -eq 0 ]]
[[ "$(find "$ROOT/rpms" -maxdepth 1 -type f -name '*.rpm' | wc -l)" -eq 8 ]]

printf '%s  %s\n' "$MANIFEST_SHA256" "$ROOT/SHA256SUMS" | sha256sum -c -
printf '%s  %s\n' "$KEY_SHA256" "$ROOT/RPM-GPG-KEY-openEuler" | sha256sum -c -
(cd "$ROOT" && sha256sum -c SHA256SUMS)

GNUPGHOME_TMP="$(mktemp -d /var/tmp/q102-nginx01-gnupg.XXXXXX)"
chmod 0700 "$GNUPGHOME_TMP"
fingerprint="$(
    GNUPGHOME="$GNUPGHOME_TMP" gpg --batch --show-keys --with-colons \
        "$ROOT/RPM-GPG-KEY-openEuler" 2>/dev/null |
        awk -F: '$1 == "fpr" && value == "" {value=$10} END {print value}'
)"
[[ "$fingerprint" == "$KEY_FINGERPRINT" ]]
printf 'Q102_NGINX01_OFFLINE_KEY_GATE=PASS|fingerprint=%s\n' "$fingerprint"

mapfile -d '' rpms < <(
    find "$ROOT/rpms" -maxdepth 1 -type f -name '*.rpm' -print0 | sort -z
)
[[ "${#rpms[@]}" -eq 8 ]]

expected_nevra="$(printf '%s\n' \
    'checkpolicy-0:3.5-1.oe2403sp1.x86_64' \
    'policycoreutils-python-utils-0:3.5-3.oe2403sp1.noarch' \
    'python3-IPy-0:1.01-2.oe2403sp1.noarch' \
    'python3-audit-1:3.1.2-7.oe2403sp1.x86_64' \
    'python3-libselinux-0:3.5-3.oe2403sp1.x86_64' \
    'python3-libsemanage-0:3.5-1.oe2403sp1.x86_64' \
    'python3-policycoreutils-0:3.5-3.oe2403sp1.noarch' \
    'python3-setools-0:4.4.3-1.oe2403sp1.x86_64')"

actual_nevra="$(
    LC_ALL=C rpm -qp \
        --qf '%{NAME}-%{EPOCHNUM}:%{VERSION}-%{RELEASE}.%{ARCH}\n' \
        "${rpms[@]}" 2>/dev/null | LC_ALL=C sort
)"
[[ "$actual_nevra" == "$expected_nevra" ]]
printf 'Q102_NGINX01_OFFLINE_NEVRA_GATE=PASS|rpm_count=8\n'

RPMDB="$(mktemp -d /var/tmp/q102-nginx01-rpmdb.XXXXXX)"
rpm --dbpath "$RPMDB" --initdb
rpm --dbpath "$RPMDB" --import "$ROOT/RPM-GPG-KEY-openEuler"
for rpm_file in "${rpms[@]}"; do
    signature_result="$(
        LC_ALL=C rpm --dbpath "$RPMDB" \
            --define '_pkgverify_level signature' -K "$rpm_file"
    )"
    grep -Fq 'digests signatures OK' <<<"$signature_result"
done
printf 'Q102_NGINX01_OFFLINE_RPM_SIGNATURE_GATE=PASS|rpm_count=8\n'

install -d -o root -g root -m 0700 "$AUDIT_DIR"
exec > >(tee -a "$AUDIT_LOG") 2>&1
printf 'AUDIT_BEGIN|%s|%s\n' "$LABEL" "$(date '+%Y-%m-%dT%H:%M:%S%z')"
printf 'RUN_ID=%s\n' "$RUN_ID"

plan_file="$AUDIT_DIR/dnf-assumeno.log"
set +e
LC_ALL=C dnf --disablerepo='*' --setopt=strict=True \
    --setopt=install_weak_deps=False --setopt=localpkg_gpgcheck=False \
    --assumeno install "${rpms[@]}" >"$plan_file" 2>&1
plan_rc=$?
set -e
cat "$plan_file"
[[ "$plan_rc" -eq 1 ]]
grep -Eq '^Install[[:space:]]+[1-8][[:space:]]+Package' "$plan_file"
! grep -Eq '^(Upgrade|Downgrade|Remove|Reinstall)[[:space:]]' "$plan_file"
! grep -Eq '^(Upgrading|Downgrading|Removing|Reinstalling):' "$plan_file"
printf 'Q102_NGINX01_OFFLINE_DNF_PLAN_PASS|action=Install-only\n'

DNF_HISTORY_BEFORE="$(
    dnf history list 2>/dev/null |
        awk 'NR > 2 && $1 ~ /^[0-9]+$/ {print $1; exit}'
)"
DNF_HISTORY_BEFORE="${DNF_HISTORY_BEFORE:-NONE}"

package_install_started=1
LC_ALL=C dnf --disablerepo='*' --setopt=strict=True \
    --setopt=install_weak_deps=False --setopt=localpkg_gpgcheck=False \
    -y install "${rpms[@]}"

DNF_HISTORY_AFTER="$(
    dnf history list 2>/dev/null |
        awk 'NR > 2 && $1 ~ /^[0-9]+$/ {print $1; exit}'
)"
DNF_HISTORY_AFTER="${DNF_HISTORY_AFTER:-NONE}"

installed_nevra="$(
    LC_ALL=C rpm -q \
        --qf '%{NAME}-%{EPOCHNUM}:%{VERSION}-%{RELEASE}.%{ARCH}\n' \
        checkpolicy policycoreutils-python-utils python3-IPy python3-audit \
        python3-libselinux python3-libsemanage python3-policycoreutils \
        python3-setools | LC_ALL=C sort
)"
[[ "$installed_nevra" == "$expected_nevra" ]]

command -v semanage >/dev/null 2>&1
semanage fcontext -l >/dev/null
semanage port -l >/dev/null

for unit in redis5.service redis7.service sentinel5.service sentinel7.service keepalived.service; do
    [[ "$(systemctl is-active "$unit" 2>/dev/null || true)" != active ]]
done
[[ "$(systemctl is-enabled orchestrator.service 2>/dev/null || true)" == "$orchestrator_enabled_before" ]]
[[ "$(systemctl is-active orchestrator.service 2>/dev/null || true)" == "$orchestrator_active_before" ]]
[[ "$(systemctl show orchestrator.service -p MainPID --value)" == "$orchestrator_pid_before" ]]

success=1
printf '%s\n' \
    'Q102_NGINX01_SEMANAGE_COMMAND_GATE=PASS' \
    'Q102_NGINX01_SEMANAGE_FCONTEXT_READONLY_GATE=PASS' \
    'Q102_NGINX01_SEMANAGE_PORT_READONLY_GATE=PASS' \
    'Q102_NGINX01_SENTINEL_STAGE=NOT_STARTED'
