#!/usr/bin/env bash

set -Eeuo pipefail
umask 077

EXPECTED_HOST="sqjcapp01"
EXPECTED_IP="10.2.203.104"
PACKAGE_ARCHIVE="/root/sanquan-app-q110-112.tar.gz"
PACKAGE_SHA256="f0133d79e006d3be738ceb38883e740fc895b83578ab28657ce4fe5eaa3f953b"
PACKAGE_TOPLEVEL="sanquan-app-q110-112"
PACKAGE_ROOT="/root/${PACKAGE_TOPLEVEL}"
ENTRY="${PACKAGE_ROOT}/scripts/app-q110-112.sh"
ENTRY_SHA256="33d10c2407b7382bd43953b6b7b60dd28021eecf599e07a2f963053e87336750"
ACTIVATE_SHA256="1cc3ae5fc7ff7485968018d4ec5ce7a5506539afce90685753cc95c7c40de516"
VERIFY_SHA256="bf81fa1e6ca8db1550e7d1ec665c188cb9808e707094006e3ae109f504f16364"

Q110_STAGE3B_RUN="q110-app02-postclone-execute-20260729T185051+0800"
Q110_STAGE3B_RESULT="PASS"
Q110_STAGE3B_OPERATOR_RC="0"

EXECUTE_GATE="/root/sanquan-app-q110-112-gates/Q111_EXECUTE.pass"
EXECUTE_GATE_SHA256="5660c78274ae8cf79dd8421d9b6007c720333d3d33dd9646ef0994a12a153421"

RUN_ID=""
BACKUP_DIR=""
AUDIT_LOG="STDOUT_ONLY_BEFORE_PERSISTENT_WRITE"
PACKAGE_INSTALLED_BY_WRAPPER=0
EXECUTION_STARTED=0
ACTIVATION_PASS=0
VERIFY_PASS=0
ARCHIVE_PASS=0
ROLLBACK_STATE="NOT_REQUIRED_PREWRITE_GATE"
FINAL_RESULT="FAIL"

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

unit_state() {
    local unit=$1
    local enabled active
    enabled=$(systemctl is-enabled "$unit" 2>/dev/null || true)
    active=$(systemctl is-active "$unit" 2>/dev/null || true)
    printf '%s enabled=%s active=%s\n' "$unit" "$enabled" "$active"
}

assert_unit_state() {
    local unit=$1 expected_enabled=$2 expected_active=$3
    local enabled active
    enabled=$(systemctl is-enabled "$unit" 2>/dev/null || true)
    active=$(systemctl is-active "$unit" 2>/dev/null || true)
    printf '%s enabled=%s active=%s\n' "$unit" "$enabled" "$active"
    [[ "$enabled" == "$expected_enabled" && "$active" == "$expected_active" ]]
}

assert_no_forbidden_runtime() {
    ! pgrep -x supervisord
    ! pgrep -x crond
    ! pgrep -x php
}

rollback_after_execution_failure() {
    local rollback_bad=0

    echo Q111_APP01_ROLLBACK_BEGIN
    timestamp

    systemctl disable --now \
        nginx.service php-fpm73.service php-fpm74.service \
        supervisor.service crond.service || rollback_bad=1
    systemctl reset-failed \
        nginx.service php-fpm73.service php-fpm74.service \
        supervisor.service crond.service || true

    if [[ -n "$BACKUP_DIR" && -f "$BACKUP_DIR/prechange-files.tar.gz" ]]; then
        tar -xzf "$BACKUP_DIR/prechange-files.tar.gz" -C / || rollback_bad=1
        systemctl daemon-reload || rollback_bad=1
    else
        echo Q111_APP01_ROLLBACK_BACKUP=ABSENT
        rollback_bad=1
    fi

    for unit in nginx.service php-fpm73.service php-fpm74.service supervisor.service crond.service; do
        assert_unit_state "$unit" disabled inactive || rollback_bad=1
    done
    if ss -H -lntup | grep -Eq ':(8081|18081)([[:space:]]|$)'; then
        echo Q111_APP01_ROLLBACK_TARGET_LISTENER=STILL_PRESENT
        rollback_bad=1
    else
        echo Q111_APP01_ROLLBACK_TARGET_LISTENER=ZERO
    fi
    for process_name in nginx php-fpm supervisord crond php; do
        if pgrep -a -x "$process_name"; then
            rollback_bad=1
        fi
    done

    if [[ "$rollback_bad" -eq 0 ]]; then
        ROLLBACK_STATE="PASS_DISABLED_INACTIVE_PRECHANGE_FILES_RESTORED"
    else
        ROLLBACK_STATE="FAIL_MANUAL_REVIEW_REQUIRED"
    fi
    echo "Q111_APP01_ROLLBACK_STATE=$ROLLBACK_STATE"
    echo Q111_APP01_ROLLBACK_END
}

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

    if [[ "$rc" -ne 0 && "$EXECUTION_STARTED" -eq 1 ]]; then
        rollback_after_execution_failure
    fi

    if [[ "$rc" -eq 0 ]]; then
        FINAL_RESULT="PASS"
        ROLLBACK_STATE="NOT_REQUIRED_ACTIVATE_VERIFY_ARCHIVE_PASS"
    fi

    echo "Q111_APP01_RUN_ID=${RUN_ID:-NOT_CREATED}"
    echo "Q111_APP01_TARGET=${EXPECTED_HOST}/${EXPECTED_IP}"
    echo "Q111_APP01_Q110_STAGE3B_RUN=$Q110_STAGE3B_RUN"
    echo "Q111_APP01_Q110_STAGE3B_RESULT=$Q110_STAGE3B_RESULT"
    echo "Q111_APP01_PACKAGE_SHA256=$PACKAGE_SHA256"
    echo "Q111_APP01_PACKAGE_INSTALLED_BY_WRAPPER=$PACKAGE_INSTALLED_BY_WRAPPER"
    echo "Q111_APP01_ACTIVATION_PASS=$ACTIVATION_PASS"
    echo "Q111_APP01_VERIFY_PASS=$VERIFY_PASS"
    echo "Q111_APP01_ARCHIVE_PASS=$ARCHIVE_PASS"
    echo "Q111_APP01_BACKUP_DIR=${BACKUP_DIR:-NOT_CREATED}"
    echo "Q111_APP01_AUDIT_LOG=$AUDIT_LOG"
    echo "Q111_APP01_RC=$rc"
    echo "Q111_APP01_RESULT=$FINAL_RESULT"
    echo "Q111_APP01_ROLLBACK_STATE=$ROLLBACK_STATE"
    echo Q111_APP01_ALLOWED_UNITS=nginx.service,php-fpm73.service,php-fpm74.service
    echo Q111_APP01_FORBIDDEN_UNITS=supervisor.service,crond.service
    echo Q111_APP01_WORKERS_CONSUMERS_SINGLETON_TASKS=NOT_STARTED
    echo Q111_APP01_Q112=NOT_PERFORMED
    echo Q111_APP01_END
    exit "$rc"
}
trap finish EXIT

echo Q111_APP01_BEGIN
timestamp
echo "EXPECTED_HOST=$EXPECTED_HOST EXPECTED_IP=$EXPECTED_IP"
echo CONTROLLER_LOCAL_PREP_AUTHORIZATION=CONTROLLER_Q111_LOCAL_FILEIZED_ENTRY_PREP_AUTHORIZED
echo REMOTE_EXECUTE_AUTHORIZATION=REQUIRES_LOCKED_GATE_FILE
echo "Q110_STAGE3B_RUN=$Q110_STAGE3B_RUN"
echo "Q110_STAGE3B_RESULT=$Q110_STAGE3B_RESULT"
echo "Q110_STAGE3B_OPERATOR_RC=$Q110_STAGE3B_OPERATOR_RC"

[[ $# -eq 1 && "$1" == "--execute" ]] || {
    echo "ERROR: exact --execute argument is required" >&2
    exit 2
}
[[ $(id -u) -eq 0 ]] || {
    echo "ERROR: root required" >&2
    exit 10
}

# All checks through Q111_APP01_PREWRITE_GATE_RESULT are read-only. On a wrong
# host, execution stops before creating directories, logs, backups, or extracts.
actual_host=$(hostname -s)
echo "ACTUAL_HOST=$actual_host"
[[ "$actual_host" == "$EXPECTED_HOST" ]] || {
    echo "ERROR: hostname mismatch; persistent write not started" >&2
    exit 11
}
ip -o -4 addr show scope global
ip -o -4 addr show scope global |
    grep -Eq "(^|[[:space:]])${EXPECTED_IP//./\\.}/" || {
    echo "ERROR: expected IPv4 missing; persistent write not started" >&2
    exit 12
}

[[ -f "$EXECUTE_GATE" && ! -L "$EXECUTE_GATE" ]] || {
    echo "ERROR: controller Q111 execute gate is absent or unsafe" >&2
    exit 13
}
[[ $(stat -c '%U:%G' "$EXECUTE_GATE") == root:root ]] || {
    echo "ERROR: controller Q111 execute gate owner mismatch" >&2
    exit 14
}
gate_mode=$(stat -c '%a' "$EXECUTE_GATE")
[[ "$gate_mode" == 400 || "$gate_mode" == 600 ]] || {
    echo "ERROR: controller Q111 execute gate mode must be 0400 or 0600" >&2
    exit 15
}
echo "${EXECUTE_GATE_SHA256}  ${EXECUTE_GATE}" | sha256sum -c -
grep -Fxq 'FORMAT=Q111_EXECUTE_GATE_V1' "$EXECUTE_GATE"
grep -Fxq 'CONTROLLER_Q111_EXECUTE_AUTHORIZED' "$EXECUTE_GATE"
grep -Fxq "TARGET=${EXPECTED_HOST}/${EXPECTED_IP}" "$EXECUTE_GATE"
grep -Fxq "PACKAGE_SHA256=${PACKAGE_SHA256}" "$EXECUTE_GATE"
grep -Fxq "Q110_STAGE3B_RUN=${Q110_STAGE3B_RUN}" "$EXECUTE_GATE"
grep -Fxq 'Q110_STAGE3B_RESULT=PASS' "$EXECUTE_GATE"

[[ -f "$PACKAGE_ARCHIVE" && ! -L "$PACKAGE_ARCHIVE" ]] || {
    echo "ERROR: package archive is absent or unsafe" >&2
    exit 16
}
[[ $(stat -c '%U:%G' "$PACKAGE_ARCHIVE") == root:root ]] || {
    echo "ERROR: package archive owner mismatch" >&2
    exit 17
}
echo "${PACKAGE_SHA256}  ${PACKAGE_ARCHIVE}" | sha256sum -c -

archive_members=$(tar -tzf "$PACKAGE_ARCHIVE" 2>/dev/null)
[[ -n "$archive_members" ]]
if awk -F/ '
    $1 == "" || $1 == "." || $1 == ".." { bad=1 }
    {
        for (i=1; i<=NF; i++) {
            if ($i == "..") bad=1
        }
    }
    END { exit bad ? 0 : 1 }
' <<<"$archive_members"; then
    echo "ERROR: archive contains unsafe path" >&2
    exit 18
fi
archive_top=$(awk -F/ 'NF && $1 != "." {print $1}' <<<"$archive_members" | sort -u)
[[ "$archive_top" == "$PACKAGE_TOPLEVEL" ]] || {
    echo "ERROR: archive top-level mismatch" >&2
    exit 19
}

echo "e637ca657a897c6d0b689bbd6347f04516baf5c9f32350cd1e8a2071c211a0a5  /opt/zhct-deploy/common/scripts/activate-role.sh" | sha256sum -c -
echo "e3eae16aa5c87f43b56495fc71de378a947578cabe18b507e8fed01ac1f32025  /opt/zhct-deploy/common/bin/role-action.sh" | sha256sum -c -
echo "03c6b13a0def5bf3954e069581b6f9fbe0f81f24285ba2319b6279c0406d70aa  /opt/zhct-deploy/roles/app/hooks.sh" | sha256sum -c -
echo "d43e179c108f6db551b452256f867d6d4f6fc9e031c1d4def2f343966af8d684  /opt/zhct-deploy/roles/app/manifests/systemd-units.tsv" | sha256sum -c -

grep -Fxq 'ROLE=app' /etc/zhct/node.env
grep -Fxq 'NODE_KEY=app01' /etc/zhct/node.env
grep -Fxq "NODE_IP=$EXPECTED_IP" /etc/zhct/node.env
grep -Fxq "NODE_NAME=$EXPECTED_HOST" /etc/zhct/node.env
[[ -s /etc/machine-id ]]
[[ -s /etc/ssh/ssh_host_ed25519_key ]]

for required_path in \
    /usr/local/nginx/sbin/nginx \
    /usr/local/php7.3/sbin/php-fpm \
    /usr/local/php7.4/sbin/php-fpm \
    /usr/local/sbin/singleton-state-check.sh \
    /etc/supervisor/conf.d/sanquan.conf \
    /etc/cron.d/zhct-sanquan \
    /etc/systemd/system/nginx.service \
    /etc/systemd/system/php-fpm73.service \
    /etc/systemd/system/php-fpm74.service \
    /etc/systemd/system/supervisor.service; do
    [[ -e "$required_path" && ! -L "$required_path" ]]
done

/usr/local/nginx/sbin/nginx -t
/usr/local/php7.3/sbin/php-fpm -tt --fpm-config /usr/local/php7.3/etc/php-fpm.conf
/usr/local/php7.4/sbin/php-fpm -tt --fpm-config /usr/local/php7.4/etc/php-fpm.conf
/usr/local/sbin/singleton-state-check.sh \
    --node app01 \
    --config /etc/supervisor/conf.d/sanquan.conf \
    --cron /etc/cron.d/zhct-sanquan

for unit in nginx.service php-fpm73.service php-fpm74.service supervisor.service crond.service; do
    assert_unit_state "$unit" disabled inactive
done
! ss -H -lntup | grep -Eq ':(8081|18081)([[:space:]]|$)'
for process_name in nginx php-fpm supervisord crond php; do
    ! pgrep -a -x "$process_name"
done

if [[ -e "$PACKAGE_ROOT" || -L "$PACKAGE_ROOT" ]]; then
    [[ -d "$PACKAGE_ROOT" && ! -L "$PACKAGE_ROOT" ]] || {
        echo "ERROR: existing package root is unsafe" >&2
        exit 20
    }
    echo "${ENTRY_SHA256}  ${ENTRY}" | sha256sum -c -
    echo "${ACTIVATE_SHA256}  ${PACKAGE_ROOT}/scripts/app-q111-q112-activate.sh" | sha256sum -c -
    echo "${VERIFY_SHA256}  ${PACKAGE_ROOT}/scripts/app-q110-q112-verify.sh" | sha256sum -c -
    "$ENTRY" --static-validate
    echo Q111_APP01_PACKAGE_ROOT_STATE=EXISTING_VALIDATED
else
    echo Q111_APP01_PACKAGE_ROOT_STATE=ABSENT_WILL_INSTALL_AFTER_GATE
fi

echo Q111_APP01_PREWRITE_GATE_RESULT=PASS
echo Q111_APP01_PREWRITE_PERSISTENT_MUTATION=ZERO

# Persistent write phase starts only after every identity, authorization,
# package, framework, config, singleton, unit, listener, and process gate passes.
RUN_ID="q111-app01-activate-$(date '+%Y%m%dT%H%M%S%z')"

if [[ ! -d "$PACKAGE_ROOT" ]]; then
    extract_parent="/root/.q111-package-extract-${RUN_ID}"
    [[ ! -e "$extract_parent" && ! -L "$extract_parent" ]]
    install -d -m 0700 -o root -g root "$extract_parent"
    tar -xzf "$PACKAGE_ARCHIVE" -C "$extract_parent" --no-same-owner
    [[ -d "$extract_parent/$PACKAGE_TOPLEVEL" && ! -L "$extract_parent/$PACKAGE_TOPLEVEL" ]]
    mv "$extract_parent/$PACKAGE_TOPLEVEL" "$PACKAGE_ROOT"
    rmdir "$extract_parent"
    PACKAGE_INSTALLED_BY_WRAPPER=1
fi

echo "${ENTRY_SHA256}  ${ENTRY}" | sha256sum -c -
echo "${ACTIVATE_SHA256}  ${PACKAGE_ROOT}/scripts/app-q111-q112-activate.sh" | sha256sum -c -
echo "${VERIFY_SHA256}  ${PACKAGE_ROOT}/scripts/app-q110-q112-verify.sh" | sha256sum -c -
"$ENTRY" --static-validate

BACKUP_DIR="/var/backups/zhct-q111/manual-activate/${RUN_ID}"
[[ ! -e "$BACKUP_DIR" && ! -L "$BACKUP_DIR" ]]
install -d -m 0700 -o root -g root "$BACKUP_DIR"
AUDIT_LOG="$BACKUP_DIR/q111-app01-activate-verify.log"
exec > >(tee "$AUDIT_LOG") 2>&1

echo Q111_APP01_PERSISTENT_PHASE_BEGIN
timestamp
echo "Q111_APP01_PREWRITE_GATE_RESULT=PASS"
echo "Q111_APP01_EXECUTE_GATE_SHA256=$EXECUTE_GATE_SHA256"
echo "Q111_APP01_PACKAGE_SHA256=$PACKAGE_SHA256"
echo "Q111_APP01_ENTRY_SHA256=$ENTRY_SHA256"
echo "Q111_APP01_RUN_ID=$RUN_ID"

for unit in nginx.service php-fpm73.service php-fpm74.service supervisor.service crond.service; do
    unit_state "$unit"
done >"$BACKUP_DIR/prechange-unit-state.tsv"
chmod 0600 "$BACKUP_DIR/prechange-unit-state.tsv"

tar -czf "$BACKUP_DIR/prechange-files.tar.gz" -C / \
    etc/zhct/node.env \
    etc/supervisor/conf.d/sanquan.conf \
    etc/cron.d/zhct-sanquan \
    etc/systemd/system/nginx.service \
    etc/systemd/system/php-fpm73.service \
    etc/systemd/system/php-fpm74.service \
    etc/systemd/system/supervisor.service \
    usr/local/nginx/conf/nginx.conf \
    usr/local/nginx/conf/conf.d/app.conf \
    usr/local/php7.3/etc/php.ini \
    usr/local/php7.3/etc/php-fpm.conf \
    usr/local/php7.4/etc/php.ini \
    usr/local/php7.4/etc/php-fpm.conf
chmod 0600 "$BACKUP_DIR/prechange-files.tar.gz"
sha256sum "$BACKUP_DIR/prechange-files.tar.gz" >"$BACKUP_DIR/prechange-files.tar.gz.sha256"
chmod 0600 "$BACKUP_DIR/prechange-files.tar.gz.sha256"
(cd "$BACKUP_DIR" && sha256sum -c prechange-files.tar.gz.sha256)
echo Q111_APP01_BACKUP_RESULT=PASS

EXECUTION_STARTED=1
"$ENTRY" activate app01 "$RUN_ID" --execute
ACTIVATION_PASS=1

"$ENTRY" verify app01 "$RUN_ID" active
VERIFY_PASS=1

/usr/local/nginx/sbin/nginx -t
/usr/local/php7.3/sbin/php-fpm -tt --fpm-config /usr/local/php7.3/etc/php-fpm.conf
/usr/local/php7.4/sbin/php-fpm -tt --fpm-config /usr/local/php7.4/etc/php-fpm.conf
/usr/local/sbin/singleton-state-check.sh \
    --node app01 \
    --config /etc/supervisor/conf.d/sanquan.conf \
    --cron /etc/cron.d/zhct-sanquan

for unit in nginx.service php-fpm73.service php-fpm74.service; do
    assert_unit_state "$unit" enabled active
done
for unit in supervisor.service crond.service; do
    assert_unit_state "$unit" disabled inactive
done
ss -H -ltn | awk '$4 ~ /:8081$/ {found=1} END {exit !found}'
ss -H -ltn | awk '$4 ~ /^127\\.0\\.0\\.1:18081$/ {found=1} END {exit !found}'
pgrep -a -x nginx
pgrep -a -x php-fpm
assert_no_forbidden_runtime

"$ENTRY" archive q111 "$RUN_ID"
archive_path="/var/backups/zhct-q111/archives/${RUN_ID}.tar.gz"
archive_sha_path="${archive_path}.sha256"
[[ -f "$archive_path" && ! -L "$archive_path" ]]
[[ -f "$archive_sha_path" && ! -L "$archive_sha_path" ]]
(cd "$(dirname "$archive_path")" && sha256sum -c "$(basename "$archive_sha_path")")
ARCHIVE_PASS=1

echo Q111_APP01_HEALTH_RESULT=PASS_CONFIG_LISTENERS_PROCESSES_UNITS
echo Q111_APP01_PERSISTENT_PHASE_END
