#!/usr/bin/env bash
# ============================================================
# 腳本名稱：install_aetherinox_fresh.sh
# 功能說明：全新安裝 Aetherinox/csf-firewall，自動偵測真實對外網卡，
#           並開放常見服務埠（不含自動關閉 TESTING 模式，需人工確認）
# 建立日期：2026-07-22
# 版本：    1.0.0
# 使用方式：sudo ./install_aetherinox_fresh.sh
# ============================================================

set -euo pipefail
IFS=$'\n\t'

export LANG=zh_TW.UTF-8
export LC_ALL=zh_TW.UTF-8

# ============================================================
# 全域變數設定（要調整開放的埠，直接改這裡）
# ============================================================
readonly SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly TIMESTAMP="$(date '+%Y%m%d_%H%M%S')"
readonly LOG_DIR="${SCRIPT_DIR}/logs"
readonly LOG_FILE="${LOG_DIR}/${SCRIPT_NAME%.sh}_${TIMESTAMP}.log"
readonly LOCK_FILE="/var/run/${SCRIPT_NAME%.sh}.pid"
readonly WORK_DIR="/usr/src/aetherinox-install-${TIMESTAMP}"

# 常見服務埠設定（要新增/刪除埠，直接改這幾行）
# TCP 常見服務：FTP(21) SSH(22) HTTP(80) HTTPS(443) SMTPS(465) IMAPS(993) POP3S(995) MySQL(3306) HTTP-ALT(8080)
readonly TCP_IN_PORTS="21,22,80,443,465,993,995,3306,8080"
# UDP 常見服務：DNS(53) NTP(123)
readonly UDP_IN_PORTS="53,123"
# DNS 在部分情境也會用到 TCP（大型查詢/區域轉送），一併加入 TCP_IN
readonly TCP_IN_PORTS_WITH_DNS="21,22,53,80,443,465,993,995,3306,8080"

# 已知的虛擬/內部網卡關鍵字（偵測時要排除的）
readonly EXCLUDE_IFACE_PATTERN='^(lo|docker|veth|br-|tun|tap|virbr|venet|vnet|cni|flannel)'

# 由偵測邏輯動態填入，不寫死
DETECTED_IFACE=""

# ============================================================
# 防重複執行機制
# ============================================================
acquire_lock() {
    if [[ -f "$LOCK_FILE" ]]; then
        local existing_pid
        existing_pid=$(cat "$LOCK_FILE")
        if kill -0 "$existing_pid" 2>/dev/null; then
            log_error "腳本已在執行中 (PID: ${existing_pid})，退出。"
            exit 1
        else
            log_warn "偵測到殘留 Lock 檔案，清除後繼續。"
            rm -f "$LOCK_FILE"
        fi
    fi
    echo $$ > "$LOCK_FILE"
}

release_lock() { rm -f "$LOCK_FILE"; }

# ============================================================
# 記錄函數
# ============================================================
setup_logging() {
    mkdir -p "$LOG_DIR"
    exec > >(tee -a "$LOG_FILE") 2>&1
    log_info "=== 開始執行：Aetherinox csf-firewall 全新安裝 ==="
    log_info "執行時間：$(date '+%Y-%m-%d %H:%M:%S %Z')"
}

log_info()  { printf '[%s] [INFO]  %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; }
log_warn()  { printf '[%s] [WARN]  %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2; }
log_error() { printf '[%s] [ERROR] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2; }

# ============================================================
# 錯誤處理
# ============================================================
handle_error() {
    local exit_code=$?
    local line_number="${1:-unknown}"
    log_error "腳本在第 ${line_number} 行發生錯誤 (Exit Code: ${exit_code})"
    log_error "工作目錄：${WORK_DIR}（可手動檢查）"
    log_error "請查閱記錄檔：${LOG_FILE}"
    read -p "發生錯誤！按 Enter 結束（請人工檢查伺服器狀態）..."
    release_lock
    exit "$exit_code"
}

trap 'handle_error $LINENO' ERR
trap 'release_lock' EXIT INT TERM

# ============================================================
# 步驟 1：安全檢查
# ============================================================
step_precheck() {
    log_info "[DEBUG] 正在檢查執行權限..."
    if [[ $EUID -ne 0 ]]; then
        log_error "此腳本必須以 root 執行"
        exit 1
    fi

    log_info "[DEBUG] 正在檢查是否已有 CSF 家族安裝（避免與既有安裝衝突）..."
    if [[ -f /etc/csf/csf.conf ]]; then
        log_warn "偵測到 /etc/csf/csf.conf 已存在，代表這台可能已經裝過 CSF 家族防火牆"
        read -p "確定要在既有安裝上繼續嗎？（建議改用遷移腳本而非全新安裝，輸入 yes 強制繼續） " confirm
        [[ "$confirm" != "yes" ]] && { log_info "使用者取消操作"; exit 0; }
    fi

    log_info "[DEBUG] 檢查必要指令是否存在..."
    for cmd in wget unzip perl ip; do
        if ! command -v "$cmd" &>/dev/null; then
            log_error "缺少必要指令：${cmd}，請先安裝（例如：apt install -y ${cmd}）"
            exit 1
        fi
    done

    read -p "前置檢查完成！按 Enter 繼續下一階段（或 Ctrl+C 中斷）..."
}

# ============================================================
# 步驟 2：自動偵測真實對外網卡
# ============================================================
step_detect_interface() {
    log_info "[DEBUG] 正在偵測預設路由使用的網卡（第一層判斷）..."
    DETECTED_IFACE=$(ip -4 route list default 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i=="dev") print $(i+1)}' | head -1)

    if [[ -z "$DETECTED_IFACE" ]]; then
        log_warn "找不到預設路由的網卡，改用第二層判斷（排除已知虛擬網卡後取第一個啟用中的實體介面）..."
        DETECTED_IFACE=$(ip -o link show up 2>/dev/null | awk -F': ' '{print $2}' | grep -Ev "$EXCLUDE_IFACE_PATTERN" | head -1)
    fi

    if [[ -z "$DETECTED_IFACE" ]]; then
        log_error "自動偵測失敗，無法找到可用的對外網卡"
        log_error "以下是目前系統上所有的網卡，請人工確認後手動指定："
        ip -o link show
        exit 1
    fi

    log_info "[DEBUG] 目前系統上所有網卡清單如下（供你交叉比對）："
    ip -o link show | awk -F': ' '{print "  - " $2}'

    log_info "[DEBUG] 自動偵測結果：${DETECTED_IFACE}"
    read -p "以上偵測到的網卡是「${DETECTED_IFACE}」，若正確請直接按 Enter；若要手動指定其他網卡，請輸入名稱後按 Enter： " manual_iface
    if [[ -n "$manual_iface" ]]; then
        DETECTED_IFACE="$manual_iface"
        log_info "[DEBUG] 已改用手動指定的網卡：${DETECTED_IFACE}"
    fi

    read -p "網卡確認完成（將使用：${DETECTED_IFACE}）！按 Enter 繼續下一階段（或 Ctrl+C 中斷）..."
}

# ============================================================
# 步驟 3：下載並安裝 Aetherinox/csf-firewall
# ============================================================
step_install_aetherinox() {
    log_info "[DEBUG] 正在下載 Aetherinox csf-firewall..."
    mkdir -p "$WORK_DIR" && cd "$WORK_DIR"
    wget -O csf.zip https://download.configserver.dev/csf.zip
    if [[ $? -ne 0 ]]; then log_error "下載失敗，請檢查網路連線"; exit 1; fi

    log_info "[DEBUG] 正在解壓縮至中性目錄，避免與 zip 內部目錄同名造成巢狀..."
    unzip -oq csf.zip -d csf_extracted
    if [[ $? -ne 0 ]]; then log_error "解壓縮失敗"; exit 1; fi

    log_info "[DEBUG] 正在動態搜尋主程式 install.sh（排除控制台外掛專用子腳本，優先取路徑最短者）..."
    local install_sh_path
    install_sh_path=$(find "${WORK_DIR}/csf_extracted" \
        -iname "install.sh" \
        -not -path "*/da/*" \
        -not -path "*/cpanel/*" \
        -not -path "*/webmin/*" \
        -not -path "*/interworx/*" \
        -not -path "*/cwp/*" \
        | awk '{ print length, $0 }' | sort -n | head -1 | cut -d' ' -f2-)

    if [[ -z "$install_sh_path" ]]; then
        log_error "解壓縮後找不到 install.sh，以下是實際目錄結構，請人工檢查："
        find "${WORK_DIR}/csf_extracted" -maxdepth 4
        exit 1
    fi

    local install_sh_dir
    install_sh_dir="$(dirname "$install_sh_path")"
    log_info "[DEBUG] 偵測到 install.sh 位於：${install_sh_dir}"
    cd "$install_sh_dir"

    log_info "[DEBUG] 正在執行安裝前相容性測試 (csftest.pl)..."
    if [[ -f csftest.pl ]]; then
        perl csftest.pl || log_warn "csftest.pl 回報部分項目不相容，請詳細檢查上方輸出"
    else
        log_warn "此目錄下找不到 csftest.pl，跳過相容性測試"
    fi

    read -p "相容性測試完成，確認上方結果後按 Enter 開始正式安裝（或 Ctrl+C 中斷）..."

    log_info "[DEBUG] 正在執行 install.sh..."
    sh install.sh
    if [[ $? -ne 0 ]]; then log_error "install.sh 執行失敗"; exit 1; fi

    log_info "[DEBUG] 安裝完成"
    read -p "Aetherinox 安裝完成！按 Enter 繼續設定階段（或 Ctrl+C 中斷）..."
}

# ============================================================
# 步驟 4：設定真實網卡與常見服務埠
# ============================================================
step_configure_csf() {
    if [[ ! -f /etc/csf/csf.conf ]]; then
        log_error "找不到 /etc/csf/csf.conf，安裝可能未正確完成"
        exit 1
    fi

    log_info "[DEBUG] 正在設定 CSF 只管理真實對外網卡（ETH_DEVICE = ${DETECTED_IFACE}）..."
    if grep -q '^ETH_DEVICE = ' /etc/csf/csf.conf; then
        sed -i "s/^ETH_DEVICE = .*/ETH_DEVICE = \"${DETECTED_IFACE}\"/" /etc/csf/csf.conf
    else
        echo "ETH_DEVICE = \"${DETECTED_IFACE}\"" >> /etc/csf/csf.conf
    fi
    if [[ $? -ne 0 ]]; then log_error "設定 ETH_DEVICE 失敗"; exit 1; fi

    log_info "[DEBUG] 正在開放常見服務埠..."
    log_info "[DEBUG] TCP_IN 將設定為：${TCP_IN_PORTS_WITH_DNS}"
    sed -i "s/^TCP_IN = .*/TCP_IN = \"${TCP_IN_PORTS_WITH_DNS}\"/" /etc/csf/csf.conf
    if [[ $? -ne 0 ]]; then log_error "設定 TCP_IN 失敗"; exit 1; fi

    log_info "[DEBUG] UDP_IN 將設定為：${UDP_IN_PORTS}"
    sed -i "s/^UDP_IN = .*/UDP_IN = \"${UDP_IN_PORTS}\"/" /etc/csf/csf.conf
    if [[ $? -ne 0 ]]; then log_error "設定 UDP_IN 失敗"; exit 1; fi

    log_warn "注意：TCP_IN 已包含 3306(MySQL) 與 21(FTP)，這兩個埠對外開放有資安風險"
    log_warn "若非必要對外提供資料庫或 FTP 服務，強烈建議之後手動從 TCP_IN 移除這兩個埠"

    log_info "[DEBUG] 確認目前 TESTING 模式狀態（本腳本不會自動關閉，僅供你確認）..."
    grep '^TESTING = ' /etc/csf/csf.conf

    read -p "服務埠與網卡設定完成！按 Enter 繼續重新載入規則（或 Ctrl+C 中斷）..."

    log_info "[DEBUG] 正在重新載入 CSF 規則..."
    csf -r
    if [[ $? -ne 0 ]]; then log_error "csf -r 重新載入失敗，請人工檢查上方錯誤訊息"; exit 1; fi
}

# ============================================================
# 步驟 5：最終驗證與強力提醒
# ============================================================
step_final_reminder() {
    log_info "[DEBUG] 正在檢查 csf/lfd 服務狀態..."
    systemctl status csf --no-pager | head -3 || log_warn "csf 服務狀態異常，請人工檢查"
    systemctl status lfd --no-pager | head -3 || log_warn "lfd 服務狀態異常，請人工檢查"

    log_info "[DEBUG] 目前已開放的 TCP 服務埠："
    grep '^TCP_IN = ' /etc/csf/csf.conf
    log_info "[DEBUG] 目前已開放的 UDP 服務埠："
    grep '^UDP_IN = ' /etc/csf/csf.conf
    log_info "[DEBUG] 目前使用的網卡："
    grep '^ETH_DEVICE = ' /etc/csf/csf.conf

    log_warn "===================================================="
    log_warn "重要提醒：TESTING 模式目前仍為開啟狀態（安全預設值）"
    log_warn "TESTING 模式下，規則約 5 分鐘後會自動清空，這是為了避免你被自己鎖在 SSH 外面"
    log_warn "請先在另一個終端機視窗，實際測試 SSH 連線是否正常"
    log_warn "確認無誤後，手動執行以下兩行指令正式啟用防護："
    log_warn "  sed -i 's/^TESTING = \"1\"/TESTING = \"0\"/' /etc/csf/csf.conf"
    log_warn "  csf -r"
    log_warn "===================================================="

    read -p "全新安裝流程結束！按 Enter 結束腳本..."
    log_info "=== 安裝流程結束 ==="
}

# ============================================================
# 主邏輯
# ============================================================
main() {
    setup_logging
    acquire_lock
    step_precheck
    step_detect_interface
    step_install_aetherinox
    step_configure_csf
    step_final_reminder
}

main "$@"
