#!/bin/bash
# OpenVPN Auto Installer - Multi-IP Edition
# Author: VietHosting (https://viethosting.com)
# Version: 1.0.0
# Supports: CentOS 7, Rocky, AlmaLinux, RHEL 8/9/10, Ubuntu 22.04+
# Description: Automated OpenVPN setup with Interface-bound SNAT and native nftables routing.
# Features: Flexible IP allocation - assign a dedicated Public IP per user (1-to-1 mapping) 
#           or group multiple users to share a single Public IP.
# Optimized for VietHosting Large VPS & Dedicated Servers environment.

set -euo pipefail

# --- Configuration ---
DEFAULT_PORT="1194"
DEFAULT_PROTO="udp"
DEFAULT_DNS_1="1.1.1.1"
DEFAULT_DNS_2="1.0.0.1"
VPN_SUBNET="10.8.0.0 255.255.240.0" # /20 subnet (~4,064 IPs) - Optimized for high-performance Dedicated Servers
VPN_CIDR="10.8.0.0/20"
EASYRSA_DIR="/etc/openvpn/easy-rsa"
CLIENT_CONFIG_DIR="/root/openvpn-clients"
CCD_DIR="/etc/openvpn/server/ccd"
IP_MAP_FILE="/etc/openvpn/server/ip_map.txt"
LOCK_FILE="/run/lock/openvpn-ip-assign.lock"
SERVER_CN="server"

# Globals
OPENVPN_USER=""
OPENVPN_GROUP=""
OVP_MAJOR=""
OVP_MINOR=""
MAIN_IFACE=""

print_color() {
    local COLOR=${1:-}
    local TEXT=${2:-}
    case $COLOR in
        "green") echo -e "\e[32m${TEXT}\e[0m" ;;
        "red") echo -e "\e[31m${TEXT}\e[0m" ;;
        "yellow") echo -e "\e[33m${TEXT}\e[0m" ;;
        "cyan") echo -e "\e[36m${TEXT}\e[0m" ;;
    esac
}

check_root() {
    if [[ $EUID -ne 0 ]]; then
       print_color "red" "Error: This script must be run as root."
       exit 1
    fi
}

detect_os() {
    source /etc/os-release
    case "${ID:-}" in
        ubuntu|debian) OS="ubuntu" ;;
        centos|almalinux|rocky|rhel|ol) OS="rhel" ;;
        *) print_color "red" "Error: Unsupported operating system."; exit 1 ;;
    esac
    VERSION_MAJOR=$(echo "${VERSION_ID:-}" | cut -d. -f1)

    if getent passwd nobody >/dev/null 2>&1; then OPENVPN_USER="nobody"; else OPENVPN_USER="root"; fi
    if getent group nogroup >/dev/null 2>&1; then OPENVPN_GROUP="nogroup"; elif getent group nobody >/dev/null 2>&1; then OPENVPN_GROUP="nobody"; else OPENVPN_GROUP="root"; fi
    
    MAIN_IFACE=$(ip -4 route show default | awk '/default/ {print $5}' | head -1)
    if [[ -z "$MAIN_IFACE" ]]; then
        print_color "red" "Error: Could not detect default network interface."
        exit 1
    fi
}

get_openvpn_version() {
    if command -v openvpn >/dev/null 2>&1; then
        local OVP_FULL_VER=$(openvpn --version | awk 'NR==1{print $2}')
        OVP_MAJOR=$(echo "$OVP_FULL_VER" | cut -d. -f1)
        OVP_MINOR=$(echo "$OVP_FULL_VER" | cut -d. -f2)
    fi
}

install_dependencies() {
    if [[ "$OS" == "ubuntu" ]]; then
        print_color "yellow" "--> Optimizing Ubuntu Archive Mirror..."
        sed -i -E 's/http:\/\/([^v][a-z]|v[^n])\.archive\.ubuntu\.com/http:\/\/archive.ubuntu.com/g' /etc/apt/sources.list 2>/dev/null || true
        sed -i -E 's/http:\/\/([^v][a-z]|v[^n])\.archive\.ubuntu\.com/http:\/\/archive.ubuntu.com/g' /etc/apt/sources.list.d/ubuntu.sources 2>/dev/null || true
        
        export DEBIAN_FRONTEND=noninteractive
        print_color "yellow" "--> Installing dependencies..."
        apt-get -o Acquire::ForceIPv4=true -o Acquire::Retries=3 -o Acquire::http::Timeout=10 -o DPkg::Lock::Timeout=300 update -y
        apt-get -o Acquire::ForceIPv4=true -o Acquire::Retries=3 -o Acquire::http::Timeout=10 -o DPkg::Lock::Timeout=300 install -y openvpn easy-rsa curl ufw bc iptables
    elif [[ "$OS" == "rhel" ]]; then
        if [[ "$VERSION_MAJOR" == "7" ]]; then
            print_color "yellow" "--> Fixing CentOS 7 (EOL) repos and installing EPEL..."
            sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* 2>/dev/null || true
            sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* 2>/dev/null || true
            yum install -y epel-release
            print_color "yellow" "--> Installing OpenVPN and dependencies..."
            yum install -y openvpn easy-rsa curl firewalld policycoreutils-python bc iptables
        else
            local DNF_OPT="--setopt=fastestmirror=True --setopt=max_parallel_downloads=10 --setopt=defaultyes=True"
            dnf install $DNF_OPT -y dnf-plugins-core epel-release
            if [[ "$VERSION_MAJOR" == "8" ]]; then dnf config-manager --set-enabled powertools || dnf config-manager --set-enabled crb || true; fi
            if [[ "$VERSION_MAJOR" == "9" ]] || [[ "$VERSION_MAJOR" == "10" ]]; then dnf config-manager --set-enabled crb || true; fi
            dnf install $DNF_OPT -y openvpn easy-rsa curl firewalld policycoreutils-python-utils bc nftables
        fi
    fi
}

setup_openvpn() {
    print_color "yellow" "--> Configuring OpenVPN Server with Multi-IP SNAT support..."
    rm -rf "$EASYRSA_DIR"
    mkdir -p "$EASYRSA_DIR" /etc/openvpn/server "$CCD_DIR"
    touch "$IP_MAP_FILE"

    if [[ -d /usr/share/easy-rsa/3 ]]; then cp -r /usr/share/easy-rsa/3/* "$EASYRSA_DIR/"; else cp -r /usr/share/easy-rsa/* "$EASYRSA_DIR/"; fi
    cd "$EASYRSA_DIR"
    if [[ -f "openssl-1.0.cnf" ]]; then sed -i 's/unique_subject = yes/unique_subject = no/' openssl-1.0.cnf; fi

    ./easyrsa init-pki
    ./easyrsa --batch build-ca nopass
    export EASYRSA_REQ_CN="${SERVER_CN}"
    ./easyrsa --batch gen-req server nopass
    unset EASYRSA_REQ_CN
    ./easyrsa --batch sign-req server server
    
    get_openvpn_version

    if [[ "${OVP_MAJOR:-2}" -eq 2 && "${OVP_MINOR:-4}" -lt 5 ]]; then
        ./easyrsa gen-dh
        DH_CONFIG="dh dh.pem"
        CIPHER_CONFIG="cipher AES-256-GCM\nncp-ciphers AES-256-GCM:AES-128-GCM"        
        openvpn --genkey --secret ta.key
    else
        DH_CONFIG="dh none"
        CIPHER_CONFIG="data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305\ndata-ciphers-fallback AES-256-GCM"
        openvpn --genkey secret ta.key
    fi

    cp pki/ca.crt pki/issued/server.crt pki/private/server.key ta.key /etc/openvpn/server/
    if [[ "$DH_CONFIG" == "dh dh.pem" ]]; then cp pki/dh.pem /etc/openvpn/server/; fi

    cat > /etc/openvpn/server/server.conf <<EOF
port ${DEFAULT_PORT}
proto ${DEFAULT_PROTO}
$( [[ "$DEFAULT_PROTO" == "udp" ]] && echo "multihome" )
dev tun
topology subnet
ca ca.crt
cert server.crt
key server.key
$( [[ -n "$DH_CONFIG" ]] && echo "$DH_CONFIG" )
auth SHA256
tls-crypt ta.key
server ${VPN_SUBNET}
client-config-dir ${CCD_DIR}
ifconfig-pool-persist ipp.txt
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS ${DEFAULT_DNS_1}"
push "dhcp-option DNS ${DEFAULT_DNS_2}"
keepalive 10 120
$(echo -e "$CIPHER_CONFIG")
user ${OPENVPN_USER}
group ${OPENVPN_GROUP}
persist-key
persist-tun
status /var/log/openvpn/openvpn-status.log
log-append /var/log/openvpn/openvpn.log
verb 3
explicit-exit-notify 1
EOF
    
    mkdir -p /var/log/openvpn
    chown "${OPENVPN_USER}":"${OPENVPN_GROUP}" /var/log/openvpn
    chown root:"${OPENVPN_GROUP}" /etc/openvpn/server/*.key
    chmod 640 /etc/openvpn/server/*.key

    if [[ "$OS" == "ubuntu" ]]; then
        touch /etc/openvpn/server/ipp.txt
        chown "${OPENVPN_USER}":"${OPENVPN_GROUP}" /etc/openvpn/server/ipp.txt
    fi

    # Create Intelligent Universal SNAT Restorer
    cat > /etc/openvpn/server/apply_snat.sh <<'EOF'
#!/bin/bash
MAIN_IFACE=$(ip -4 route show default | awk '/default/ {print $5}' | head -1)
source /etc/os-release
VERSION_MAJOR=$(echo "${VERSION_ID:-}" | cut -d. -f1)

if [[ "$ID" == "ubuntu" || "$ID" == "debian" || "$VERSION_MAJOR" == "7" ]]; then
    if [[ -f /etc/openvpn/server/ip_map.txt ]]; then
        while read -r user priv pub; do
            iptables -t nat -C POSTROUTING -s "$priv" -o "$MAIN_IFACE" -j SNAT --to-source "$pub" 2>/dev/null || iptables -t nat -I POSTROUTING 1 -s "$priv" -o "$MAIN_IFACE" -j SNAT --to-source "$pub"
        done < /etc/openvpn/server/ip_map.txt
    fi
else
    nft add table ip ovpn_nat 2>/dev/null || true
    nft add chain ip ovpn_nat postrouting '{ type nat hook postrouting priority 90; policy accept; }' 2>/dev/null || true
    nft flush chain ip ovpn_nat postrouting
    if [[ -f /etc/openvpn/server/ip_map.txt ]]; then
        while read -r user priv pub; do
            nft add rule ip ovpn_nat postrouting ip saddr "$priv" oifname "$MAIN_IFACE" snat to "$pub"
        done < /etc/openvpn/server/ip_map.txt
    fi
fi
EOF
    chmod +x /etc/openvpn/server/apply_snat.sh
}

setup_firewall_and_forwarding() {
    print_color "yellow" "--> Configuring Firewall Base Rules..."
    echo "net.ipv4.ip_forward=1" > /etc/sysctl.d/99-openvpn.conf
    sysctl -p /etc/sysctl.d/99-openvpn.conf >/dev/null 2>&1 || true

    if [[ "$OS" == "ubuntu" ]]; then
        sed -i 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/' /etc/default/ufw
        ufw allow ${DEFAULT_PORT}/${DEFAULT_PROTO} >/dev/null 2>&1 || true
        ufw allow OpenSSH >/dev/null 2>&1 || true
        
        if ! grep -q "# NAT table rules for OpenVPN SNAT" /etc/ufw/before.rules; then
            sed -i '1s;^;# NAT table rules for OpenVPN SNAT\n*nat\n:POSTROUTING ACCEPT [0:0]\nCOMMIT\n\n;' /etc/ufw/before.rules
        fi
        ufw disable >/dev/null 2>&1 || true
        ufw --force enable >/dev/null 2>&1 || true
        
    elif [[ "$OS" == "rhel" ]]; then
        systemctl start firewalld && systemctl enable firewalld
        firewall-cmd --zone=public --add-port=${DEFAULT_PORT}/${DEFAULT_PROTO} --permanent >/dev/null 2>&1 || true
        
        # Native Firewalld Trust config (No deprecated --direct needed)
        firewall-cmd --zone=trusted --add-source=${VPN_CIDR} --permanent >/dev/null 2>&1 || true
        firewall-cmd --zone=trusted --add-interface=tun+ --permanent >/dev/null 2>&1 || true
        firewall-cmd --zone=public --add-masquerade --permanent >/dev/null 2>&1 || true
        firewall-cmd --reload >/dev/null 2>&1 || true

        if command -v sestatus >/dev/null 2>&1 && sestatus | grep -q "enforcing"; then
            semanage port -a -t openvpn_port_t -p ${DEFAULT_PROTO} ${DEFAULT_PORT} 2>/dev/null || true
        fi
    fi
}

get_public_ips() {
    ip -4 addr show scope global | awk '/inet / {print $2}' | cut -d/ -f1 | \
    grep -vE '^10\.|^192\.168\.|^172\.(1[6-9]|2[0-9]|3[0-1])\.' | grep -vE '^127\.|^169\.254\.'
}

generate_next_private_ip() {
    local BASE_PREFIX="10.8"
    local LAST_IP=""

    if [[ -f "$IP_MAP_FILE" ]] && [[ -s "$IP_MAP_FILE" ]]; then
        LAST_IP=$(awk '{print $2}' "$IP_MAP_FILE" | sort -t. -k3,3n -k4,4n | tail -1)
    fi

    if [[ -z "$LAST_IP" ]]; then
        echo "${BASE_PREFIX}.0.2"
        return
    fi

    IFS='.' read -r A B C D <<< "$LAST_IP"

    D=$((D + 1))
    if [[ "$D" -gt 254 ]]; then
        D=1
        C=$((C + 1))
    fi

    if [[ "$C" -gt 15 ]]; then
        print_color "red" "Private subnet exhausted (10.8.0.0/20 full)."
        exit 1
    fi

    echo "${A}.${B}.${C}.${D}"
}

apply_snat_rule() {
    local PRIV=$1
    local PUB=$2
    local ACTION=${3:-"add"}

    if [[ "$OS" == "ubuntu" ]]; then
        if [[ "$ACTION" == "add" ]]; then
            if ! iptables -t nat -C POSTROUTING -s "$PRIV" -o "$MAIN_IFACE" -j SNAT --to-source "$PUB" 2>/dev/null; then
                iptables -t nat -I POSTROUTING 1 -s "$PRIV" -o "$MAIN_IFACE" -j SNAT --to-source "$PUB"
            fi
            if ! grep -q -- "-A POSTROUTING -s $PRIV -o $MAIN_IFACE -j SNAT --to-source $PUB" /etc/ufw/before.rules; then
                sed -i "/:POSTROUTING ACCEPT \[0:0\]/a -A POSTROUTING -s $PRIV -o $MAIN_IFACE -j SNAT --to-source $PUB" /etc/ufw/before.rules
            fi
        else
            iptables -t nat -D POSTROUTING -s "$PRIV" -o "$MAIN_IFACE" -j SNAT --to-source "$PUB" 2>/dev/null || true
            sed -i "\\|-A POSTROUTING -s $PRIV -o $MAIN_IFACE -j SNAT --to-source $PUB|d" /etc/ufw/before.rules
        fi
        
    elif [[ "$OS" == "rhel" ]]; then
        if [[ "$VERSION_MAJOR" == "7" ]]; then
            # Legacy iptables for CentOS 7
            if [[ "$ACTION" == "add" ]]; then
                if ! iptables -t nat -C POSTROUTING -s "$PRIV" -o "$MAIN_IFACE" -j SNAT --to-source "$PUB" 2>/dev/null; then
                    iptables -t nat -I POSTROUTING 1 -s "$PRIV" -o "$MAIN_IFACE" -j SNAT --to-source "$PUB"
                fi
            else
                iptables -t nat -D POSTROUTING -s "$PRIV" -o "$MAIN_IFACE" -j SNAT --to-source "$PUB" 2>/dev/null || true
            fi
        else
            # Modern Native nftables for RHEL 8, 9, 10
            nft add table ip ovpn_nat 2>/dev/null || true
            nft add chain ip ovpn_nat postrouting '{ type nat hook postrouting priority 90; policy accept; }' 2>/dev/null || true
            nft flush chain ip ovpn_nat postrouting
            if [[ -f "$IP_MAP_FILE" ]]; then
                while read -r u priv_ip pub_ip; do
                    nft add rule ip ovpn_nat postrouting ip saddr "$priv_ip" oifname "$MAIN_IFACE" snat to "$pub_ip"
                done < "$IP_MAP_FILE"
            fi
        fi
    fi
}

start_openvpn() {
    print_color "yellow" "--> Starting and verifying OpenVPN Service..."
    
    local SERVICE_NAME="openvpn@server"
    if [[ -f /lib/systemd/system/openvpn-server@.service || -f /usr/lib/systemd/system/openvpn-server@.service ]]; then
        SERVICE_NAME="openvpn-server@server"
    fi

    # Inject Universal SNAT Restorer into Systemd
    mkdir -p "/etc/systemd/system/${SERVICE_NAME}.service.d"
    cat > "/etc/systemd/system/${SERVICE_NAME}.service.d/snat.conf" <<EOF
[Service]
ExecStartPost=-/bin/bash /etc/openvpn/server/apply_snat.sh
EOF
    systemctl daemon-reload

    systemctl enable $SERVICE_NAME >/dev/null 2>&1 || true
    systemctl restart $SERVICE_NAME
    
    sleep 2
    if ! systemctl is-active --quiet $SERVICE_NAME; then
        print_color "red" "❌ OpenVPN failed to start. Check logs below:"
        journalctl -xeu $SERVICE_NAME --no-pager | tail -n 15
        exit 1
    fi
    
    if ! ip link show tun0 >/dev/null 2>&1; then
        print_color "red" "❌ Service is active, but 'tun0' interface was not created. Check VPS kernel modules (TUN/TAP)."
        exit 1
    fi
    
    print_color "green" "--> OpenVPN service is running securely."
}

create_user() {
    cd "$EASYRSA_DIR"
    local USERNAME=${1:-}
    if [[ -z "$USERNAME" ]]; then
        read -p "Enter username (e.g., client1): " USERNAME
        if [[ -z "$USERNAME" ]]; then print_color "red" "Username cannot be empty."; return 1; fi
    fi

    if [[ ! "$USERNAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
        print_color "red" "Error: Invalid username format."
        return 1
    fi

    if grep -E -q "/CN=${USERNAME}$" pki/index.txt 2>/dev/null; then
        print_color "red" "Error: User '${USERNAME}' already exists."
        return 1
    fi

    mapfile -t AVAILABLE_IPS < <(get_public_ips)
    if [[ ${#AVAILABLE_IPS[@]} -eq 0 ]]; then
        print_color "red" "Error: No public IPs detected (excluding RFC1918/link-local)."
        return 1
    fi

    print_color "cyan" "--- Select Public IP for [${USERNAME}] to exit from ---"
    for i in "${!AVAILABLE_IPS[@]}"; do
        echo "$((i+1)). ${AVAILABLE_IPS[$i]}"
    done
    
    local SELECTED_IP=""
    while true; do
        read -p "Choose an IP [1-${#AVAILABLE_IPS[@]}]: " IP_CHOICE
        if [[ "$IP_CHOICE" =~ ^[0-9]+$ ]] && [[ "$IP_CHOICE" -ge 1 ]] && [[ "$IP_CHOICE" -le ${#AVAILABLE_IPS[@]} ]]; then
            SELECTED_IP=${AVAILABLE_IPS[$((IP_CHOICE-1))]}
            break
        else
            print_color "red" "Invalid choice. Please select a valid number."
        fi
    done
    
    if grep -q " $SELECTED_IP$" "$IP_MAP_FILE" 2>/dev/null; then
        print_color "yellow" "--> Note: Public IP $SELECTED_IP is already shared with other user(s)."
    fi

    read -p "Set a password for the VPN profile? (y/N): " SET_PASS
    if [[ "$SET_PASS" == "y" || "$SET_PASS" == "Y" ]]; then
        while true; do
            read -s -p "Enter PEM pass phrase (min 4 chars): " CLIENT_PASS; echo
            read -s -p "Verify PEM pass phrase: " CLIENT_PASS_VERIFY; echo
            if [[ "$CLIENT_PASS" != "$CLIENT_PASS_VERIFY" ]]; then print_color "red" "Passwords do not match."; elif [[ ${#CLIENT_PASS} -lt 4 ]]; then print_color "red" "Min 4 characters."; else break; fi
        done
        export EASYRSA_PASSIN="pass:${CLIENT_PASS}"
        export EASYRSA_PASSOUT="pass:${CLIENT_PASS}"
        ./easyrsa --batch build-client-full "$USERNAME" >/dev/null
        unset EASYRSA_PASSIN EASYRSA_PASSOUT CLIENT_PASS CLIENT_PASS_VERIFY
    else
        ./easyrsa --batch build-client-full "$USERNAME" nopass >/dev/null
    fi

    local PRIVATE_IP=""
    {
        flock -x 200
        PRIVATE_IP=$(generate_next_private_ip)
        echo "ifconfig-push $PRIVATE_IP 255.255.240.0" > "${CCD_DIR}/${USERNAME}"
        echo "$USERNAME $PRIVATE_IP $SELECTED_IP" >> "$IP_MAP_FILE"
        
        print_color "yellow" "--> Applying 1-to-1 SNAT Rule ($PRIVATE_IP -> $SELECTED_IP on $MAIN_IFACE)..."
        apply_snat_rule "$PRIVATE_IP" "$SELECTED_IP" "add"
    } 200>"$LOCK_FILE"

    mkdir -p "$CLIENT_CONFIG_DIR"
    get_openvpn_version

    if [[ "${OVP_MAJOR:-2}" -eq 2 && "${OVP_MINOR:-4}" -lt 5 ]]; then
        CIPHER_CLIENT="cipher AES-256-GCM"
    else
        CIPHER_CLIENT="data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305\ndata-ciphers-fallback AES-256-GCM"
    fi

    local CLIENT_OVPN_FILE="${CLIENT_CONFIG_DIR}/${USERNAME}.ovpn"

    cat > "$CLIENT_OVPN_FILE" <<EOF
client
dev tun
proto ${DEFAULT_PROTO}
remote ${SELECTED_IP} ${DEFAULT_PORT}
resolv-retry infinite
nobind
persist-key
persist-tun
persist-remote-ip
remote-cert-tls server
verify-x509-name ${SERVER_CN} name
auth SHA256
$(echo -e "$CIPHER_CLIENT")
verb 3

<ca>
$(cat ${EASYRSA_DIR}/pki/ca.crt)
</ca>
<cert>
$(awk '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/' ${EASYRSA_DIR}/pki/issued/${USERNAME}.crt)
</cert>
<key>
$(cat ${EASYRSA_DIR}/pki/private/${USERNAME}.key)
</key>
<tls-crypt>
$(cat /etc/openvpn/server/ta.key)
</tls-crypt>
EOF

    echo "================================================================="
    print_color "green" "✓ USER [${USERNAME}] CREATED SUCESSFULLY!"
    echo -e "LAN IP Assigned: \e[36m${PRIVATE_IP}\e[0m"
    echo -e "Public Exit IP : \e[36m${SELECTED_IP}\e[0m"
    echo -e "Config File    : \e[33m${CLIENT_OVPN_FILE}\e[0m"
    echo "================================================================="
}

revoke_user() {
    cd "$EASYRSA_DIR"
    local USER_LIST=$(awk -F'=' "/^V/ && !/\/CN=${SERVER_CN}$/ {print \$NF}" pki/index.txt)

    if [[ -z "$USER_LIST" ]]; then print_color "yellow" "No users to revoke."; return 1; fi

    print_color "yellow" "Existing users:"
    echo "$USER_LIST" | nl
    
    read -p "Enter username to revoke: " USERNAME
    if [[ -z "$USERNAME" ]] || ! echo "$USER_LIST" | grep -qw "$USERNAME"; then print_color "red" "Invalid user."; return 1; fi

    ./easyrsa --batch revoke "$USERNAME" >/dev/null
    ./easyrsa gen-crl >/dev/null
    cp pki/crl.pem /etc/openvpn/server/
    chmod 644 /etc/openvpn/server/crl.pem
    chown "${OPENVPN_USER}":"${OPENVPN_GROUP}" /etc/openvpn/server/crl.pem
    
    if ! grep -q "crl-verify" /etc/openvpn/server/server.conf; then echo "crl-verify crl.pem" >> /etc/openvpn/server/server.conf; fi

    {
        flock -x 200
        if grep -q "^$USERNAME " "$IP_MAP_FILE"; then
            local PRIVATE_IP=$(grep "^$USERNAME " "$IP_MAP_FILE" | awk '{print $2}')
            local PUBLIC_IP=$(grep "^$USERNAME " "$IP_MAP_FILE" | awk '{print $3}')
            
            print_color "yellow" "--> Removing SNAT Rule ($PRIVATE_IP -> $PUBLIC_IP)..."
            apply_snat_rule "$PRIVATE_IP" "$PUBLIC_IP" "remove"
            
            sed -i "/^$USERNAME /d" "$IP_MAP_FILE"
        fi
    } 200>"$LOCK_FILE"

    rm -f "${CCD_DIR}/${USERNAME}"

    local SERVICE_NAME="openvpn@server"
    if [[ -f /lib/systemd/system/openvpn-server@.service || -f /usr/lib/systemd/system/openvpn-server@.service ]]; then
        SERVICE_NAME="openvpn-server@server"
    fi
    systemctl restart $SERVICE_NAME

    print_color "green" "✓ User [${USERNAME}] has been completely revoked and routing cleared."
    rm -f "${CLIENT_CONFIG_DIR}/${USERNAME}.ovpn"
}

view_user_mappings() {
    echo "================================================================="
    echo -e "\e[36mUSERNAME\t\tPRIVATE LAN IP\t\tPUBLIC EXIT IP\e[0m"
    echo "-----------------------------------------------------------------"
    if [[ -f "$IP_MAP_FILE" ]] && [[ -s "$IP_MAP_FILE" ]]; then
        cat "$IP_MAP_FILE" | awk '{printf "%-24s%-24s%-15s\n", $1, $2, $3}'
    else
        echo "No users configured yet."
    fi
    echo "================================================================="
}

remove_openvpn() {
    print_color "red" "WARNING: COMPLETELY REMOVE OPENVPN AND ALL SNAT RULES?"
    read -p "Type 'y' to confirm: " CONFIRM
    if [[ "$CONFIRM" != "y" ]] && [[ "$CONFIRM" != "Y" ]]; then exit 0; fi

    local SERVICE_NAME="openvpn@server"
    if [[ -f /lib/systemd/system/openvpn-server@.service || -f /usr/lib/systemd/system/openvpn-server@.service ]]; then
        SERVICE_NAME="openvpn-server@server"
    fi

    systemctl stop $SERVICE_NAME 2>/dev/null || true
    systemctl disable $SERVICE_NAME 2>/dev/null || true

    if [[ -f "$IP_MAP_FILE" ]]; then
        local PRIV PUB
        while read -r line; do
            PRIV=$(echo "$line" | awk '{print $2}')
            PUB=$(echo "$line" | awk '{print $3}')
            apply_snat_rule "$PRIV" "$PUB" "remove"
        done < "$IP_MAP_FILE"
    fi

    if [[ "$OS" == "ubuntu" ]]; then
        sed -i 's/DEFAULT_FORWARD_POLICY="ACCEPT"/DEFAULT_FORWARD_POLICY="DROP"/' /etc/default/ufw
        ufw delete allow ${DEFAULT_PORT}/${DEFAULT_PROTO} >/dev/null 2>&1 || true
        sed -i '/# NAT table rules for OpenVPN SNAT/,/COMMIT/d' /etc/ufw/before.rules
        ufw reload >/dev/null 2>&1 || true
        apt-get remove --purge -y openvpn >/dev/null 2>&1
    elif [[ "$OS" == "rhel" ]]; then
        firewall-cmd --zone=public --remove-port=${DEFAULT_PORT}/${DEFAULT_PROTO} --permanent >/dev/null 2>&1 || true
        firewall-cmd --zone=trusted --remove-source=${VPN_CIDR} --permanent >/dev/null 2>&1 || true
        firewall-cmd --zone=trusted --remove-interface=tun+ --permanent >/dev/null 2>&1 || true
        firewall-cmd --zone=public --remove-masquerade --permanent >/dev/null 2>&1 || true
        firewall-cmd --reload >/dev/null 2>&1 || true
        yum remove -y openvpn 2>/dev/null || dnf remove -y openvpn 2>/dev/null || true
        
        # Xóa nốt table nftables nếu có
        if [[ "$VERSION_MAJOR" != "7" ]]; then
            nft delete table ip ovpn_nat 2>/dev/null || true
        fi
    fi

    rm -rf /etc/openvpn /var/log/openvpn "$CLIENT_CONFIG_DIR"
    rm -f /etc/systemd/system/${SERVICE_NAME}.service.d/snat.conf 2>/dev/null || true
    systemctl daemon-reload
    
    if [[ -f /etc/sysctl.d/99-openvpn.conf ]]; then rm -f /etc/sysctl.d/99-openvpn.conf; sysctl --system >/dev/null 2>&1; fi

    print_color "green" "✓ OpenVPN and Multi-IP routing removal completed!"
}

main() {
    check_root
    detect_os

    if [[ -f /etc/openvpn/server/server.conf ]]; then
        print_color "green" "OpenVPN (Multi-IP Edition) is already installed."
        echo "---------------------------------"
        echo "    MULTI-IP MANAGEMENT MENU"
        echo "---------------------------------"
        echo "1. Add a new user (Assign Public IP)"
        echo "2. Revoke a user"
        echo "3. View User IP Mappings"
        echo "4. Completely remove OpenVPN"
        echo "5. Exit"
        echo "---------------------------------"
        read -p "Select an option [1-5]: " choice

        case ${choice:-} in
            1) create_user ;;
            2) revoke_user ;;
            3) view_user_mappings ;;
            4) remove_openvpn ;;
            5) exit 0 ;;
            *) print_color "red" "Invalid option." ;;
        esac
    else
        print_color "yellow" "Starting OpenVPN Enterprise Multi-IP Installation..."
        install_dependencies
        setup_openvpn
        setup_firewall_and_forwarding
        start_openvpn
        print_color "green" "✓ Server installed successfully!"
        echo
        create_user
    fi
}

main "$@"