#!/usr/bin/env bash
# Warden 원격 설치 스크립트
#
# 사용법:
#   curl -sSO https://warden.imcat.dev/install.sh && bash install.sh
#   또는
#   curl -fsSL https://warden.imcat.dev/install.sh | bash
#
# 환경변수:
#   WARDEN_MIRROR_URL  기본 https://warden.imcat.dev
#   WARDEN_PORT        웹 UI 포트 (미지정 시 49152~65000 중 랜덤)
#   WARDEN_VERSION     설치할 버전(미지정 시 dist/warden 의 최신 버전)
#
# 설치 루트는 /WardenRoot 로 고정한다. 없으면 생성한다.
set -euo pipefail

MIRROR_URL="${WARDEN_MIRROR_URL:-https://warden.imcat.dev}"
WARDEN_ROOT="/WardenRoot"
WARDEN_PORT="${WARDEN_PORT:-}"

# 마지막 / 를 제거해 URL 조립 시 // 가 생기지 않게 한다.
MIRROR_URL="${MIRROR_URL%/}"

# 색상 사용 여부: 출력이 터미널이면 색상 허용
if [[ -t 1 ]]; then
    C_R="\033[0;31m"
    C_G="\033[0;32m"
    C_Y="\033[0;33m"
    C_B="\033[0;34m"
    C_N="\033[0m"
else
    C_R=""; C_G=""; C_Y=""; C_B=""; C_N=""
fi

log()  { echo -e "${C_B}[Warden]${C_N} $*"; }
ok()   { echo -e "${C_G}[OK]${C_N}   $*"; }
warn() { echo -e "${C_Y}[WARN]${C_N} $*" >&2; }
err()  { echo -e "${C_R}[ERR]${C_N}  $*" >&2; }

# --- 사전 검증 --------------------------------------------------------------
if [[ $EUID -ne 0 ]]; then
    err "root 권한으로 실행하세요: sudo bash install.sh"
    exit 1
fi

# set -u 환경에서 os-release 에 ID/VERSION_ID 가 없으면 스크립트가 죽으므로
# 서브셸에서 기본값을 주고 읽는다.
OS_ID=""
OS_LIKE=""
OS_VER=""
if [[ -r /etc/os-release ]]; then
    OS_ID="$( (. /etc/os-release 2>/dev/null; echo "${ID:-}") || true)"
    OS_LIKE="$( (. /etc/os-release 2>/dev/null; echo "${ID_LIKE:-}") || true)"
    OS_VER="$( (. /etc/os-release 2>/dev/null; echo "${VERSION_ID:-}") || true)"
fi
# os-release 에 버전이 없으면 redhat-release 에서 숫자를 추출한다.
if [[ -z "$OS_VER" && -r /etc/redhat-release ]]; then
    OS_VER="$(grep -oE '[0-9]+(\.[0-9]+)*' /etc/redhat-release | head -1 || true)"
fi
if [[ -z "$OS_ID" && -r /etc/redhat-release ]]; then
    OS_ID="$(awk '{print tolower($1)}' /etc/redhat-release || true)"
fi
OS_ID="${OS_ID,,}"
OS_VER="${OS_VER%%.*}"
[[ "$OS_VER" =~ ^[0-9]+$ ]] || OS_VER=""

OS_OK=0
if [[ "$OS_ID" == "fedora" ]]; then
    OS_OK=1
elif [[ "$OS_ID" =~ ^(rhel|rocky|almalinux|centos|ol|oracle|circle|navy|eurolinux)$ || " $OS_LIKE " =~ \ (rhel|centos)\  ]]; then
    # 패널 바이너리는 glibc 2.28(EL8) 기준으로 빌드하므로 EL8·9·10 에서 모두 실행된다.
    if [[ -n "$OS_VER" && "$OS_VER" -ge 8 ]]; then OS_OK=1; fi
fi

# CentOS Linux 8 / Stream 8 은 지원이 끝나 기본 저장소가 vault 로 옮겨졌다.
# dnf 가 미러를 찾지 못해 아래 패키지 설치가 실패할 수 있으므로 미리 알린다.
if [[ "$OS_ID" == "centos" && "$OS_VER" == "8" ]]; then
    warn "CentOS 8 / CentOS Stream 8 은 지원 종료(EOL) 배포판입니다."
    warn "dnf 저장소가 동작하지 않으면 vault.centos.org 로 저장소를 바꾸거나 Rocky/Alma 8 로 전환하세요."
fi

if [[ $OS_OK -ne 1 ]]; then
    warn "RHEL·Rocky·AlmaLinux·CentOS Stream 8/9/10 (또는 Fedora)에서만 지원합니다. (현재: ${OS_ID:-알수없음} ${OS_VER:-알수없음})"
    warn "계속 진행하면 예상치 못한 문제가 생길 수 있습니다."
    # curl | bash 로 실행하면 stdin 이 스크립트 본문이므로 /dev/tty 에서 읽는다.
    ans=""
    if [[ -r /dev/tty ]]; then
        read -rp "계속할까요? [y/N] " ans </dev/tty || ans=""
    fi
    [[ "$ans" =~ ^[Yy]$ ]] || exit 1
fi

# --- 의존성 패키지 자동 설치 -------------------------------------------------
# Warden 설치 및 운영에 필요한 패키지 목록
DEPS=(
    curl           # 다운로드/API 호출
    tar            # 백업/소프트웨어 압축 해제
    gzip           # GeoIP csv.gz 해제 등
    nftables       # 방화벽
    openssl        # SSL 인증서 생성/검증
    socat          # acme.sh standalone 모드
    cronie         # cron 스케줄(acme.sh 자동갱신, warden 작업)
    coreutils      # sha256sum, mktemp, install, date, fallocate 등
    sed
    grep
    gawk
    util-linux     # kill, mount, swapon, mkswap 등
    glibc
    libgcc
    chrony         # NTP 시간 동기화 (timedatectl set-ntp)
    shadow-utils   # chpasswd (사용자 비밀번호 변경)
    hostname       # hostname 명령
    sqlite         # DB 진단/디버깅
    policycoreutils-python-utils  # semanage/restorecon (SELinux 영구 규칙)
    procps-ng      # ps, top, free (시스템 모니터링)
    iproute        # ip, ss (네트워크 진단)
    findutils      # find (파일 탐색)
    python3        # 설치 스크립트의 JSON 해석, 소프트웨어 설치 스크립트 (EL8 최소 설치에는 없다)
)

log "시스템 패키지 의존성 확인 및 설치..."
PKG_LOG="$(mktemp /tmp/warden-deps.XXXXXX.log)"
PKG_MGR=""
if command -v dnf >/dev/null 2>&1; then PKG_MGR=dnf; elif command -v yum >/dev/null 2>&1; then PKG_MGR=yum; fi
if [[ -n "$PKG_MGR" ]]; then
    if ! "$PKG_MGR" install -y "${DEPS[@]}" >"$PKG_LOG" 2>&1; then
        err "패키지 설치 실패. 네트워크·저장소 설정을 확인하세요. (마지막 출력)"
        tail -n 15 "$PKG_LOG" >&2
        exit 1
    fi
    rm -f "$PKG_LOG"
else
    err "dnf/yum 패키지 관리자를 찾을 수 없습니다."
    exit 1
fi
ok "시스템 패키지 준비 완료"

# 필수 명령어 최종 확인
REQUIRED_CMDS=(curl sha256sum tar nft systemctl openssl)
MISSING=()
for cmd in "${REQUIRED_CMDS[@]}"; do
    if ! command -v "$cmd" >/dev/null 2>&1; then
        MISSING+=("$cmd")
    fi
done
if [[ ${#MISSING[@]} -gt 0 ]]; then
    err "필수 명령이 여전히 없습니다: ${MISSING[*]}"
    exit 1
fi

# curl HTTPS 지원 확인
if ! curl --version 2>/dev/null | grep -qi 'https'; then
    err "curl 이 https 프로토콜을 지원하지 않습니다."
    exit 1
fi

# x86_64 동적 라이브러리 확인
for lib in /lib64/libc.so.6 /lib64/libm.so.6 /lib64/libgcc_s.so.1 /lib64/ld-linux-x86-64.so.2; do
    if [[ ! -f "$lib" ]]; then
        err "필수 동적 라이브러리가 없습니다: $lib"
        exit 1
    fi
done

# --- NTP 시간 동기화 활성화 ---------------------------------------------------
if command -v chronyd >/dev/null 2>&1; then
    systemctl enable --now chronyd.service 2>/dev/null || true
    ok "NTP 시간 동기화 활성 (chrony)"
elif command -v timedatectl >/dev/null 2>&1; then
    timedatectl set-ntp true 2>/dev/null || true
    ok "NTP 시간 동기화 활성 (timedatectl)"
fi

# --- acme.sh 설치 ------------------------------------------------------------
log "acme.sh 확인..."
if [[ ! -f /root/.acme.sh/acme.sh ]]; then
    log "acme.sh 설치 중..."
    curl -fsSL https://get.acme.sh | sh -s email=admin@localhost >/dev/null 2>&1 || {
        warn "acme.sh 설치 실패 (SSL 인증서 발급 시 수동 설치 필요: curl https://get.acme.sh | sh)"
    }
    if [[ -f /root/.acme.sh/acme.sh ]]; then
        ok "acme.sh 설치 완료"
    fi
else
    ok "acme.sh 이미 설치됨"
fi

# --- 임시 작업공간 -----------------------------------------------------------
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT

# --- dist 메타데이터 확보 ----------------------------------------------------
# dist/ 디렉터리 안의 파일은 설치에 필요한 부팅 파일이다.
log "미러에서 설치 파일 목록을 가져옵니다: $MIRROR_URL"

# dist/version 파일이 있으면 버전/해시 정보를 얻는다
# 캐시 무효화를 위해 현재 epoch 를 쿼리에 부착
_TS="$(date +%s)"
VERSION_INFO=""
if curl -fsSL "$MIRROR_URL/dist/version?t=${_TS}" -o "$TMPDIR/version" 2>/dev/null; then
    VERSION_INFO=$(<"$TMPDIR/version")
    log "배포 버전: $VERSION_INFO"
fi

# --- 바이너리 다운로드 및 검증 -----------------------------------------------
log "Warden 바이너리 다운로드..."
curl -fSL --progress-bar "$MIRROR_URL/dist/warden?t=${_TS}" -o "$TMPDIR/warden" || { err "바이너리 다운로드 실패"; exit 1; }
curl -fsSL "$MIRROR_URL/dist/warden.sha256?t=${_TS}" -o "$TMPDIR/warden.sha256" || { err "체크섬 파일 다운로드 실패"; exit 1; }

# 체크섬 파일은 "hash  filename" 형태. 임시 디렉터리에서 검증.
(
    cd "$TMPDIR"
    # 일부 체크섬 파일은 경로를 포함하므로 파일명만 남긴다
    awk '{print $1 "  warden"}' warden.sha256 > warden.sha256.fixed
    if ! sha256sum -c warden.sha256.fixed; then
        err "바이너리 sha256 불일치 — 다운로드가 손상되었거나 변조되었습니다"
        exit 1
    fi
)
ok "바이너리 sha256 검증 통과"

# 이 서버에서 실제로 실행되는지 미리 확인한다. glibc 가 바이너리 요구보다 낮으면
# (예: 'GLIBC_2.34 not found') 서비스가 시작조차 못 하는데, 설치가 끝난 뒤에야 알게 된다.
chmod +x "$TMPDIR/warden"
if ! RUN_OUT="$("$TMPDIR/warden" --version 2>&1)"; then
    err "Warden 바이너리를 이 서버에서 실행할 수 없습니다: $RUN_OUT"
    err "glibc $(ldd --version 2>/dev/null | head -1 | awk '{print $NF}') — EL8(glibc 2.28) 이상이 필요합니다."
    exit 1
fi
ok "실행 확인: $RUN_OUT"

# --- 부팅 파일 다운로드 ------------------------------------------------------
log "부팅 파일 다운로드..."
for f in warden.service nftables.conf config.example.toml; do
    curl -fsSL "$MIRROR_URL/dist/$f?t=${_TS}" -o "$TMPDIR/$f" || { err "$f 다운로드 실패"; exit 1; }
done
ok "부팅 파일 다운로드 완료"

# --- 디스크 공간 확보 및 최적화 -----------------------------------------------
MIN_DISK_MB=500
RECOMMENDED_MB=4096

# 1단계: 불필요 파일 정리로 공간 확보
log "디스크 공간 확보: 불필요 파일 정리..."
if command -v dnf >/dev/null 2>&1; then
    dnf clean all >/dev/null 2>&1 || true
elif command -v yum >/dev/null 2>&1; then
    yum clean all >/dev/null 2>&1 || true
fi
# systemd 저널 로그 500MB 이내로 제한
if command -v journalctl >/dev/null 2>&1; then
    journalctl --vacuum-size=500M >/dev/null 2>&1 || true
fi
# 오래된 임시 파일 정리
rm -rf /tmp/warden-* 2>/dev/null || true
rm -rf /var/tmp/dnf-* 2>/dev/null || true
ok "캐시/로그 정리 완료"

# 2단계: LVM 감지 및 확장 — 루트(/) 가 LVM 이면 남은 PE를 최대한 할당
ROOT_DEV=$(df -P / 2>/dev/null | awk 'NR==2{print $1}')
if command -v lvs >/dev/null 2>&1 && lvs "$ROOT_DEV" >/dev/null 2>&1; then
    VG_NAME=$(lvs --noheadings -o vg_name "$ROOT_DEV" 2>/dev/null | tr -d ' ')
    FREE_PE=$(vgs --noheadings --units m -o vg_free "$VG_NAME" 2>/dev/null | tr -d ' m' | cut -d. -f1)
    if [[ "${FREE_PE:-0}" -gt 100 ]]; then
        log "LVM VG '$VG_NAME' 에 미할당 공간 ${FREE_PE}MB 감지 — 루트 LV 확장 중..."
        lvextend -l +100%FREE "$ROOT_DEV" >/dev/null 2>&1 || true
        # 파일시스템 확장 (xfs 또는 ext4)
        if command -v xfs_growfs >/dev/null 2>&1; then
            xfs_growfs / >/dev/null 2>&1 || true
        elif command -v resize2fs >/dev/null 2>&1; then
            resize2fs "$ROOT_DEV" >/dev/null 2>&1 || true
        fi
        ok "LVM 확장: ${FREE_PE}MB 추가 할당됨"
    fi
fi

# 3단계: 설치 루트는 /WardenRoot 고정 — 없으면 생성
if [[ -d "$WARDEN_ROOT" ]]; then
    log "기존 설치 디렉터리 감지: $WARDEN_ROOT (재설치)"
else
    log "설치 디렉터리 생성: $WARDEN_ROOT"
    mkdir -p "$WARDEN_ROOT" || { err "$WARDEN_ROOT 생성 실패"; exit 1; }
fi

# 최종 공간 확인 (/WardenRoot 가 별도 마운트여도 실제 위치 기준으로 측정)
AVAIL_MB=$(df -Pm "$WARDEN_ROOT" 2>/dev/null | awk 'NR==2{print $4}')
TOTAL_MB=$(df -Pm "$WARDEN_ROOT" 2>/dev/null | awk 'NR==2{print $2}')

if [[ -n "$AVAIL_MB" ]]; then
    log "디스크 공간: ${AVAIL_MB}MB 가용 / ${TOTAL_MB}MB 전체 ($(df -Pm "$WARDEN_ROOT" | awk 'NR==2{print $1}'))"
    if [[ "$AVAIL_MB" -lt "$MIN_DISK_MB" ]]; then
        err "디스크 가용 공간이 부족합니다: ${AVAIL_MB}MB (최소 ${MIN_DISK_MB}MB 필요)"
        err "소프트웨어 소스 빌드(PHP/Nginx/MariaDB 등)에는 ${RECOMMENDED_MB}MB 이상을 권장합니다."
        exit 1
    elif [[ "$AVAIL_MB" -lt "$RECOMMENDED_MB" ]]; then
        warn "가용 공간 ${AVAIL_MB}MB — 소프트웨어 소스 빌드에는 ${RECOMMENDED_MB}MB 이상 권장"
    else
        ok "디스크 공간 충분: ${AVAIL_MB}MB 가용"
    fi
else
    warn "디스크 공간을 확인할 수 없습니다. 계속 진행합니다."
fi

# --- WardenRoot 생성 ---------------------------------------------------------
log "설치 루트 준비: $WARDEN_ROOT"
mkdir -p "$WARDEN_ROOT"/{data,logs,ssl,geoip,deploy,tmp,server}
mkdir -p "$WARDEN_ROOT"/vhost/{nginx,apache,rewrite,ssl_saved}
mkdir -p "$WARDEN_ROOT"/{wwwroot,wwwlogs}
mkdir -p "$WARDEN_ROOT"/backup/warden

# --- 파일 배치 ---------------------------------------------------------------
log "바이너리 및 설정 복사..."
install -m 0755 "$TMPDIR/warden" "$WARDEN_ROOT/warden"
cp "$TMPDIR/nftables.conf" "$WARDEN_ROOT/deploy/nftables.conf"
cp "$TMPDIR/warden.service" "$WARDEN_ROOT/deploy/warden.service"

if [[ ! -f "$WARDEN_ROOT/config.toml" ]]; then
    cp "$TMPDIR/config.example.toml" "$WARDEN_ROOT/config.toml"
    # 포트 자동 선택: WARDEN_PORT 환경변수가 없으면 49152~65000 중 미사용 포트 무작위 선택
    if [[ -z "$WARDEN_PORT" ]]; then
        for _try in $(seq 1 20); do
            _candidate=$(( RANDOM % 15849 + 49152 ))
            if ! ss -tlnH 2>/dev/null | awk '{print $4}' | grep -q ":${_candidate}$"; then
                WARDEN_PORT="$_candidate"
                break
            fi
        done
        # 20회 모두 실패 시 폴백
        WARDEN_PORT="${WARDEN_PORT:-9443}"
    fi
    # bind_addr 에 선택된 포트 적용
    sed -i "s#^bind_addr = .*#bind_addr = \"0.0.0.0:${WARDEN_PORT}\"#" "$WARDEN_ROOT/config.toml"
    # allowed_ports 에도 반영
    sed -i "s#^allowed_ports = \[22, 9443\]#allowed_ports = [22, ${WARDEN_PORT}]#" "$WARDEN_ROOT/config.toml"
    ok "웹 UI 포트: ${WARDEN_PORT}"
    # 설치를 실행하는 관리자의 IP를 화이트리스트에 추가 (첫 접속 시 자동차단 방지)
    INSTALLER_IP=""
    if [[ -n "${SSH_CLIENT:-}" ]]; then
        INSTALLER_IP="$(echo "$SSH_CLIENT" | awk '{print $1}')"
    elif [[ -n "${SSH_CONNECTION:-}" ]]; then
        INSTALLER_IP="$(echo "$SSH_CONNECTION" | awk '{print $1}')"
    fi
    if [[ -n "$INSTALLER_IP" && "$INSTALLER_IP" != "127.0.0.1" && "$INSTALLER_IP" != "::1" ]]; then
        sed -i "s#^whitelist = \[\"127.0.0.1\", \"::1\"\]#whitelist = [\"127.0.0.1\", \"::1\", \"$INSTALLER_IP\"]#" "$WARDEN_ROOT/config.toml"
        ok "관리자 IP($INSTALLER_IP)를 화이트리스트에 추가"
    fi
    # --- 블랙리스트 미러 연동 자동 등록 ---
    MIRROR_API_URL="https://warden.imcat.dev/api"
    SOURCE_ID="$(hostname -s 2>/dev/null || echo unknown)"
    WARDEN_VER="$("$WARDEN_ROOT/warden" --version 2>/dev/null | awk '{print $2}' || echo '0.0.0')"
    MIRROR_TOKEN=""
    REG_RESP=$(curl -fsSL --retry 2 --retry-delay 1 -X POST \
        -H "Content-Type: application/json" \
        -d "{\"source_id\":\"$SOURCE_ID\",\"version\":\"$WARDEN_VER\"}" \
        "$MIRROR_API_URL/register.php" 2>/dev/null || echo "")
    if [[ -n "$REG_RESP" ]]; then
        MIRROR_TOKEN=$(echo "$REG_RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('token') or '')" 2>/dev/null || echo "")
        REG_EXISTING=$(echo "$REG_RESP" | python3 -c "import sys,json; print(1 if json.load(sys.stdin).get('existing') else '')" 2>/dev/null || echo "")
    fi
    # config.example.toml 에 이미 [mirror_sync] 섹션이 있으므로 sed 로 값만 교체
    if [[ -n "$MIRROR_TOKEN" ]]; then
        sed -i '/^\[mirror_sync\]/,/^$\|^\[/{
            s#^enabled = .*#enabled = true#
            s#^token = .*#token = "'"$MIRROR_TOKEN"'"#
            s#^source_id = .*#source_id = "'"$SOURCE_ID"'"#
        }' "$WARDEN_ROOT/config.toml"
        ok "블랙리스트 미러 연동 등록 완료 (source: $SOURCE_ID)"
    else
        # 등록 실패 시 기본값(enabled=false) 유지 — source_id 만 채운다
        sed -i '/^\[mirror_sync\]/,/^$\|^\[/{
            s#^source_id = .*#source_id = "'"$SOURCE_ID"'"#
        }' "$WARDEN_ROOT/config.toml"
        if [[ -n "${REG_EXISTING:-}" ]]; then
            warn "미러에 이미 등록된 호스트입니다 — 재발급은 지원하지 않습니다. 이전 설치의 config.toml mirror_sync.token 을 옮겨 적으세요"
        else
            warn "미러 등록 실패 — [mirror_sync] 비활성 상태로 유지됨 (나중에 수동 설정 가능)"
        fi
    fi
    ok "config.toml 생성됨 (필요시 수정하세요)"
else
    warn "기존 config.toml 이 존재합니다. 새 예시 설정은 복사하지 않습니다."
    # 기존 설정에서 포트를 읽어온다
    if [[ -z "$WARDEN_PORT" ]]; then
        WARDEN_PORT=$(sed -n 's/^bind_addr.*:\([0-9]\+\)".*/\1/p' "$WARDEN_ROOT/config.toml" 2>/dev/null || echo "")
        WARDEN_PORT="${WARDEN_PORT:-9443}"
    fi
    # 기존 config 에 [mirror_sync] 가 없으면 추가
    if ! grep -q '^\[mirror_sync\]' "$WARDEN_ROOT/config.toml" 2>/dev/null; then
        MIRROR_API_URL="https://warden.imcat.dev/api"
        SOURCE_ID="$(hostname -s 2>/dev/null || echo unknown)"
        WARDEN_VER="$("$WARDEN_ROOT/warden" --version 2>/dev/null | awk '{print $2}' || echo '0.0.0')"
        MIRROR_TOKEN=""
        REG_RESP=$(curl -fsSL --retry 2 --retry-delay 1 -X POST \
            -H "Content-Type: application/json" \
            -d "{\"source_id\":\"$SOURCE_ID\",\"version\":\"$WARDEN_VER\"}" \
            "$MIRROR_API_URL/register.php" 2>/dev/null || echo "")
        if [[ -n "$REG_RESP" ]]; then
            MIRROR_TOKEN=$(echo "$REG_RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('token') or '')" 2>/dev/null || echo "")
            REG_EXISTING=$(echo "$REG_RESP" | python3 -c "import sys,json; print(1 if json.load(sys.stdin).get('existing') else '')" 2>/dev/null || echo "")
        fi
        if [[ -n "$MIRROR_TOKEN" ]]; then
            cat >> "$WARDEN_ROOT/config.toml" <<EOF

[mirror_sync]
enabled = true
token = "$MIRROR_TOKEN"
source_id = "$SOURCE_ID"
upload = true
download = true
EOF
            ok "블랙리스트 미러 연동 등록 완료 (source: $SOURCE_ID)"
        else
            cat >> "$WARDEN_ROOT/config.toml" <<EOF

[mirror_sync]
enabled = false
token = ""
source_id = ""
upload = true
download = true
EOF
            if [[ -n "${REG_EXISTING:-}" ]]; then
                warn "미러에 이미 등록된 호스트입니다 — 재발급은 지원하지 않습니다. 기존 토큰을 찾아 mirror_sync.token 에 적으세요"
            else
                warn "미러 등록 실패 — [mirror_sync] 비활성 상태로 생성됨"
            fi
        fi
    fi
fi

# --- SELinux 컨텍스트 --------------------------------------------------------
log "SELinux 컨텍스트 조정..."
if command -v getenforce >/dev/null 2>&1 && [[ "$(getenforce 2>/dev/null)" != "Disabled" ]]; then
    if command -v semanage >/dev/null 2>&1; then
        semanage port -a -t http_port_t -p tcp "$WARDEN_PORT" 2>/dev/null || \
            semanage port -m -t http_port_t -p tcp "$WARDEN_PORT" 2>/dev/null || true
        semanage fcontext -a -t httpd_sys_content_t "$WARDEN_ROOT/wwwroot(/.*)?" 2>/dev/null || \
            semanage fcontext -m -t httpd_sys_content_t "$WARDEN_ROOT/wwwroot(/.*)?" 2>/dev/null || true
        semanage fcontext -a -t bin_t "$WARDEN_ROOT/warden" 2>/dev/null || \
            semanage fcontext -m -t bin_t "$WARDEN_ROOT/warden" 2>/dev/null || true
        restorecon -Rv "$WARDEN_ROOT" >/dev/null 2>&1 || true
    else
        # semanage 없으면 chcon 으로 즉시 적용 (영구는 아니지만 동작 보장)
        chcon -t bin_t "$WARDEN_ROOT/warden" 2>/dev/null || true
        chcon -R -t httpd_sys_content_t "$WARDEN_ROOT/wwwroot" 2>/dev/null || true
        warn "policycoreutils-python-utils 가 없어 chcon 으로 대체 (영구 적용: dnf install -y policycoreutils-python-utils 후 재설치)"
    fi
    ok "SELinux 조정 완료"
else
    ok "SELinux 비활성 — 건너뜀"
fi

# --- 방화벽 규칙 적용 ---------------------------------------------------------
# firewalld 가 활성이면 포트만 열고 nftables 직접 관리는 건너뛴다.
# (firewalld 가 nftables 백엔드를 쓰므로 직접 nft -f 하면 충돌)
if systemctl is-active firewalld >/dev/null 2>&1; then
    log "firewalld 활성 감지 — firewalld 로 포트 개방..."
    firewall-cmd --add-port=${WARDEN_PORT}/tcp --permanent 2>/dev/null || true
    firewall-cmd --add-port=22/tcp --permanent 2>/dev/null || true
    firewall-cmd --reload 2>/dev/null || true
    ok "firewalld: 22/tcp, ${WARDEN_PORT}/tcp 개방"
else
    log "nftables 방화벽 규칙 적용..."
    mkdir -p /etc/nftables
    cp "$WARDEN_ROOT/deploy/nftables.conf" /etc/nftables/warden.nft
    # nftables 허용 포트에 동적 포트 반영
    sed -i "s#elements = { 22, 9443 }#elements = { 22, ${WARDEN_PORT} }#" /etc/nftables/warden.nft
    if ! grep -q 'warden.nft' /etc/sysconfig/nftables.conf 2>/dev/null; then
        echo 'include "/etc/nftables/warden.nft"' >> /etc/sysconfig/nftables.conf
    fi
    nft -f /etc/nftables/warden.nft
    systemctl enable --now nftables.service || true
    ok "nftables 적용 완료"
fi

# --- GeoIP 데이터 설치 --------------------------------------------------------
GEOIP_DIR="$WARDEN_ROOT/geoip"
GEOIP_FILE="$GEOIP_DIR/dbip-country-lite.csv"
if [[ ! -f "$GEOIP_FILE" ]]; then
    log "GeoIP 데이터 다운로드 (DB-IP country lite)..."
    mkdir -p "$GEOIP_DIR"
    GEOIP_YM=$(date +%Y-%m)
    GEOIP_URL="https://download.db-ip.com/free/dbip-country-lite-${GEOIP_YM}.csv.gz"
    if curl -fSL "$GEOIP_URL" -o "$TMPDIR/dbip.csv.gz" 2>/dev/null; then
        gunzip -f "$TMPDIR/dbip.csv.gz"
        mv "$TMPDIR/dbip.csv" "$GEOIP_FILE"
        ok "GeoIP 설치 완료: $(wc -l < "$GEOIP_FILE") 행"
    else
        # 이번 달 실패 시 지난 달 시도
        GEOIP_YM=$(date -d 'last month' +%Y-%m 2>/dev/null || date -v-1m +%Y-%m 2>/dev/null || echo "")
        if [[ -n "$GEOIP_YM" ]]; then
            GEOIP_URL="https://download.db-ip.com/free/dbip-country-lite-${GEOIP_YM}.csv.gz"
            if curl -fSL "$GEOIP_URL" -o "$TMPDIR/dbip.csv.gz" 2>/dev/null; then
                gunzip -f "$TMPDIR/dbip.csv.gz"
                mv "$TMPDIR/dbip.csv" "$GEOIP_FILE"
                ok "GeoIP 설치 완료 (지난달 데이터): $(wc -l < "$GEOIP_FILE") 행"
            else
                warn "GeoIP 다운로드 실패 — 국기 표시 비활성 (수동: setup_geoip.sh)"
            fi
        else
            warn "GeoIP 다운로드 실패 — 국기 표시 비활성 (수동: setup_geoip.sh)"
        fi
    fi
else
    ok "GeoIP 데이터 이미 존재"
fi

# --- 기존 DB 방화벽 포트 갱신 (재설치 시 이전 포트 잔존 방지) ------------------
DB_FILE="$WARDEN_ROOT/data/panel.db"
if [[ -f "$DB_FILE" ]] && command -v sqlite3 >/dev/null 2>&1; then
    # 이전 기본 포트(9443)가 남아 있으면 새 포트로 교체
    if [[ "$WARDEN_PORT" != "9443" ]]; then
        sqlite3 "$DB_FILE" "UPDATE fw_ports SET port = ${WARDEN_PORT} WHERE port = 9443;" 2>/dev/null || true
    fi
fi

# --- systemd 서비스 등록 -----------------------------------------------------
log "systemd 서비스 등록..."
cp "$WARDEN_ROOT/deploy/warden.service" /etc/systemd/system/warden.service
systemctl daemon-reload
systemctl enable --now warden.service
ok "warden 서비스 시작됨"

# --- 미러에 설치 기보고 ------------------------------------------------------
report_install() {
    local payload
    local hostname
    hostname="$(hostname -f 2>/dev/null || hostname 2>/dev/null || echo 'unknown')"
    payload="$(cat <<EOF
{
  \"version\": \"${VERSION_INFO:-unknown}\",
  \"os_id\": \"${OS_ID:-unknown}\",
  \"os_ver\": \"${OS_VER:-unknown}\",
  \"warden_root\": \"$WARDEN_ROOT\",
  \"hostname\": \"$hostname\"
}
EOF
)"
    # 실패해도 설치를 막지 않는다
    if curl -fsS -X POST \
            -H "Content-Type: application/json" \
            -d "$payload" \
            "$MIRROR_URL/api/install.php" >/dev/null 2>&1; then
        ok "설치 기록이 미러로 전송되었습니다"
    else
        warn "미러 설치 기록 전송 실패 (무시)"
    fi
}
report_install

# --- 완료 메시지 -------------------------------------------------------------
echo ""
echo -e "${C_G}==> Warden 설치 완료${C_N}"
sleep 1
systemctl --no-pager status warden.service | head -n 5 || true
echo ""

SERVER_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
SERVER_IP="${SERVER_IP:-<서버IP>}"

# --- 대화형 관리자 계정 생성 -------------------------------------------------
# 터미널이 연결된 경우에만 대화형으로 진행 (pipe 로 실행하면 건너뜀)
if [[ -t 0 ]]; then
    echo ""
    log "관리자 계정을 지금 생성하시겠습니까?"
    read -rp "관리자 계정 생성 [Y/n]: " create_admin
    if [[ ! "$create_admin" =~ ^[Nn]$ ]]; then
        read -rp "관리자 아이디: " admin_user
        if [[ -n "$admin_user" ]]; then
            log "관리자 '$admin_user' 생성 중... (비밀번호를 입력하세요)"
            "$WARDEN_ROOT/warden" --config "$WARDEN_ROOT/config.toml" admin add "$admin_user" --no-totp || {
                warn "관리자 생성 실패. 수동으로 추가하세요:"
                echo "    $WARDEN_ROOT/warden --config $WARDEN_ROOT/config.toml admin add <username>"
            }
        else
            warn "아이디가 비어 있습니다. 건너뜁니다."
        fi
    fi
else
    echo ""
    log "관리자 계정을 추가하세요:"
    echo "    $WARDEN_ROOT/warden --config $WARDEN_ROOT/config.toml admin add <username>"
fi

echo ""
log "패널 접속: https://${SERVER_IP}:${WARDEN_PORT}  (자체서명 인증서 경고는 정상)"

