#!/bin/bash

# ============================================================
# OpenVPN client one-shot installer for Anolis OS 8
# Supported OS: Anolis OS 8 / RHEL 8 compatible hosts
# Published by: helper.sh
# Script note: curated by helper.sh
# ============================================================

set -euo pipefail

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

CONFIG_DIR="/etc/openvpn/client"
CONFIG_NAME="client"
SOURCE_CONF="./client.conf"
SOURCE_LOGIN="./login.txt"
SOURCE_DNS_HELPER="./update-resolv-conf"
DNS_HELPER_PATH="/etc/openvpn/update-resolv-conf"
NO_DNS=0
EPEL_GPG_KEY_URL="https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8"
EPEL_RELEASE_URL="https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm"
DNS_HELPER_INSTALLED=0
# DNS values applied by the helper and persisted into NetworkManager (env-overridable)
VPN_DNS_PRIMARY="${VPN_DNS_PRIMARY:-10.7.7.53}"
VPN_DNS_FALLBACK="${VPN_DNS_FALLBACK:-114.114.114.114}"

info()    { echo -e "${BLUE}[INFO]${NC}  $1"; }
success() { echo -e "${GREEN}[OK]${NC}    $1"; }
warning() { echo -e "${YELLOW}[WARN]${NC}  $1"; }
error()   { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }

# When NetworkManager manages the uplink it periodically regenerates
# /etc/resolv.conf, wiping whatever the up hook wrote (symptom: internal
# domains resolve right after the VPN connects, then fail some time later).
# Persist the VPN DNS in the connection profile so every rewrite keeps it.
persist_dns_via_networkmanager() {
  systemctl is-active NetworkManager >/dev/null 2>&1 || return 0
  command -v nmcli >/dev/null 2>&1 || return 0
  local dev con
  dev="$(ip route show default 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i=="dev") print $(i+1)}' | head -1)"
  if [ -z "${dev}" ]; then
    warning "NetworkManager is active but no default route device was found; skipping DNS persistence."
    return 0
  fi
  con="$(nmcli -t -f NAME,DEVICE connection show --active | awk -F: -v d="${dev}" '$2==d {print $1; exit}')"
  if [ -z "${con}" ]; then
    warning "No active NetworkManager connection found on ${dev}; skipping DNS persistence."
    return 0
  fi
  info "NetworkManager manages ${dev} (connection: ${con}); persisting VPN DNS in the profile ..."
  if nmcli connection modify "${con}" \
       ipv4.dns "${VPN_DNS_PRIMARY} ${VPN_DNS_FALLBACK}" \
       ipv4.ignore-auto-dns yes \
       ipv4.dns-options "timeout:2" \
     && nmcli connection up "${con}" >/dev/null; then
    success "NetworkManager now always writes ${VPN_DNS_PRIMARY} + ${VPN_DNS_FALLBACK} to /etc/resolv.conf"
  else
    warning "Failed to persist DNS via nmcli; NetworkManager may later rewrite /etc/resolv.conf without the VPN DNS."
  fi
}

usage() {
  cat <<'EOF'
Usage: install_openvpn_client_anolis8.sh [options]

Options:
  --config-dir PATH       Install destination (default: /etc/openvpn/client)
  --config-name NAME      Systemd instance name (default: client)
  --source-conf PATH      Source client.conf path (default: ./client.conf)
  --source-login PATH     Source login.txt path (default: ./login.txt)
  --source-dns-helper     Optional local update-resolv-conf path (default: ./update-resolv-conf)
  --no-dns                Do not install the DNS helper or up/down hooks; leave /etc/resolv.conf untouched
  -h, --help              Show this help message

Optional local files in the current directory by default:
  ./client.conf
  ./login.txt
If these are absent, OpenVPN is installed and /etc/openvpn/client is
prepared so you can drop the files in afterwards.
EOF
}

while [ $# -gt 0 ]; do
  case "$1" in
    --config-dir)
      CONFIG_DIR="$2"
      shift 2
      ;;
    --config-name)
      CONFIG_NAME="$2"
      shift 2
      ;;
    --source-conf)
      SOURCE_CONF="$2"
      shift 2
      ;;
    --source-login)
      SOURCE_LOGIN="$2"
      shift 2
      ;;
    --source-dns-helper)
      SOURCE_DNS_HELPER="$2"
      shift 2
      ;;
    --no-dns)
      NO_DNS=1
      shift
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      error "Unknown argument: $1"
      ;;
  esac
done

if [ "${EUID}" -ne 0 ]; then
  error "Run this script with root privileges: sudo bash $0"
fi

TARGET_CONF="${CONFIG_DIR}/${CONFIG_NAME}.conf"
TARGET_LOGIN="${CONFIG_DIR}/login.txt"

HAVE_CONFIG=1
NEED_COPY=1
if [ -f "${SOURCE_CONF}" ] && [ -f "${SOURCE_LOGIN}" ]; then
  info "Found ${SOURCE_CONF} and ${SOURCE_LOGIN} in the current directory."
elif [ -f "${TARGET_CONF}" ] && [ -f "${TARGET_LOGIN}" ]; then
  NEED_COPY=0
  info "Using existing config already present in ${CONFIG_DIR} (${TARGET_CONF}, ${TARGET_LOGIN})."
else
  HAVE_CONFIG=0
  [ -f "${SOURCE_CONF}" ] || [ -f "${TARGET_CONF}" ] || warning "Missing client.conf in ./ and ${CONFIG_DIR}."
  [ -f "${SOURCE_LOGIN}" ] || [ -f "${TARGET_LOGIN}" ] || warning "Missing login.txt in ./ and ${CONFIG_DIR}."
  warning "Will install OpenVPN and prepare ${CONFIG_DIR} without applying a client config."
fi

info "Checking operating system..."
. /etc/os-release
if [ "${ID:-}" != "anolis" ] && [[ "${ID_LIKE:-}" != *"rhel"* ]]; then
  warning "This script was prepared for Anolis OS 8 style hosts. Detected: ${ID:-unknown}"
fi

if [ "${VERSION_ID%%.*}" != "8" ]; then
  warning "This script was tested for Anolis OS 8. Continuing on ${PRETTY_NAME:-unknown}."
else
  success "Detected system: ${PRETTY_NAME:-Anolis OS 8}"
fi

info "Importing EPEL 8 GPG key..."
rpm --import "${EPEL_GPG_KEY_URL}" || warning "Could not import the Fedora EPEL 8 GPG key; continuing (the distro epel-release ships its own key)."

ensure_epel_repo() {
  if rpm -q epel-release >/dev/null 2>&1; then
    success "epel-release is already installed"
    return
  fi

  info "Installing epel-release from the distribution repositories ..."
  if dnf install -y epel-release >/dev/null 2>&1; then
    success "epel-release installed from the distribution repositories"
    return
  fi

  warning "epel-release not found in the distribution repositories; installing EPEL 8 from Fedora ..."
  dnf install -y "${EPEL_RELEASE_URL}"
  success "epel-release installed from Fedora"
}

info "Ensuring the EPEL repository is available..."
ensure_epel_repo

info "Refreshing dnf metadata..."
dnf clean all >/dev/null 2>&1 || true
if ! timeout -k 10 120 dnf makecache -y >/dev/null 2>&1; then
  warning "dnf makecache failed or timed out after 120s. Check network access and mirror reachability, then re-run this installer."
fi
success "dnf metadata refreshed"

if ! rpm -q openvpn >/dev/null 2>&1 && ! dnf list --available openvpn >/dev/null 2>&1; then
  error "The openvpn package is unavailable. Check 'dnf repolist' and confirm the EPEL repository is enabled and reachable."
fi

info "Installing OpenVPN..."
dnf install -y openvpn
success "OpenVPN installed"

info "Preparing ${CONFIG_DIR} ..."
mkdir -p "${CONFIG_DIR}"
success "${CONFIG_DIR} is ready"

if [ "${HAVE_CONFIG}" -eq 0 ]; then
  echo ""
  echo -e "${GREEN}============================================================${NC}"
  echo -e "${GREEN} OpenVPN installed. Client config not applied yet.           ${NC}"
  echo -e "${GREEN}============================================================${NC}"
  echo ""
  echo -e "  ${YELLOW}Next steps:${NC} put your client files into ${CONFIG_DIR}:"
  echo -e "    ${CONFIG_DIR}/${CONFIG_NAME}.conf   (your client.conf)"
  echo -e "    ${CONFIG_DIR}/login.txt             (username/password)"
  echo ""
  echo -e "  Then enable and start the service:"
  echo -e "    systemctl enable --now openvpn-client@${CONFIG_NAME}"
  echo ""
  echo -e "  ${BLUE}Or${NC} re-run this installer from a directory that contains"
  echo -e "  ./client.conf and ./login.txt to configure everything automatically."
  echo ""
  exit 0
fi

BACKUP_SUFFIX="$(date +%Y%m%d%H%M%S)"

ensure_line() {
  local file="$1"
  local line="$2"
  if ! grep -Fxq "$line" "$file"; then
    printf '\n%s\n' "$line" >> "$file"
  fi
}

if [ "${NEED_COPY}" -eq 1 ]; then
  if [ -f "${TARGET_CONF}" ]; then
    cp -f "${TARGET_CONF}" "${TARGET_CONF}.bak.${BACKUP_SUFFIX}"
    warning "Backed up existing config to ${TARGET_CONF}.bak.${BACKUP_SUFFIX}"
  fi

  if [ -f "${TARGET_LOGIN}" ]; then
    cp -f "${TARGET_LOGIN}" "${TARGET_LOGIN}.bak.${BACKUP_SUFFIX}"
    warning "Backed up existing login file to ${TARGET_LOGIN}.bak.${BACKUP_SUFFIX}"
  fi

  cp -f "${SOURCE_CONF}" "${TARGET_CONF}"
  cp -f "${SOURCE_LOGIN}" "${TARGET_LOGIN}"
else
  info "Skipping copy; config files already in ${CONFIG_DIR}."
fi

sed -i 's/\r$//' "${TARGET_CONF}"
sed -i 's/\r$//' "${TARGET_LOGIN}"

chmod 600 "${TARGET_CONF}"
chmod 600 "${TARGET_LOGIN}"

if ! grep -q "auth-user-pass" "${TARGET_CONF}"; then
  warning "client.conf does not contain auth-user-pass. Username/password login may not be enabled."
fi

if grep -q "auth-user-pass login.txt" "${TARGET_CONF}"; then
  success "client.conf references login.txt in the target directory"
else
  warning "client.conf does not reference 'login.txt' exactly. Confirm the auth-user-pass path manually."
fi

if [ "${NO_DNS}" -eq 1 ]; then
  info "--no-dns requested: skipping DNS helper install and up/down hooks. The VPN will not modify /etc/resolv.conf."
else
if [ -f "${SOURCE_DNS_HELPER}" ]; then
  info "Using local DNS helper from ${SOURCE_DNS_HELPER} ..."
  cp -f "${SOURCE_DNS_HELPER}" "${DNS_HELPER_PATH}"
  sed -i 's/\r$//' "${DNS_HELPER_PATH}"
else
  info "No local DNS helper provided. Installing bundled Anolis OS 8 DNS helper ..."
  cat > "${DNS_HELPER_PATH}" <<'EOF'
#!/bin/bash
# OpenVPN DNS switch helper for Anolis OS 8 (NetworkManager-aware)
set -euo pipefail

# OpenVPN launches hooks with a sanitized PATH that lacks sbin; ip/nmcli need it
export PATH="${PATH}:/usr/local/sbin:/usr/sbin:/sbin"

LOG_FILE="/var/log/openvpn-dns-update.log"
VPN_DNS_PRIMARY="${VPN_DNS_PRIMARY:-10.7.7.53}"
VPN_DNS_FALLBACK="${VPN_DNS_FALLBACK:-114.114.114.114}"
VPN_SEARCH_DOMAIN="${VPN_SEARCH_DOMAIN:-reshub.cn}"

mkdir -p "$(dirname "${LOG_FILE}")"
exec >> "${LOG_FILE}" 2>&1

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

UPLINK_DEV=""
UPLINK_CON=""

nm_uplink() {
  systemctl is-active NetworkManager >/dev/null 2>&1 || return 1
  command -v nmcli >/dev/null 2>&1 || return 1
  UPLINK_DEV="$(ip route show default 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i=="dev") print $(i+1)}' | head -1)"
  [ -n "${UPLINK_DEV}" ] || return 1
  UPLINK_CON="$(nmcli -t -f NAME,DEVICE connection show --active | awk -F: -v d="${UPLINK_DEV}" '$2==d {print $1; exit}')"
  [ -n "${UPLINK_CON}" ]
}

apply_dns_nm() {
  nmcli connection modify "${UPLINK_CON}" \
    ipv4.dns "$1" \
    ipv4.dns-search "$2" \
    ipv4.ignore-auto-dns yes \
    ipv4.dns-options "timeout:2" || return 1
  nmcli device reapply "${UPLINK_DEV}" >/dev/null 2>&1 \
    || nmcli connection up "${UPLINK_CON}" >/dev/null || return 1
}

write_resolv_conf() {
  {
    echo "# Generated by OpenVPN DNS helper"
    [ -n "$2" ] && echo "search $2"
    for ns in $1; do
      echo "nameserver ${ns}"
    done
  } > /etc/resolv.conf
}

apply_dns() {
  if nm_uplink; then
    if apply_dns_nm "$1" "$2"; then
      echo "[$(timestamp)] [INFO] Applied via NetworkManager (${UPLINK_CON} on ${UPLINK_DEV}): $1"
      return 0
    fi
    echo "[$(timestamp)] [WARN] nmcli failed; falling back to /etc/resolv.conf"
  fi
  write_resolv_conf "$1" "$2"
  echo "[$(timestamp)] [INFO] Wrote /etc/resolv.conf directly: $1"
}

case "${script_type:-}" in
  up)
    echo "[$(timestamp)] [INFO] VPN connected - switching to private DNS"
    apply_dns "${VPN_DNS_PRIMARY} ${VPN_DNS_FALLBACK}" "${VPN_SEARCH_DOMAIN}" || true
    ;;
  down)
    echo "[$(timestamp)] [INFO] VPN disconnected - switching to public DNS"
    apply_dns "${VPN_DNS_FALLBACK}" "" || true
    ;;
  *)
    echo "[$(timestamp)] [WARN] Unknown script type: ${script_type:-unset}"
    ;;
esac
EOF
fi

chmod +x "${DNS_HELPER_PATH}"
DNS_HELPER_INSTALLED=1
success "DNS helper installed to ${DNS_HELPER_PATH}"

info "Ensuring DNS helper hooks exist in ${TARGET_CONF} ..."
ensure_line "${TARGET_CONF}" "script-security 2"
ensure_line "${TARGET_CONF}" "up ${DNS_HELPER_PATH}"
ensure_line "${TARGET_CONF}" "down ${DNS_HELPER_PATH}"
success "OpenVPN DNS helper hooks updated"

persist_dns_via_networkmanager
fi

SERVICE_NAME="openvpn-client@${CONFIG_NAME}"

info "Enabling ${SERVICE_NAME} ..."
systemctl enable "${SERVICE_NAME}" >/dev/null

info "Starting ${SERVICE_NAME} ..."
systemctl restart "${SERVICE_NAME}"
sleep 3

if systemctl is-active --quiet "${SERVICE_NAME}"; then
  success "${SERVICE_NAME} is active"
else
  warning "${SERVICE_NAME} is not active yet. Review: journalctl -u ${SERVICE_NAME} -n 100 --no-pager"
fi

echo ""
echo -e "${GREEN}============================================================${NC}"
echo -e "${GREEN} OpenVPN client installation completed                       ${NC}"
echo -e "${GREEN}============================================================${NC}"
echo ""
echo -e "  ${BLUE}Installed config:${NC} ${TARGET_CONF}"
echo -e "  ${BLUE}Credentials file:${NC} ${TARGET_LOGIN}"
echo -e "  ${BLUE}Service name:${NC}     ${SERVICE_NAME}"
if [ "${DNS_HELPER_INSTALLED}" -eq 1 ]; then
  echo -e "  ${BLUE}DNS helper:${NC}       ${DNS_HELPER_PATH}"
elif [ "${NO_DNS}" -eq 1 ]; then
  echo -e "  ${BLUE}DNS:${NC}              not managed (--no-dns); set /etc/resolv.conf yourself"
fi
echo ""
echo -e "  ${YELLOW}Useful commands:${NC}"
echo -e "    systemctl status ${SERVICE_NAME}"
echo -e "    journalctl -u ${SERVICE_NAME} -n 100 --no-pager"
echo -e "    ip addr show tun0"
echo -e "    curl ipinfo.im"
echo -e "    cat /etc/resolv.conf"
echo ""
echo -e "  ${YELLOW}Optional config notes:${NC}"
echo -e "    route-nopull"
echo -e "    route 10.7.0.0 255.255.0.0"
echo -e "    route 10.2.0.0 255.255.0.0"
echo -e "    route 192.168.1.0 255.255.255.0"
echo -e "    crontab: 0 5 * * * /usr/bin/systemctl restart ${SERVICE_NAME}"
echo ""
