#!/bin/bash


AUTHOR="[hamid-gh98](https://github.com/hamid-gh98)"
VERSION="1.2.0"


#
# Version: 1.2.0
# Date Created: 2023-04-18
# Date Modified: 2023-05-30
# 
# Script: install_warp_proxy.sh
# 
# Description:
#   This script installs Warp Socks5 Proxy (WireProxy) for your system.
#   WireProxy is a secure and fast proxy service that routes your network traffic through Cloudflare's global network.
# 
# Author: [hamid-gh98](https://github.com/hamid-gh98)
# 
# Usage: bash ./install-warp-proxy.sh [-y] [-f]
# 
# Options:
#   -y      Accept default inputs.
#   -f      Force reinstallation of Warp Socks5 Proxy (WireProxy) even if it's already installed.
# 
# Note:
#   By default, the script checks whether Warp Socks5 Proxy (WireProxy) is already installed before proceeding.
#   Use the -y option to accept defaults.
#   Use the -f option to force reinstallation.
# 
# Thanks To: [fscarmen](https://github.com/fscarmen)
# 
# Supported OS:
#   1. Ubuntu
#   2. Debian
#   3. CentOS
#   4. Alpine
#   5. Arch
#   6. Oracle
#   7. Alma
#   8. Rocky
# 
# One-Line Command for installation: (use of this commands)
#   not-forced: `curl -fsSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh | bash`
#   not-forced: `bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh)`
#   forced: `bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh) -yf`
# 


# Define colors
red="\e[31m\e[01m"
blue="\e[36m\e[01m"
green="\e[32m\e[01m"
yellow="\e[33m\e[01m"
bYellow="\e[1;33m"
plain="\e[0m"


# Draw ASCII-ART
function draw_ascii_art() {
  echo -e "
   ____  ____       _       ____    ____  _____  ______              ______  ____  ____    
  |_   ||   _|     / \     |_   \  /   _||_   _||_   _ \`.          .' ___  ||_   ||   _|  
    | |__| |      / _ \      |   \/   |    | |    | | \`. \ ______ / .'   \_|  | |__| |    
    |  __  |     / ___ \     | |\  /| |    | |    | |  | ||______|| |   ____  |  __  |     
   _| |  | |_  _/ /   \ \_  _| |_\/_| |_  _| |_  _| |_.' /        \ \`.___]  |_| |  | |_   
  |____||____||____| |____||_____||_____||_____||______.'          \`._____.'|____||____|  
  "
}


# ===============================
# ********** Variables **********
# ===============================
# General Variables
CAN_USE_TPUT=$(command -v tput >/dev/null 2>&1 && echo "true" || echo "false")
USE_DEFAULT="false"
FORCE="false"
SPIN_TEXT_LEN=0
SPIN_PID=
WP_INSTALL_PORT="40000"
WP_LISTENING_PORT=


# Status Variables
# STEP_STATUS ==>  (0: failed) | (1: success) 
# WP_STATUS ==>  (0: not installed) | (1: off) | (2: on)
STEP_STATUS=1
WP_STATUS=0


# OS Variables
OS_SYS=
OS_INDEX=
RELEASE=("Debian" "Ubuntu" "CentOS" "CentOS" "Alpine" "Arch")
RELEASE_REGEX=("debian" "ubuntu" "centos|red hat|kernel|oracle linux|alma|rocky" "amazon linux" "alpine" "arch linux")
PKG_UPDATE=("apt -y update" "apt -y update" "yum -y update" "yum -y update" "apk update -f" "pacman -Sy")
PKG_INSTALL=("apt -y --fix-broken install" "apt -y --fix-broken install" "yum -y install" "yum -y install" "apk add -f --no-cache" "pacman -S --noconfirm")


# ===============================
# *********** Message ***********
# ===============================
declare -A T
# base
T[000]="Option requires an argument:"
T[001]="Invalid option:"
T[002]="Invalid choice."
T[003]=""
T[004]=""
T[005]="[INFO]"
T[006]="[ALERT]"
T[007]="[ERROR]"
T[008]="[DEBUG]"
T[009]="[WARNING]"
# intro
T[010]="Thanks for using this script to install warp. \n  Please give a star on github if you find this script useful!"
T[011]="Version:"
T[012]="Author:"
T[013]=""
T[014]="Options:"
T[015]="Accept default values."
T[016]="Force reinstall Warp Socks5 Proxy (WireProxy)."
T[017]=""
T[018]=""
T[019]=""
T[020]="Useful Commands:"
T[021]="Uninstall Warp"
T[022]="Change Warp Account Type (free, plus, ...)"
T[023]="Turn on/off WireProxy"
T[024]=""
T[025]=""
T[026]=""
T[027]=""
T[028]=""
T[029]=""
T[030]=""
T[031]=""
T[032]=""
T[033]=""
T[034]=""
T[035]=""
T[036]=""
T[037]=""
T[038]=""
T[039]=""
# show_warnings
T[040]="You're using this options:"
T[041]="Accepting default values"
T[042]="Forcing reinstall Warp Socks5 Proxy (WireProxy)"
T[043]=""
T[044]=""
T[045]=""
T[046]=""
T[047]=""
T[048]=""
T[049]=""
# prompts
T[050]="Default"
T[051]="Exceeded maximum attempts. Exiting..."
T[052]="Remaining attempts:"
T[053]="Last attempt! Remaining attempt:"
T[054]="Please enter a port for"
T[055]="Oops! Invalid input. Please enter a port between 1 and 65535."
T[056]="Oops! The port is already in use. Please choose another port between 1 and 65535!"
T[057]="WireProxy"
T[058]=""
T[059]=""
# check_root
T[060]="Verifying root access..."
T[061]="Please run this script as root!"
T[062]="Great News! You have Superuser privileges. Let's continue..."
# check_os
T[063]="Verifying if your OS is supported..."
T[064]="Unfortunately, your OS is not supported at this time! \n  The script supports Debian, Ubuntu, CentOS, Arch or Alpine systems only.\n"
T[065]="Your os is compatible with this installation. Moving forward..."
# install_base_packages
T[066]="Installing essential packages for your OS..."
T[067]="There was an error during the installation of essential packages! \n  Please check your connection or try again later."
T[068]="All required packages have been successfully installed."
# warp_command
T[069]="Checking if [warp] command shortcut exists, and creating it if necessary..."
T[070]="Failed to create [warp] command shortcut! Please try again later."
T[071]="[warp] command shortcut created successfully."
T[072]="[warp] command shortcut is already set up."
# warp_status
T[073]="Checking WARP Status..."
T[074]="The WARP socks5 proxy (WireProxy) is already installed and currently running. \n  WARP is listening on socks5://127.0.0.1:"
T[075]="The WARP socks5 proxy (WireProxy) has already been installed, but it's currently not running."
T[076]="The WARP socks5 proxy (WireProxy) isn't installed yet. No worries, we'll take care of it."
# install_warp
T[077]="Installing WARP socks5 proxy (WireProxy)..."
T[078]="Sorry, the installation of WARP socks5 proxy (WireProxy) failed! Please try again later."
T[079]="You're all set! WARP socks5 proxy (WireProxy) has been installed and is ready to use. \n  WARP is listening on socks5://127.0.0.1:"
# start_warp
T[080]="Activating the WARP socks5 proxy (WireProxy)..."
T[081]="The WARP socks5 proxy (WireProxy) failed to start! Please try again later."
T[082]="The WARP socks5 proxy (WireProxy) is active and listening on socks5://127.0.0.1:"
# confirm reinstall_warp
T[083]="Do you want to proceed with reinstalling the WARP socks5 proxy?"
# reinstall_warp
T[084]="Performing a fresh installation of WARP socks5 proxy (WireProxy)..."
T[085]="Sorry, the reinstallation of WARP socks5 proxy (WireProxy) failed! Please try again later."
T[086]="Completed! WARP socks5 proxy (WireProxy) has been reinstalled and is ready to use. \n  WARP is listening on socks5://127.0.0.1:"
T[087]=""
T[088]=""
T[089]=""
T[090]=""
T[091]=""
T[092]=""
T[093]=""
T[094]=""
T[095]=""
T[096]=""
T[097]=""
T[098]=""
T[099]=""
T[100]=""


# ===============================
# ******** Base Function ********
# ===============================
# Get Options
while getopts ":yf" opt; do
  case ${opt} in
    f)
      FORCE="true"
      ;;
    y)
      USE_DEFAULT="true"
      ;;
    :)
      echo -e "  ${red}${T[000]} -${OPTARG}${plain}" 1>&2
      exit 1
      ;;
    \?)
      echo -e "  ${red}${T[001]} -${OPTARG}${plain}" 1>&2
      exit 1
      ;;
  esac
done
shift $((OPTIND -1))


function escaped_length() {
  # escape color from string
  local str="${1}"
  local stripped_len=$(echo -e "${str}" | sed 's|\x1B\[[0-9;]\{1,\}[A-Za-z]||g' | tr '\n' ' ' | wc -m)
  echo ${stripped_len}
}


function draw_line() {
  local line=""
  local width=$(( ${COLUMNS:-${CAN_USE_TPUT:+$(tput cols)}:-92} ))
  line=$(printf "%*s" "${width}" | tr ' ' '_')
  echo "${line}"
}


function confirm() {
  local question="${1}"
  local options="${2:-Y/n}"
  local RESPONSE=""
  read -rep "  > ${question} [${options}] " RESPONSE
  RESPONSE=$(echo "${RESPONSE}" | tr '[:upper:]' '[:lower:]') || return
  if [[ -z "${RESPONSE}" ]]; then
    case "${options}" in
      "Y/n") RESPONSE="y";;
      "y/N") RESPONSE="n";;
    esac
  fi
  # return (yes=0) (no=1)
  case "${RESPONSE}" in
    "y"|"yes") return 0;;
    "n"|"no") return 1;;
    *)
      echo -e "${red}${T[002]}${plain}"
      confirm "${question}" "${options}"
      ;;
  esac
}


function run_step() {
  {
    $@
  } &> /dev/null
}


# Spinner Function
function start_spin() {
  local spin_chars='/-\|'
  local sc=0
  local delay=0.1
  local text="${1}"
  SPIN_TEXT_LEN=$(escaped_length "${text}")
  # Hide cursor
  [[ "${CAN_USE_TPUT}" == "true" ]] && tput civis
  while true; do
    printf "\r  [%s] ${text}"  "${spin_chars:sc++:1}"
    sleep ${delay}
    ((sc==${#spin_chars})) && sc=0
  done &
  SPIN_PID=$!
  # Show cursor
  [[ "${CAN_USE_TPUT}" == "true" ]] && tput cnorm
}


function kill_spin() {
  kill "${SPIN_PID}"
  wait "${SPIN_PID}" 2>/dev/null
}


function end_spin() {
  local text="${1}"
  local text_len=$(escaped_length "${text}")
  run_step "kill_spin"
  if [[ ! -z "${text}" ]]; then
    printf "\r  ${text}"
    # Due to the preceding space in the text, we append '6' to the total length.
    printf "%*s\n" $((${SPIN_TEXT_LEN} - ${text_len} + 6)) ""
  fi
  # Reset Status
  STEP_STATUS=1
}


# Clean up if script terminated.
function clean_up() {
  # Show cursor && Kill spinner
  [[ "${CAN_USE_TPUT}" == "true" ]] && tput cnorm
  end_spin ""
}
trap clean_up EXIT TERM SIGHUP SIGTERM SIGKILL


# Check OS Function
function get_os_release() {
  local RELEASE_OS=
  local RELEASE_CMD=(
    "$(grep -i pretty_name /etc/os-release 2>/dev/null | cut -d \" -f2)"
    "$(hostnamectl 2>/dev/null | grep -i system | cut -d : -f2)"
    "$(lsb_release -sd 2>/dev/null)"
    "$(grep -i description /etc/lsb-release 2>/dev/null | cut -d \" -f2)"
    "$(grep . /etc/redhat-release 2>/dev/null)"
    "$(grep . /etc/issue 2>/dev/null | cut -d \\ -f1 | sed '/^[ ]*$/d')"
  )

  for i in "${RELEASE_CMD[@]}"; do
    RELEASE_OS="${i}" && [[ -n "${RELEASE_OS}" ]] && break
  done

  echo "${RELEASE_OS}"
}


# Prompt Function
function prompt_port() {
  local for_text="${1}"
  local var_text="${2}"
  local attempts="${3:-0}"
  local check_occupied="${4:-false}"
  local default_port=""
  local error_msg=""

  # set defaults
  eval "default_port=\"\$${var_text}\""
  local ports_str="${default_port}"

  # remaining attempts
  local current_attempt=1
  local remaining_attempts=$((attempts - current_attempt + 1))
  local remaining_msg=""

  # array commands to check port occupation
  local check_cmds=(
    "ss:-nltp | grep -q"
    "lsof:-i"
  )

  # loop to get a correct port
  while true; do
    # reset error msg
    error_msg=""

    # calculate remaining attempts to show user
    remaining_attempts=$((attempts - current_attempt + 1))
    if [[ $remaining_attempts -gt 1 ]]; then
      remaining_msg="(${T[052]} ${remaining_attempts})"
    else
      remaining_msg="(${T[053]} ${remaining_attempts})"
    fi

    # ask user for input
    read -rep "  ${T[054]} ${for_text} (1-65535): `echo $'\n  > '` ${for_text} [${T[050]} '${default_port}'] ${remaining_msg}: " ports_str

    # Set default if input is empty
    if [[ -z "$ports_str" ]]; then
      ports_str=${default_port}
    fi

    # Check if port is a valid number between 1 and 65535
    is_invalid="false"
    if [[ ! "${ports_str}" =~ ^[0-9]+$ || ${ports_str} -lt 1 || ${ports_str} -gt 65535 ]]; then
      is_invalid="true"
      error_msg="${T[055]}"
    fi

    # Check if port is occupied
    if [[ "${check_occupied}" == "true" ]]; then
      for cmd_arg in "${check_cmds[@]}"; do
        IFS=':' read -r cmd args <<< "${cmd_arg}"
        if command -v "${cmd}" &> /dev/null; then
          if eval "${cmd} ${args} \":${ports_str}\"" &> /dev/null; then
            is_invalid="true"
            error_msg="${T[056]}"
            break
          fi
        fi
      done
    fi

    # if port is valid, set value and then break the loop
    if [[ "${is_invalid}" == "false" ]]; then
      eval "${var_text}=\$ports_str"
      break
    fi

    # check attempts
    if [[ ${attempts} -gt 0 && ${current_attempt} -ge ${attempts} ]]; then
      echo -e "  ${red}${T[051]}${plain}" 1>&2
      exit 1
    fi
    current_attempt=$((current_attempt + 1))

    # if invalid, show error
    echo -e "  ${red}${error_msg}${plain}"
  done
}


# ===============================
# ********** BaseSteps **********
# ===============================
function step_check_os() {
  for ((OS_INDEX=0; OS_INDEX<${#RELEASE_REGEX[@]}; OS_INDEX++)); do
    [[ $(get_os_release | tr '[:upper:]' '[:lower:]') =~ ${RELEASE_REGEX[OS_INDEX]} ]] \
    && export OS_SYS="${RELEASE[OS_INDEX]}" \
    && [ -n "${OS_SYS}" ] && break
  done
}


function step_install_pkgs() {
  {
    case "${OS_SYS}" in
      "Arch")
        ${PKG_UPDATE[OS_INDEX]}
        ;;
      *)
        ${PKG_UPDATE[OS_INDEX]}
        ${PKG_INSTALL[OS_INDEX]} wget net-tools
        ;;
    esac
  }
  [[ $? -ne 0 ]] && STEP_STATUS=0
}


function step_create_command() {
  {
    mkdir -p /etc/wireguard
    wget -N -P /etc/wireguard https://gitlab.com/fscarmen/warp/-/raw/main/menu.sh
    chmod +x /etc/wireguard/menu.sh
    ln -sf /etc/wireguard/menu.sh /usr/bin/warp
  }
  [[ $? -ne 0 ]] && STEP_STATUS=0
}


function step_check_status() {
  WP_STATUS=0
  if [[ $(type -p wireproxy) ]]; then
    WP_STATUS=1
    if [[ $(ss -nltp) =~ wireproxy ]]; then
      WP_STATUS=2
      WP_LISTENING_PORT=$(ss -nltp | grep 'wireproxy' | awk '{print $(NF-2)}' | cut -d: -f2)
    fi
  fi
}


function step_install_warp() {
  warp w <<< $'1\n1\n'"${WP_INSTALL_PORT}"$'\n1\n'
  [[ $? -ne 0 ]] && STEP_STATUS=0 || STEP_STATUS=1
}


function step_start_warp() {
  systemctl start wireproxy
  sleep 2
}


function step_reinstall_warp() {
  {
    warp u <<< $'y\n'
    run_step "step_create_command"
    warp w <<< $'1\n1\n'"${WP_INSTALL_PORT}"$'\n1\n'
  }
  [[ $? -ne 0 ]] && STEP_STATUS=0 || STEP_STATUS=1
}


# ===============================
# ************ Steps ************
# ===============================
function intro() {
  echo -e "${blue}
$(draw_line)
$(draw_line)
$(draw_ascii_art)
${plain}
  ${green}${T[011]}${plain} ${bYellow}${VERSION}${plain}
  ${green}${T[012]}${plain} ${bYellow}${AUTHOR}${plain}

  ${blue}${T[010]}${plain}

  ${red}${T[014]}${plain}
    ${green}-y${plain}     => ${bYellow}${T[015]}${plain}
    ${green}-f${plain}     => ${bYellow}${T[016]}${plain}

  ${red}${T[020]}${plain}
    ${green}warp u${plain} => ${bYellow}${T[021]}${plain}
    ${green}warp a${plain} => ${bYellow}${T[022]}${plain}
    ${green}warp y${plain} => ${bYellow}${T[023]}${plain}
${blue}
$(draw_line)
$(draw_line)
${plain}"
}


function show_warnings() {
  local should_show="false"
  local alert_msgs=()
  local alert_vars=(
    "USE_DEFAULT:-y:T[041]"
    "FORCE:-f:T[042]"
  )

  # loop through options variables and check if they exist, add to final message
  for alert in "${alert_vars[@]}"; do
    IFS=':' read -r var option message <<< "${alert}"
    if [[ "${!var}" == "true" ]]; then
      should_show="true"
      alert_msgs+=("    ${red}${option}${plain}   =>   ${blue}${!message}${plain}")
    fi
  done

  # if there is any message to show, echo it
  if [[ "${should_show}" == "true" ]]; then
    echo -e "  ${yellow}${T[006]} ${T[040]}${plain}"
    for msg in "${alert_msgs[@]}"; do
      echo -e "${msg}"
    done
    echo ""
  fi
}


function check_root() {
  start_spin "${yellow}${T[060]}${plain}"
  [[ $EUID -ne 0 ]] && end_spin "${red}${T[007]} ${T[061]}${plain}" && exit 1
  end_spin "${green}${T[062]}${plain}"
}


function check_os() {
  start_spin "${yellow}${T[063]}${plain}"
  run_step "step_check_os"
  if [[ -z "${OS_SYS}" ]]; then
    end_spin "${red}${T[007]} ${T[064]}${plain}" && exit 1
  fi
  if echo "${OS_SYS}" | grep -qiE "debian|ubuntu"; then
    export DEBIAN_FRONTEND="noninteractive"
  fi
  end_spin "${green}${T[065]}${plain}"
}


function prompt_all() {
  local attempts=5
  local check_occupied=$( [[ "${WP_STATUS}" == "0" ]] && echo "true" || echo "false" )
  [[ "${USE_DEFAULT}" == "false" ]] && prompt_port "${T[057]}" "WP_INSTALL_PORT" "${attempts}" "${check_occupied}"
}


function install_base_packages() {
  start_spin "${yellow}${T[066]}${plain}"
  run_step "step_install_pkgs"
  if [[ "${STEP_STATUS}" -eq 0 ]]; then
    end_spin "${red}${T[007]} ${T[067]}${plain}" && exit 1
  fi
  end_spin "${green}${T[068]}${plain}"
}


function warp_command() {
  start_spin "${yellow}${T[069]}${plain}"
  if ! command -v warp &> /dev/null; then
    run_step "step_create_command"
    if [[ "${STEP_STATUS}" -eq 0 ]]; then
      end_spin "${red}${T[007]} ${T[070]}${plain}" && exit 1
    fi
    end_spin "${green}${T[071]}${plain}"
  else
    end_spin "${green}${T[072]}${plain}"
  fi
}


function warp_status() {
  start_spin "${yellow}${T[073]}${plain}"
  run_step "step_check_status"
  case "${WP_STATUS}" in
    2)
      end_spin "${green}${T[074]}${WP_LISTENING_PORT}${plain}"
      ;;
    1)
      end_spin "${yellow}${T[075]}${plain}"
      ;;
    0)
      end_spin "${yellow}${T[076]}${plain}"
      ;;
  esac
}


function install_warp() {
  start_spin "${yellow}${T[077]}${plain}"
  run_step "step_install_warp"
  run_step "step_check_status"
  if [[ "${STEP_STATUS}" -eq 0 ]]; then
    end_spin "${red}${T[007]} ${T[078]}${plain}" && exit 1
  fi
  end_spin "${green}${T[079]}${WP_LISTENING_PORT}${plain}\n"
}


function start_warp() {
  start_spin "${yellow}${T[080]}${plain}"
  run_step "step_start_warp"
  run_step "step_check_status"
  if [[ "${WP_STATUS}" -ne 2 ]]; then
    end_spin "${red}${T[007]} ${T[081]}${plain}" && exit 1
  fi
  end_spin "${green}${T[082]}${WP_LISTENING_PORT}${plain}\n"
}


function reinstall_warp() {
  start_spin "${yellow}${T[084]}${plain}"
  run_step "step_reinstall_warp"
  run_step "step_check_status"
  if [[ "${STEP_STATUS}" -eq 0 ]]; then
    end_spin "${red}${T[007]} ${T[085]}${plain}" && exit 1
  fi
  end_spin "${green}${T[086]}${WP_LISTENING_PORT}${plain}\n"
}


# ===============================
# ************* Run *************
# ===============================
clear
intro
show_warnings
check_root
check_os
install_base_packages
warp_command
warp_status
case "${WP_STATUS}" in
  0)
    prompt_all
    install_warp
    ;;
  1|2)
    [[ "${FORCE}" == "false" ]] && ! confirm "${T[083]}" "y/N" && exit 0
    prompt_all
    reinstall_warp
    run_step "step_check_status"
    [[ "${WP_STATUS}" -eq 1 ]] && start_warp
    ;;
esac


# END
clean_up
# END

