Skip to content

Instantly share code, notes, and snippets.

@default-writer
Created November 30, 2025 18:53
Show Gist options
  • Select an option

  • Save default-writer/da0b49895aee15066f296e0383c2f6f5 to your computer and use it in GitHub Desktop.

Select an option

Save default-writer/da0b49895aee15066f296e0383c2f6f5 to your computer and use it in GitHub Desktop.
WireGuard script archive
#!/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-2024 Lin Song <linsongui@gmail.com>
# Copyright (c) 2020-2023 Nyr
#
# Released under the MIT License, see the accompanying file LICENSE.txt
# or https://opensource.org/licenses/MIT
exiterr() { echo "Error: $1" >&2; exit 1; }
exiterr2() { exiterr "'apt-get install' failed."; }
exiterr3() { exiterr "'yum install' failed."; }
exiterr4() { exiterr "'zypper install' failed."; }
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"
}
check_pvt_ip() {
IPP_REGEX='^(10|127|172\.(1[6-9]|2[0-9]|3[0-1])|192\.168|169\.254)\.'
printf '%s' "$1" | tr -d '\n' | grep -Eq "$IPP_REGEX"
}
check_dns_name() {
FQDN_REGEX='^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$'
printf '%s' "$1" | tr -d '\n' | grep -Eq "$FQDN_REGEX"
}
check_root() {
if [ "$(id -u)" != 0 ]; then
exiterr "This installer must be run as root. Try 'sudo bash $0'"
fi
}
check_shell() {
# Detect Debian users running the script with "sh" instead of bash
if readlink /proc/$$/exe | grep -q "dash"; then
exiterr 'This installer needs to be run with "bash", not "sh".'
fi
}
check_kernel() {
# Detect OpenVZ 6
if [[ $(uname -r | cut -d "." -f 1) -eq 2 ]]; then
exiterr "The system is running an old kernel, which is incompatible with this installer."
fi
}
check_os() {
if grep -qs "ubuntu" /etc/os-release; then
os="ubuntu"
os_version=$(grep 'VERSION_ID' /etc/os-release | cut -d '"' -f 2 | tr -d '.')
elif [[ -e /etc/debian_version ]]; then
os="debian"
os_version=$(grep -oE '[0-9]+' /etc/debian_version | head -1)
elif [[ -e /etc/almalinux-release || -e /etc/rocky-release || -e /etc/centos-release ]]; then
os="centos"
os_version=$(grep -shoE '[0-9]+' /etc/almalinux-release /etc/rocky-release /etc/centos-release | head -1)
elif [[ -e /etc/fedora-release ]]; then
os="fedora"
os_version=$(grep -oE '[0-9]+' /etc/fedora-release | head -1)
elif [[ -e /etc/SUSE-brand && "$(head -1 /etc/SUSE-brand)" == "openSUSE" ]]; then
os="openSUSE"
os_version=$(tail -1 /etc/SUSE-brand | grep -oE '[0-9\\.]+')
else
exiterr "This installer seems to be running on an unsupported distribution.
Supported distros are Ubuntu, Debian, AlmaLinux, Rocky Linux, CentOS, Fedora and openSUSE."
fi
}
check_os_ver() {
if [[ "$os" == "ubuntu" && "$os_version" -lt 2004 ]]; then
exiterr "Ubuntu 20.04 or higher is required to use this installer.
This version of Ubuntu is too old and unsupported."
fi
if [[ "$os" == "debian" && "$os_version" -lt 11 ]]; then
exiterr "Debian 11 or higher is required to use this installer.
This version of Debian is too old and unsupported."
fi
if [[ "$os" == "centos" && "$os_version" -lt 8 ]]; then
exiterr "CentOS 8 or higher is required to use this installer.
This version of CentOS is too old and unsupported."
fi
}
check_container() {
if systemd-detect-virt -cq 2>/dev/null; then
exiterr "This system is running inside a container, which is not supported by this installer."
fi
}
set_client_name() {
# Allow a limited set of characters to avoid conflicts
# Limit to 15 characters for compatibility with Linux clients
client=$(sed 's/[^0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-]/_/g' <<< "$unsanitized_client" | cut -c-15)
}
parse_args() {
while [ "$#" -gt 0 ]; do
case $1 in
--auto)
auto=1
shift
;;
--addclient)
add_client=1
unsanitized_client="$2"
shift
shift
;;
--listclients)
list_clients=1
shift
;;
--removeclient)
remove_client=1
unsanitized_client="$2"
shift
shift
;;
--showclientqr)
show_client_qr=1
unsanitized_client="$2"
shift
shift
;;
--uninstall)
remove_wg=1
shift
;;
--serveraddr)
server_addr="$2"
shift
shift
;;
--port)
server_port="$2"
shift
shift
;;
--clientname)
first_client_name="$2"
shift
shift
;;
--dns1)
dns1="$2"
shift
shift
;;
--dns2)
dns2="$2"
shift
shift
;;
-y|--yes)
assume_yes=1
shift
;;
-h|--help)
show_usage
;;
*)
show_usage "Unknown parameter: $1"
;;
esac
done
}
check_args() {
if [ "$auto" != 0 ] && [ -e "$WG_CONF" ]; then
show_usage "Invalid parameter '--auto'. WireGuard is already set up on this server."
fi
if [ "$((add_client + list_clients + remove_client + show_client_qr))" -gt 1 ]; then
show_usage "Invalid parameters. Specify only one of '--addclient', '--listclients', '--removeclient' or '--showclientqr'."
fi
if [ "$remove_wg" = 1 ]; then
if [ "$((add_client + list_clients + remove_client + show_client_qr + auto))" -gt 0 ]; then
show_usage "Invalid parameters. '--uninstall' cannot be specified with other parameters."
fi
fi
if [ ! -e "$WG_CONF" ]; then
st_text="You must first set up WireGuard before"
[ "$add_client" = 1 ] && exiterr "$st_text adding a client."
[ "$list_clients" = 1 ] && exiterr "$st_text listing clients."
[ "$remove_client" = 1 ] && exiterr "$st_text removing a client."
[ "$show_client_qr" = 1 ] && exiterr "$st_text showing QR code for a client."
[ "$remove_wg" = 1 ] && exiterr "Cannot remove WireGuard because it has not been set up on this server."
fi
if [ "$((add_client + remove_client + show_client_qr))" = 1 ] && [ -n "$first_client_name" ]; then
show_usage "Invalid parameters. '--clientname' can only be specified when installing WireGuard."
fi
if [ -n "$server_addr" ] || [ -n "$server_port" ] || [ -n "$first_client_name" ]; then
if [ -e "$WG_CONF" ]; then
show_usage "Invalid parameters. WireGuard is already set up on this server."
elif [ "$auto" = 0 ]; then
show_usage "Invalid parameters. You must specify '--auto' when using these parameters."
fi
fi
if [ "$add_client" = 1 ]; then
set_client_name
if [ -z "$client" ]; then
exiterr "Invalid client name. Use one word only, no special characters except '-' and '_'."
elif grep -q "^# BEGIN_PEER $client$" "$WG_CONF"; then
exiterr "$client: invalid name. Client already exists."
fi
fi
if [ "$remove_client" = 1 ] || [ "$show_client_qr" = 1 ]; then
set_client_name
if [ -z "$client" ] || ! grep -q "^# BEGIN_PEER $client$" "$WG_CONF"; then
exiterr "Invalid client name, or client does not exist."
fi
fi
if [ -n "$server_addr" ] && { ! check_dns_name "$server_addr" && ! check_ip "$server_addr"; }; then
exiterr "Invalid server address. Must be a fully qualified domain name (FQDN) or an IPv4 address."
fi
if [ -n "$first_client_name" ]; then
unsanitized_client="$first_client_name"
set_client_name
if [ -z "$client" ]; then
exiterr "Invalid client name. Use one word only, no special characters except '-' and '_'."
fi
fi
if [ -n "$server_port" ]; then
if [[ ! "$server_port" =~ ^[0-9]+$ || "$server_port" -gt 65535 ]]; then
exiterr "Invalid port. Must be an integer between 1 and 65535."
fi
fi
if [ -n "$dns1" ]; then
if [ -e "$WG_CONF" ] && [ "$add_client" = 0 ]; then
show_usage "Invalid parameters. Custom DNS server(s) can only be specified when installing WireGuard or adding a client."
fi
fi
if { [ -n "$dns1" ] && ! check_ip "$dns1"; } \
|| { [ -n "$dns2" ] && ! check_ip "$dns2"; }; then
exiterr "Invalid DNS server(s)."
fi
if [ -z "$dns1" ] && [ -n "$dns2" ]; then
show_usage "Invalid DNS server. --dns2 cannot be specified without --dns1."
fi
if [ -n "$dns1" ] && [ -n "$dns2" ]; then
dns="$dns1, $dns2"
elif [ -n "$dns1" ]; then
dns="$dns1"
else
dns="8.8.8.8, 8.8.4.4"
fi
}
check_nftables() {
if [ "$os" = "centos" ]; then
if grep -qs "hwdsl2 VPN script" /etc/sysconfig/nftables.conf \
|| systemctl is-active --quiet nftables 2>/dev/null; then
exiterr "This system has nftables enabled, which is not supported by this installer."
fi
fi
}
install_wget() {
# 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
) || exiterr2
fi
}
install_iproute() {
if ! hash ip 2>/dev/null; then
if [ "$auto" = 0 ]; then
echo "iproute is required to use this installer."
read -n1 -r -p "Press any key to install iproute and continue..."
fi
if [ "$os" = "debian" ] || [ "$os" = "ubuntu" ]; then
export DEBIAN_FRONTEND=noninteractive
(
set -x
apt-get -yqq update || apt-get -yqq update
apt-get -yqq install iproute2 >/dev/null
) || exiterr2
elif [ "$os" = "openSUSE" ]; then
(
set -x
zypper install iproute2 >/dev/null
) || exiterr4
else
(
set -x
yum -y -q install iproute >/dev/null
) || exiterr3
fi
fi
}
show_header() {
cat <<'EOF'
WireGuard Script
https://github.com/hwdsl2/wireguard-install
EOF
}
show_header2() {
cat <<'EOF'
Welcome to this WireGuard server installer!
GitHub: https://github.com/hwdsl2/wireguard-install
EOF
}
show_header3() {
cat <<'EOF'
Copyright (c) 2022-2024 Lin Song
Copyright (c) 2020-2023 Nyr
EOF
}
show_usage() {
if [ -n "$1" ]; then
echo "Error: $1" >&2
fi
show_header
show_header3
cat 1>&2 <<EOF
Usage: bash $0 [options]
Options:
--addclient [client name] add a new client
--dns1 [DNS server IP] primary DNS server for new client (optional, default: Google Public DNS)
--dns2 [DNS server IP] secondary DNS server for new client (optional)
--listclients list the names of existing clients
--removeclient [client name] remove an existing client
--showclientqr [client name] show QR code for an existing client
--uninstall remove WireGuard and delete all configuration
-y, --yes assume "yes" as answer to prompts when removing a client or removing WireGuard
-h, --help show this help message and exit
Install options (optional):
--auto auto install WireGuard using default or custom options
--serveraddr [DNS name or IP] server address, must be a fully qualified domain name (FQDN) or an IPv4 address
--port [number] port for WireGuard (1-65535, default: 51820)
--clientname [client name] name for the first WireGuard client (default: client)
--dns1 [DNS server IP] primary DNS server for first client (default: Google Public DNS)
--dns2 [DNS server IP] secondary DNS server for first client
To customize options, you may also run this script without arguments.
EOF
exit 1
}
show_welcome() {
if [ "$auto" = 0 ]; then
show_header2
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
show_header
op_text=default
if [ -n "$server_addr" ] || [ -n "$server_port" ] \
|| [ -n "$first_client_name" ] || [ -n "$dns1" ]; then
op_text=custom
fi
echo
echo "Starting WireGuard setup using $op_text options."
fi
}
show_dns_name_note() {
cat <<EOF
Note: Make sure this DNS name '$1'
resolves to the IPv4 address of this server.
EOF
}
enter_server_address() {
echo
echo "Do you want WireGuard VPN clients to connect to this server using a DNS name,"
printf "e.g. vpn.example.com, instead of its IP address? [y/N] "
read -r response
case $response in
[yY][eE][sS]|[yY])
use_dns_name=1
echo
;;
*)
use_dns_name=0
;;
esac
if [ "$use_dns_name" = 1 ]; then
read -rp "Enter the DNS name of this VPN server: " server_addr_i
until check_dns_name "$server_addr_i"; do
echo "Invalid DNS name. You must enter a fully qualified domain name (FQDN)."
read -rp "Enter the DNS name of this VPN server: " server_addr_i
done
ip="$server_addr_i"
show_dns_name_note "$ip"
else
detect_ip
check_nat_ip
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
}
detect_ip() {
# 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?"
num_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_num
until [[ -z "$ip_num" || "$ip_num" =~ ^[0-9]+$ && "$ip_num" -le "$num_of_ip" ]]; do
echo "$ip_num: invalid selection."
read -rp "IPv4 address [1]: " ip_num
done
[[ -z "$ip_num" ]] && ip_num=1
else
ip_num=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_num"p)
fi
fi
fi
if ! check_ip "$ip"; then
echo "Error: Could not detect this server's IP address." >&2
echo "Abort. No changes were made." >&2
exit 1
fi
}
check_nat_ip() {
# If $ip is a private IP address, the server must be behind NAT
if check_pvt_ip "$ip"; 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
}
show_config() {
if [ "$auto" != 0 ]; then
echo
if [ -n "$server_addr" ]; then
echo "Server address: $server_addr"
else
printf '%s' "Server IP: "
[ -n "$public_ip" ] && printf '%s\n' "$public_ip" || printf '%s\n' "$ip"
fi
[ -n "$server_port" ] && port_text="$server_port" || port_text=51820
[ -n "$first_client_name" ] && client_text="$client" || client_text=client
if [ -n "$dns1" ] && [ -n "$dns2" ]; then
dns_text="$dns1, $dns2"
elif [ -n "$dns1" ]; then
dns_text="$dns1"
else
dns_text="Google Public DNS"
fi
echo "Port: UDP/$port_text"
echo "Client name: $client_text"
echo "Client DNS: $dns_text"
fi
}
detect_ipv6() {
ip6=""
if [[ $(ip -6 addr | grep -c 'inet6 [23]') -ne 0 ]]; 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}' | sed -n 1p)
fi
}
select_port() {
if [ "$auto" = 0 ]; then
echo
echo "Which port should WireGuard listen on?"
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
[ -n "$server_port" ] && port="$server_port" || port=51820
fi
}
enter_custom_dns() {
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
}
enter_first_client_name() {
if [ "$auto" = 0 ]; then
echo
echo "Enter a name for the first client:"
read -rp "Name [client]: " unsanitized_client
set_client_name
[[ -z "$client" ]] && client=client
else
if [ -n "$first_client_name" ]; then
unsanitized_client="$first_client_name"
set_client_name
else
client=client
fi
fi
}
show_setup_ready() {
if [ "$auto" = 0 ]; then
echo
echo "WireGuard installation is ready to begin."
fi
}
check_firewall() {
# 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"
elif [[ "$os" == "openSUSE" ]]; then
firewall="firewalld"
elif [[ "$os" == "debian" || "$os" == "ubuntu" ]]; then
firewall="iptables"
fi
if [[ "$firewall" == "firewalld" ]]; then
# 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."
fi
fi
}
abort_and_exit() {
echo "Abort. No changes were made." >&2
exit 1
}
confirm_setup() {
if [ "$auto" = 0 ]; then
printf "Do you want to continue? [Y/n] "
read -r response
case $response in
[yY][eE][sS]|[yY]|'')
:
;;
*)
abort_and_exit
;;
esac
fi
}
show_start_setup() {
echo
echo "Installing WireGuard, please wait..."
}
install_pkgs() {
if [[ "$os" == "ubuntu" ]]; then
export DEBIAN_FRONTEND=noninteractive
(
set -x
apt-get -yqq update || apt-get -yqq update
apt-get -yqq install wireguard qrencode $firewall >/dev/null
) || exiterr2
elif [[ "$os" == "debian" ]]; then
export DEBIAN_FRONTEND=noninteractive
(
set -x
apt-get -yqq update || apt-get -yqq update
apt-get -yqq install wireguard qrencode $firewall >/dev/null
) || exiterr2
elif [[ "$os" == "centos" && "$os_version" -eq 9 ]]; then
(
set -x
yum -y -q install epel-release >/dev/null
yum -y -q install wireguard-tools qrencode $firewall >/dev/null 2>&1
) || exiterr3
mkdir -p /etc/wireguard/
elif [[ "$os" == "centos" && "$os_version" -eq 8 ]]; then
(
set -x
yum -y -q install epel-release elrepo-release >/dev/null
yum -y -q --nobest install kmod-wireguard >/dev/null 2>&1
yum -y -q install wireguard-tools qrencode $firewall >/dev/null 2>&1
) || exiterr3
mkdir -p /etc/wireguard/
elif [[ "$os" == "fedora" ]]; then
(
set -x
dnf install -y wireguard-tools qrencode $firewall >/dev/null
) || exiterr "'dnf install' failed."
mkdir -p /etc/wireguard/
elif [[ "$os" == "openSUSE" ]]; then
(
set -x
zypper install -y wireguard-tools qrencode $firewall >/dev/null
) || exiterr4
mkdir -p /etc/wireguard/
fi
[ ! -d /etc/wireguard ] && exiterr2
# If firewalld was just installed, enable it
if [[ "$firewall" == "firewalld" ]]; then
(
set -x
systemctl enable --now firewalld.service >/dev/null 2>&1
)
fi
}
remove_pkgs() {
if [[ "$os" == "ubuntu" ]]; then
(
set -x
rm -rf /etc/wireguard/
apt-get remove --purge -y wireguard wireguard-tools >/dev/null
)
elif [[ "$os" == "debian" ]]; then
(
set -x
rm -rf /etc/wireguard/
apt-get remove --purge -y wireguard wireguard-tools >/dev/null
)
elif [[ "$os" == "centos" && "$os_version" -eq 9 ]]; then
(
set -x
yum -y -q remove wireguard-tools >/dev/null
rm -rf /etc/wireguard/
)
elif [[ "$os" == "centos" && "$os_version" -le 8 ]]; then
(
set -x
yum -y -q remove kmod-wireguard wireguard-tools >/dev/null
rm -rf /etc/wireguard/
)
elif [[ "$os" == "fedora" ]]; then
(
set -x
dnf remove -y wireguard-tools >/dev/null
rm -rf /etc/wireguard/
)
elif [[ "$os" == "openSUSE" ]]; then
(
set -x
zypper remove -y wireguard-tools >/dev/null
rm -rf /etc/wireguard/
)
fi
}
create_server_config() {
# Generate wg0.conf
cat << EOF > "$WG_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 "$WG_CONF"
}
create_firewall_rules() {
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 MASQUERADE
firewall-cmd -q --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.7.0.0/24 ! -d 10.7.0.0/24 -j MASQUERADE
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 MASQUERADE
firewall-cmd -q --permanent --direct --add-rule ipv6 nat POSTROUTING 0 -s fddd:2c4:2c4:2c4::/64 ! -d fddd:2c4:2c4:2c4::/64 -j MASQUERADE
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]
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=$iptables_path -w 5 -t nat -A POSTROUTING -s 10.7.0.0/24 ! -d 10.7.0.0/24 -j MASQUERADE
ExecStart=$iptables_path -w 5 -I INPUT -p udp --dport $port -j ACCEPT
ExecStart=$iptables_path -w 5 -I FORWARD -s 10.7.0.0/24 -j ACCEPT
ExecStart=$iptables_path -w 5 -I FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
ExecStop=$iptables_path -w 5 -t nat -D POSTROUTING -s 10.7.0.0/24 ! -d 10.7.0.0/24 -j MASQUERADE
ExecStop=$iptables_path -w 5 -D INPUT -p udp --dport $port -j ACCEPT
ExecStop=$iptables_path -w 5 -D FORWARD -s 10.7.0.0/24 -j ACCEPT
ExecStop=$iptables_path -w 5 -D FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT" > /etc/systemd/system/wg-iptables.service
if [[ -n "$ip6" ]]; then
echo "ExecStart=$ip6tables_path -w 5 -t nat -A POSTROUTING -s fddd:2c4:2c4:2c4::/64 ! -d fddd:2c4:2c4:2c4::/64 -j MASQUERADE
ExecStart=$ip6tables_path -w 5 -I FORWARD -s fddd:2c4:2c4:2c4::/64 -j ACCEPT
ExecStart=$ip6tables_path -w 5 -I FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
ExecStop=$ip6tables_path -w 5 -t nat -D POSTROUTING -s fddd:2c4:2c4:2c4::/64 ! -d fddd:2c4:2c4:2c4::/64 -j MASQUERADE
ExecStop=$ip6tables_path -w 5 -D FORWARD -s fddd:2c4:2c4:2c4::/64 -j ACCEPT
ExecStop=$ip6tables_path -w 5 -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
}
remove_firewall_rules() {
port=$(grep '^ListenPort' "$WG_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 MASQUERADE
firewall-cmd -q --permanent --direct --remove-rule ipv4 nat POSTROUTING 0 -s 10.7.0.0/24 ! -d 10.7.0.0/24 -j MASQUERADE
if grep -qs 'fddd:2c4:2c4:2c4::1/64' "$WG_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 MASQUERADE
firewall-cmd -q --permanent --direct --remove-rule ipv6 nat POSTROUTING 0 -s fddd:2c4:2c4:2c4::/64 ! -d fddd:2c4:2c4:2c4::/64 -j MASQUERADE
fi
else
systemctl disable --now wg-iptables.service
rm -f /etc/systemd/system/wg-iptables.service
fi
}
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
}
select_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)
enter_custom_dns
if [ -n "$dns2" ]; then
dns="$dns1, $dns2"
else
dns="$dns1"
fi
;;
esac
}
select_client_ip() {
# 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 "$WG_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
exiterr "253 clients are already configured. The WireGuard internal subnet is full!"
fi
}
new_client() {
select_client_ip
specify_ip=n
if [ "$1" = "add_client" ] && [ "$add_client" = 0 ]; then
echo
read -rp "Do you want to specify an internal IP address for the new client? [y/N]: " specify_ip
until [[ "$specify_ip" =~ ^[yYnN]*$ ]]; do
echo "$specify_ip: invalid selection."
read -rp "Do you want to specify an internal IP address for the new client? [y/N]: " specify_ip
done
if [[ ! "$specify_ip" =~ ^[yY]$ ]]; then
echo "Using auto assigned IP address 10.7.0.$octet."
fi
fi
if [[ "$specify_ip" =~ ^[yY]$ ]]; then
echo
read -rp "Enter IP address for the new client (e.g. 10.7.0.X): " client_ip
octet=$(printf '%s' "$client_ip" | cut -d "." -f 4)
until [[ $client_ip =~ ^10\.7\.0\.([2-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-4])$ ]] \
&& ! grep AllowedIPs "$WG_CONF" | cut -d "." -f 4 | cut -d "/" -f 1 | grep -q "^$octet$"; do
if [[ ! $client_ip =~ ^10\.7\.0\.([2-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-4])$ ]]; then
echo "Invalid IP address. Must be within the range 10.7.0.2 to 10.7.0.254."
else
echo "The IP address is already in use. Please choose another one."
fi
read -rp "Enter IP address for the new client (e.g. 10.7.0.X): " client_ip
octet=$(printf '%s' "$client_ip" | cut -d "." -f 4)
done
fi
key=$(wg genkey)
psk=$(wg genpsk)
# Configure client in the server
cat << EOF >> "$WG_CONF"
# BEGIN_PEER $client
[Peer]
PublicKey = $(wg pubkey <<< "$key")
PresharedKey = $psk
AllowedIPs = 10.7.0.$octet/32$(grep -q 'fddd:2c4:2c4:2c4::1' "$WG_CONF" && echo ", fddd:2c4:2c4:2c4::$octet/128")
# END_PEER $client
EOF
# Create client configuration
get_export_dir
cat << EOF > "$export_dir$client".conf
[Interface]
Address = 10.7.0.$octet/24$(grep -q 'fddd:2c4:2c4:2c4::1' "$WG_CONF" && echo ", fddd:2c4:2c4:2c4::$octet/64")
DNS = $dns
PrivateKey = $key
[Peer]
PublicKey = $(grep PrivateKey "$WG_CONF" | cut -d " " -f 3 | wg pubkey)
PresharedKey = $psk
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = $(grep '^# ENDPOINT' "$WG_CONF" | cut -d " " -f 3):$(grep ListenPort "$WG_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
}
update_sysctl() {
mkdir -p /etc/sysctl.d
conf_fwd="/etc/sysctl.d/99-wireguard-forward.conf"
conf_opt="/etc/sysctl.d/99-wireguard-optimize.conf"
# Enable net.ipv4.ip_forward for the system
echo 'net.ipv4.ip_forward=1' > "$conf_fwd"
if [[ -n "$ip6" ]]; then
# Enable net.ipv6.conf.all.forwarding for the system
echo "net.ipv6.conf.all.forwarding=1" >> "$conf_fwd"
fi
# Optimize sysctl settings such as TCP buffer sizes
base_url="https://github.com/hwdsl2/vpn-extras/releases/download/v1.0.0"
conf_url="$base_url/sysctl-wg-$os"
[ "$auto" != 0 ] && conf_url="${conf_url}-auto"
wget -t 3 -T 30 -q -O "$conf_opt" "$conf_url" 2>/dev/null \
|| curl -m 30 -fsL "$conf_url" -o "$conf_opt" 2>/dev/null \
|| { /bin/rm -f "$conf_opt"; touch "$conf_opt"; }
# 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 \
&& [ -f /proc/sys/net/ipv4/tcp_congestion_control ]; then
cat >> "$conf_opt" <<'EOF'
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF
fi
# Apply sysctl settings
sysctl -e -q -p "$conf_fwd"
sysctl -e -q -p "$conf_opt"
}
update_rclocal() {
ipt_cmd="systemctl restart wg-iptables.service"
if ! grep -qs "$ipt_cmd" /etc/rc.local; then
if [ ! -f /etc/rc.local ]; then
echo '#!/bin/sh' > /etc/rc.local
else
if [ "$os" = "ubuntu" ] || [ "$os" = "debian" ]; then
sed --follow-symlinks -i '/^exit 0/d' /etc/rc.local
fi
fi
cat >> /etc/rc.local <<EOF
$ipt_cmd
EOF
if [ "$os" = "ubuntu" ] || [ "$os" = "debian" ]; then
echo "exit 0" >> /etc/rc.local
fi
chmod +x /etc/rc.local
fi
}
start_wg_service() {
# Enable and start the wg-quick service
(
set -x
systemctl enable --now wg-quick@wg0.service >/dev/null 2>&1
)
}
show_client_qr_code() {
qrencode -t UTF8 < "$export_dir$client".conf
echo -e '\xE2\x86\x91 That is a QR code containing the client configuration.'
}
finish_setup() {
echo
# If the kernel module didn't load, system probably had an outdated kernel
if ! modprobe -nq wireguard; then
echo "Warning!"
echo "Installation was finished, but the WireGuard kernel module could not load."
echo "Reboot the system to load the most recent kernel."
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."
}
select_menu_option() {
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) Show QR code for a client"
echo " 5) Remove WireGuard"
echo " 6) Exit"
read -rp "Option: " option
until [[ "$option" =~ ^[1-6]$ ]]; do
echo "$option: invalid selection."
read -rp "Option: " option
done
}
show_clients() {
grep '^# BEGIN_PEER' "$WG_CONF" | cut -d ' ' -f 3 | nl -s ') '
}
enter_client_name() {
echo
echo "Provide a name for the client:"
read -rp "Name: " unsanitized_client
[ -z "$unsanitized_client" ] && abort_and_exit
set_client_name
while [[ -z "$client" ]] || grep -q "^# BEGIN_PEER $client$" "$WG_CONF"; do
if [ -z "$client" ]; then
echo "Invalid client name. Use one word only, no special characters except '-' and '_'."
else
echo "$client: invalid name. Client already exists."
fi
read -rp "Name: " unsanitized_client
[ -z "$unsanitized_client" ] && abort_and_exit
set_client_name
done
}
update_wg_conf() {
# Append new client configuration to the WireGuard interface
wg addconf wg0 <(sed -n "/^# BEGIN_PEER $client/,/^# END_PEER $client/p" "$WG_CONF")
}
print_client_added() {
echo
echo "$client added. Configuration available in: $export_dir$client.conf"
}
print_check_clients() {
echo
echo "Checking for existing client(s)..."
}
check_clients() {
num_of_clients=$(grep -c '^# BEGIN_PEER' "$WG_CONF")
if [[ "$num_of_clients" = 0 ]]; then
echo
echo "There are no existing clients!"
exit 1
fi
}
print_client_total() {
if [ "$num_of_clients" = 1 ]; then
printf '\n%s\n' "Total: 1 client"
elif [ -n "$num_of_clients" ]; then
printf '\n%s\n' "Total: $num_of_clients clients"
fi
}
select_client_to() {
echo
echo "Select the client to $1:"
show_clients
read -rp "Client: " client_num
[ -z "$client_num" ] && abort_and_exit
until [[ "$client_num" =~ ^[0-9]+$ && "$client_num" -le "$num_of_clients" ]]; do
echo "$client_num: invalid selection."
read -rp "Client: " client_num
[ -z "$client_num" ] && abort_and_exit
done
client=$(grep '^# BEGIN_PEER' "$WG_CONF" | cut -d ' ' -f 3 | sed -n "$client_num"p)
}
confirm_remove_client() {
if [ "$assume_yes" != 1 ]; then
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
else
remove=y
fi
}
remove_client_conf() {
get_export_dir
wg_file="$export_dir$client.conf"
if [ -f "$wg_file" ]; then
echo "Removing $wg_file..."
rm -f "$wg_file"
fi
}
print_remove_client() {
echo
echo "Removing $client..."
}
remove_client_wg() {
# 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" "$WG_CONF" | grep -m 1 PublicKey | cut -d " " -f 3)" remove
# Remove from the configuration file
sed -i "/^# BEGIN_PEER $client$/,/^# END_PEER $client$/d" "$WG_CONF"
remove_client_conf
}
print_client_removed() {
echo
echo "$client removed!"
}
print_client_removal_aborted() {
echo
echo "$client removal aborted!"
}
check_client_conf() {
wg_file="$export_dir$client.conf"
if [ ! -f "$wg_file" ]; then
echo "Error: Cannot show QR code. Missing client config file $wg_file" >&2
echo " You may instead re-run this script and add a new client." >&2
exit 1
fi
}
print_client_conf() {
echo
echo "Configuration for '$client' is available in: $wg_file"
}
confirm_remove_wg() {
if [ "$assume_yes" != 1 ]; then
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
else
remove=y
fi
}
print_remove_wg() {
echo
echo "Removing WireGuard, please wait..."
}
disable_wg_service() {
systemctl disable --now wg-quick@wg0.service
}
remove_sysctl_rules() {
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
}
remove_rclocal_rules() {
ipt_cmd="systemctl restart wg-iptables.service"
if grep -qs "$ipt_cmd" /etc/rc.local; then
sed --follow-symlinks -i "/^$ipt_cmd/d" /etc/rc.local
fi
}
print_wg_removed() {
echo
echo "WireGuard removed!"
}
print_wg_removal_aborted() {
echo
echo "WireGuard removal aborted!"
}
wgsetup() {
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
check_root
check_shell
check_kernel
check_os
check_os_ver
check_container
WG_CONF="/etc/wireguard/wg0.conf"
auto=0
assume_yes=0
add_client=0
list_clients=0
remove_client=0
show_client_qr=0
remove_wg=0
public_ip=""
server_addr=""
server_port=""
first_client_name=""
unsanitized_client=""
client=""
dns=""
dns1=""
dns2=""
parse_args "$@"
check_args
if [ "$add_client" = 1 ]; then
show_header
new_client add_client
update_wg_conf
echo
show_client_qr_code
print_client_added
exit 0
fi
if [ "$list_clients" = 1 ]; then
show_header
print_check_clients
check_clients
echo
show_clients
print_client_total
exit 0
fi
if [ "$remove_client" = 1 ]; then
show_header
confirm_remove_client
if [[ "$remove" =~ ^[yY]$ ]]; then
print_remove_client
remove_client_wg
print_client_removed
exit 0
else
print_client_removal_aborted
exit 1
fi
fi
if [ "$show_client_qr" = 1 ]; then
show_header
echo
get_export_dir
check_client_conf
show_client_qr_code
print_client_conf
exit 0
fi
if [ "$remove_wg" = 1 ]; then
show_header
confirm_remove_wg
if [[ "$remove" =~ ^[yY]$ ]]; then
print_remove_wg
remove_firewall_rules
disable_wg_service
remove_sysctl_rules
remove_rclocal_rules
remove_pkgs
print_wg_removed
exit 0
else
print_wg_removal_aborted
exit 1
fi
fi
if [[ ! -e "$WG_CONF" ]]; then
check_nftables
install_wget
install_iproute
show_welcome
if [ "$auto" = 0 ]; then
enter_server_address
else
if [ -n "$server_addr" ]; then
ip="$server_addr"
else
detect_ip
check_nat_ip
fi
fi
show_config
detect_ipv6
select_port
enter_first_client_name
if [ "$auto" = 0 ]; then
select_dns
fi
show_setup_ready
check_firewall
confirm_setup
show_start_setup
install_pkgs
create_server_config
update_sysctl
create_firewall_rules
if [ "$os" != "openSUSE" ]; then
update_rclocal
fi
new_client
start_wg_service
echo
show_client_qr_code
if [ "$auto" != 0 ] && check_dns_name "$server_addr"; then
show_dns_name_note "$server_addr"
fi
finish_setup
else
show_header
select_menu_option
case "$option" in
1)
enter_client_name
select_dns
new_client add_client
update_wg_conf
echo
show_client_qr_code
print_client_added
exit 0
;;
2)
print_check_clients
check_clients
echo
show_clients
print_client_total
exit 0
;;
3)
check_clients
select_client_to remove
confirm_remove_client
if [[ "$remove" =~ ^[yY]$ ]]; then
print_remove_client
remove_client_wg
print_client_removed
exit 0
else
print_client_removal_aborted
exit 1
fi
;;
4)
check_clients
select_client_to "show QR code for"
echo
get_export_dir
check_client_conf
show_client_qr_code
print_client_conf
exit 0
;;
5)
confirm_remove_wg
if [[ "$remove" =~ ^[yY]$ ]]; then
print_remove_wg
remove_firewall_rules
disable_wg_service
remove_sysctl_rules
remove_rclocal_rules
remove_pkgs
print_wg_removed
exit 0
else
print_wg_removal_aborted
exit 1
fi
;;
6)
exit 0
;;
esac
fi
}
## Defer setup until we have the complete script
wgsetup "$@"
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment