#!/usr/bin/env bash

set -euo pipefail

# ERR trap: 输出失败行号，便于调试（尤其在开机脚本静默模式下）
trap 'echo "[FATAL] 脚本在第 ${LINENO} 行失败，退出码 $?" >&2' ERR

SCRIPT_NAME="$(basename "$0")"

OPENVPN_DIR="/etc/openvpn/server"
EASYRSA_DIR="${OPENVPN_DIR}/easy-rsa"
CCD_DIR="${OPENVPN_DIR}/ccd"
CLIENT_OUTPUT_DIR="/root"
SERVER_CONF="${OPENVPN_DIR}/server.conf"
CLIENT_BASE_CONF="${OPENVPN_DIR}/client-common.conf"
RINETD_CONF="/etc/rinetd.conf"
RINETD_CONF_BACKUP="/etc/rinetd.conf.bak.$(date +%s)"
RINETD_CONF_PREEXISTED=0

OPENVPN_NET="10.8.0.0"
OPENVPN_MASK="255.255.255.0"
OPENVPN_CIDR="10.8.0.0/24"
OPENVPN_SERVER_PORT=$(( RANDOM % 9000 + 1000 ))
OPENVPN_SERVER_PROTO="tcp"
OPENVPN_CIPHER_LEGACY="AES-256-CBC"

RINETD_BIND_IP="0.0.0.0"
RINETD_PORT_START=31400
RINETD_PORT_END=31409
RINETD_PORTS_PER_CLIENT=$((RINETD_PORT_END - RINETD_PORT_START + 1))

IPTABLES_RULES_FILE="/etc/openvpn/openvpn-rinetd-iptables.rules"
IPTABLES_SERVICE_FILE="/etc/systemd/system/openvpn-rinetd-iptables.service"
OPENVPN_SERVICE_FILE="/etc/systemd/system/openvpn-autoinstall.service"
SYSCTL_FILE="/etc/sysctl.d/99-openvpn-rinetd-forward.conf"

CLIENT_COUNT="${1:-${CLIENT_COUNT:-1}}"
LOG_FILE="${LOG_FILE:-/var/log/openvpn-rinetd-auto.log}"

# SCP 上传配置（开机脚本批量部署用，sshpass 密码认证）
SCP_ENABLED=1
SCP_TARGET_HOST="114.66.30.180"
SCP_TARGET_PORT=22
SCP_TARGET_USER="root"
SCP_TARGET_DIR="/www/wwwroot/test"
SCP_PASSWORD="5XdSeKfXBMEN"
HAS_DATA_CIPHERS=0

log() {
  printf '[INFO] %s\n' "$*"
}

warn() {
  printf '[WARN] %s\n' "$*"
}

die() {
  printf '[ERROR] %s\n' "$*" >&2
  exit 1
}

require_root() {
  if [[ "${EUID}" -ne 0 ]]; then
    die "请使用 root 权限运行：sudo bash ${SCRIPT_NAME} [客户端数量|--uninstall]"
  fi
}

validate_client_count() {
  if ! [[ "${CLIENT_COUNT}" =~ ^[0-9]+$ ]]; then
    die "客户端数量必须是正整数，当前值：${CLIENT_COUNT}"
  fi

  if (( CLIENT_COUNT < 1 || CLIENT_COUNT > 253 )); then
    die "客户端数量必须在 1-253 之间（对应 10.8.0.2 到 10.8.0.254）。"
  fi

  if (( RINETD_PORTS_PER_CLIENT < 1 )); then
    die "rinetd 端口区间配置非法：${RINETD_PORT_START}-${RINETD_PORT_END}"
  fi

  local rinetd_external_end
  rinetd_external_end=$((RINETD_PORT_START + CLIENT_COUNT * RINETD_PORTS_PER_CLIENT - 1))
  if (( rinetd_external_end > 65535 )); then
    die "rinetd 外部端口总区间超出 65535：${RINETD_PORT_START}-${rinetd_external_end}。请减少客户端数量或缩小每客户端端口段。"
  fi

}

detect_os() {
  if [[ ! -f /etc/os-release ]]; then
    die "未找到 /etc/os-release，无法识别系统。"
  fi

  # shellcheck source=/dev/null
  . /etc/os-release

  OS_ID="${ID:-}"
  OS_ID_LIKE="${ID_LIKE:-}"

  case "${OS_ID}" in
    ubuntu|debian)
      PKG_MGR="apt"
      ;;
    centos|rhel|rocky|almalinux)
      PKG_MGR="yum"
      ;;
    arch)
      PKG_MGR="pacman"
      ;;
    *)
      if [[ "${OS_ID_LIKE}" == *"debian"* ]]; then
        PKG_MGR="apt"
      elif [[ "${OS_ID_LIKE}" == *"rhel"* ]] || [[ "${OS_ID_LIKE}" == *"fedora"* ]]; then
        PKG_MGR="yum"
      elif [[ "${OS_ID_LIKE}" == *"arch"* ]]; then
        PKG_MGR="pacman"
      else
        die "暂不支持的系统：ID=${OS_ID}, ID_LIKE=${OS_ID_LIKE}"
      fi
      ;;
  esac

  log "检测到系统：${PRETTY_NAME:-${OS_ID}}，包管理器：${PKG_MGR}"
}

install_packages() {
  case "${PKG_MGR}" in
    apt)
      export DEBIAN_FRONTEND=noninteractive
      apt-get update -y
      apt-get install -y openvpn easy-rsa rinetd iptables openssl ca-certificates curl
      ;;
    yum)
      if command -v dnf >/dev/null 2>&1; then
        dnf install -y epel-release || true
        dnf install -y openvpn easy-rsa rinetd iptables openssl ca-certificates curl
      else
        yum install -y epel-release || true
        yum install -y openvpn easy-rsa rinetd iptables openssl ca-certificates curl
      fi
      ;;
    pacman)
      pacman -Sy --noconfirm openvpn easy-rsa rinetd iptables openssl ca-certificates curl
      ;;
    *)
      die "未知包管理器：${PKG_MGR}"
      ;;
  esac
}

detect_openvpn_features() {
  if openvpn --help 2>/dev/null | grep -q -- '--data-ciphers'; then
    HAS_DATA_CIPHERS=1
  else
    HAS_DATA_CIPHERS=0
  fi
}

check_preconditions() {
  if [[ ! -e /dev/net/tun ]]; then
    die "未检测到 /dev/net/tun，OpenVPN 需要 TUN 设备。"
  fi

  if [[ -f "${SERVER_CONF}" ]]; then
    log "检测到已有 OpenVPN 配置，执行覆盖安装..."
    rm -f "${CLIENT_OUTPUT_DIR}"/Server_*.ovpn "${CLIENT_OUTPUT_DIR}/client-mapping.txt"
    uninstall
  fi
}

get_main_ipv4() {
  local ip
  ip="$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{for (i=1;i<=NF;i++) if ($i=="src") {print $(i+1); exit}}')"
  if [[ -z "${ip}" ]]; then
    ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
  fi
  [[ -n "${ip}" ]] || die "无法获取服务器 IPv4 地址。"
  printf '%s' "${ip}"
}

get_public_endpoint() {
  local public_ip
  public_ip="$(curl -4 -fsSL --max-time 8 https://api.ipify.org || true)"
  if [[ -z "${public_ip}" ]]; then
    public_ip="$(curl -4 -fsSL --max-time 8 https://ifconfig.me || true)"
  fi
  printf '%s' "${public_ip}"
}

get_default_interface() {
  local nic
  nic="$(ip -4 route ls default | awk '{print $5; exit}')"
  [[ -n "${nic}" ]] || die "无法获取默认网卡。"
  printf '%s' "${nic}"
}

setup_directories() {
  mkdir -p "${OPENVPN_DIR}" "${EASYRSA_DIR}" "${CCD_DIR}" "${CLIENT_OUTPUT_DIR}" /etc/openvpn
  chmod 700 "${CLIENT_OUTPUT_DIR}"
}

setup_easyrsa() {
  if [[ -d /usr/share/easy-rsa/3 ]]; then
    cp -a /usr/share/easy-rsa/3/. "${EASYRSA_DIR}/"
  elif [[ -d /usr/share/easy-rsa ]]; then
    cp -a /usr/share/easy-rsa/. "${EASYRSA_DIR}/"
  else
    die "未找到 easy-rsa 安装目录（/usr/share/easy-rsa）。"
  fi

  chmod +x "${EASYRSA_DIR}/easyrsa"

  pushd "${EASYRSA_DIR}" >/dev/null
  ./easyrsa --batch init-pki          </dev/null
  ./easyrsa --batch build-ca nopass    </dev/null
  ./easyrsa --batch build-server-full server nopass </dev/null
  ./easyrsa --batch gen-dh             </dev/null
  openvpn --genkey secret "${OPENVPN_DIR}/tc.key" 2>/dev/null || openvpn --genkey --secret "${OPENVPN_DIR}/tc.key"
  cp pki/ca.crt "${OPENVPN_DIR}/ca.crt"
  cp pki/dh.pem "${OPENVPN_DIR}/dh.pem"
  cp pki/issued/server.crt "${OPENVPN_DIR}/server.crt"
  cp pki/private/server.key "${OPENVPN_DIR}/server.key"
  ./easyrsa --batch gen-crl            </dev/null
  cp pki/crl.pem "${OPENVPN_DIR}/crl.pem"
  chmod 644 "${OPENVPN_DIR}/crl.pem"
  popd >/dev/null
}

write_server_conf() {
  cat > "${SERVER_CONF}" <<EOF
port ${OPENVPN_SERVER_PORT}
proto ${OPENVPN_SERVER_PROTO}
dev tun
topology subnet
server ${OPENVPN_NET} ${OPENVPN_MASK}
client-config-dir ${CCD_DIR}
ifconfig-pool-persist ${OPENVPN_DIR}/ipp.txt
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 1.1.1.1"
push "dhcp-option DNS 8.8.8.8"
keepalive 10 120
cipher ${OPENVPN_CIPHER_LEGACY}
tls-version-min 1.2
persist-key
persist-tun
user nobody
group nogroup
ca ${OPENVPN_DIR}/ca.crt
cert ${OPENVPN_DIR}/server.crt
key ${OPENVPN_DIR}/server.key
dh ${OPENVPN_DIR}/dh.pem
tls-crypt ${OPENVPN_DIR}/tc.key
auth SHA256
crl-verify ${OPENVPN_DIR}/crl.pem
verb 3
EOF

  if (( HAS_DATA_CIPHERS == 1 )); then
    sed -i '/^cipher /a data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305\ndata-ciphers-fallback AES-256-CBC' "${SERVER_CONF}"
  fi

  if getent group nogroup >/dev/null 2>&1; then
    :
  elif getent group nobody >/dev/null 2>&1; then
    sed -i 's/^group nogroup$/group nobody/' "${SERVER_CONF}"
  else
    warn "系统不存在 nogroup/nobody 组，保留 group nogroup（可能需要手动调整）。"
  fi

  if [[ "${OPENVPN_SERVER_PROTO}" == "udp" ]]; then
    echo "explicit-exit-notify 1" >> "${SERVER_CONF}"
  fi
}

write_client_base_conf() {
  local endpoint="$1"
  cat > "${CLIENT_BASE_CONF}" <<EOF
client
dev tun
proto ${OPENVPN_SERVER_PROTO}
remote ${endpoint} __PORT__
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
cipher ${OPENVPN_CIPHER_LEGACY}
auth SHA256
verb 3
<ca>
$(cat "${OPENVPN_DIR}/ca.crt")
</ca>
<tls-crypt>
$(cat "${OPENVPN_DIR}/tc.key")
</tls-crypt>
EOF

  if (( HAS_DATA_CIPHERS == 1 )); then
    sed -i '/^cipher /a data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305\ndata-ciphers-fallback AES-256-CBC' "${CLIENT_BASE_CONF}"
  fi

  if [[ "${OPENVPN_SERVER_PROTO}" == "udp" ]]; then
    echo "explicit-exit-notify 1" >> "${CLIENT_BASE_CONF}"
  fi
}

generate_unique_port() {
  local candidate attempts=0
  while (( attempts < 1000 )); do
    candidate="$(( (RANDOM % 9000) + 1000 ))"
    if [[ -z "${USED_PORTS[${candidate}]+x}" ]] && [[ -z "${RESERVED_PORTS[${candidate}]+x}" ]]; then
      USED_PORTS["${candidate}"]=1
      printf '%s' "${candidate}"
      return 0
    fi
    attempts=$((attempts + 1))
  done
  die "随机 4 位端口生成失败，请重试。"
}

extract_cert_body() {
  local cert_file="$1"
  awk '/-----BEGIN CERTIFICATE-----/{flag=1} flag{print} /-----END CERTIFICATE-----/{flag=0}' "${cert_file}"
}

sanitize_endpoint_for_filename() {
  local raw="$1"
  printf '%s' "${raw}" | tr -c '[:alnum:].-' '_'
}

random_hash() {
  local h
  h="$(openssl rand -hex 3 2>/dev/null || true)"
  if [[ -z "${h}" ]]; then
    h="$(date +%s%N | sha256sum | cut -c1-6)"
  fi
  printf '%s' "${h}"
}

declare -A USED_PORTS
declare -A RESERVED_PORTS=([22]=1 [80]=1 [443]=1 [1194]=1 [3306]=1 [5432]=1 [6379]=1 [8080]=1 [8443]=1)

populate_reserved_ports() {
  # 将服务端随机端口加入保留表，防止客户端端口撞车
  RESERVED_PORTS["${OPENVPN_SERVER_PORT}"]=1
  local port
  while read -r port; do
    [[ -n "${port}" ]] && RESERVED_PORTS["${port}"]=1
  done < <({ ss -tuln 2>/dev/null || true; } | awk 'NR>1 {split($5,a,":"); print a[length(a)]}' | sort -u)
}

generate_clients() {
  local endpoint="$1"
  local mapping_file="${CLIENT_OUTPUT_DIR}/client-mapping.txt"
  local endpoint_tag

  endpoint_tag="$(sanitize_endpoint_for_filename "${endpoint}")"

  : > "${mapping_file}"
  printf 'client_name vpn_ip connect_port ovpn_file rinetd_ext_start rinetd_ext_end\n' >> "${mapping_file}"

  pushd "${EASYRSA_DIR}" >/dev/null

  for ((i=1; i<=CLIENT_COUNT; i++)); do
    local client_name client_ip_last client_ip client_port client_conf_file cert_file key_file ip_tail2 file_hash file_name

    client_name="client$(printf '%03d' "${i}")"
    client_ip_last=$((i + 1))
    client_ip="10.8.0.${client_ip_last}"
    client_port="$(generate_unique_port)"

    ./easyrsa --batch build-client-full "${client_name}" nopass </dev/null

    printf 'ifconfig-push %s %s\n' "${client_ip}" "${OPENVPN_MASK}" > "${CCD_DIR}/${client_name}"

    cert_file="${EASYRSA_DIR}/pki/issued/${client_name}.crt"
    key_file="${EASYRSA_DIR}/pki/private/${client_name}.key"
    ip_tail2="$(printf '%02d' $((client_ip_last % 100)))"
    file_hash="$(random_hash)"
    file_name="Server_${endpoint_tag}_${ip_tail2}_${file_hash}.ovpn"
    client_conf_file="${CLIENT_OUTPUT_DIR}/${file_name}"

    {
      sed "s/__PORT__/${client_port}/g" "${CLIENT_BASE_CONF}"
      echo "<cert>"
      extract_cert_body "${cert_file}"
      echo "</cert>"
      echo "<key>"
      cat "${key_file}"
      echo "</key>"
    } > "${client_conf_file}"

    local ext_start ext_end
    ext_start=$((RINETD_PORT_START + (i - 1) * RINETD_PORTS_PER_CLIENT))
    ext_end=$((ext_start + RINETD_PORTS_PER_CLIENT - 1))

    printf '%s %s %s %s %s %s\n' "${client_name}" "${client_ip}" "${client_port}" "${file_name}" "${ext_start}" "${ext_end}" >> "${mapping_file}"
  done

  popd >/dev/null
}

write_openvpn_service() {
  local openvpn_bin
  openvpn_bin="$(command -v openvpn || true)"
  [[ -n "${openvpn_bin}" ]] || die "未找到 openvpn 可执行文件。"

  cat > "${OPENVPN_SERVICE_FILE}" <<EOF
[Unit]
Description=Auto OpenVPN Server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=${openvpn_bin} --config ${SERVER_CONF}
Restart=on-failure
RestartSec=3

[Install]
WantedBy=multi-user.target
EOF
}

write_iptables_rules() {
  local nic="$1"
  local server_ip="$2"
  local mapping_file="${CLIENT_OUTPUT_DIR}/client-mapping.txt"
  local rinetd_external_end
  rinetd_external_end=$((RINETD_PORT_START + CLIENT_COUNT * RINETD_PORTS_PER_CLIENT - 1))

  cat > "${IPTABLES_RULES_FILE}" <<EOF
#!/usr/bin/env bash
set -euo pipefail

ACTION=""

if [[ \$# -gt 0 ]]; then
  ACTION="\$1"
fi

apply_rules() {
  iptables -C FORWARD -s ${OPENVPN_CIDR} -j ACCEPT >/dev/null 2>&1 || iptables -A FORWARD -s ${OPENVPN_CIDR} -j ACCEPT
  iptables -C FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT >/dev/null 2>&1 || iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
  iptables -C INPUT -p ${OPENVPN_SERVER_PROTO} --dport ${OPENVPN_SERVER_PORT} -j ACCEPT >/dev/null 2>&1 || iptables -A INPUT -p ${OPENVPN_SERVER_PROTO} --dport ${OPENVPN_SERVER_PORT} -j ACCEPT
  iptables -t nat -C POSTROUTING -s ${OPENVPN_CIDR} -o ${nic} -j MASQUERADE >/dev/null 2>&1 || iptables -t nat -A POSTROUTING -s ${OPENVPN_CIDR} -o ${nic} -j MASQUERADE
EOF

  while read -r client_name client_ip client_port ovpn_file ext_start ext_end; do
    cat >> "${IPTABLES_RULES_FILE}" <<EOF
  iptables -t nat -C PREROUTING -p ${OPENVPN_SERVER_PROTO} --dport ${client_port} -j DNAT --to-destination ${server_ip}:${OPENVPN_SERVER_PORT} >/dev/null 2>&1 || iptables -t nat -A PREROUTING -p ${OPENVPN_SERVER_PROTO} --dport ${client_port} -j DNAT --to-destination ${server_ip}:${OPENVPN_SERVER_PORT}
  iptables -C INPUT -p ${OPENVPN_SERVER_PROTO} --dport ${client_port} -j ACCEPT >/dev/null 2>&1 || iptables -A INPUT -p ${OPENVPN_SERVER_PROTO} --dport ${client_port} -j ACCEPT
EOF
  done < <(tail -n +2 "${mapping_file}")

  cat >> "${IPTABLES_RULES_FILE}" <<'EOF'
}

clear_rules() {
EOF

  while read -r client_name client_ip client_port ovpn_file ext_start ext_end; do
    cat >> "${IPTABLES_RULES_FILE}" <<EOF
  iptables -C INPUT -p ${OPENVPN_SERVER_PROTO} --dport ${client_port} -j ACCEPT >/dev/null 2>&1 && iptables -D INPUT -p ${OPENVPN_SERVER_PROTO} --dport ${client_port} -j ACCEPT
  iptables -t nat -C PREROUTING -p ${OPENVPN_SERVER_PROTO} --dport ${client_port} -j DNAT --to-destination ${server_ip}:${OPENVPN_SERVER_PORT} >/dev/null 2>&1 && iptables -t nat -D PREROUTING -p ${OPENVPN_SERVER_PROTO} --dport ${client_port} -j DNAT --to-destination ${server_ip}:${OPENVPN_SERVER_PORT}
EOF
  done < <(tail -n +2 "${mapping_file}")

  cat >> "${IPTABLES_RULES_FILE}" <<EOF
  iptables -C INPUT -p tcp --dport ${RINETD_PORT_START}:${rinetd_external_end} -j ACCEPT >/dev/null 2>&1 && iptables -D INPUT -p tcp --dport ${RINETD_PORT_START}:${rinetd_external_end} -j ACCEPT
  iptables -t nat -C POSTROUTING -s ${OPENVPN_CIDR} -o ${nic} -j MASQUERADE >/dev/null 2>&1 && iptables -t nat -D POSTROUTING -s ${OPENVPN_CIDR} -o ${nic} -j MASQUERADE
  iptables -C INPUT -p ${OPENVPN_SERVER_PROTO} --dport ${OPENVPN_SERVER_PORT} -j ACCEPT >/dev/null 2>&1 && iptables -D INPUT -p ${OPENVPN_SERVER_PROTO} --dport ${OPENVPN_SERVER_PORT} -j ACCEPT
  iptables -C FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT >/dev/null 2>&1 && iptables -D FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
  iptables -C FORWARD -s ${OPENVPN_CIDR} -j ACCEPT >/dev/null 2>&1 && iptables -D FORWARD -s ${OPENVPN_CIDR} -j ACCEPT
}

if [[ "\${ACTION}" == "clear" ]]; then
  clear_rules
else
  apply_rules
  iptables -C INPUT -p tcp --dport ${RINETD_PORT_START}:${rinetd_external_end} -j ACCEPT >/dev/null 2>&1 || iptables -A INPUT -p tcp --dport ${RINETD_PORT_START}:${rinetd_external_end} -j ACCEPT
fi
EOF

  chmod +x "${IPTABLES_RULES_FILE}"
}

write_iptables_service() {
  cat > "${IPTABLES_SERVICE_FILE}" <<EOF
[Unit]
Description=OpenVPN and client-port NAT iptables rules
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=${IPTABLES_RULES_FILE}
ExecStop=${IPTABLES_RULES_FILE} clear
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF
}

configure_sysctl() {
  cat > "${SYSCTL_FILE}" <<EOF
net.ipv4.ip_forward=1
EOF
  sysctl -p "${SYSCTL_FILE}" >/dev/null
}

configure_rinetd() {
  local mapping_file="${CLIENT_OUTPUT_DIR}/client-mapping.txt"

  if [[ -f "${RINETD_CONF}" ]]; then
    RINETD_CONF_PREEXISTED=1
    cp "${RINETD_CONF}" "${RINETD_CONF_BACKUP}"
  else
    RINETD_CONF_PREEXISTED=0
  fi

  cat > "${RINETD_CONF}" <<EOF
# Auto-generated by ${SCRIPT_NAME}
# Format: bindaddress bindport connectaddress connectport
EOF

  while read -r client_name client_ip client_port ovpn_file ext_start ext_end; do
    printf '# %s %s ext:%s-%s -> %s:%s-%s\n' "${client_name}" "${ovpn_file}" "${ext_start}" "${ext_end}" "${client_ip}" "${RINETD_PORT_START}" "${RINETD_PORT_END}" >> "${RINETD_CONF}"
    local listen_port target_port offset
    for ((offset=0; offset<RINETD_PORTS_PER_CLIENT; offset++)); do
      listen_port=$((ext_start + offset))
      target_port=$((RINETD_PORT_START + offset))
      printf '%s %s %s %s\n' "${RINETD_BIND_IP}" "${listen_port}" "${client_ip}" "${target_port}" >> "${RINETD_CONF}"
    done
  done < <(tail -n +2 "${mapping_file}")
}

show_rinetd_diagnostics() {
  warn "rinetd 启动失败，输出诊断信息..."
  systemctl status rinetd --no-pager -l || true
  journalctl -xeu rinetd --no-pager -n 80 || true
}

rollback_rinetd_conf() {
  if (( RINETD_CONF_PREEXISTED == 1 )) && [[ -f "${RINETD_CONF_BACKUP}" ]]; then
    warn "回滚 rinetd 配置：恢复备份 ${RINETD_CONF_BACKUP}"
    cp -f "${RINETD_CONF_BACKUP}" "${RINETD_CONF}"
  else
    warn "未发现可回滚备份，删除当前生成的 ${RINETD_CONF}"
    rm -f "${RINETD_CONF}"
  fi
}

enable_services() {
  systemctl daemon-reload || die "systemd daemon-reload 执行失败。"
  systemctl enable --now openvpn-autoinstall.service || die "启用 OpenVPN 服务失败。"
  systemctl enable --now openvpn-rinetd-iptables.service || die "启用 iptables 规则服务失败。"

  if ! systemctl enable --now rinetd; then
    show_rinetd_diagnostics
    rollback_rinetd_conf
    systemctl restart rinetd >/dev/null 2>&1 || true
    die "rinetd 启动失败，已执行配置回滚。"
  fi

  if ! systemctl restart rinetd; then
    show_rinetd_diagnostics
    rollback_rinetd_conf
    systemctl restart rinetd >/dev/null 2>&1 || true
    die "rinetd 重启失败，已执行配置回滚。"
  fi
}

verify_services() {
  systemctl is-active --quiet openvpn-autoinstall.service || die "OpenVPN 服务启动失败。"
  systemctl is-active --quiet openvpn-rinetd-iptables.service || die "iptables 规则服务启动失败。"
  systemctl is-active --quiet rinetd || die "rinetd 服务启动失败。"
}

print_summary() {
  local endpoint="$1"
  local rinetd_external_end
  rinetd_external_end=$((RINETD_PORT_START + CLIENT_COUNT * RINETD_PORTS_PER_CLIENT - 1))
  log "安装完成。"
  log "OpenVPN 服务端入口：${endpoint}:${OPENVPN_SERVER_PORT}（基础端口）"
  log "客户端配置目录：${CLIENT_OUTPUT_DIR}"
  log "客户端映射文件：${CLIENT_OUTPUT_DIR}/client-mapping.txt"
  log "rinetd 每客户端内网目标区间：${RINETD_PORT_START}-${RINETD_PORT_END}"
  log "rinetd 外网监听总区间：${RINETD_BIND_IP}:${RINETD_PORT_START}-${rinetd_external_end}（按客户端切分）"

  echo
  echo "===== 客户端映射（name ip random_port ovpn_file rinetd_ext_start rinetd_ext_end）====="
  cat "${CLIENT_OUTPUT_DIR}/client-mapping.txt"
}

upload_client_configs() {
  if [[ "${SCP_ENABLED}" -ne 1 ]]; then
    log "SCP 上传未启用（SCP_ENABLED!=1），跳过。"
    return 0
  fi

  if [[ -z "${SCP_TARGET_HOST}" ]]; then
    log "警告：SCP_TARGET_HOST 未设置，跳过上传。"
    return 1
  fi

  if [[ -z "${SCP_PASSWORD}" ]]; then
    log "警告：SCP_PASSWORD 未设置，跳过上传。"
    return 1
  fi

  if ! command -v sshpass &>/dev/null; then
    log "安装 sshpass..."
    if command -v yum &>/dev/null; then
      yum install -y sshpass &>/dev/null
    elif command -v apt-get &>/dev/null; then
      apt-get install -y sshpass &>/dev/null
    fi
    if ! command -v sshpass &>/dev/null; then
      log "警告：sshpass 安装失败，跳过上传。"
      return 1
    fi
  fi

  local ssh_opts="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10"

  # 远端创建目标目录
  sshpass -p "${SCP_PASSWORD}" ssh ${ssh_opts} -p "${SCP_TARGET_PORT}" \
    "${SCP_TARGET_USER}@${SCP_TARGET_HOST}" \
    "mkdir -p '${SCP_TARGET_DIR}'" </dev/null 2>/dev/null || {
    log "警告：无法连接目标服务器 ${SCP_TARGET_HOST}，跳过上传。"
    return 1
  }

  local ovpn_files=("${CLIENT_OUTPUT_DIR}"/*.ovpn)
  if [[ ! -e "${ovpn_files[0]}" ]]; then
    log "警告：未找到 .ovpn 文件，跳过上传。"
    return 1
  fi

  local count=0
  for f in "${ovpn_files[@]}"; do
    if sshpass -p "${SCP_PASSWORD}" scp ${ssh_opts} -P "${SCP_TARGET_PORT}" \
         "${f}" \
         "${SCP_TARGET_USER}@${SCP_TARGET_HOST}:${SCP_TARGET_DIR}/" </dev/null 2>/dev/null; then
      count=$((count + 1))
      log "已上传：$(basename "${f}")"
    else
      log "上传失败：$(basename "${f}")"
    fi
  done

  log "SCP 上传完成：${count}/${#ovpn_files[@]} 个 .ovpn → ${SCP_TARGET_USER}@${SCP_TARGET_HOST}:${SCP_TARGET_DIR}/"
}

uninstall() {
  require_root
  log "开始卸载 OpenVPN + rinetd 自动化配置..."

  systemctl stop openvpn-autoinstall.service 2>/dev/null || true
  systemctl disable openvpn-autoinstall.service 2>/dev/null || true
  systemctl stop openvpn-rinetd-iptables.service 2>/dev/null || true
  systemctl disable openvpn-rinetd-iptables.service 2>/dev/null || true

  if [[ -x "${IPTABLES_RULES_FILE}" ]]; then
    bash "${IPTABLES_RULES_FILE}" clear 2>/dev/null || true
  fi

  rm -f "${OPENVPN_SERVICE_FILE}" "${IPTABLES_SERVICE_FILE}"
  rm -f "${IPTABLES_RULES_FILE}"
  rm -f "${SYSCTL_FILE}"
  sysctl --system >/dev/null 2>&1 || true

  local latest_backup
  latest_backup="$(ls -t /etc/rinetd.conf.bak.* 2>/dev/null | head -1)"
  if [[ -n "${latest_backup}" ]]; then
    cp -f "${latest_backup}" "${RINETD_CONF}"
    log "已恢复 rinetd 备份：${latest_backup}"
  else
    rm -f "${RINETD_CONF}"
  fi
  systemctl restart rinetd 2>/dev/null || true

  rm -rf "${OPENVPN_DIR}"

  systemctl daemon-reload
  log "卸载完成。如需移除 openvpn/rinetd 软件包，请手动执行包管理器卸载。"
}

wait_for_network() {
  local retries=30
  while (( retries > 0 )); do
    if curl -sf --max-time 5 https://1.1.1.1 -o /dev/null 2>/dev/null; then
      return 0
    fi
    log "等待网络就绪... (剩余 ${retries} 次)"
    sleep 3
    retries=$((retries - 1))
  done
  die "网络未就绪，无法继续安装。"
}

main() {
  if [[ "${1:-}" == "--uninstall" ]]; then
    uninstall
    exit 0
  fi

  require_root

  # 等待网络就绪（云平台开机脚本场景下，网络可能尚未初始化）
  wait_for_network

  # 静默模式：所有输出重定向到日志文件（开机脚本使用）
  mkdir -p "$(dirname "${LOG_FILE}")"
  exec >> "${LOG_FILE}" 2>&1
  log "========== $(date '+%F %T') 开始执行 =========="

  validate_client_count
  detect_os
  install_packages
  detect_openvpn_features
  check_preconditions

  local local_ip public_ip endpoint nic
  local_ip="$(get_main_ipv4)"
  public_ip="$(get_public_endpoint)"
  endpoint="${public_ip:-${local_ip}}"
  nic="$(get_default_interface)"

  log "服务入口 IP/域名将使用：${endpoint}"

  setup_directories
  setup_easyrsa
  write_server_conf
  write_client_base_conf "${endpoint}"
  populate_reserved_ports
  generate_clients "${endpoint}"
  upload_client_configs

  write_openvpn_service
  configure_sysctl
  write_iptables_rules "${nic}" "${local_ip}"
  write_iptables_service
  configure_rinetd

  enable_services
  verify_services
  print_summary "${endpoint}"
}

# ── Pipe-safe 入口 ─────────────────────────────────────────────────────────────
# 花括号包裹确保 bash 在执行 main() 前读取完整脚本。
# 这对 curl -sL URL | bash 管道执行至关重要，防止子进程抢占 stdin 导致脚本中断。
{
  main "$@"
}
