├── LICENSE.txt ├── README.md └── wireguard-install.sh /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Nyr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## wireguard-install 2 | WireGuard [road warrior](http://en.wikipedia.org/wiki/Road_warrior_%28computing%29) installer for Ubuntu, Debian, AlmaLinux, Rocky Linux, CentOS and Fedora. 3 | 4 | This script will let you set up your own VPN server in no more than a minute, even if you haven't used WireGuard before. It has been designed to be as unobtrusive and universal as possible. 5 | 6 | ### Installation 7 | Run the script and follow the assistant: 8 | 9 | ```plain text 10 | wget https://git.io/wireguard -O wireguard-install.sh && bash wireguard-install.sh 11 | ``` 12 | 13 | Once it ends, you can run it again to add more users, remove some of them or even completely uninstall WireGuard. 14 | 15 | ### I want to run my own VPN but don't have a server for that 16 | You can get a VPS from just [2 EUR](https://alphavps.com/clients/aff.php?aff=474&pid=457¤cy=1) or [2 USD](https://alphavps.com/clients/aff.php?aff=474&pid=457¤cy=6) per month at [AlphaVPS](https://alphavps.com/clients/aff.php?aff=474&pid=457¤cy=1). 17 | 18 | ### Donations 19 | If you want to show your appreciation, you can donate via [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VBAYDL34Z7J6L) or [cryptocurrency](https://pastebin.com/raw/M2JJpQpC). Thanks! 20 | 21 | ### Sponsors 22 | [Clever SaaS](https://www.clever-vpn.net/en?wg-referral=01LOULuQoi) – Launch your professional VPN service in 5 minutes. No tech team needed. Just $1. -------------------------------------------------------------------------------- /wireguard-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # https://github.com/Nyr/wireguard-install 4 | # 5 | # Copyright (c) 2020 Nyr. Released under the MIT License. 6 | 7 | 8 | # Detect Debian users running the script with "sh" instead of bash 9 | if readlink /proc/$$/exe | grep -q "dash"; then 10 | echo 'This installer needs to be run with "bash", not "sh".' 11 | exit 12 | fi 13 | 14 | # Discard stdin. Needed when running from a one-liner which includes a newline 15 | read -N 999999 -t 0.001 16 | 17 | # Detect OS 18 | # $os_version variables aren't always in use, but are kept here for convenience 19 | if grep -qs "ubuntu" /etc/os-release; then 20 | os="ubuntu" 21 | os_version=$(grep 'VERSION_ID' /etc/os-release | cut -d '"' -f 2 | tr -d '.') 22 | elif [[ -e /etc/debian_version ]]; then 23 | os="debian" 24 | os_version=$(grep -oE '[0-9]+' /etc/debian_version | head -1) 25 | elif [[ -e /etc/almalinux-release || -e /etc/rocky-release || -e /etc/centos-release ]]; then 26 | os="centos" 27 | os_version=$(grep -shoE '[0-9]+' /etc/almalinux-release /etc/rocky-release /etc/centos-release | head -1) 28 | elif [[ -e /etc/fedora-release ]]; then 29 | os="fedora" 30 | os_version=$(grep -oE '[0-9]+' /etc/fedora-release | head -1) 31 | else 32 | echo "This installer seems to be running on an unsupported distribution. 33 | Supported distros are Ubuntu, Debian, AlmaLinux, Rocky Linux, CentOS and Fedora." 34 | exit 35 | fi 36 | 37 | if [[ "$os" == "ubuntu" && "$os_version" -lt 2204 ]]; then 38 | echo "Ubuntu 22.04 or higher is required to use this installer. 39 | This version of Ubuntu is too old and unsupported." 40 | exit 41 | fi 42 | 43 | if [[ "$os" == "debian" ]]; then 44 | if grep -q '/sid' /etc/debian_version; then 45 | echo "Debian Testing and Debian Unstable are unsupported by this installer." 46 | exit 47 | fi 48 | if [[ "$os_version" -lt 11 ]]; then 49 | echo "Debian 11 or higher is required to use this installer. 50 | This version of Debian is too old and unsupported." 51 | exit 52 | fi 53 | fi 54 | 55 | if [[ "$os" == "centos" && "$os_version" -lt 9 ]]; then 56 | os_name=$(sed 's/ release.*//' /etc/almalinux-release /etc/rocky-release /etc/centos-release 2>/dev/null | head -1) 57 | echo "$os_name 9 or higher is required to use this installer. 58 | This version of $os_name is too old and unsupported." 59 | exit 60 | fi 61 | 62 | # Detect environments where $PATH does not include the sbin directories 63 | if ! grep -q sbin <<< "$PATH"; then 64 | echo '$PATH does not include sbin. Try using "su -" instead of "su".' 65 | exit 66 | fi 67 | 68 | # Detect if BoringTun (userspace WireGuard) needs to be used 69 | if ! systemd-detect-virt -cq; then 70 | # Not running inside a container 71 | use_boringtun="0" 72 | elif grep -q '^wireguard ' /proc/modules; then 73 | # Running inside a container, but the wireguard kernel module is available 74 | use_boringtun="0" 75 | else 76 | # Running inside a container and the wireguard kernel module is not available 77 | use_boringtun="1" 78 | fi 79 | 80 | if [[ "$EUID" -ne 0 ]]; then 81 | echo "This installer needs to be run with superuser privileges." 82 | exit 83 | fi 84 | 85 | if [[ "$use_boringtun" -eq 1 ]]; then 86 | if [ "$(uname -m)" != "x86_64" ]; then 87 | echo "In containerized systems without the wireguard kernel module, this installer 88 | supports only the x86_64 architecture. 89 | The system runs on $(uname -m) and is unsupported." 90 | exit 91 | fi 92 | # TUN device is required to use BoringTun 93 | if [[ ! -e /dev/net/tun ]] || ! ( exec 7<>/dev/net/tun ) 2>/dev/null; then 94 | echo "The system does not have the TUN device available. 95 | TUN needs to be enabled before running this installer." 96 | exit 97 | fi 98 | fi 99 | 100 | # Store the absolute path of the directory where the script is located 101 | script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 102 | 103 | new_client_dns () { 104 | echo "Select a DNS server for the client:" 105 | echo " 1) Default system resolvers" 106 | echo " 2) Google" 107 | echo " 3) 1.1.1.1" 108 | echo " 4) OpenDNS" 109 | echo " 5) Quad9" 110 | echo " 6) Gcore" 111 | echo " 7) AdGuard" 112 | echo " 8) Specify custom resolvers" 113 | read -p "DNS server [1]: " dns 114 | until [[ -z "$dns" || "$dns" =~ ^[1-8]$ ]]; do 115 | echo "$dns: invalid selection." 116 | read -p "DNS server [1]: " dns 117 | done 118 | case "$dns" in 119 | 1|"") 120 | # Locate the proper resolv.conf 121 | # Needed for systems running systemd-resolved 122 | if grep '^nameserver' "/etc/resolv.conf" | grep -qv '127.0.0.53' ; then 123 | resolv_conf="/etc/resolv.conf" 124 | else 125 | resolv_conf="/run/systemd/resolve/resolv.conf" 126 | fi 127 | # Extract nameservers and provide them in the required format 128 | 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') 129 | ;; 130 | 2) 131 | dns="8.8.8.8, 8.8.4.4" 132 | ;; 133 | 3) 134 | dns="1.1.1.1, 1.0.0.1" 135 | ;; 136 | 4) 137 | dns="208.67.222.222, 208.67.220.220" 138 | ;; 139 | 5) 140 | dns="9.9.9.9, 149.112.112.112" 141 | ;; 142 | 6) 143 | dns="95.85.95.85, 2.56.220.2" 144 | ;; 145 | 7) 146 | dns="94.140.14.14, 94.140.15.15" 147 | ;; 148 | 8) 149 | echo 150 | until [[ -n "$custom_dns" ]]; do 151 | echo "Enter DNS servers (one or more IPv4 addresses, separated by commas or spaces):" 152 | read -p "DNS servers: " dns_input 153 | # Convert comma delimited to space delimited 154 | dns_input=$(echo "$dns_input" | tr ',' ' ') 155 | # Validate and build custom DNS IP list 156 | for dns_ip in $dns_input; do 157 | if [[ "$dns_ip" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]]; then 158 | if [[ -z "$custom_dns" ]]; then 159 | custom_dns="$dns_ip" 160 | else 161 | custom_dns="$custom_dns, $dns_ip" 162 | fi 163 | fi 164 | done 165 | if [ -z "$custom_dns" ]; then 166 | echo "Invalid input." 167 | else 168 | dns="$custom_dns" 169 | fi 170 | done 171 | ;; 172 | esac 173 | } 174 | 175 | new_client_setup () { 176 | # Given a list of the assigned internal IPv4 addresses, obtain the lowest still 177 | # available octet. Important to start looking at 2, because 1 is our gateway. 178 | octet=2 179 | while grep AllowedIPs /etc/wireguard/wg0.conf | cut -d "." -f 4 | cut -d "/" -f 1 | grep -q "^$octet$"; do 180 | (( octet++ )) 181 | done 182 | # Don't break the WireGuard configuration in case the address space is full 183 | if [[ "$octet" -eq 255 ]]; then 184 | echo "253 clients are already configured. The WireGuard internal subnet is full!" 185 | exit 186 | fi 187 | key=$(wg genkey) 188 | psk=$(wg genpsk) 189 | # Configure client in the server 190 | cat << EOF >> /etc/wireguard/wg0.conf 191 | # BEGIN_PEER $client 192 | [Peer] 193 | PublicKey = $(wg pubkey <<< $key) 194 | PresharedKey = $psk 195 | AllowedIPs = 10.7.0.$octet/32$(grep -q 'fddd:2c4:2c4:2c4::1' /etc/wireguard/wg0.conf && echo ", fddd:2c4:2c4:2c4::$octet/128") 196 | # END_PEER $client 197 | EOF 198 | # Create client configuration 199 | cat << EOF > "$script_dir"/"$client".conf 200 | [Interface] 201 | Address = 10.7.0.$octet/24$(grep -q 'fddd:2c4:2c4:2c4::1' /etc/wireguard/wg0.conf && echo ", fddd:2c4:2c4:2c4::$octet/64") 202 | DNS = $dns 203 | PrivateKey = $key 204 | 205 | [Peer] 206 | PublicKey = $(grep PrivateKey /etc/wireguard/wg0.conf | cut -d " " -f 3 | wg pubkey) 207 | PresharedKey = $psk 208 | AllowedIPs = 0.0.0.0/0, ::/0 209 | Endpoint = $(grep '^# ENDPOINT' /etc/wireguard/wg0.conf | cut -d " " -f 3):$(grep ListenPort /etc/wireguard/wg0.conf | cut -d " " -f 3) 210 | PersistentKeepalive = 25 211 | EOF 212 | } 213 | 214 | if [[ ! -e /etc/wireguard/wg0.conf ]]; then 215 | # Detect some Debian minimal setups where neither wget nor curl are installed 216 | if ! hash wget 2>/dev/null && ! hash curl 2>/dev/null; then 217 | echo "Wget is required to use this installer." 218 | read -n1 -r -p "Press any key to install Wget and continue..." 219 | apt-get update 220 | apt-get install -y wget 221 | fi 222 | clear 223 | echo 'Welcome to this WireGuard road warrior installer!' 224 | # If system has a single IPv4, it is selected automatically. Else, ask the user 225 | if [[ $(ip -4 addr | grep inet | grep -vEc '127(\.[0-9]{1,3}){3}') -eq 1 ]]; then 226 | 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}') 227 | else 228 | number_of_ip=$(ip -4 addr | grep inet | grep -vEc '127(\.[0-9]{1,3}){3}') 229 | echo 230 | echo "Which IPv4 address should be used?" 231 | 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 ') ' 232 | read -p "IPv4 address [1]: " ip_number 233 | until [[ -z "$ip_number" || "$ip_number" =~ ^[0-9]+$ && "$ip_number" -le "$number_of_ip" ]]; do 234 | echo "$ip_number: invalid selection." 235 | read -p "IPv4 address [1]: " ip_number 236 | done 237 | [[ -z "$ip_number" ]] && ip_number="1" 238 | 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) 239 | fi 240 | # If $ip is a private IP address, the server must be behind NAT 241 | if echo "$ip" | grep -qE '^(10\.|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.|192\.168)'; then 242 | echo 243 | echo "This server is behind NAT. What is the public IPv4 address or hostname?" 244 | # Get public IP and sanitize with grep 245 | get_public_ip=$(grep -m 1 -oE '^[0-9]{1,3}(\.[0-9]{1,3}){3}$' <<< "$(wget -T 10 -t 1 -4qO- "http://ip1.dynupdate.no-ip.com/" || curl -m 10 -4Ls "http://ip1.dynupdate.no-ip.com/")") 246 | read -p "Public IPv4 address / hostname [$get_public_ip]: " public_ip 247 | # If the checkip service is unavailable and user didn't provide input, ask again 248 | until [[ -n "$get_public_ip" || -n "$public_ip" ]]; do 249 | echo "Invalid input." 250 | read -p "Public IPv4 address / hostname: " public_ip 251 | done 252 | [[ -z "$public_ip" ]] && public_ip="$get_public_ip" 253 | fi 254 | # If system has a single IPv6, it is selected automatically 255 | if [[ $(ip -6 addr | grep -c 'inet6 [23]') -eq 1 ]]; then 256 | 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}') 257 | fi 258 | # If system has multiple IPv6, ask the user to select one 259 | if [[ $(ip -6 addr | grep -c 'inet6 [23]') -gt 1 ]]; then 260 | number_of_ip6=$(ip -6 addr | grep -c 'inet6 [23]') 261 | echo 262 | echo "Which IPv6 address should be used?" 263 | 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 ') ' 264 | read -p "IPv6 address [1]: " ip6_number 265 | until [[ -z "$ip6_number" || "$ip6_number" =~ ^[0-9]+$ && "$ip6_number" -le "$number_of_ip6" ]]; do 266 | echo "$ip6_number: invalid selection." 267 | read -p "IPv6 address [1]: " ip6_number 268 | done 269 | [[ -z "$ip6_number" ]] && ip6_number="1" 270 | 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) 271 | fi 272 | echo 273 | echo "What port should WireGuard listen on?" 274 | read -p "Port [51820]: " port 275 | until [[ -z "$port" || "$port" =~ ^[0-9]+$ && "$port" -le 65535 ]]; do 276 | echo "$port: invalid port." 277 | read -p "Port [51820]: " port 278 | done 279 | [[ -z "$port" ]] && port="51820" 280 | echo 281 | echo "Enter a name for the first client:" 282 | read -p "Name [client]: " unsanitized_client 283 | # Allow a limited length and set of characters to avoid conflicts 284 | client=$(sed 's/[^0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-]/_/g' <<< "$unsanitized_client" | cut -c-15) 285 | [[ -z "$client" ]] && client="client" 286 | echo 287 | new_client_dns 288 | # Set up automatic updates for BoringTun if the user is fine with that 289 | if [[ "$use_boringtun" -eq 1 ]]; then 290 | echo 291 | echo "BoringTun will be installed to set up WireGuard on the system." 292 | read -p "Should automatic updates be enabled for it? [Y/n]: " boringtun_updates 293 | until [[ "$boringtun_updates" =~ ^[yYnN]*$ ]]; do 294 | echo "$remove: invalid selection." 295 | read -p "Should automatic updates be enabled for it? [Y/n]: " boringtun_updates 296 | done 297 | [[ -z "$boringtun_updates" ]] && boringtun_updates="y" 298 | if [[ "$boringtun_updates" =~ ^[yY]$ ]]; then 299 | if [[ "$os" == "centos" || "$os" == "fedora" ]]; then 300 | cron="cronie" 301 | elif [[ "$os" == "debian" || "$os" == "ubuntu" ]]; then 302 | cron="cron" 303 | fi 304 | fi 305 | fi 306 | echo 307 | echo "WireGuard installation is ready to begin." 308 | # Install a firewall if firewalld or iptables are not already available 309 | if ! systemctl is-active --quiet firewalld.service && ! hash iptables 2>/dev/null; then 310 | if [[ "$os" == "centos" || "$os" == "fedora" ]]; then 311 | firewall="firewalld" 312 | # We don't want to silently enable firewalld, so we give a subtle warning 313 | # If the user continues, firewalld will be installed and enabled during setup 314 | echo "firewalld, which is required to manage routing tables, will also be installed." 315 | elif [[ "$os" == "debian" || "$os" == "ubuntu" ]]; then 316 | # iptables is way less invasive than firewalld so no warning is given 317 | firewall="iptables" 318 | fi 319 | fi 320 | read -n1 -r -p "Press any key to continue..." 321 | # Install WireGuard 322 | # If BoringTun is not required, set up with the WireGuard kernel module 323 | if [[ "$use_boringtun" -eq 0 ]]; then 324 | if [[ "$os" == "ubuntu" ]]; then 325 | # Ubuntu 326 | apt-get update 327 | apt-get install -y wireguard qrencode $firewall 328 | elif [[ "$os" == "debian" ]]; then 329 | # Debian 330 | apt-get update 331 | apt-get install -y wireguard qrencode $firewall 332 | elif [[ "$os" == "centos" ]]; then 333 | # CentOS 334 | dnf install -y epel-release 335 | dnf install -y wireguard-tools qrencode $firewall 336 | elif [[ "$os" == "fedora" ]]; then 337 | # Fedora 338 | dnf install -y wireguard-tools qrencode $firewall 339 | mkdir -p /etc/wireguard/ 340 | fi 341 | # Else, BoringTun needs to be used 342 | else 343 | # Install required packages 344 | if [[ "$os" == "ubuntu" ]]; then 345 | # Ubuntu 346 | apt-get update 347 | apt-get install -y qrencode ca-certificates $cron $firewall 348 | apt-get install -y wireguard-tools --no-install-recommends 349 | elif [[ "$os" == "debian" ]]; then 350 | # Debian 351 | apt-get update 352 | apt-get install -y qrencode ca-certificates $cron $firewall 353 | apt-get install -y wireguard-tools --no-install-recommends 354 | elif [[ "$os" == "centos" ]]; then 355 | # CentOS 356 | dnf install -y epel-release 357 | dnf install -y wireguard-tools qrencode ca-certificates tar $cron $firewall 358 | elif [[ "$os" == "fedora" ]]; then 359 | # Fedora 360 | dnf install -y wireguard-tools qrencode ca-certificates tar $cron $firewall 361 | mkdir -p /etc/wireguard/ 362 | fi 363 | # Grab the BoringTun binary using wget or curl and extract into the right place. 364 | # Don't use this service elsewhere without permission! Contact me before you do! 365 | { wget -qO- https://wg.nyr.be/1/latest/download 2>/dev/null || curl -sL https://wg.nyr.be/1/latest/download ; } | tar xz -C /usr/local/sbin/ --wildcards 'boringtun-*/boringtun' --strip-components 1 366 | # Configure wg-quick to use BoringTun 367 | mkdir /etc/systemd/system/wg-quick@wg0.service.d/ 2>/dev/null 368 | echo "[Service] 369 | Environment=WG_QUICK_USERSPACE_IMPLEMENTATION=boringtun 370 | Environment=WG_SUDO=1" > /etc/systemd/system/wg-quick@wg0.service.d/boringtun.conf 371 | if [[ -n "$cron" ]] && [[ "$os" == "centos" || "$os" == "fedora" ]]; then 372 | systemctl enable --now crond.service 373 | fi 374 | fi 375 | # If firewalld was just installed, enable it 376 | if [[ "$firewall" == "firewalld" ]]; then 377 | systemctl enable --now firewalld.service 378 | fi 379 | # Generate wg0.conf 380 | cat << EOF > /etc/wireguard/wg0.conf 381 | # Do not alter the commented lines 382 | # They are used by wireguard-install 383 | # ENDPOINT $([[ -n "$public_ip" ]] && echo "$public_ip" || echo "$ip") 384 | 385 | [Interface] 386 | Address = 10.7.0.1/24$([[ -n "$ip6" ]] && echo ", fddd:2c4:2c4:2c4::1/64") 387 | PrivateKey = $(wg genkey) 388 | ListenPort = $port 389 | 390 | EOF 391 | chmod 600 /etc/wireguard/wg0.conf 392 | # Enable net.ipv4.ip_forward for the system 393 | echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-wireguard-forward.conf 394 | # Enable without waiting for a reboot or service restart 395 | echo 1 > /proc/sys/net/ipv4/ip_forward 396 | if [[ -n "$ip6" ]]; then 397 | # Enable net.ipv6.conf.all.forwarding for the system 398 | echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.d/99-wireguard-forward.conf 399 | # Enable without waiting for a reboot or service restart 400 | echo 1 > /proc/sys/net/ipv6/conf/all/forwarding 401 | fi 402 | if systemctl is-active --quiet firewalld.service; then 403 | # Using both permanent and not permanent rules to avoid a firewalld 404 | # reload. 405 | firewall-cmd --add-port="$port"/udp 406 | firewall-cmd --zone=trusted --add-source=10.7.0.0/24 407 | firewall-cmd --permanent --add-port="$port"/udp 408 | firewall-cmd --permanent --zone=trusted --add-source=10.7.0.0/24 409 | # Set NAT for the VPN subnet 410 | firewall-cmd --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.7.0.0/24 ! -d 10.7.0.0/24 -j SNAT --to "$ip" 411 | firewall-cmd --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.7.0.0/24 ! -d 10.7.0.0/24 -j SNAT --to "$ip" 412 | if [[ -n "$ip6" ]]; then 413 | firewall-cmd --zone=trusted --add-source=fddd:2c4:2c4:2c4::/64 414 | firewall-cmd --permanent --zone=trusted --add-source=fddd:2c4:2c4:2c4::/64 415 | firewall-cmd --direct --add-rule ipv6 nat POSTROUTING 0 -s fddd:2c4:2c4:2c4::/64 ! -d fddd:2c4:2c4:2c4::/64 -j SNAT --to "$ip6" 416 | firewall-cmd --permanent --direct --add-rule ipv6 nat POSTROUTING 0 -s fddd:2c4:2c4:2c4::/64 ! -d fddd:2c4:2c4:2c4::/64 -j SNAT --to "$ip6" 417 | fi 418 | else 419 | # Create a service to set up persistent iptables rules 420 | iptables_path=$(command -v iptables) 421 | ip6tables_path=$(command -v ip6tables) 422 | # nf_tables is not available as standard in OVZ kernels. So use iptables-legacy 423 | # if we are in OVZ, with a nf_tables backend and iptables-legacy is available. 424 | if [[ $(systemd-detect-virt) == "openvz" ]] && readlink -f "$(command -v iptables)" | grep -q "nft" && hash iptables-legacy 2>/dev/null; then 425 | iptables_path=$(command -v iptables-legacy) 426 | ip6tables_path=$(command -v ip6tables-legacy) 427 | fi 428 | echo "[Unit] 429 | After=network-online.target 430 | Wants=network-online.target 431 | [Service] 432 | Type=oneshot 433 | ExecStart=$iptables_path -w 5 -t nat -A POSTROUTING -s 10.7.0.0/24 ! -d 10.7.0.0/24 -j SNAT --to $ip 434 | ExecStart=$iptables_path -w 5 -I INPUT -p udp --dport $port -j ACCEPT 435 | ExecStart=$iptables_path -w 5 -I FORWARD -s 10.7.0.0/24 -j ACCEPT 436 | ExecStart=$iptables_path -w 5 -I FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT 437 | ExecStop=$iptables_path -w 5 -t nat -D POSTROUTING -s 10.7.0.0/24 ! -d 10.7.0.0/24 -j SNAT --to $ip 438 | ExecStop=$iptables_path -w 5 -D INPUT -p udp --dport $port -j ACCEPT 439 | ExecStop=$iptables_path -w 5 -D FORWARD -s 10.7.0.0/24 -j ACCEPT 440 | ExecStop=$iptables_path -w 5 -D FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT" > /etc/systemd/system/wg-iptables.service 441 | if [[ -n "$ip6" ]]; then 442 | echo "ExecStart=$ip6tables_path -w 5 -t nat -A POSTROUTING -s fddd:2c4:2c4:2c4::/64 ! -d fddd:2c4:2c4:2c4::/64 -j SNAT --to $ip6 443 | ExecStart=$ip6tables_path -w 5 -I FORWARD -s fddd:2c4:2c4:2c4::/64 -j ACCEPT 444 | ExecStart=$ip6tables_path -w 5 -I FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT 445 | ExecStop=$ip6tables_path -w 5 -t nat -D POSTROUTING -s fddd:2c4:2c4:2c4::/64 ! -d fddd:2c4:2c4:2c4::/64 -j SNAT --to $ip6 446 | ExecStop=$ip6tables_path -w 5 -D FORWARD -s fddd:2c4:2c4:2c4::/64 -j ACCEPT 447 | ExecStop=$ip6tables_path -w 5 -D FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT" >> /etc/systemd/system/wg-iptables.service 448 | fi 449 | echo "RemainAfterExit=yes 450 | [Install] 451 | WantedBy=multi-user.target" >> /etc/systemd/system/wg-iptables.service 452 | systemctl enable --now wg-iptables.service 453 | fi 454 | # Generates the custom client.conf 455 | new_client_setup 456 | # Enable and start the wg-quick service 457 | systemctl enable --now wg-quick@wg0.service 458 | # Set up automatic updates for BoringTun if the user wanted to 459 | if [[ "$boringtun_updates" =~ ^[yY]$ ]]; then 460 | # Deploy upgrade script 461 | cat << 'EOF' > /usr/local/sbin/boringtun-upgrade 462 | #!/bin/bash 463 | latest=$(wget -qO- https://wg.nyr.be/1/latest 2>/dev/null || curl -sL https://wg.nyr.be/1/latest 2>/dev/null) 464 | # If server did not provide an appropriate response, exit 465 | if ! head -1 <<< "$latest" | grep -qiE "^boringtun.+[0-9]+\.[0-9]+.*$"; then 466 | echo "Update server unavailable" 467 | exit 468 | fi 469 | current=$(/usr/local/sbin/boringtun -V) 470 | if [[ "$current" != "$latest" ]]; then 471 | download="https://wg.nyr.be/1/latest/download" 472 | xdir=$(mktemp -d) 473 | # If download and extraction are successful, upgrade the boringtun binary 474 | if { wget -qO- "$download" 2>/dev/null || curl -sL "$download" ; } | tar xz -C "$xdir" --wildcards "boringtun-*/boringtun" --strip-components 1; then 475 | systemctl stop wg-quick@wg0.service 476 | rm -f /usr/local/sbin/boringtun 477 | mv "$xdir"/boringtun /usr/local/sbin/boringtun 478 | systemctl start wg-quick@wg0.service 479 | echo "Successfully updated to $(/usr/local/sbin/boringtun -V)" 480 | else 481 | echo "boringtun update failed" 482 | fi 483 | rm -rf "$xdir" 484 | else 485 | echo "$current is up to date" 486 | fi 487 | EOF 488 | chmod +x /usr/local/sbin/boringtun-upgrade 489 | # Add cron job to run the updater daily at a random time between 3:00 and 5:59 490 | { crontab -l 2>/dev/null; echo "$(( $RANDOM % 60 )) $(( $RANDOM % 3 + 3 )) * * * /usr/local/sbin/boringtun-upgrade &>/dev/null" ; } | crontab - 491 | fi 492 | echo 493 | qrencode -t ANSI256UTF8 < "$script_dir"/"$client.conf" 494 | echo -e '\xE2\x86\x91 That is a QR code containing the client configuration.' 495 | echo 496 | echo "Finished!" 497 | echo 498 | echo "The client configuration is available in:" "$script_dir"/"$client.conf" 499 | echo "New clients can be added by running this script again." 500 | else 501 | clear 502 | echo "WireGuard is already installed." 503 | echo 504 | echo "Select an option:" 505 | echo " 1) Add a new client" 506 | echo " 2) Remove an existing client" 507 | echo " 3) Remove WireGuard" 508 | echo " 4) Exit" 509 | read -p "Option: " option 510 | until [[ "$option" =~ ^[1-4]$ ]]; do 511 | echo "$option: invalid selection." 512 | read -p "Option: " option 513 | done 514 | case "$option" in 515 | 1) 516 | echo 517 | echo "Provide a name for the client:" 518 | read -p "Name: " unsanitized_client 519 | # Allow a limited lenght and set of characters to avoid conflicts 520 | client=$(sed 's/[^0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-]/_/g' <<< "$unsanitized_client" | cut -c-15) 521 | while [[ -z "$client" ]] || grep -q "^# BEGIN_PEER $client$" /etc/wireguard/wg0.conf; do 522 | echo "$client: invalid name." 523 | read -p "Name: " unsanitized_client 524 | client=$(sed 's/[^0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-]/_/g' <<< "$unsanitized_client" | cut -c-15) 525 | done 526 | echo 527 | new_client_dns 528 | new_client_setup 529 | # Append new client configuration to the WireGuard interface 530 | wg addconf wg0 <(sed -n "/^# BEGIN_PEER $client/,/^# END_PEER $client/p" /etc/wireguard/wg0.conf) 531 | echo 532 | qrencode -t ANSI256UTF8 < "$script_dir"/"$client.conf" 533 | echo -e '\xE2\x86\x91 That is a QR code containing your client configuration.' 534 | echo 535 | echo "$client added. Configuration available in:" "$script_dir"/"$client.conf" 536 | exit 537 | ;; 538 | 2) 539 | # This option could be documented a bit better and maybe even be simplified 540 | # ...but what can I say, I want some sleep too 541 | number_of_clients=$(grep -c '^# BEGIN_PEER' /etc/wireguard/wg0.conf) 542 | if [[ "$number_of_clients" = 0 ]]; then 543 | echo 544 | echo "There are no existing clients!" 545 | exit 546 | fi 547 | echo 548 | echo "Select the client to remove:" 549 | grep '^# BEGIN_PEER' /etc/wireguard/wg0.conf | cut -d ' ' -f 3 | nl -s ') ' 550 | read -p "Client: " client_number 551 | until [[ "$client_number" =~ ^[0-9]+$ && "$client_number" -le "$number_of_clients" ]]; do 552 | echo "$client_number: invalid selection." 553 | read -p "Client: " client_number 554 | done 555 | client=$(grep '^# BEGIN_PEER' /etc/wireguard/wg0.conf | cut -d ' ' -f 3 | sed -n "$client_number"p) 556 | echo 557 | read -p "Confirm $client removal? [y/N]: " remove 558 | until [[ "$remove" =~ ^[yYnN]*$ ]]; do 559 | echo "$remove: invalid selection." 560 | read -p "Confirm $client removal? [y/N]: " remove 561 | done 562 | if [[ "$remove" =~ ^[yY]$ ]]; then 563 | # The following is the right way to avoid disrupting other active connections: 564 | # Remove from the live interface 565 | wg set wg0 peer "$(sed -n "/^# BEGIN_PEER $client$/,\$p" /etc/wireguard/wg0.conf | grep -m 1 PublicKey | cut -d " " -f 3)" remove 566 | # Remove from the configuration file 567 | sed -i "/^# BEGIN_PEER $client$/,/^# END_PEER $client$/d" /etc/wireguard/wg0.conf 568 | echo 569 | echo "$client removed!" 570 | else 571 | echo 572 | echo "$client removal aborted!" 573 | fi 574 | exit 575 | ;; 576 | 3) 577 | echo 578 | read -p "Confirm WireGuard removal? [y/N]: " remove 579 | until [[ "$remove" =~ ^[yYnN]*$ ]]; do 580 | echo "$remove: invalid selection." 581 | read -p "Confirm WireGuard removal? [y/N]: " remove 582 | done 583 | if [[ "$remove" =~ ^[yY]$ ]]; then 584 | port=$(grep '^ListenPort' /etc/wireguard/wg0.conf | cut -d " " -f 3) 585 | if systemctl is-active --quiet firewalld.service; then 586 | ip=$(firewall-cmd --direct --get-rules ipv4 nat POSTROUTING | grep '\-s 10.7.0.0/24 '"'"'!'"'"' -d 10.7.0.0/24' | grep -oE '[^ ]+$') 587 | # Using both permanent and not permanent rules to avoid a firewalld reload. 588 | firewall-cmd --remove-port="$port"/udp 589 | firewall-cmd --zone=trusted --remove-source=10.7.0.0/24 590 | firewall-cmd --permanent --remove-port="$port"/udp 591 | firewall-cmd --permanent --zone=trusted --remove-source=10.7.0.0/24 592 | firewall-cmd --direct --remove-rule ipv4 nat POSTROUTING 0 -s 10.7.0.0/24 ! -d 10.7.0.0/24 -j SNAT --to "$ip" 593 | firewall-cmd --permanent --direct --remove-rule ipv4 nat POSTROUTING 0 -s 10.7.0.0/24 ! -d 10.7.0.0/24 -j SNAT --to "$ip" 594 | if grep -qs 'fddd:2c4:2c4:2c4::1/64' /etc/wireguard/wg0.conf; then 595 | ip6=$(firewall-cmd --direct --get-rules ipv6 nat POSTROUTING | grep '\-s fddd:2c4:2c4:2c4::/64 '"'"'!'"'"' -d fddd:2c4:2c4:2c4::/64' | grep -oE '[^ ]+$') 596 | firewall-cmd --zone=trusted --remove-source=fddd:2c4:2c4:2c4::/64 597 | firewall-cmd --permanent --zone=trusted --remove-source=fddd:2c4:2c4:2c4::/64 598 | firewall-cmd --direct --remove-rule ipv6 nat POSTROUTING 0 -s fddd:2c4:2c4:2c4::/64 ! -d fddd:2c4:2c4:2c4::/64 -j SNAT --to "$ip6" 599 | firewall-cmd --permanent --direct --remove-rule ipv6 nat POSTROUTING 0 -s fddd:2c4:2c4:2c4::/64 ! -d fddd:2c4:2c4:2c4::/64 -j SNAT --to "$ip6" 600 | fi 601 | else 602 | systemctl disable --now wg-iptables.service 603 | rm -f /etc/systemd/system/wg-iptables.service 604 | fi 605 | systemctl disable --now wg-quick@wg0.service 606 | rm -f /etc/systemd/system/wg-quick@wg0.service.d/boringtun.conf 607 | rm -f /etc/sysctl.d/99-wireguard-forward.conf 608 | # Different stuff was installed depending on whether BoringTun was used or not 609 | if [[ "$use_boringtun" -eq 0 ]]; then 610 | if [[ "$os" == "ubuntu" ]]; then 611 | # Ubuntu 612 | rm -rf /etc/wireguard/ 613 | apt-get remove --purge -y wireguard wireguard-tools 614 | elif [[ "$os" == "debian" ]]; then 615 | # Debian 616 | rm -rf /etc/wireguard/ 617 | apt-get remove --purge -y wireguard wireguard-tools 618 | elif [[ "$os" == "centos" ]]; then 619 | # CentOS 620 | dnf remove -y wireguard-tools 621 | rm -rf /etc/wireguard/ 622 | elif [[ "$os" == "fedora" ]]; then 623 | # Fedora 624 | dnf remove -y wireguard-tools 625 | rm -rf /etc/wireguard/ 626 | fi 627 | else 628 | { crontab -l 2>/dev/null | grep -v '/usr/local/sbin/boringtun-upgrade' ; } | crontab - 629 | if [[ "$os" == "ubuntu" ]]; then 630 | # Ubuntu 631 | rm -rf /etc/wireguard/ 632 | apt-get remove --purge -y wireguard-tools 633 | elif [[ "$os" == "debian" ]]; then 634 | # Debian 635 | rm -rf /etc/wireguard/ 636 | apt-get remove --purge -y wireguard-tools 637 | elif [[ "$os" == "centos" ]]; then 638 | # CentOS 639 | dnf remove -y wireguard-tools 640 | rm -rf /etc/wireguard/ 641 | elif [[ "$os" == "fedora" ]]; then 642 | # Fedora 643 | dnf remove -y wireguard-tools 644 | rm -rf /etc/wireguard/ 645 | fi 646 | rm -f /usr/local/sbin/boringtun /usr/local/sbin/boringtun-upgrade 647 | fi 648 | echo 649 | echo "WireGuard removed!" 650 | else 651 | echo 652 | echo "WireGuard removal aborted!" 653 | fi 654 | exit 655 | ;; 656 | 4) 657 | exit 658 | ;; 659 | esac 660 | fi 661 | --------------------------------------------------------------------------------