#!/bin/bash

### Constantss
# Some vars that might be empty but need to be defined for checks
pivpnPERSISTENTKEEPALIVE=""
pivpnDNS2=""

setupVars="/etc/pivpn/wireguard/setupVars.conf"

# shellcheck disable=SC1090
source "${setupVars}"

if [ ! -r /opt/pivpn/ipaddr_utils.sh ]; then
  exit 1
fi
# shellcheck disable=SC1091
source /opt/pivpn/ipaddr_utils.sh

# shellcheck disable=SC2154
userGroup="${install_user}:${install_user}"

### Functions
err() {
  echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2
}

helpFunc() {
  echo "::: Create a client conf profile"
  echo ":::"
  echo "::: Usage: pivpn <-a|add> [-n|--name <arg>] [-ip|--client-ip <ipv4>] [-h|--help]"
  echo ":::"
  echo "::: Commands:"
  echo ":::  [none]               Interactive mode"
  echo ":::  -n,--name            Name for the Client (default: '${HOSTNAME}')"
  echo ":::  -ip,--client-ip      IPv4 address of the Client ('auto' for automatically assigning IP)"
  echo ":::  -h,--help            Show this help dialog"
}

checkName() {
  # check name
  if [[ -z "${CLIENT_NAME}" ]]; then
    err "::: Name is blank. Defaulting to '${HOSTNAME}'."
    CLIENT_NAME=$HOSTNAME
  fi
  if [[ "${CLIENT_NAME}" =~ [^a-zA-Z0-9.@_-] ]]; then
    err "Name can only contain alphanumeric characters and these symbols (.-@_)."
    exit 1
  elif [[ "${CLIENT_NAME}" =~ ^[0-9]+$ ]]; then
    err "Names cannot be integers."
    exit 1
  elif [[ "${CLIENT_NAME}" =~ \ |\' ]]; then
    err "Names cannot contain spaces."
    exit 1
  elif [[ "${CLIENT_NAME:0:1}" == "-" ]]; then
    err "Name cannot start with - (dash)"
    exit 1
  elif [[ "${CLIENT_NAME::1}" == "." ]]; then
    err "Names cannot start with a . (dot)."
    exit 1
  elif [[ "${#CLIENT_NAME}" -gt 15 ]]; then
    err "::: Names cannot be longer than 15 characters."
    exit 1
  elif [[ "${CLIENT_NAME}" == "server" ]]; then
    err "Sorry, this is in use by the server and cannot be used by clients."
    exit 1
  elif [[ -f "configs/${CLIENT_NAME}.conf" ]]; then
    err "::: A client with this name already exists."
    exit 1
  fi
}

checkClientIP() {
  local ip ipv4_regex
  ip="$1"
  ipv4_regex="^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}"
  ipv4_regex+="(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])$"

  if [[ ! "${ip}" =~ $ipv4_regex ]]; then
    err "::: Invalid IP: ${ip}"
    exit 1
  fi
}

### Script
if [[ ! -f "${setupVars}" ]]; then
  err "::: Missing setup vars file!"
  exit 1
fi

# Parse input arguments
while [[ "$#" -gt 0 ]]; do
  _key="${1}"

  case "${_key}" in
    -n | --name | --name=*)
      _val="${_key##--name=}"

      if [[ "${_val}" == "${_key}" ]]; then
        [[ "$#" -lt 2 ]] \
          && err "::: Missing value for the optional argument '${_key}'." \
          && exit 1

        _val="${2}"
        shift
      fi

      CLIENT_NAME="${_val}"
      checkName
      ;;
    -ip | --client-ip | --client-ip=*)
      _val="${_key##--client-ip=}"

      if [[ "${_val}" == "${_key}" ]]; then
        [[ "$#" -lt 2 ]] \
          && err "::: Missing value for the optional argument '${_key}'." \
          && exit 1

        _val="${2}"
        shift
      fi

      CLIENT_IP="${_val}"
      ;;
    -h | --help)
      helpFunc
      exit 0
      ;;
    *)
      err "::: Error: Got an unexpected argument '${1}'"
      helpFunc
      exit 1
      ;;
  esac

  shift
done

# Disabling SC2154, variables sourced externaly
# shellcheck disable=SC2154
# The home folder variable was sourced from the settings file.
if [[ ! -d "${install_home}/configs" ]]; then
  mkdir "${install_home}/configs"
  chown "${userGroup}" "${install_home}/configs"
  chmod 0750 "${install_home}/configs"
fi

cd /etc/wireguard || exit

# Exclude first, last and server addresses
# shellcheck disable=SC2154
MAX_CLIENTS="$((2 ** (32 - subnetClass) - 3))"

if [ "$(wc -l configs/clients.txt | awk '{print $1}')" -ge "${MAX_CLIENTS}" ]; then
  echo "::: Can't add any more clients (max. ${MAX_CLIENTS})!"
  exit 1
fi

# shellcheck disable=SC2154
NETID_IPV4_DEC="$(dotIPv4FirstDec "${pivpnNET}" "${subnetClass}")"
BROADCAST_IPV4_DEC="$(dotIPv4LastDec "${pivpnNET}" "${subnetClass}")"

FIRST_IPV4_DEC=$((NETID_IPV4_DEC + 2))
LAST_IPV4_DEC=$((BROADCAST_IPV4_DEC - 1))
FIRST_IPV4="$(decIPv4ToDot "${FIRST_IPV4_DEC}")"
LAST_IPV4="$(decIPv4ToDot "${LAST_IPV4_DEC}")"

if [[ -z "${CLIENT_IP}" ]]; then
  read -p "Enter the Client IP from range ${FIRST_IPV4} - ${LAST_IPV4} (optional): " CLIENT_IP
fi

if [[ -n "${CLIENT_IP}" && "${CLIENT_IP}" != "auto" ]]; then
  checkClientIP "${CLIENT_IP}"
  ip="$(dotIPv4ToDec "${CLIENT_IP}")"

  if [[ "${ip}" -lt "${FIRST_IPV4_DEC}" || "${ip}" -gt "${LAST_IPV4_DEC}" ]]; then
    err "::: The specified IP ${CLIENT_IP} is not in range ${FIRST_IPV4} - ${LAST_IPV4}"
    exit 1
  fi

  if ! grep -q " ${ip}$" configs/clients.txt; then
    UNUSED_IPV4_DEC="${ip}"
  else
    err "::: IP address ${CLIENT_IP} is already in use"
    exit 1
  fi
else
  # Find an unused address for the client IP
  for ((ip = FIRST_IPV4_DEC; ip <= LAST_IPV4_DEC; ip++)); do
    if ! grep -q " ${ip}$" configs/clients.txt; then
      UNUSED_IPV4_DEC="${ip}"
      echo "::: Chosen Client IP: $(decIPv4ToDot "${ip}")"
      break
    fi
  done
fi

if [[ -z "${CLIENT_NAME}" ]]; then
  read -r -p "Enter a Name for the Client (default: '${HOSTNAME}'): " CLIENT_NAME
  checkName
else
  checkName
fi

wg genkey \
  | tee "keys/${CLIENT_NAME}_priv" \
  | wg pubkey > "keys/${CLIENT_NAME}_pub"
wg genpsk | tee "keys/${CLIENT_NAME}_psk" &> /dev/null
echo "::: Client Keys generated"

UNUSED_IPV4_DOT="$(decIPv4ToDot "${UNUSED_IPV4_DEC}")"
UNUSED_IPV4_HEX="$(decIPv4ToHex "${UNUSED_IPV4_DEC}")"

# shellcheck disable=SC2154
{
  echo '[Interface]'
  echo "PrivateKey = $(cat "keys/${CLIENT_NAME}_priv")"
  echo -n "Address = ${UNUSED_IPV4_DOT}/${subnetClass}"

  if [[ "${pivpnenableipv6}" == 1 ]]; then
    echo ",${pivpnNETv6}${UNUSED_IPV4_HEX}/${subnetClassv6}"
  else
    echo
  fi

  echo -n "DNS = ${pivpnDNS1}"

  if [[ -n "${pivpnDNS2}" ]]; then
    echo ", ${pivpnDNS2}"
  else
    echo
  fi

  echo
  echo '[Peer]'
  echo "PublicKey = $(cat keys/server_pub)"
  echo "PresharedKey = $(cat "keys/${CLIENT_NAME}_psk")"
  echo "Endpoint = ${pivpnHOST}:${pivpnPORT}"
  echo "AllowedIPs = ${ALLOWED_IPS}"

  if [[ -n "${pivpnPERSISTENTKEEPALIVE}" ]]; then
    echo "PersistentKeepalive = ${pivpnPERSISTENTKEEPALIVE}"
  fi
} > "configs/${CLIENT_NAME}.conf"

echo "::: Client config generated"

{
  echo "### begin ${CLIENT_NAME} ###"
  echo '[Peer]'
  echo "PublicKey = $(cat "keys/${CLIENT_NAME}_pub")"
  echo "PresharedKey = $(cat "keys/${CLIENT_NAME}_psk")"
  echo -n "AllowedIPs = ${UNUSED_IPV4_DOT}/32"

  if [[ "${pivpnenableipv6}" == 1 ]]; then
    echo ",${pivpnNETv6}${UNUSED_IPV4_HEX}/128"
  else
    echo
  fi

  echo "### end ${CLIENT_NAME} ###"
} >> wg0.conf

echo "::: Updated server config"

echo "${CLIENT_NAME} $(< keys/"${CLIENT_NAME}"_pub) $(date +%s) ${UNUSED_IPV4_DEC}" \
  | tee -a configs/clients.txt > /dev/null

if [[ -f /etc/pivpn/hosts.wireguard ]]; then
  echo "${UNUSED_IPV4_DOT} ${CLIENT_NAME}.pivpn" \
    | tee -a /etc/pivpn/hosts.wireguard > /dev/null

  if [[ "${pivpnenableipv6}" == 1 ]]; then
    echo "${pivpnNETv6}${UNUSED_IPV4_HEX} ${CLIENT_NAME}.pivpn" \
      | tee -a /etc/pivpn/hosts.wireguard > /dev/null
  fi

  if killall -SIGHUP pihole-FTL; then
    echo "::: Updated hosts file for Pi-hole"
  else
    err "::: Failed to reload pihole-FTL configuration"
  fi
fi

if [[ "${PLAT}" == 'Alpine' ]]; then
  if rc-service wg-quick restart; then
    echo "::: WireGuard reloaded"
  else
    err "::: Failed to reload WireGuard"
  fi
else
  if systemctl reload wg-quick@wg0; then
    echo "::: WireGuard reloaded"
  else
    err "::: Failed to reload WireGuard"
  fi
fi

cp "configs/${CLIENT_NAME}.conf" "${install_home}/configs/${CLIENT_NAME}.conf"
chown "${userGroup}" "${install_home}/configs/${CLIENT_NAME}.conf"
chmod 640 "${install_home}/configs/${CLIENT_NAME}.conf"

echo "======================================================================"
echo -e "::: Done! \e[1m${CLIENT_NAME}.conf successfully created!\e[0m"
echo -n "::: ${CLIENT_NAME}.conf was copied to ${install_home}/configs for easy"
echo "transfer."
echo "::: Please use this profile only on one device and create additional"
echo -e "::: profiles for other devices. You can also use \e[1mpivpn -qr\e[0m"
echo "::: to generate a QR Code you can scan with the mobile app."
echo "======================================================================"
