#!/bin/bash # # https://github.com/hwdsl2/wireguard-install # # Based on the work of Nyr and contributors at: # https://github.com/Nyr/wireguard-install # # Copyright (c) 2022 Lin Song # Copyright (c) 2020-2022 Nyr # # Released under the MIT License. # # Permission is hereby granted, free of charge, to any person obtaining a copy of # this software and associated documentation files (the "Software"), to deal in # the Software without restriction, including without limitation the rights to # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of # the Software, and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. check_ip() { IP_REGEX='^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$' printf '%s' "$1" | tr -d '\n' | grep -Eq "$IP_REGEX" } abort_and_exit() { echo "Abort. No changes were made." >&2 exit 1 } get_export_dir() { export_to_home_dir=0 export_dir=~/ if [ -n "$SUDO_USER" ] && getent group "$SUDO_USER" >/dev/null 2>&1; then user_home_dir=$(getent passwd "$SUDO_USER" 2>/dev/null | cut -d: -f6) if [ -d "$user_home_dir" ] && [ "$user_home_dir" != "/" ]; then export_dir="$user_home_dir/" export_to_home_dir=1 fi fi } find_public_ip() { ip_url1="http://ipv4.icanhazip.com" ip_url2="http://ip1.dynupdate.no-ip.com" # Get public IP and sanitize with grep get_public_ip=$(grep -m 1 -oE '^[0-9]{1,3}(\.[0-9]{1,3}){3}$' <<< "$(wget -T 10 -t 1 -4qO- "$ip_url1" || curl -m 10 -4Ls "$ip_url1")") if ! check_ip "$get_public_ip"; then get_public_ip=$(grep -m 1 -oE '^[0-9]{1,3}(\.[0-9]{1,3}){3}$' <<< "$(wget -T 10 -t 1 -4qO- "$ip_url2" || curl -m 10 -4Ls "$ip_url2")") fi } update_sysctl() { # Enable net.ipv4.ip_forward for the system echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-wireguard-forward.conf if [[ -n "$ip6" ]]; then # Enable net.ipv6.conf.all.forwarding for the system echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.d/99-wireguard-forward.conf fi # Optimize sysctl settings such as TCP buffer sizes cat > /etc/sysctl.d/99-wireguard-optimize.conf <<'EOF' kernel.msgmnb = 65536 kernel.msgmax = 65536 net.core.wmem_max = 16777216 net.core.rmem_max = 16777216 net.ipv4.tcp_rmem = 4096 87380 16777216 net.ipv4.tcp_wmem = 4096 87380 16777216 EOF # Enable TCP BBR congestion control if kernel version >= 4.20 if modprobe -q tcp_bbr \ && printf '%s\n%s' "4.20" "$(uname -r)" | sort -C -V; then cat >> /etc/sysctl.d/99-wireguard-optimize.conf <<'EOF' net.core.default_qdisc = fq net.ipv4.tcp_congestion_control = bbr EOF fi # Apply sysctl settings sysctl -e -q -p /etc/sysctl.d/99-wireguard-forward.conf sysctl -e -q -p /etc/sysctl.d/99-wireguard-optimize.conf } new_client_dns() { if [ "$auto" = 0 ]; then echo echo "Select a DNS server for the client:" echo " 1) Current system resolvers" echo " 2) Google Public DNS" echo " 3) Cloudflare DNS" echo " 4) OpenDNS" echo " 5) Quad9" echo " 6) AdGuard DNS" echo " 7) Custom" read -rp "DNS server [2]: " dns until [[ -z "$dns" || "$dns" =~ ^[1-7]$ ]]; do echo "$dns: invalid selection." read -rp "DNS server [2]: " dns done else dns=2 fi # DNS case "$dns" in 1) # Locate the proper resolv.conf # Needed for systems running systemd-resolved if grep '^nameserver' "/etc/resolv.conf" | grep -qv '127.0.0.53' ; then resolv_conf="/etc/resolv.conf" else resolv_conf="/run/systemd/resolve/resolv.conf" fi # Extract nameservers and provide them in the required format dns=$(grep -v '^#\|^;' "$resolv_conf" | grep '^nameserver' | grep -v '127.0.0.53' | grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}' | xargs | sed -e 's/ /, /g') ;; 2|"") dns="8.8.8.8, 8.8.4.4" ;; 3) dns="1.1.1.1, 1.0.0.1" ;; 4) dns="208.67.222.222, 208.67.220.220" ;; 5) dns="9.9.9.9, 149.112.112.112" ;; 6) dns="94.140.14.14, 94.140.15.15" ;; 7) read -rp "Enter primary DNS server: " dns1 until check_ip "$dns1"; do echo "Invalid DNS server." read -rp "Enter primary DNS server: " dns1 done read -rp "Enter secondary DNS server (Enter to skip): " dns2 until [ -z "$dns2" ] || check_ip "$dns2"; do echo "Invalid DNS server." read -rp "Enter secondary DNS server (Enter to skip): " dns2 done if [ -n "$dns2" ]; then dns="$dns1, $dns2" else dns="$dns1" fi ;; esac } new_client_setup() { get_export_dir # Given a list of the assigned internal IPv4 addresses, obtain the lowest still # available octet. Important to start looking at 2, because 1 is our gateway. octet=2 while grep AllowedIPs /etc/wireguard/wg0.conf | cut -d "." -f 4 | cut -d "/" -f 1 | grep -q "$octet"; do (( octet++ )) done # Don't break the WireGuard configuration in case the address space is full if [[ "$octet" -eq 255 ]]; then echo "253 clients are already configured. The WireGuard internal subnet is full!" exit 1 fi key=$(wg genkey) psk=$(wg genpsk) # Configure client in the server cat << EOF >> /etc/wireguard/wg0.conf # BEGIN_PEER $client [Peer] PublicKey = $(wg pubkey <<< "$key") PresharedKey = $psk AllowedIPs = 10.7.0.$octet/32$(grep -q 'fddd:2c4:2c4:2c4::1' /etc/wireguard/wg0.conf && echo ", fddd:2c4:2c4:2c4::$octet/128") # END_PEER $client EOF # Create client configuration cat << EOF > "$export_dir$client".conf [Interface] Address = 10.7.0.$octet/24$(grep -q 'fddd:2c4:2c4:2c4::1' /etc/wireguard/wg0.conf && echo ", fddd:2c4:2c4:2c4::$octet/64") DNS = $dns PrivateKey = $key [Peer] PublicKey = $(grep PrivateKey /etc/wireguard/wg0.conf | cut -d " " -f 3 | wg pubkey) PresharedKey = $psk AllowedIPs = 0.0.0.0/0, ::/0 Endpoint = $(grep '^# ENDPOINT' /etc/wireguard/wg0.conf | cut -d " " -f 3):$(grep ListenPort /etc/wireguard/wg0.conf | cut -d " " -f 3) PersistentKeepalive = 25 EOF if [ "$export_to_home_dir" = 1 ]; then chown "$SUDO_USER:$SUDO_USER" "$export_dir$client".conf fi chmod 600 "$export_dir$client".conf } show_usage() { if [ -n "$1" ]; then echo "Error: $1" >&2 fi cat 1>&2 </dev/null; then echo "This system is running inside a container, which is not supported by this installer." exit 1 fi auto=0 if [[ ! -e /etc/wireguard/wg0.conf ]]; then if [ "$os" = "centos" ]; then if grep -qs "hwdsl2 VPN script" /etc/sysconfig/nftables.conf \ || systemctl is-active --quiet nftables 2>/dev/null; then echo "This system has nftables enabled, which is not supported by this installer." exit 1 fi fi while [ "$#" -gt 0 ]; do case $1 in --auto) auto=1 shift ;; -h|--help) show_usage ;; *) show_usage "Unknown parameter: $1" ;; esac done # Detect some Debian minimal setups where neither wget nor curl are installed if ! hash wget 2>/dev/null && ! hash curl 2>/dev/null; then if [ "$auto" = 0 ]; then echo "Wget is required to use this installer." read -n1 -r -p "Press any key to install Wget and continue..." fi export DEBIAN_FRONTEND=noninteractive ( set -x apt-get -yqq update || apt-get -yqq update apt-get -yqq install wget >/dev/null ) || exit 1 fi if [ "$auto" = 0 ]; then echo echo 'Welcome to this WireGuard VPN server installer!' echo echo 'I need to ask you a few questions before starting setup.' echo 'You can use the default options and just press enter if you are OK with them.' else echo echo 'Starting WireGuard setup using default options.' fi # If system has a single IPv4, it is selected automatically. if [[ $(ip -4 addr | grep inet | grep -vEc '127(\.[0-9]{1,3}){3}') -eq 1 ]]; then ip=$(ip -4 addr | grep inet | grep -vE '127(\.[0-9]{1,3}){3}' | cut -d '/' -f 1 | grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}') else # Use the IP address on the default route ip=$(ip -4 route get 1 | sed 's/ uid .*//' | awk '{print $NF;exit}' 2>/dev/null) if ! check_ip "$ip"; then find_public_ip ip_match=0 if [ -n "$get_public_ip" ]; then ip_list=$(ip -4 addr | grep inet | grep -vE '127(\.[0-9]{1,3}){3}' | cut -d '/' -f 1 | grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}') while IFS= read -r line; do if [ "$line" = "$get_public_ip" ]; then ip_match=1 ip="$line" fi done <<< "$ip_list" fi if [ "$ip_match" = 0 ]; then if [ "$auto" = 0 ]; then echo echo "Which IPv4 address should be used?" number_of_ip=$(ip -4 addr | grep inet | grep -vEc '127(\.[0-9]{1,3}){3}') ip -4 addr | grep inet | grep -vE '127(\.[0-9]{1,3}){3}' | cut -d '/' -f 1 | grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}' | nl -s ') ' read -rp "IPv4 address [1]: " ip_number until [[ -z "$ip_number" || "$ip_number" =~ ^[0-9]+$ && "$ip_number" -le "$number_of_ip" ]]; do echo "$ip_number: invalid selection." read -rp "IPv4 address [1]: " ip_number done [[ -z "$ip_number" ]] && ip_number=1 else ip_number=1 fi ip=$(ip -4 addr | grep inet | grep -vE '127(\.[0-9]{1,3}){3}' | cut -d '/' -f 1 | grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}' | sed -n "$ip_number"p) fi fi fi # If $ip is a private IP address, the server must be behind NAT if printf '%s' "$ip" | grep -qE '^(10|127|172\.(1[6-9]|2[0-9]|3[0-1])|192\.168|169\.254)\.'; then find_public_ip if ! check_ip "$get_public_ip"; then if [ "$auto" = 0 ]; then echo echo "This server is behind NAT. What is the public IPv4 address?" read -rp "Public IPv4 address: " public_ip until check_ip "$public_ip"; do echo "Invalid input." read -rp "Public IPv4 address: " public_ip done else echo "Error: Could not detect this server's public IP." >&2 echo "Abort. No changes were made." >&2 exit 1 fi else public_ip="$get_public_ip" fi fi if [ "$auto" != 0 ]; then echo printf '%s' "Server IP: " [ -n "$public_ip" ] && printf '%s\n' "$public_ip" || printf '%s\n' "$ip" echo "Port: UDP/51820" echo "Client name: client" echo "Client DNS: Google Public DNS" fi # If system has a single IPv6, it is selected automatically if [[ $(ip -6 addr | grep -c 'inet6 [23]') -eq 1 ]]; then ip6=$(ip -6 addr | grep 'inet6 [23]' | cut -d '/' -f 1 | grep -oE '([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}') fi # If system has multiple IPv6, ask the user to select one if [[ $(ip -6 addr | grep -c 'inet6 [23]') -gt 1 ]]; then if [ "$auto" = 0 ]; then echo echo "Which IPv6 address should be used?" number_of_ip6=$(ip -6 addr | grep -c 'inet6 [23]') ip -6 addr | grep 'inet6 [23]' | cut -d '/' -f 1 | grep -oE '([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}' | nl -s ') ' read -rp "IPv6 address [1]: " ip6_number until [[ -z "$ip6_number" || "$ip6_number" =~ ^[0-9]+$ && "$ip6_number" -le "$number_of_ip6" ]]; do echo "$ip6_number: invalid selection." read -rp "IPv6 address [1]: " ip6_number done [[ -z "$ip6_number" ]] && ip6_number=1 else ip6_number=1 fi ip6=$(ip -6 addr | grep 'inet6 [23]' | cut -d '/' -f 1 | grep -oE '([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}' | sed -n "$ip6_number"p) fi if [ "$auto" = 0 ]; then echo echo "What port should WireGuard listen to?" read -rp "Port [51820]: " port until [[ -z "$port" || "$port" =~ ^[0-9]+$ && "$port" -le 65535 ]]; do echo "$port: invalid port." read -rp "Port [51820]: " port done [[ -z "$port" ]] && port=51820 else port=51820 fi if [ "$auto" = 0 ]; then echo echo "Enter a name for the first client:" read -rp "Name [client]: " unsanitized_client # Allow a limited set of characters to avoid conflicts client=$(sed 's/[^0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-]/_/g' <<< "$unsanitized_client") [[ -z "$client" ]] && client=client else client=client fi new_client_dns if [ "$auto" = 0 ]; then echo echo "WireGuard installation is ready to begin." fi # Install a firewall if firewalld or iptables are not already available if ! systemctl is-active --quiet firewalld.service && ! hash iptables 2>/dev/null; then if [[ "$os" == "centos" || "$os" == "fedora" ]]; then firewall="firewalld" # We don't want to silently enable firewalld, so we give a subtle warning # If the user continues, firewalld will be installed and enabled during setup echo echo "Note: firewalld, which is required to manage routing tables, will also be installed." elif [[ "$os" == "debian" || "$os" == "ubuntu" ]]; then # iptables is way less invasive than firewalld so no warning is given firewall="iptables" fi fi if [ "$auto" = 0 ]; then read -n1 -r -p "Press any key to continue..." fi # Install WireGuard # Set up the WireGuard kernel module echo echo "Installing WireGuard, please wait..." if [[ "$os" == "ubuntu" ]]; then # Ubuntu export DEBIAN_FRONTEND=noninteractive ( set -x apt-get -yqq update || apt-get -yqq update apt-get -yqq install wireguard qrencode $firewall >/dev/null ) || exit 1 elif [[ "$os" == "debian" && "$os_version" -ge 11 ]]; then # Debian 11 or higher export DEBIAN_FRONTEND=noninteractive ( set -x apt-get -yqq update || apt-get -yqq update apt-get -yqq install wireguard qrencode $firewall >/dev/null ) || exit 1 elif [[ "$os" == "debian" && "$os_version" -eq 10 ]]; then # Debian 10 if ! grep -qs '^deb .* buster-backports main' /etc/apt/sources.list /etc/apt/sources.list.d/*.list; then echo "deb http://deb.debian.org/debian buster-backports main" >> /etc/apt/sources.list fi export DEBIAN_FRONTEND=noninteractive ( set -x apt-get -yqq update || apt-get -yqq update # Try to install kernel headers for the running kernel and avoid a reboot. This # can fail, so it's important to run separately from the other apt-get command. apt-get -yqq install linux-headers-"$(uname -r)" >/dev/null ) # There are cleaner ways to find out the $architecture, but we require an # specific format for the package name and this approach provides what we need. architecture=$(dpkg --get-selections 'linux-image-*-*' | cut -f 1 | grep -oE '[^-]*$' -m 1) # linux-headers-$architecture points to the latest headers. We install it # because if the system has an outdated kernel, there is no guarantee that old # headers were still downloadable and to provide suitable headers for future # kernel updates. ( set -x apt-get -yqq install linux-headers-"$architecture" >/dev/null apt-get -yqq install wireguard qrencode $firewall >/dev/null ) || exit 1 elif [[ "$os" == "centos" && "$os_version" -eq 9 ]]; then # CentOS 9 ( set -x yum -y -q install epel-release >/dev/null yum -y -q install wireguard-tools qrencode $firewall >/dev/null ) || exit 1 mkdir -p /etc/wireguard/ elif [[ "$os" == "centos" && "$os_version" -eq 8 ]]; then # CentOS 8 ( set -x yum -y -q install epel-release elrepo-release >/dev/null yum -y -q install kmod-wireguard wireguard-tools qrencode $firewall >/dev/null ) || exit 1 mkdir -p /etc/wireguard/ elif [[ "$os" == "centos" && "$os_version" -eq 7 ]]; then # CentOS 7 ( set -x yum -y -q install epel-release https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm >/dev/null yum -y -q install yum-plugin-elrepo >/dev/null yum -y -q install kmod-wireguard wireguard-tools qrencode $firewall >/dev/null ) || exit 1 mkdir -p /etc/wireguard/ elif [[ "$os" == "fedora" ]]; then # Fedora ( set -x dnf install -y wireguard-tools qrencode $firewall >/dev/null ) || exit 1 mkdir -p /etc/wireguard/ fi # If firewalld was just installed, enable it if [[ "$firewall" == "firewalld" ]]; then ( set -x systemctl enable --now firewalld.service >/dev/null 2>&1 ) fi # Generate wg0.conf cat << EOF > /etc/wireguard/wg0.conf # Do not alter the commented lines # They are used by wireguard-install # ENDPOINT $([[ -n "$public_ip" ]] && echo "$public_ip" || echo "$ip") [Interface] Address = 10.7.0.1/24$([[ -n "$ip6" ]] && echo ", fddd:2c4:2c4:2c4::1/64") PrivateKey = $(wg genkey) ListenPort = $port EOF chmod 600 /etc/wireguard/wg0.conf update_sysctl if systemctl is-active --quiet firewalld.service; then # Using both permanent and not permanent rules to avoid a firewalld # reload. firewall-cmd -q --add-port="$port"/udp firewall-cmd -q --zone=trusted --add-source=10.7.0.0/24 firewall-cmd -q --permanent --add-port="$port"/udp firewall-cmd -q --permanent --zone=trusted --add-source=10.7.0.0/24 # Set NAT for the VPN subnet firewall-cmd -q --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.7.0.0/24 ! -d 10.7.0.0/24 -j SNAT --to "$ip" firewall-cmd -q --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.7.0.0/24 ! -d 10.7.0.0/24 -j SNAT --to "$ip" if [[ -n "$ip6" ]]; then firewall-cmd -q --zone=trusted --add-source=fddd:2c4:2c4:2c4::/64 firewall-cmd -q --permanent --zone=trusted --add-source=fddd:2c4:2c4:2c4::/64 firewall-cmd -q --direct --add-rule ipv6 nat POSTROUTING 0 -s fddd:2c4:2c4:2c4::/64 ! -d fddd:2c4:2c4:2c4::/64 -j SNAT --to "$ip6" firewall-cmd -q --permanent --direct --add-rule ipv6 nat POSTROUTING 0 -s fddd:2c4:2c4:2c4::/64 ! -d fddd:2c4:2c4:2c4::/64 -j SNAT --to "$ip6" fi else # Create a service to set up persistent iptables rules iptables_path=$(command -v iptables) ip6tables_path=$(command -v ip6tables) # nf_tables is not available as standard in OVZ kernels. So use iptables-legacy # if we are in OVZ, with a nf_tables backend and iptables-legacy is available. if [[ $(systemd-detect-virt) == "openvz" ]] && readlink -f "$(command -v iptables)" | grep -q "nft" && hash iptables-legacy 2>/dev/null; then iptables_path=$(command -v iptables-legacy) ip6tables_path=$(command -v ip6tables-legacy) fi echo "[Unit] Before=network.target [Service] Type=oneshot ExecStart=$iptables_path -t nat -A POSTROUTING -s 10.7.0.0/24 ! -d 10.7.0.0/24 -j SNAT --to $ip ExecStart=$iptables_path -I INPUT -p udp --dport $port -j ACCEPT ExecStart=$iptables_path -I FORWARD -s 10.7.0.0/24 -j ACCEPT ExecStart=$iptables_path -I FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT ExecStop=$iptables_path -t nat -D POSTROUTING -s 10.7.0.0/24 ! -d 10.7.0.0/24 -j SNAT --to $ip ExecStop=$iptables_path -D INPUT -p udp --dport $port -j ACCEPT ExecStop=$iptables_path -D FORWARD -s 10.7.0.0/24 -j ACCEPT ExecStop=$iptables_path -D FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT" > /etc/systemd/system/wg-iptables.service if [[ -n "$ip6" ]]; then echo "ExecStart=$ip6tables_path -t nat -A POSTROUTING -s fddd:2c4:2c4:2c4::/64 ! -d fddd:2c4:2c4:2c4::/64 -j SNAT --to $ip6 ExecStart=$ip6tables_path -I FORWARD -s fddd:2c4:2c4:2c4::/64 -j ACCEPT ExecStart=$ip6tables_path -I FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT ExecStop=$ip6tables_path -t nat -D POSTROUTING -s fddd:2c4:2c4:2c4::/64 ! -d fddd:2c4:2c4:2c4::/64 -j SNAT --to $ip6 ExecStop=$ip6tables_path -D FORWARD -s fddd:2c4:2c4:2c4::/64 -j ACCEPT ExecStop=$ip6tables_path -D FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT" >> /etc/systemd/system/wg-iptables.service fi echo "RemainAfterExit=yes [Install] WantedBy=multi-user.target" >> /etc/systemd/system/wg-iptables.service ( set -x systemctl enable --now wg-iptables.service >/dev/null 2>&1 ) fi # Generates the custom client.conf new_client_setup # Enable and start the wg-quick service ( set -x systemctl enable --now wg-quick@wg0.service >/dev/null 2>&1 ) echo qrencode -t UTF8 < "$export_dir$client".conf echo -e '\xE2\x86\x91 That is a QR code containing the client configuration.' echo # If the kernel module didn't load, system probably had an outdated kernel # We'll try to help, but will not force a kernel upgrade upon the user if ! modprobe -nq wireguard; then echo "Warning!" echo "Installation was finished, but the WireGuard kernel module could not load." if [[ "$os" == "ubuntu" && "$os_version" -eq 1804 ]]; then echo 'Upgrade the kernel and headers with "apt-get install linux-generic" and restart.' elif [[ "$os" == "debian" && "$os_version" -eq 10 ]]; then echo "Upgrade the kernel with \"apt-get install linux-image-$architecture\" and restart." elif [[ "$os" == "centos" && "$os_version" -le 8 ]]; then echo "Reboot the system to load the most recent kernel." fi else echo "Finished!" fi echo echo "The client configuration is available in: $export_dir$client.conf" echo "New clients can be added by running this script again." else echo echo "WireGuard is already installed." echo echo "Select an option:" echo " 1) Add a new client" echo " 2) List existing clients" echo " 3) Remove an existing client" echo " 4) Remove WireGuard" echo " 5) Exit" read -rp "Option: " option until [[ "$option" =~ ^[1-5]$ ]]; do echo "$option: invalid selection." read -rp "Option: " option done case "$option" in 1) echo echo "Provide a name for the client:" read -rp "Name: " unsanitized_client [ -z "$unsanitized_client" ] && abort_and_exit # Allow a limited set of characters to avoid conflicts client=$(sed 's/[^0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-]/_/g' <<< "$unsanitized_client") while [[ -z "$client" ]] || grep -q "^# BEGIN_PEER $client$" /etc/wireguard/wg0.conf; do echo "$client: invalid name." read -rp "Name: " unsanitized_client [ -z "$unsanitized_client" ] && abort_and_exit client=$(sed 's/[^0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-]/_/g' <<< "$unsanitized_client") done new_client_dns new_client_setup # Append new client configuration to the WireGuard interface wg addconf wg0 <(sed -n "/^# BEGIN_PEER $client/,/^# END_PEER $client/p" /etc/wireguard/wg0.conf) echo qrencode -t UTF8 < "$export_dir$client".conf echo -e '\xE2\x86\x91 That is a QR code containing the client configuration.' echo echo "$client added. Configuration available in: $export_dir$client.conf" exit ;; 2) echo echo "Checking for existing client(s)..." number_of_clients=$(grep -c '^# BEGIN_PEER' /etc/wireguard/wg0.conf) if [[ "$number_of_clients" = 0 ]]; then echo echo "There are no existing clients!" exit fi echo grep '^# BEGIN_PEER' /etc/wireguard/wg0.conf | cut -d ' ' -f 3 | nl -s ') ' if [ "$number_of_clients" = 1 ]; then printf '\n%s\n' "Total: 1 client" elif [ -n "$number_of_clients" ]; then printf '\n%s\n' "Total: $number_of_clients clients" fi ;; 3) number_of_clients=$(grep -c '^# BEGIN_PEER' /etc/wireguard/wg0.conf) if [[ "$number_of_clients" = 0 ]]; then echo echo "There are no existing clients!" exit fi echo echo "Select the client to remove:" grep '^# BEGIN_PEER' /etc/wireguard/wg0.conf | cut -d ' ' -f 3 | nl -s ') ' read -rp "Client: " client_number [ -z "$client_number" ] && abort_and_exit until [[ "$client_number" =~ ^[0-9]+$ && "$client_number" -le "$number_of_clients" ]]; do echo "$client_number: invalid selection." read -rp "Client: " client_number [ -z "$client_number" ] && abort_and_exit done client=$(grep '^# BEGIN_PEER' /etc/wireguard/wg0.conf | cut -d ' ' -f 3 | sed -n "$client_number"p) echo read -rp "Confirm $client removal? [y/N]: " remove until [[ "$remove" =~ ^[yYnN]*$ ]]; do echo "$remove: invalid selection." read -rp "Confirm $client removal? [y/N]: " remove done if [[ "$remove" =~ ^[yY]$ ]]; then echo echo "Removing $client..." # The following is the right way to avoid disrupting other active connections: # Remove from the live interface wg set wg0 peer "$(sed -n "/^# BEGIN_PEER $client$/,\$p" /etc/wireguard/wg0.conf | grep -m 1 PublicKey | cut -d " " -f 3)" remove # Remove from the configuration file sed -i "/^# BEGIN_PEER $client$/,/^# END_PEER $client$/d" /etc/wireguard/wg0.conf get_export_dir wg_file="$export_dir$client.conf" if [ -f "$wg_file" ]; then echo "Removing $wg_file..." rm -f "$wg_file" fi echo echo "$client removed!" else echo echo "$client removal aborted!" fi exit ;; 4) echo read -rp "Confirm WireGuard removal? [y/N]: " remove until [[ "$remove" =~ ^[yYnN]*$ ]]; do echo "$remove: invalid selection." read -rp "Confirm WireGuard removal? [y/N]: " remove done if [[ "$remove" =~ ^[yY]$ ]]; then echo echo "Removing WireGuard, please wait..." port=$(grep '^ListenPort' /etc/wireguard/wg0.conf | cut -d " " -f 3) if systemctl is-active --quiet firewalld.service; then ip=$(firewall-cmd --direct --get-rules ipv4 nat POSTROUTING | grep '\-s 10.7.0.0/24 '"'"'!'"'"' -d 10.7.0.0/24' | grep -oE '[^ ]+$') # Using both permanent and not permanent rules to avoid a firewalld reload. firewall-cmd -q --remove-port="$port"/udp firewall-cmd -q --zone=trusted --remove-source=10.7.0.0/24 firewall-cmd -q --permanent --remove-port="$port"/udp firewall-cmd -q --permanent --zone=trusted --remove-source=10.7.0.0/24 firewall-cmd -q --direct --remove-rule ipv4 nat POSTROUTING 0 -s 10.7.0.0/24 ! -d 10.7.0.0/24 -j SNAT --to "$ip" firewall-cmd -q --permanent --direct --remove-rule ipv4 nat POSTROUTING 0 -s 10.7.0.0/24 ! -d 10.7.0.0/24 -j SNAT --to "$ip" if grep -qs 'fddd:2c4:2c4:2c4::1/64' /etc/wireguard/wg0.conf; then ip6=$(firewall-cmd --direct --get-rules ipv6 nat POSTROUTING | grep '\-s fddd:2c4:2c4:2c4::/64 '"'"'!'"'"' -d fddd:2c4:2c4:2c4::/64' | grep -oE '[^ ]+$') firewall-cmd -q --zone=trusted --remove-source=fddd:2c4:2c4:2c4::/64 firewall-cmd -q --permanent --zone=trusted --remove-source=fddd:2c4:2c4:2c4::/64 firewall-cmd -q --direct --remove-rule ipv6 nat POSTROUTING 0 -s fddd:2c4:2c4:2c4::/64 ! -d fddd:2c4:2c4:2c4::/64 -j SNAT --to "$ip6" firewall-cmd -q --permanent --direct --remove-rule ipv6 nat POSTROUTING 0 -s fddd:2c4:2c4:2c4::/64 ! -d fddd:2c4:2c4:2c4::/64 -j SNAT --to "$ip6" fi else systemctl disable --now wg-iptables.service rm -f /etc/systemd/system/wg-iptables.service fi systemctl disable --now wg-quick@wg0.service rm -f /etc/sysctl.d/99-wireguard-forward.conf /etc/sysctl.d/99-wireguard-optimize.conf if [ ! -f /usr/sbin/openvpn ] && [ ! -f /usr/sbin/ipsec ] \ && [ ! -f /usr/local/sbin/ipsec ]; then echo 0 > /proc/sys/net/ipv4/ip_forward echo 0 > /proc/sys/net/ipv6/conf/all/forwarding fi if [[ "$os" == "ubuntu" ]]; then # Ubuntu ( set -x rm -rf /etc/wireguard/ apt-get remove --purge -y wireguard wireguard-tools >/dev/null ) elif [[ "$os" == "debian" && "$os_version" -ge 11 ]]; then # Debian 11 or higher ( set -x rm -rf /etc/wireguard/ apt-get remove --purge -y wireguard wireguard-tools >/dev/null ) elif [[ "$os" == "debian" && "$os_version" -eq 10 ]]; then # Debian 10 ( set -x rm -rf /etc/wireguard/ apt-get remove --purge -y wireguard wireguard-dkms wireguard-tools >/dev/null ) elif [[ "$os" == "centos" && "$os_version" -eq 9 ]]; then # CentOS 9 ( set -x yum -y -q remove wireguard-tools >/dev/null rm -rf /etc/wireguard/ ) elif [[ "$os" == "centos" && "$os_version" -le 8 ]]; then # CentOS 8 or 7 ( set -x yum -y -q remove kmod-wireguard wireguard-tools >/dev/null rm -rf /etc/wireguard/ ) elif [[ "$os" == "fedora" ]]; then # Fedora ( set -x dnf remove -y wireguard-tools >/dev/null rm -rf /etc/wireguard/ ) fi echo echo "WireGuard removed!" else echo echo "WireGuard removal aborted!" fi exit ;; 5) exit ;; esac fi } ## Defer setup until we have the complete script wgsetup "$@" exit 0