|
| 1 | +#!/bin/sh |
| 2 | +# |
| 3 | +# Script for automatic setup of an IPsec VPN server on Ubuntu, Debian, CentOS/RHEL, |
| 4 | +# Rocky Linux, AlmaLinux, Oracle Linux, Amazon Linux 2 and Alpine Linux |
| 5 | +# |
| 6 | +# DO NOT RUN THIS SCRIPT ON YOUR PC OR MAC! |
| 7 | +# |
| 8 | +# The latest version of this script is available at: |
| 9 | +# https://github.com/hwdsl2/setup-ipsec-vpn |
| 10 | +# |
| 11 | +# Copyright (C) 2021-2026 Lin Song <linsongui@gmail.com> |
| 12 | +# |
| 13 | +# This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 |
| 14 | +# Unported License: http://creativecommons.org/licenses/by-sa/3.0/ |
| 15 | +# |
| 16 | +# Attribution required: please include my name in any derivative and let me |
| 17 | +# know how you have improved it! |
| 18 | + |
| 19 | +# ===================================================== |
| 20 | + |
| 21 | +# Define your own values for these variables |
| 22 | +# - IPsec pre-shared key, VPN username and password |
| 23 | +# - All values MUST be placed inside 'single quotes' |
| 24 | +# - DO NOT use these special characters within values: \ " ' |
| 25 | + |
| 26 | +YOUR_IPSEC_PSK='' |
| 27 | +YOUR_USERNAME='' |
| 28 | +YOUR_PASSWORD='' |
| 29 | + |
| 30 | +# ===================================================== |
| 31 | + |
| 32 | +export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" |
| 33 | + |
| 34 | +exiterr() { echo "Error: $1" >&2; exit 1; } |
| 35 | + |
| 36 | +check_ip() { |
| 37 | + 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])$' |
| 38 | + printf '%s' "$1" | tr -d '\n' | grep -Eq "$IP_REGEX" |
| 39 | +} |
| 40 | + |
| 41 | +check_dns_name() { |
| 42 | + FQDN_REGEX='^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$' |
| 43 | + printf '%s' "$1" | tr -d '\n' | grep -Eq "$FQDN_REGEX" |
| 44 | +} |
| 45 | + |
| 46 | +check_root() { |
| 47 | + if [ "$(id -u)" != 0 ]; then |
| 48 | + exiterr "Script must be run as root. Try 'sudo sh $0'" |
| 49 | + fi |
| 50 | +} |
| 51 | + |
| 52 | +check_vz() { |
| 53 | + if [ -f /proc/user_beancounters ]; then |
| 54 | + exiterr "OpenVZ VPS is not supported." |
| 55 | + fi |
| 56 | +} |
| 57 | + |
| 58 | +check_lxc() { |
| 59 | + # shellcheck disable=SC2154 |
| 60 | + if [ "$container" = "lxc" ] && [ ! -e /dev/ppp ]; then |
| 61 | +cat 1>&2 <<'EOF' |
| 62 | +Error: /dev/ppp is missing. LXC containers require configuration. |
| 63 | + See: https://github.com/hwdsl2/setup-ipsec-vpn/issues/1014 |
| 64 | +EOF |
| 65 | + exit 1 |
| 66 | + fi |
| 67 | +} |
| 68 | + |
| 69 | +check_os() { |
| 70 | + rh_file="/etc/redhat-release" |
| 71 | + if [ -f "$rh_file" ]; then |
| 72 | + os_type=centos |
| 73 | + if grep -q "Red Hat" "$rh_file"; then |
| 74 | + os_type=rhel |
| 75 | + fi |
| 76 | + [ -f /etc/oracle-release ] && os_type=ol |
| 77 | + grep -qi rocky "$rh_file" && os_type=rocky |
| 78 | + grep -qi alma "$rh_file" && os_type=alma |
| 79 | + if grep -q "release 7" "$rh_file"; then |
| 80 | + os_ver=7 |
| 81 | + elif grep -q "release 8" "$rh_file"; then |
| 82 | + os_ver=8 |
| 83 | + grep -qi stream "$rh_file" && os_ver=8s |
| 84 | + elif grep -q "release 9" "$rh_file"; then |
| 85 | + os_ver=9 |
| 86 | + grep -qi stream "$rh_file" && os_ver=9s |
| 87 | + elif grep -q "release 10" "$rh_file"; then |
| 88 | + os_ver=10 |
| 89 | + grep -qi stream "$rh_file" && os_ver=10s |
| 90 | + else |
| 91 | + exiterr "This script only supports CentOS/RHEL 7-10." |
| 92 | + fi |
| 93 | + if [ "$os_type" = "centos" ] \ |
| 94 | + && { [ "$os_ver" = 7 ] || [ "$os_ver" = 8 ] || [ "$os_ver" = 8s ]; }; then |
| 95 | + exiterr "CentOS Linux $os_ver is EOL and not supported." |
| 96 | + fi |
| 97 | + elif grep -qs "Amazon Linux release 2 " /etc/system-release; then |
| 98 | + os_type=amzn |
| 99 | + os_ver=2 |
| 100 | + elif grep -qs "Amazon Linux release 2023" /etc/system-release; then |
| 101 | + exiterr "Amazon Linux 2023 is not supported." |
| 102 | + else |
| 103 | + os_type=$(lsb_release -si 2>/dev/null) |
| 104 | + [ -z "$os_type" ] && [ -f /etc/os-release ] && os_type=$(. /etc/os-release && printf '%s' "$ID") |
| 105 | + case $os_type in |
| 106 | + [Uu]buntu) |
| 107 | + os_type=ubuntu |
| 108 | + ;; |
| 109 | + [Dd]ebian|[Kk]ali|[Rr]aspbian) |
| 110 | + os_type=debian |
| 111 | + ;; |
| 112 | + [Aa]lpine) |
| 113 | + os_type=alpine |
| 114 | + ;; |
| 115 | + *) |
| 116 | +cat 1>&2 <<'EOF' |
| 117 | +Error: This script only supports one of the following OS: |
| 118 | + Ubuntu, Debian, CentOS/RHEL, Rocky Linux, AlmaLinux, |
| 119 | + Oracle Linux, Amazon Linux 2 or Alpine Linux |
| 120 | +EOF |
| 121 | + exit 1 |
| 122 | + ;; |
| 123 | + esac |
| 124 | + if [ "$os_type" = "alpine" ]; then |
| 125 | + os_ver=$(. /etc/os-release && printf '%s' "$VERSION_ID" | cut -d '.' -f 1,2) |
| 126 | + if [ "$os_ver" != "3.22" ] && [ "$os_ver" != "3.23" ]; then |
| 127 | + exiterr "This script only supports Alpine Linux 3.22/3.23." |
| 128 | + fi |
| 129 | + else |
| 130 | + os_ver=$(sed 's/\..*//' /etc/debian_version | tr -dc 'A-Za-z0-9') |
| 131 | + if [ "$os_ver" = 8 ] || [ "$os_ver" = 9 ] || [ "$os_ver" = "stretchsid" ] \ |
| 132 | + || [ "$os_ver" = "bustersid" ] || [ -z "$os_ver" ]; then |
| 133 | +cat 1>&2 <<EOF |
| 134 | +Error: This script requires Debian >= 10 or Ubuntu >= 20.04. |
| 135 | + This version of Ubuntu/Debian is too old and not supported. |
| 136 | +EOF |
| 137 | + exit 1 |
| 138 | + fi |
| 139 | + fi |
| 140 | + fi |
| 141 | +} |
| 142 | + |
| 143 | +check_iface() { |
| 144 | + def_iface=$(route 2>/dev/null | grep -m 1 '^default' | grep -o '[^ ]*$') |
| 145 | + if [ "$os_type" != "alpine" ]; then |
| 146 | + [ -z "$def_iface" ] && def_iface=$(ip -4 route list 0/0 2>/dev/null | grep -m 1 -Po '(?<=dev )(\S+)') |
| 147 | + fi |
| 148 | + def_state=$(cat "/sys/class/net/$def_iface/operstate" 2>/dev/null) |
| 149 | + check_wl=0 |
| 150 | + if [ -n "$def_state" ] && [ "$def_state" != "down" ]; then |
| 151 | + if [ "$os_type" = "ubuntu" ] || [ "$os_type" = "debian" ]; then |
| 152 | + if ! uname -m | grep -qi -e '^arm' -e '^aarch64'; then |
| 153 | + check_wl=1 |
| 154 | + fi |
| 155 | + else |
| 156 | + check_wl=1 |
| 157 | + fi |
| 158 | + fi |
| 159 | + if [ "$check_wl" = 1 ]; then |
| 160 | + case $def_iface in |
| 161 | + wl*) |
| 162 | + exiterr "Wireless interface '$def_iface' detected. DO NOT run this script on your PC or Mac!" |
| 163 | + ;; |
| 164 | + esac |
| 165 | + fi |
| 166 | +} |
| 167 | + |
| 168 | +check_creds() { |
| 169 | + [ -n "$YOUR_IPSEC_PSK" ] && VPN_IPSEC_PSK="$YOUR_IPSEC_PSK" |
| 170 | + [ -n "$YOUR_USERNAME" ] && VPN_USER="$YOUR_USERNAME" |
| 171 | + [ -n "$YOUR_PASSWORD" ] && VPN_PASSWORD="$YOUR_PASSWORD" |
| 172 | + if [ -z "$VPN_IPSEC_PSK" ] && [ -z "$VPN_USER" ] && [ -z "$VPN_PASSWORD" ]; then |
| 173 | + return 0 |
| 174 | + fi |
| 175 | + if [ -z "$VPN_IPSEC_PSK" ] || [ -z "$VPN_USER" ] || [ -z "$VPN_PASSWORD" ]; then |
| 176 | + exiterr "All VPN credentials must be specified. Edit the script and re-enter them." |
| 177 | + fi |
| 178 | + if printf '%s' "$VPN_IPSEC_PSK $VPN_USER $VPN_PASSWORD" | LC_ALL=C grep -q '[^ -~]\+'; then |
| 179 | + exiterr "VPN credentials must not contain non-ASCII characters." |
| 180 | + fi |
| 181 | + case "$VPN_IPSEC_PSK $VPN_USER $VPN_PASSWORD" in |
| 182 | + *[\\\"\']*) |
| 183 | + exiterr "VPN credentials must not contain these special characters: \\ \" '" |
| 184 | + ;; |
| 185 | + esac |
| 186 | +} |
| 187 | + |
| 188 | +check_dns() { |
| 189 | + if { [ -n "$VPN_DNS_SRV1" ] && ! check_ip "$VPN_DNS_SRV1"; } \ |
| 190 | + || { [ -n "$VPN_DNS_SRV2" ] && ! check_ip "$VPN_DNS_SRV2"; }; then |
| 191 | + exiterr "The DNS server specified is invalid." |
| 192 | + fi |
| 193 | +} |
| 194 | + |
| 195 | +check_server_dns() { |
| 196 | + if [ -n "$VPN_DNS_NAME" ] && ! check_dns_name "$VPN_DNS_NAME"; then |
| 197 | + exiterr "Invalid DNS name. 'VPN_DNS_NAME' must be a fully qualified domain name (FQDN)." |
| 198 | + fi |
| 199 | +} |
| 200 | + |
| 201 | +check_client_name() { |
| 202 | + if [ -n "$VPN_CLIENT_NAME" ]; then |
| 203 | + name_len="$(printf '%s' "$VPN_CLIENT_NAME" | wc -m)" |
| 204 | + if [ "$name_len" -gt "64" ] || printf '%s' "$VPN_CLIENT_NAME" | LC_ALL=C grep -q '[^A-Za-z0-9_-]\+' \ |
| 205 | + || case $VPN_CLIENT_NAME in -*) true ;; *) false ;; esac; then |
| 206 | + exiterr "Invalid client name. Use one word only, no special characters except '-' and '_'." |
| 207 | + fi |
| 208 | + fi |
| 209 | +} |
| 210 | + |
| 211 | +wait_for_apt() { |
| 212 | + count=0 |
| 213 | + apt_lk=/var/lib/apt/lists/lock |
| 214 | + pkg_lk=/var/lib/dpkg/lock |
| 215 | + while fuser "$apt_lk" "$pkg_lk" >/dev/null 2>&1 \ |
| 216 | + || lsof "$apt_lk" >/dev/null 2>&1 || lsof "$pkg_lk" >/dev/null 2>&1; do |
| 217 | + [ "$count" = 0 ] && echo "## Waiting for apt to be available..." |
| 218 | + [ "$count" -ge 200 ] && exiterr "Could not get apt/dpkg lock." |
| 219 | + count=$((count+1)) |
| 220 | + printf '%s' '.' |
| 221 | + sleep 3 |
| 222 | + done |
| 223 | +} |
| 224 | + |
| 225 | +install_pkgs() { |
| 226 | + if ! command -v wget >/dev/null 2>&1; then |
| 227 | + if [ "$os_type" = "ubuntu" ] || [ "$os_type" = "debian" ]; then |
| 228 | + wait_for_apt |
| 229 | + export DEBIAN_FRONTEND=noninteractive |
| 230 | + ( |
| 231 | + set -x |
| 232 | + apt-get -yqq update || apt-get -yqq update |
| 233 | + ) || exiterr "'apt-get update' failed." |
| 234 | + ( |
| 235 | + set -x |
| 236 | + apt-get -yqq install wget >/dev/null || apt-get -yqq install wget >/dev/null |
| 237 | + ) || exiterr "'apt-get install wget' failed." |
| 238 | + elif [ "$os_type" != "alpine" ]; then |
| 239 | + ( |
| 240 | + set -x |
| 241 | + yum -y -q install wget >/dev/null || yum -y -q install wget >/dev/null |
| 242 | + ) || exiterr "'yum install wget' failed." |
| 243 | + fi |
| 244 | + fi |
| 245 | + if [ "$os_type" = "alpine" ]; then |
| 246 | + ( |
| 247 | + set -x |
| 248 | + apk add -U -q bash coreutils grep net-tools sed wget |
| 249 | + ) || exiterr "'apk add' failed." |
| 250 | + fi |
| 251 | +} |
| 252 | + |
| 253 | +get_setup_url() { |
| 254 | + base_url1="https://raw.githubusercontent.com/hwdsl2/setup-ipsec-vpn/master" |
| 255 | + base_url2="https://gitlab.com/hwdsl2/setup-ipsec-vpn/-/raw/master" |
| 256 | + sh_file="vpnsetup_ubuntu.sh" |
| 257 | + if [ "$os_type" = "centos" ] || [ "$os_type" = "rhel" ] || [ "$os_type" = "rocky" ] \ |
| 258 | + || [ "$os_type" = "alma" ] || [ "$os_type" = "ol" ]; then |
| 259 | + sh_file="vpnsetup_centos.sh" |
| 260 | + elif [ "$os_type" = "amzn" ]; then |
| 261 | + sh_file="vpnsetup_amzn.sh" |
| 262 | + elif [ "$os_type" = "alpine" ]; then |
| 263 | + sh_file="vpnsetup_alpine.sh" |
| 264 | + fi |
| 265 | + setup_url1="$base_url1/$sh_file" |
| 266 | + setup_url2="$base_url2/$sh_file" |
| 267 | +} |
| 268 | + |
| 269 | +run_setup() { |
| 270 | + status=0 |
| 271 | + if tmpdir=$(mktemp --tmpdir -d vpn.XXXXX 2>/dev/null); then |
| 272 | + if ( set -x; wget -t 3 -T 30 -q -O "$tmpdir/vpn.sh" "$setup_url1" \ |
| 273 | + || wget -t 3 -T 30 -q -O "$tmpdir/vpn.sh" "$setup_url2" \ |
| 274 | + || curl -m 30 -fsL "$setup_url1" -o "$tmpdir/vpn.sh" 2>/dev/null ); then |
| 275 | + VPN_IPSEC_PSK="$VPN_IPSEC_PSK" VPN_USER="$VPN_USER" \ |
| 276 | + VPN_PASSWORD="$VPN_PASSWORD" \ |
| 277 | + VPN_PUBLIC_IP="$VPN_PUBLIC_IP" VPN_L2TP_NET="$VPN_L2TP_NET" \ |
| 278 | + VPN_L2TP_LOCAL="$VPN_L2TP_LOCAL" VPN_L2TP_POOL="$VPN_L2TP_POOL" \ |
| 279 | + VPN_XAUTH_NET="$VPN_XAUTH_NET" VPN_XAUTH_POOL="$VPN_XAUTH_POOL" \ |
| 280 | + VPN_DNS_SRV1="$VPN_DNS_SRV1" VPN_DNS_SRV2="$VPN_DNS_SRV2" \ |
| 281 | + VPN_DNS_NAME="$VPN_DNS_NAME" VPN_CLIENT_NAME="$VPN_CLIENT_NAME" \ |
| 282 | + VPN_PROTECT_CONFIG="$VPN_PROTECT_CONFIG" \ |
| 283 | + VPN_CLIENT_VALIDITY="$VPN_CLIENT_VALIDITY" \ |
| 284 | + VPN_SKIP_IKEV2="$VPN_SKIP_IKEV2" VPN_SWAN_VER="$VPN_SWAN_VER" \ |
| 285 | + VPN_PUBLIC_IP6="$VPN_PUBLIC_IP6" VPN_IP6_NET="$VPN_IP6_NET" \ |
| 286 | + /bin/bash "$tmpdir/vpn.sh" || status=1 |
| 287 | + else |
| 288 | + status=1 |
| 289 | + echo "Error: Could not download VPN setup script." >&2 |
| 290 | + fi |
| 291 | + /bin/rm -f "$tmpdir/vpn.sh" |
| 292 | + /bin/rmdir "$tmpdir" |
| 293 | + else |
| 294 | + exiterr "Could not create temporary directory." |
| 295 | + fi |
| 296 | +} |
| 297 | + |
| 298 | +vpnsetup() { |
| 299 | + check_root |
| 300 | + check_vz |
| 301 | + check_lxc |
| 302 | + check_os |
| 303 | + check_iface |
| 304 | + check_creds |
| 305 | + check_dns |
| 306 | + check_server_dns |
| 307 | + check_client_name |
| 308 | + install_pkgs |
| 309 | + get_setup_url |
| 310 | + run_setup |
| 311 | +} |
| 312 | + |
| 313 | +## Defer setup until we have the complete script |
| 314 | +vpnsetup "$@" |
| 315 | + |
| 316 | +exit "$status" |
0 commit comments