mirror of
https://github.com/pivpn/pivpn.git
synced 2025-02-07 03:58:52 +00:00
2cafbbf997
script/wireguard: add the '--client-ip' option to enter any Client IP from a given range when creating a client profile. This gives the user better control over which IP address will be assigned to the client.
308 lines
8.2 KiB
Bash
Executable file
308 lines
8.2 KiB
Bash
Executable file
#!/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"
|
|
echo "::: -h,--help Show this help dialog"
|
|
}
|
|
|
|
checkName() {
|
|
# check name
|
|
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 [[ -z "${CLIENT_NAME}" ]]; then
|
|
err "::: You cannot leave the name blank."
|
|
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}" ]]; 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: " 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 "======================================================================"
|