Improve VPN script

- Refactor the script into Bash functions for improved organization
  and readability.
This commit is contained in:
hwdsl2
2024-06-22 17:18:09 -05:00
parent 3a004d20e2
commit 70ea744f66

View File

@@ -21,6 +21,11 @@ check_ip() {
printf '%s' "$1" | tr -d '\n' | grep -Eq "$IP_REGEX" printf '%s' "$1" | tr -d '\n' | grep -Eq "$IP_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() { check_root() {
if [ "$(id -u)" != 0 ]; then if [ "$(id -u)" != 0 ]; then
exiterr "This installer must be run as root. Try 'sudo bash $0'" exiterr "This installer must be run as root. Try 'sudo bash $0'"
@@ -79,12 +84,10 @@ check_os_ver() {
exiterr "Ubuntu 20.04 or higher is required to use this installer. exiterr "Ubuntu 20.04 or higher is required to use this installer.
This version of Ubuntu is too old and unsupported." This version of Ubuntu is too old and unsupported."
fi fi
if [[ "$os" == "debian" && "$os_version" -lt 10 ]]; then if [[ "$os" == "debian" && "$os_version" -lt 10 ]]; then
exiterr "Debian 10 or higher is required to use this installer. exiterr "Debian 10 or higher is required to use this installer.
This version of Debian is too old and unsupported." This version of Debian is too old and unsupported."
fi fi
if [[ "$os" == "centos" && "$os_version" -lt 7 ]]; then if [[ "$os" == "centos" && "$os_version" -lt 7 ]]; then
exiterr "CentOS 7 or higher is required to use this installer. exiterr "CentOS 7 or higher is required to use this installer.
This version of CentOS is too old and unsupported." This version of CentOS is too old and unsupported."
@@ -124,11 +127,6 @@ check_nftables() {
fi fi
} }
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"
}
install_wget() { install_wget() {
# Detect some Debian minimal setups where neither wget nor curl are installed # 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 ! hash wget 2>/dev/null && ! hash curl 2>/dev/null; then
@@ -172,11 +170,52 @@ install_iproute() {
fi fi
} }
show_start_setup() { show_header() {
cat <<'EOF'
OpenVPN Script
https://github.com/hwdsl2/openvpn-install
EOF
}
show_header2() {
cat <<'EOF'
Welcome to this OpenVPN server installer!
GitHub: https://github.com/hwdsl2/openvpn-install
EOF
}
show_header3() {
cat <<'EOF'
Copyright (c) 2022-2024 Lin Song
Copyright (c) 2013-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:
--auto auto install OpenVPN using default options
-h, --help show this help message and exit
To customize install options, run this script without arguments.
EOF
exit 1
}
show_welcome() {
if [ "$auto" = 0 ]; then if [ "$auto" = 0 ]; then
echo show_header2
echo 'Welcome to this OpenVPN server installer!'
echo 'GitHub: https://github.com/hwdsl2/openvpn-install'
echo echo
echo 'I need to ask you a few questions before starting setup.' 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.' echo 'You can use the default options and just press enter if you are OK with them.'
@@ -326,17 +365,17 @@ select_protocol() {
echo "$protocol: invalid selection." echo "$protocol: invalid selection."
read -rp "Protocol [1]: " protocol read -rp "Protocol [1]: " protocol
done done
case "$protocol" in
1|"")
protocol=udp
;;
2)
protocol=tcp
;;
esac
else else
protocol=1
fi
case "$protocol" in
1|"")
protocol=udp protocol=udp
;; fi
2)
protocol=tcp
;;
esac
} }
select_port() { select_port() {
@@ -396,7 +435,7 @@ set_client_name() {
client=$(sed 's/[^0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-]/_/g' <<< "$unsanitized_client") client=$(sed 's/[^0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-]/_/g' <<< "$unsanitized_client")
} }
enter_client_name() { enter_first_client_name() {
if [ "$auto" = 0 ]; then if [ "$auto" = 0 ]; then
echo echo
echo "Enter a name for the first client:" echo "Enter a name for the first client:"
@@ -408,6 +447,13 @@ enter_client_name() {
fi fi
} }
show_setup_ready() {
if [ "$auto" = 0 ]; then
echo
echo "OpenVPN installation is ready to begin."
fi
}
check_firewall() { check_firewall() {
# Install a firewall if firewalld or iptables are not already available # 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 ! systemctl is-active --quiet firewalld.service && ! hash iptables 2>/dev/null; then
@@ -447,6 +493,11 @@ confirm_setup() {
fi fi
} }
show_start_setup() {
echo
echo "Installing OpenVPN, please wait..."
}
disable_limitnproc() { disable_limitnproc() {
# If running inside a container, disable LimitNPROC to prevent conflicts # If running inside a container, disable LimitNPROC to prevent conflicts
if systemd-detect-virt -cq; then if systemd-detect-virt -cq; then
@@ -742,10 +793,6 @@ crl-verify crl.pem" >> "$OVPN_CONF"
fi fi
} }
show_clients() {
tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | nl -s ') '
}
get_export_dir() { get_export_dir() {
export_to_home_dir=0 export_to_home_dir=0
export_dir=~/ export_dir=~/
@@ -858,6 +905,8 @@ update_selinux() {
} }
create_client_common() { create_client_common() {
# If the server is behind NAT, use the correct IP address
[[ -n "$public_ip" ]] && ip="$public_ip"
# client-common.txt is created so we have a template to add further users later # client-common.txt is created so we have a template to add further users later
echo "client echo "client
dev tun dev tun
@@ -875,7 +924,6 @@ verb 3" > /etc/openvpn/server/client-common.txt
} }
start_openvpn_service() { start_openvpn_service() {
# Enable and start the OpenVPN service
if [ "$os" != "openSUSE" ]; then if [ "$os" != "openSUSE" ]; then
( (
set -x set -x
@@ -898,22 +946,6 @@ finish_setup() {
echo "New clients can be added by running this script again." echo "New clients can be added by running this script again."
} }
show_header() {
cat <<'EOF'
OpenVPN Script
https://github.com/hwdsl2/openvpn-install
EOF
}
show_header2() {
cat <<'EOF'
Copyright (c) 2022-2024 Lin Song
Copyright (c) 2013-2023 Nyr
EOF
}
select_menu_option() { select_menu_option() {
echo echo
echo "OpenVPN is already installed." echo "OpenVPN is already installed."
@@ -932,23 +964,167 @@ select_menu_option() {
done done
} }
show_usage() { show_clients() {
if [ -n "$1" ]; then tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | nl -s ') '
echo "Error: $1" >&2 }
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" || -e /etc/openvpn/server/easy-rsa/pki/issued/"$client".crt ]]; do
echo "$client: invalid name."
read -rp "Name: " unsanitized_client
[ -z "$unsanitized_client" ] && abort_and_exit
set_client_name
done
}
build_client_config() {
cd /etc/openvpn/server/easy-rsa/ || exit 1
(
set -x
./easyrsa --batch --days=3650 build-client-full "$client" nopass >/dev/null 2>&1
)
}
print_client_action() {
echo
echo "$client $1. Configuration available in: $export_dir$client.ovpn"
}
print_check_clients() {
echo
echo "Checking for existing client(s)..."
}
check_clients() {
num_of_clients=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c "^V")
if [[ "$num_of_clients" = 0 ]]; then
echo
echo "There are no existing clients!"
exit
fi fi
show_header }
show_header2
cat 1>&2 <<EOF
Usage: bash $0 [options] 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
}
Options: select_client_to() {
--auto auto install OpenVPN using default options echo
-h, --help show this help message and exit 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=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | sed -n "$client_num"p)
}
To customize install options, run this script without arguments. confirm_revoke_client() {
EOF echo
exit 1 read -rp "Confirm $client revocation? [y/N]: " revoke
until [[ "$revoke" =~ ^[yYnN]*$ ]]; do
echo "$revoke: invalid selection."
read -rp "Confirm $client revocation? [y/N]: " revoke
done
}
print_revoke_client() {
echo
echo "Revoking $client..."
}
remove_client_conf() {
get_export_dir
ovpn_file="$export_dir$client.ovpn"
if [ -f "$ovpn_file" ]; then
echo "Removing $ovpn_file..."
rm -f "$ovpn_file"
fi
}
revoke_client() {
cd /etc/openvpn/server/easy-rsa/ || exit 1
(
set -x
./easyrsa --batch revoke "$client" >/dev/null 2>&1
./easyrsa --batch --days=3650 gen-crl >/dev/null 2>&1
)
rm -f /etc/openvpn/server/crl.pem
cp /etc/openvpn/server/easy-rsa/pki/crl.pem /etc/openvpn/server/crl.pem
# CRL is read with each client connection, when OpenVPN is dropped to nobody
chown nobody:"$group_name" /etc/openvpn/server/crl.pem
remove_client_conf
}
print_client_revoked() {
echo
echo "$client revoked!"
}
print_client_revocation_aborted() {
echo
echo "$client revocation aborted!"
}
confirm_remove_ovpn() {
echo
read -rp "Confirm OpenVPN removal? [y/N]: " remove
until [[ "$remove" =~ ^[yYnN]*$ ]]; do
echo "$remove: invalid selection."
read -rp "Confirm OpenVPN removal? [y/N]: " remove
done
}
print_remove_ovpn() {
echo
echo "Removing OpenVPN, please wait..."
}
disable_ovpn_service() {
if [ "$os" != "openSUSE" ]; then
systemctl disable --now openvpn-server@server.service
else
systemctl disable --now openvpn@server.service
fi
rm -f /etc/systemd/system/openvpn-server@server.service.d/disable-limitnproc.conf
}
remove_sysctl_rules() {
rm -f /etc/sysctl.d/99-openvpn-forward.conf /etc/sysctl.d/99-openvpn-optimize.conf
if [ ! -f /usr/bin/wg-quick ] && [ ! -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 openvpn-iptables.service"
if grep -qs "$ipt_cmd" /etc/rc.local; then
sed --follow-symlinks -i "/^$ipt_cmd/d" /etc/rc.local
fi
}
print_ovpn_removed() {
echo
echo "OpenVPN removed!"
}
print_ovpn_removal_aborted() {
echo
echo "OpenVPN removal aborted!"
} }
ovpnsetup() { ovpnsetup() {
@@ -970,7 +1146,7 @@ if [[ ! -e "$OVPN_CONF" ]]; then
parse_args "$@" parse_args "$@"
install_wget install_wget
install_iproute install_iproute
show_start_setup show_welcome
public_ip="" public_ip=""
if [ "$auto" = 0 ]; then if [ "$auto" = 0 ]; then
enter_server_address enter_server_address
@@ -983,15 +1159,11 @@ if [[ ! -e "$OVPN_CONF" ]]; then
select_protocol select_protocol
select_port select_port
select_dns select_dns
enter_client_name enter_first_client_name
if [ "$auto" = 0 ]; then show_setup_ready
echo
echo "OpenVPN installation is ready to begin."
fi
check_firewall check_firewall
confirm_setup confirm_setup
echo show_start_setup
echo "Installing OpenVPN, please wait..."
disable_limitnproc disable_limitnproc
install_pkgs install_pkgs
install_easyrsa install_easyrsa
@@ -1003,8 +1175,6 @@ if [[ ! -e "$OVPN_CONF" ]]; then
update_rclocal update_rclocal
fi fi
update_selinux update_selinux
# If the server is behind NAT, use the correct IP address
[[ -n "$public_ip" ]] && ip="$public_ip"
create_client_common create_client_common
start_openvpn_service start_openvpn_service
new_client new_client
@@ -1014,153 +1184,52 @@ else
select_menu_option select_menu_option
case "$option" in case "$option" in
1) 1)
echo enter_client_name
echo "Provide a name for the client:" build_client_config
read -rp "Name: " unsanitized_client
[ -z "$unsanitized_client" ] && abort_and_exit
set_client_name
while [[ -z "$client" || -e /etc/openvpn/server/easy-rsa/pki/issued/"$client".crt ]]; do
echo "$client: invalid name."
read -rp "Name: " unsanitized_client
[ -z "$unsanitized_client" ] && abort_and_exit
set_client_name
done
cd /etc/openvpn/server/easy-rsa/ || exit 1
(
set -x
./easyrsa --batch --days=3650 build-client-full "$client" nopass >/dev/null 2>&1
)
# Generates the custom client.ovpn
new_client new_client
echo print_client_action added
echo "$client added. Configuration available in: $export_dir$client.ovpn"
exit exit
;; ;;
2) 2)
num_of_clients=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c "^V") check_clients
if [[ "$num_of_clients" = 0 ]]; then select_client_to export
echo
echo "There are no existing clients!"
exit
fi
echo
echo "Select the client to export:"
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=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | sed -n "$client_num"p)
new_client new_client
echo print_client_action exported
echo "$client exported. Configuration available in: $export_dir$client.ovpn"
exit exit
;; ;;
3) 3)
echo print_check_clients
echo "Checking for existing client(s)..." check_clients
num_of_clients=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c "^V")
if [[ "$num_of_clients" = 0 ]]; then
echo
echo "There are no existing clients!"
exit
fi
echo echo
show_clients show_clients
if [ "$num_of_clients" = 1 ]; then print_client_total
printf '\n%s\n' "Total: 1 client"
elif [ -n "$num_of_clients" ]; then
printf '\n%s\n' "Total: $num_of_clients clients"
fi
exit exit
;; ;;
4) 4)
num_of_clients=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c "^V") check_clients
if [[ "$num_of_clients" = 0 ]]; then select_client_to revoke
echo confirm_revoke_client
echo "There are no existing clients!"
exit
fi
echo
echo "Select the client to revoke:"
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=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | sed -n "$client_num"p)
echo
read -rp "Confirm $client revocation? [y/N]: " revoke
until [[ "$revoke" =~ ^[yYnN]*$ ]]; do
echo "$revoke: invalid selection."
read -rp "Confirm $client revocation? [y/N]: " revoke
done
if [[ "$revoke" =~ ^[yY]$ ]]; then if [[ "$revoke" =~ ^[yY]$ ]]; then
echo print_revoke_client
echo "Revoking $client..." revoke_client
cd /etc/openvpn/server/easy-rsa/ || exit 1 print_client_revoked
(
set -x
./easyrsa --batch revoke "$client" >/dev/null 2>&1
./easyrsa --batch --days=3650 gen-crl >/dev/null 2>&1
)
rm -f /etc/openvpn/server/crl.pem
cp /etc/openvpn/server/easy-rsa/pki/crl.pem /etc/openvpn/server/crl.pem
# CRL is read with each client connection, when OpenVPN is dropped to nobody
chown nobody:"$group_name" /etc/openvpn/server/crl.pem
get_export_dir
ovpn_file="$export_dir$client.ovpn"
if [ -f "$ovpn_file" ]; then
echo "Removing $ovpn_file..."
rm -f "$ovpn_file"
fi
echo
echo "$client revoked!"
else else
echo print_client_revocation_aborted
echo "$client revocation aborted!"
fi fi
exit exit
;; ;;
5) 5)
echo confirm_remove_ovpn
read -rp "Confirm OpenVPN removal? [y/N]: " remove
until [[ "$remove" =~ ^[yYnN]*$ ]]; do
echo "$remove: invalid selection."
read -rp "Confirm OpenVPN removal? [y/N]: " remove
done
if [[ "$remove" =~ ^[yY]$ ]]; then if [[ "$remove" =~ ^[yY]$ ]]; then
echo print_remove_ovpn
echo "Removing OpenVPN, please wait..."
remove_firewall_rules remove_firewall_rules
if [ "$os" != "openSUSE" ]; then disable_ovpn_service
systemctl disable --now openvpn-server@server.service remove_sysctl_rules
else remove_rclocal_rules
systemctl disable --now openvpn@server.service
fi
rm -f /etc/systemd/system/openvpn-server@server.service.d/disable-limitnproc.conf
rm -f /etc/sysctl.d/99-openvpn-forward.conf /etc/sysctl.d/99-openvpn-optimize.conf
if [ ! -f /usr/bin/wg-quick ] && [ ! -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
ipt_cmd="systemctl restart openvpn-iptables.service"
if grep -qs "$ipt_cmd" /etc/rc.local; then
sed --follow-symlinks -i "/^$ipt_cmd/d" /etc/rc.local
fi
remove_pkgs remove_pkgs
echo print_ovpn_removed
echo "OpenVPN removed!"
else else
echo print_ovpn_removal_aborted
echo "OpenVPN removal aborted!"
fi fi
exit exit
;; ;;