#!/bin/bash
# OpenVPN Auto Installer - SINGLE IP EDITION (The Architect Edition)
# Author: VietHosting (https://viethosting.com)
# Version: 2.0.0
# Supports: CentOS 7 (Vault), Rocky, AlmaLinux, RHEL 8/9/10, Ubuntu 22.04+
# Description: Automated, secure, and robust OpenVPN setup using native Firewall Masquerade.
# Features: Hardened TLS, Pre-generated CRL, Logrotate, and robust Public IP detection.

set -euo pipefail

# --- Configuration ---
DEFAULT_PORT="1194"
DEFAULT_PROTO="udp"
DEFAULT_DNS_1="1.1.1.1" # Cloudflare DNS Primary
DEFAULT_DNS_2="1.0.0.1" # Cloudflare DNS Secondary
VPN_SUBNET="10.8.0.0 255.255.255.0"
VPN_CIDR="10.8.0.0/24"
EASYRSA_DIR="/etc/openvpn/easy-rsa"
CLIENT_CONFIG_DIR="/root/openvpn-clients"
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
    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
        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
        fi
    fi
}

setup_openvpn() {
    print_color "yellow" "--> Configuring OpenVPN Server and CA..."
    rm -rf "$EASYRSA_DIR"
    mkdir -p "$EASYRSA_DIR" /etc/openvpn/server

    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
    
    # Pre-generate CRL for safe user revocation later
    ./easyrsa gen-crl >/dev/null
    
    get_openvpn_version

    if [[ "${OVP_MAJOR:-2}" -eq 2 && "${OVP_MINOR:-4}" -lt 5 ]]; then
        print_color "yellow" "--> Generating Diffie-Hellman (DH)..."
        ./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 pki/crl.pem 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" )
crl-verify crl.pem
auth SHA256
tls-crypt ta.key
tls-version-min 1.2
server ${VPN_SUBNET}
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
    chmod 644 /etc/openvpn/server/crl.pem
    chown "${OPENVPN_USER}":"${OPENVPN_GROUP}" /etc/openvpn/server/crl.pem

    if [[ "$OS" == "ubuntu" ]]; then
        touch /etc/openvpn/server/ipp.txt
        chown "${OPENVPN_USER}":"${OPENVPN_GROUP}" /etc/openvpn/server/ipp.txt
    fi
    
    # Configure Logrotate
    cat > /etc/logrotate.d/openvpn <<EOF
/var/log/openvpn/*.log {
    daily
    rotate 7
    compress
    missingok
    notifempty
    copytruncate
}
EOF
    print_color "green" "--> OpenVPN server configuration completed."
}

setup_firewall_and_forwarding() {
    print_color "yellow" "--> Configuring Firewall and IP Forwarding..."
    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" /etc/ufw/before.rules; then
            sed -i '1s;^;# NAT table rules for OpenVPN\n*nat\n:POSTROUTING ACCEPT [0:0]\n-A POSTROUTING -s '"$VPN_CIDR"' -o '"$MAIN_IFACE"' -j MASQUERADE\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
        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
            semanage fcontext -a -t openvpn_var_log_t "/var/log/openvpn(/.*)?" 2>/dev/null || true
            restorecon -Rv /var/log/openvpn >/dev/null 2>&1 || true
            restorecon -Rv /etc/openvpn >/dev/null 2>&1 || true
        fi
    fi
    print_color "green" "--> Firewall configuration completed."
}

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

    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" "Error: Username cannot be empty."; return 1; fi
    fi

    if [[ ! "$USERNAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
        print_color "red" "Error: Username can only contain letters, numbers, hyphens (-), and underscores (_)."
        return 1
    fi

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

    read -p "Do you want to set a password for the VPN client 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" "Error: Passwords do not match. Please try again."; elif [[ ${#CLIENT_PASS} -lt 4 ]]; then print_color "red" "Error: Password must be at least 4 characters long."; else break; fi
        done
        print_color "yellow" "--> Generating password-protected client certificate..."
        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
        print_color "yellow" "--> Generating password-less client certificate..."
        ./easyrsa --batch build-client-full "$USERNAME" nopass >/dev/null
    fi

    mkdir -p "$CLIENT_CONFIG_DIR"

    # Intelligent Public IP Fetching
    local LOCAL_IP=$(ip -4 addr show "$MAIN_IFACE" 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1 | head -1)
    if [[ -z "$LOCAL_IP" ]] || [[ "$LOCAL_IP" =~ ^(10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|169\.254\.|127\.) ]]; then
        PUBLIC_IP=$(curl -s -4 --max-time 3 https://api.ipify.org || curl -s -4 --max-time 3 https://ifconfig.me || echo "")
    else
        PUBLIC_IP="$LOCAL_IP"
    fi
    
    if [[ -z "$PUBLIC_IP" ]]; then
        read -p "Cannot automatically retrieve IPv4. Please enter manually: " PUBLIC_IP
    fi

    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 ${PUBLIC_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
tls-version-min 1.2
$(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 SUCCESSFULLY!"
    echo -e "Configuration file is ready at: \e[33m${CLIENT_OVPN_FILE}\e[0m"
    echo "You can use WinSCP/FileZilla or similar tools to download this file."
    echo "Then import it into the OpenVPN Client on your PC or mobile device."
    echo "================================================================="
}

create_multiple_users() {
    read -p "How many users do you want to create? " COUNT
    if ! [[ "$COUNT" =~ ^[0-9]+$ ]] || [[ "$COUNT" -eq 0 ]]; then
        print_color "red" "Please enter a valid number greater than 0."
        return 1
    fi

    for i in $(seq 1 "$COUNT"); do
        read -p "Enter name for user $i: " USERNAME
        if [[ -n "$USERNAME" ]]; then
            create_user "$USERNAME"
        else
            print_color "red" "Invalid username, skipping."
        fi
    done
}

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 found to revoke."; return 1; fi

    print_color "yellow" "List of existing users:"
    echo "$USER_LIST" | nl
    
    read -p "Enter the 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

    # crl-verify is already in server.conf from the start. We just need to restart.
    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" "✓ Certificate for user [${USERNAME}] has been revoked."

    local CLIENT_OVPN_FILE="${CLIENT_CONFIG_DIR}/${USERNAME}.ovpn"
    if [[ -f "$CLIENT_OVPN_FILE" ]]; then
        read -p "Do you want to delete the configuration file ${CLIENT_OVPN_FILE}? [y/N]: " CONFIRM_DELETE
        if [[ "$CONFIRM_DELETE" == "y" ]] || [[ "$CONFIRM_DELETE" == "Y" ]]; then
            rm -f "$CLIENT_OVPN_FILE"
            print_color "green" "✓ Configuration file for user [${USERNAME}] has been deleted."
        fi
    fi
}

remove_openvpn() {
    print_color "red" "ARE YOU SURE YOU WANT TO COMPLETELY REMOVE OPENVPN?"
    read -p "This action cannot be undone. [y/N]: " 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 [[ "$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/,/COMMIT/d' /etc/ufw/before.rules
        ufw reload >/dev/null 2>&1 || true
    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-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
        if command -v sestatus >/dev/null 2>&1 && sestatus | grep -q "enforcing"; then
            semanage port -d -t openvpn_port_t -p ${DEFAULT_PROTO} ${DEFAULT_PORT} 2>/dev/null || true
        fi
    fi

    print_color "yellow" "--> Removing installed packages..."
    if [[ "$OS" == "ubuntu" ]]; then
        apt-get remove --purge -y openvpn >/dev/null 2>&1
    elif [[ "$OS" == "rhel" ]]; then
        yum remove -y openvpn 2>/dev/null || dnf remove -y openvpn 2>/dev/null || true
    fi

    print_color "yellow" "--> Deleting all configuration files..."    
    rm -rf /etc/openvpn /var/log/openvpn "$CLIENT_CONFIG_DIR" /etc/logrotate.d/openvpn
    
    if [[ -f /etc/sysctl.d/99-openvpn.conf ]]; then
        rm -f /etc/sysctl.d/99-openvpn.conf
        sysctl --system >/dev/null 2>&1 || true
    fi

    print_color "green" "✓ OpenVPN removal completed!"
}

main() {
    check_root
    detect_os

    if [[ -f /etc/openvpn/server/server.conf ]]; then
        print_color "green" "OpenVPN is already installed."
        echo "---------------------------------"
        echo "     OPENVPN MANAGEMENT MENU"
        echo "---------------------------------"
        echo "1. Add a new user"
        echo "2. Add multiple users"
        echo "3. Revoke (delete) a user"
        echo "4. Completely remove OpenVPN"
        echo "5. Exit"
        echo "---------------------------------"
        read -p "Select an option [1-5]: " choice

        case ${choice:-} in
            1) create_user ;;
            2) create_multiple_users ;;
            3) revoke_user ;;
            4) remove_openvpn ;;
            5) exit 0 ;;
            *) print_color "red" "Invalid option." ;;
        esac
    else
        print_color "yellow" "OpenVPN is not installed. Starting installation..."
        install_dependencies
        setup_openvpn
        setup_firewall_and_forwarding
        start_openvpn
        print_color "green" "✓ OpenVPN server installed successfully!"
        echo
        create_user
        print_color "green" "Done! Your OpenVPN server is ready to use."
    fi
}

main "$@"