comparison openvpn-install.sh @ 0:ebdb0cecebc0 default tip

新增
author Pluto <meokcin@gmail.com>
date Sun, 01 Sep 2024 16:38:41 +0800
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:ebdb0cecebc0
1 #!/bin/bash
2 #
3 # https://github.com/hwdsl2/openvpn-install
4 #
5 # Based on the work of Nyr and contributors at:
6 # https://github.com/Nyr/openvpn-install
7 #
8 # Copyright (c) 2022-2024 Lin Song <linsongui@gmail.com>
9 # Copyright (c) 2013-2023 Nyr
10 #
11 # Released under the MIT License, see the accompanying file LICENSE.txt
12 # or https://opensource.org/licenses/MIT
13
14 exiterr() { echo "Error: $1" >&2; exit 1; }
15 exiterr2() { exiterr "'apt-get install' failed."; }
16 exiterr3() { exiterr "'yum install' failed."; }
17 exiterr4() { exiterr "'zypper install' failed."; }
18
19 check_ip() {
20 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])$'
21 printf '%s' "$1" | tr -d '\n' | grep -Eq "$IP_REGEX"
22 }
23
24 check_os() {
25 if grep -qs "ubuntu" /etc/os-release; then
26 os="ubuntu"
27 os_version=$(grep 'VERSION_ID' /etc/os-release | cut -d '"' -f 2 | tr -d '.')
28 group_name="nogroup"
29 elif [[ -e /etc/debian_version ]]; then
30 os="debian"
31 os_version=$(grep -oE '[0-9]+' /etc/debian_version | head -1)
32 group_name="nogroup"
33 elif [[ -e /etc/almalinux-release || -e /etc/rocky-release || -e /etc/centos-release ]]; then
34 os="centos"
35 os_version=$(grep -shoE '[0-9]+' /etc/almalinux-release /etc/rocky-release /etc/centos-release | head -1)
36 group_name="nobody"
37 elif grep -qs "Amazon Linux release 2 " /etc/system-release; then
38 os="centos"
39 os_version="7"
40 group_name="nobody"
41 elif grep -qs "Amazon Linux release 2023" /etc/system-release; then
42 exiterr "Amazon Linux 2023 is not supported."
43 elif [[ -e /etc/fedora-release ]]; then
44 os="fedora"
45 os_version=$(grep -oE '[0-9]+' /etc/fedora-release | head -1)
46 group_name="nobody"
47 elif [[ -e /etc/SUSE-brand && "$(head -1 /etc/SUSE-brand)" == "openSUSE" ]]; then
48 os="openSUSE"
49 os_version=$(tail -1 /etc/SUSE-brand | grep -oE '[0-9\\.]+')
50 group_name="nogroup"
51 else
52 exiterr "This installer seems to be running on an unsupported distribution.
53 Supported distros are Ubuntu, Debian, AlmaLinux, Rocky Linux, CentOS, Fedora, openSUSE and Amazon Linux 2."
54 fi
55 }
56
57 check_os_ver() {
58 if [[ "$os" == "ubuntu" && "$os_version" -lt 1804 ]]; then
59 exiterr "Ubuntu 18.04 or higher is required to use this installer.
60 This version of Ubuntu is too old and unsupported."
61 fi
62
63 if [[ "$os" == "debian" && "$os_version" -lt 9 ]]; then
64 exiterr "Debian 9 or higher is required to use this installer.
65 This version of Debian is too old and unsupported."
66 fi
67
68 if [[ "$os" == "centos" && "$os_version" -lt 7 ]]; then
69 exiterr "CentOS 7 or higher is required to use this installer.
70 This version of CentOS is too old and unsupported."
71 fi
72 }
73
74 check_nftables() {
75 if [ "$os" = "centos" ]; then
76 if grep -qs "hwdsl2 VPN script" /etc/sysconfig/nftables.conf \
77 || systemctl is-active --quiet nftables 2>/dev/null; then
78 exiterr "This system has nftables enabled, which is not supported by this installer."
79 fi
80 fi
81 }
82
83 check_dns_name() {
84 FQDN_REGEX='^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$'
85 printf '%s' "$1" | tr -d '\n' | grep -Eq "$FQDN_REGEX"
86 }
87
88 install_wget() {
89 # Detect some Debian minimal setups where neither wget nor curl are installed
90 if ! hash wget 2>/dev/null && ! hash curl 2>/dev/null; then
91 if [ "$auto" = 0 ]; then
92 echo "Wget is required to use this installer."
93 read -n1 -r -p "Press any key to install Wget and continue..."
94 fi
95 export DEBIAN_FRONTEND=noninteractive
96 (
97 set -x
98 apt-get -yqq update || apt-get -yqq update
99 apt-get -yqq install wget >/dev/null
100 ) || exiterr2
101 fi
102 }
103
104 install_iproute() {
105 if ! hash ip 2>/dev/null; then
106 if [ "$auto" = 0 ]; then
107 echo "iproute is required to use this installer."
108 read -n1 -r -p "Press any key to install iproute and continue..."
109 fi
110 if [ "$os" = "debian" ] || [ "$os" = "ubuntu" ]; then
111 export DEBIAN_FRONTEND=noninteractive
112 (
113 set -x
114 apt-get -yqq update || apt-get -yqq update
115 apt-get -yqq install iproute2 >/dev/null
116 ) || exiterr2
117 elif [ "$os" = "openSUSE" ]; then
118 (
119 set -x
120 zypper install iproute2 >/dev/null
121 ) || exiterr4
122 else
123 (
124 set -x
125 yum -y -q install iproute >/dev/null
126 ) || exiterr3
127 fi
128 fi
129 }
130
131 show_start_setup() {
132 if [ "$auto" = 0 ]; then
133 echo
134 echo 'Welcome to this OpenVPN server installer!'
135 echo 'GitHub: https://github.com/hwdsl2/openvpn-install'
136 echo
137 echo 'I need to ask you a few questions before starting setup.'
138 echo 'You can use the default options and just press enter if you are OK with them.'
139 else
140 show_header
141 echo
142 echo 'Starting OpenVPN setup using default options.'
143 fi
144 }
145
146 enter_server_address() {
147 echo
148 echo "Do you want OpenVPN clients to connect to this server using a DNS name,"
149 printf "e.g. vpn.example.com, instead of its IP address? [y/N] "
150 read -r response
151 case $response in
152 [yY][eE][sS]|[yY])
153 use_dns_name=1
154 echo
155 ;;
156 *)
157 use_dns_name=0
158 ;;
159 esac
160 if [ "$use_dns_name" = 1 ]; then
161 read -rp "Enter the DNS name of this VPN server: " server_addr
162 until check_dns_name "$server_addr"; do
163 echo "Invalid DNS name. You must enter a fully qualified domain name (FQDN)."
164 read -rp "Enter the DNS name of this VPN server: " server_addr
165 done
166 ip="$server_addr"
167 echo
168 echo "Note: Make sure this DNS name resolves to the IPv4 address"
169 echo " of this server. If you add or update the DNS record"
170 echo " at a later time, reboot this server to take effect."
171 else
172 detect_ip
173 check_nat_ip
174 fi
175 }
176
177 find_public_ip() {
178 ip_url1="http://ipv4.icanhazip.com"
179 ip_url2="http://ip1.dynupdate.no-ip.com"
180 # Get public IP and sanitize with grep
181 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")")
182 if ! check_ip "$get_public_ip"; then
183 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")")
184 fi
185 }
186
187 detect_ip() {
188 # If system has a single IPv4, it is selected automatically.
189 if [[ $(ip -4 addr | grep inet | grep -vEc '127(\.[0-9]{1,3}){3}') -eq 1 ]]; then
190 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}')
191 else
192 # Use the IP address on the default route
193 ip=$(ip -4 route get 1 | sed 's/ uid .*//' | awk '{print $NF;exit}' 2>/dev/null)
194 if ! check_ip "$ip"; then
195 find_public_ip
196 ip_match=0
197 if [ -n "$get_public_ip" ]; then
198 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}')
199 while IFS= read -r line; do
200 if [ "$line" = "$get_public_ip" ]; then
201 ip_match=1
202 ip="$line"
203 fi
204 done <<< "$ip_list"
205 fi
206 if [ "$ip_match" = 0 ]; then
207 if [ "$auto" = 0 ]; then
208 echo
209 echo "Which IPv4 address should be used?"
210 num_of_ip=$(ip -4 addr | grep inet | grep -vEc '127(\.[0-9]{1,3}){3}')
211 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 ') '
212 read -rp "IPv4 address [1]: " ip_num
213 until [[ -z "$ip_num" || "$ip_num" =~ ^[0-9]+$ && "$ip_num" -le "$num_of_ip" ]]; do
214 echo "$ip_num: invalid selection."
215 read -rp "IPv4 address [1]: " ip_num
216 done
217 [[ -z "$ip_num" ]] && ip_num=1
218 else
219 ip_num=1
220 fi
221 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)
222 fi
223 fi
224 fi
225 if ! check_ip "$ip"; then
226 echo "Error: Could not detect this server's IP address." >&2
227 echo "Abort. No changes were made." >&2
228 exit 1
229 fi
230 }
231
232 check_nat_ip() {
233 # If $ip is a private IP address, the server must be behind NAT
234 if printf '%s' "$ip" | grep -qE '^(10|127|172\.(1[6-9]|2[0-9]|3[0-1])|192\.168|169\.254)\.'; then
235 find_public_ip
236 if ! check_ip "$get_public_ip"; then
237 if [ "$auto" = 0 ]; then
238 echo
239 echo "This server is behind NAT. What is the public IPv4 address?"
240 read -rp "Public IPv4 address: " public_ip
241 until check_ip "$public_ip"; do
242 echo "Invalid input."
243 read -rp "Public IPv4 address: " public_ip
244 done
245 else
246 echo "Error: Could not detect this server's public IP." >&2
247 echo "Abort. No changes were made." >&2
248 exit 1
249 fi
250 else
251 public_ip="$get_public_ip"
252 fi
253 fi
254 }
255
256 show_config() {
257 if [ "$auto" != 0 ]; then
258 echo
259 printf '%s' "Server IP: "
260 [ -n "$public_ip" ] && printf '%s\n' "$public_ip" || printf '%s\n' "$ip"
261 echo "Port: UDP/1194"
262 echo "Client name: client"
263 echo "Client DNS: Google Public DNS"
264 fi
265 }
266
267 detect_ipv6() {
268 ip6=""
269 if [[ $(ip -6 addr | grep -c 'inet6 [23]') -ne 0 ]]; then
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 1p)
271 fi
272 }
273
274 select_protocol() {
275 if [ "$auto" = 0 ]; then
276 echo
277 echo "Which protocol should OpenVPN use?"
278 echo " 1) UDP (recommended)"
279 echo " 2) TCP"
280 read -rp "Protocol [1]: " protocol
281 until [[ -z "$protocol" || "$protocol" =~ ^[12]$ ]]; do
282 echo "$protocol: invalid selection."
283 read -rp "Protocol [1]: " protocol
284 done
285 else
286 protocol=1
287 fi
288 case "$protocol" in
289 1|"")
290 protocol=udp
291 ;;
292 2)
293 protocol=tcp
294 ;;
295 esac
296 }
297
298 select_port() {
299 if [ "$auto" = 0 ]; then
300 echo
301 echo "Which port should OpenVPN listen to?"
302 read -rp "Port [1194]: " port
303 until [[ -z "$port" || "$port" =~ ^[0-9]+$ && "$port" -le 65535 ]]; do
304 echo "$port: invalid port."
305 read -rp "Port [1194]: " port
306 done
307 [[ -z "$port" ]] && port=1194
308 else
309 port=1194
310 fi
311 }
312
313 enter_custom_dns() {
314 read -rp "Enter primary DNS server: " dns1
315 until check_ip "$dns1"; do
316 echo "Invalid DNS server."
317 read -rp "Enter primary DNS server: " dns1
318 done
319 read -rp "Enter secondary DNS server (Enter to skip): " dns2
320 until [ -z "$dns2" ] || check_ip "$dns2"; do
321 echo "Invalid DNS server."
322 read -rp "Enter secondary DNS server (Enter to skip): " dns2
323 done
324 }
325
326 select_dns() {
327 if [ "$auto" = 0 ]; then
328 echo
329 echo "Select a DNS server for the clients:"
330 echo " 1) Current system resolvers"
331 echo " 2) Google Public DNS"
332 echo " 3) Cloudflare DNS"
333 echo " 4) OpenDNS"
334 echo " 5) Quad9"
335 echo " 6) AdGuard DNS"
336 echo " 7) Custom"
337 read -rp "DNS server [2]: " dns
338 until [[ -z "$dns" || "$dns" =~ ^[1-7]$ ]]; do
339 echo "$dns: invalid selection."
340 read -rp "DNS server [2]: " dns
341 done
342 else
343 dns=2
344 fi
345 if [ "$dns" = 7 ]; then
346 enter_custom_dns
347 fi
348 }
349
350 set_client_name() {
351 # Allow a limited set of characters to avoid conflicts
352 client=$(sed 's/[^0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-]/_/g' <<< "$unsanitized_client")
353 }
354
355 enter_client_name() {
356 if [ "$auto" = 0 ]; then
357 echo
358 echo "Enter a name for the first client:"
359 read -rp "Name [client]: " unsanitized_client
360 set_client_name
361 [[ -z "$client" ]] && client=client
362 else
363 client=client
364 fi
365 }
366
367 check_firewall() {
368 # Install a firewall if firewalld or iptables are not already available
369 if ! systemctl is-active --quiet firewalld.service && ! hash iptables 2>/dev/null; then
370 if [[ "$os" == "centos" || "$os" == "fedora" ]]; then
371 firewall="firewalld"
372 elif [[ "$os" == "openSUSE" ]]; then
373 firewall="firewalld"
374 elif [[ "$os" == "debian" || "$os" == "ubuntu" ]]; then
375 firewall="iptables"
376 fi
377 if [[ "$firewall" == "firewalld" ]]; then
378 # We don't want to silently enable firewalld, so we give a subtle warning
379 # If the user continues, firewalld will be installed and enabled during setup
380 echo
381 echo "Note: firewalld, which is required to manage routing tables, will also be installed."
382 fi
383 fi
384 }
385
386 abort_and_exit() {
387 echo "Abort. No changes were made." >&2
388 exit 1
389 }
390
391 confirm_setup() {
392 if [ "$auto" = 0 ]; then
393 printf "Do you want to continue? [Y/n] "
394 read -r response
395 case $response in
396 [yY][eE][sS]|[yY]|'')
397 :
398 ;;
399 *)
400 abort_and_exit
401 ;;
402 esac
403 fi
404 }
405
406 get_export_dir() {
407 export_to_home_dir=0
408 export_dir=~/
409 if [ -n "$SUDO_USER" ] && getent group "$SUDO_USER" >/dev/null 2>&1; then
410 user_home_dir=$(getent passwd "$SUDO_USER" 2>/dev/null | cut -d: -f6)
411 if [ -d "$user_home_dir" ] && [ "$user_home_dir" != "/" ]; then
412 export_dir="$user_home_dir/"
413 export_to_home_dir=1
414 fi
415 fi
416 }
417
418 new_client() {
419 get_export_dir
420 # Generates the custom client.ovpn
421 {
422 cat /etc/openvpn/server/client-common.txt
423 echo "<ca>"
424 cat /etc/openvpn/server/easy-rsa/pki/ca.crt
425 echo "</ca>"
426 echo "<cert>"
427 sed -ne '/BEGIN CERTIFICATE/,$ p' /etc/openvpn/server/easy-rsa/pki/issued/"$client".crt
428 echo "</cert>"
429 echo "<key>"
430 cat /etc/openvpn/server/easy-rsa/pki/private/"$client".key
431 echo "</key>"
432 echo "<tls-crypt>"
433 sed -ne '/BEGIN OpenVPN Static key/,$ p' /etc/openvpn/server/tc.key
434 echo "</tls-crypt>"
435 } > "$export_dir$client".ovpn
436 if [ "$export_to_home_dir" = 1 ]; then
437 chown "$SUDO_USER:$SUDO_USER" "$export_dir$client".ovpn
438 fi
439 chmod 600 "$export_dir$client".ovpn
440 }
441
442 update_sysctl() {
443 mkdir -p /etc/sysctl.d
444 conf_fwd="/etc/sysctl.d/99-openvpn-forward.conf"
445 conf_opt="/etc/sysctl.d/99-openvpn-optimize.conf"
446 # Enable net.ipv4.ip_forward for the system
447 echo 'net.ipv4.ip_forward=1' > "$conf_fwd"
448 if [[ -n "$ip6" ]]; then
449 # Enable net.ipv6.conf.all.forwarding for the system
450 echo "net.ipv6.conf.all.forwarding=1" >> "$conf_fwd"
451 fi
452 # Optimize sysctl settings such as TCP buffer sizes
453 base_url="https://github.com/hwdsl2/vpn-extras/releases/download/v1.0.0"
454 conf_url="$base_url/sysctl-ovpn-$os"
455 [ "$auto" != 0 ] && conf_url="${conf_url}-auto"
456 wget -t 3 -T 30 -q -O "$conf_opt" "$conf_url" 2>/dev/null \
457 || curl -m 30 -fsL "$conf_url" -o "$conf_opt" 2>/dev/null \
458 || { /bin/rm -f "$conf_opt"; touch "$conf_opt"; }
459 # Enable TCP BBR congestion control if kernel version >= 4.20
460 if modprobe -q tcp_bbr \
461 && printf '%s\n%s' "4.20" "$(uname -r)" | sort -C -V \
462 && [ -f /proc/sys/net/ipv4/tcp_congestion_control ]; then
463 cat >> "$conf_opt" <<'EOF'
464 net.core.default_qdisc = fq
465 net.ipv4.tcp_congestion_control = bbr
466 EOF
467 fi
468 # Apply sysctl settings
469 sysctl -e -q -p "$conf_fwd"
470 sysctl -e -q -p "$conf_opt"
471 }
472
473 update_rclocal() {
474 ipt_cmd="systemctl restart openvpn-iptables.service"
475 if ! grep -qs "$ipt_cmd" /etc/rc.local; then
476 if [ ! -f /etc/rc.local ]; then
477 echo '#!/bin/sh' > /etc/rc.local
478 else
479 if [ "$os" = "ubuntu" ] || [ "$os" = "debian" ]; then
480 sed --follow-symlinks -i '/^exit 0/d' /etc/rc.local
481 fi
482 fi
483 cat >> /etc/rc.local <<EOF
484
485 $ipt_cmd
486 EOF
487 if [ "$os" = "ubuntu" ] || [ "$os" = "debian" ]; then
488 echo "exit 0" >> /etc/rc.local
489 fi
490 chmod +x /etc/rc.local
491 fi
492 }
493
494 show_header() {
495 cat <<'EOF'
496
497 OpenVPN Script
498 https://github.com/hwdsl2/openvpn-install
499 EOF
500 }
501
502 show_header2() {
503 cat <<'EOF'
504
505 Copyright (c) 2022-2024 Lin Song
506 Copyright (c) 2013-2023 Nyr
507 EOF
508 }
509
510 show_usage() {
511 if [ -n "$1" ]; then
512 echo "Error: $1" >&2
513 fi
514 show_header
515 show_header2
516 cat 1>&2 <<EOF
517
518 Usage: bash $0 [options]
519
520 Options:
521 --auto auto install OpenVPN using default options
522 -h, --help show this help message and exit
523
524 To customize install options, run this script without arguments.
525 EOF
526 exit 1
527 }
528
529 ovpnsetup() {
530
531 export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
532
533 if [ "$(id -u)" != 0 ]; then
534 exiterr "This installer must be run as root. Try 'sudo bash $0'"
535 fi
536
537 # Detect Debian users running the script with "sh" instead of bash
538 if readlink /proc/$$/exe | grep -q "dash"; then
539 exiterr 'This installer needs to be run with "bash", not "sh".'
540 fi
541
542 # Detect OpenVZ 6
543 if [[ $(uname -r | cut -d "." -f 1) -eq 2 ]]; then
544 exiterr "The system is running an old kernel, which is incompatible with this installer."
545 fi
546
547 check_os
548 check_os_ver
549
550 if [[ ! -e /dev/net/tun ]] || ! ( exec 7<>/dev/net/tun ) 2>/dev/null; then
551 exiterr "The system does not have the TUN device available.
552 TUN needs to be enabled before running this installer."
553 fi
554
555 auto=0
556 if [[ ! -e /etc/openvpn/server/server.conf ]]; then
557 check_nftables
558 while [ "$#" -gt 0 ]; do
559 case $1 in
560 --auto)
561 auto=1
562 shift
563 ;;
564 -h|--help)
565 show_usage
566 ;;
567 *)
568 show_usage "Unknown parameter: $1"
569 ;;
570 esac
571 done
572 install_wget
573 install_iproute
574 show_start_setup
575 public_ip=""
576 if [ "$auto" = 0 ]; then
577 enter_server_address
578 else
579 detect_ip
580 check_nat_ip
581 fi
582 show_config
583 detect_ipv6
584 select_protocol
585 select_port
586 select_dns
587 enter_client_name
588 if [ "$auto" = 0 ]; then
589 echo
590 echo "OpenVPN installation is ready to begin."
591 fi
592 check_firewall
593 confirm_setup
594 echo
595 echo "Installing OpenVPN, please wait..."
596 # If running inside a container, disable LimitNPROC to prevent conflicts
597 if systemd-detect-virt -cq; then
598 mkdir /etc/systemd/system/openvpn-server@server.service.d/ 2>/dev/null
599 echo "[Service]
600 LimitNPROC=infinity" > /etc/systemd/system/openvpn-server@server.service.d/disable-limitnproc.conf
601 fi
602 if [[ "$os" = "debian" || "$os" = "ubuntu" ]]; then
603 export DEBIAN_FRONTEND=noninteractive
604 (
605 set -x
606 apt-get -yqq update || apt-get -yqq update
607 apt-get -yqq --no-install-recommends install openvpn >/dev/null
608 ) || exiterr2
609 (
610 set -x
611 apt-get -yqq install openssl ca-certificates $firewall >/dev/null
612 ) || exiterr2
613 elif [[ "$os" = "centos" ]]; then
614 if grep -qs "Amazon Linux release 2 " /etc/system-release; then
615 (
616 set -x
617 amazon-linux-extras install epel -y >/dev/null
618 ) || exit 1
619 else
620 (
621 set -x
622 yum -y -q install epel-release >/dev/null
623 ) || exiterr3
624 fi
625 (
626 set -x
627 yum -y -q install openvpn openssl ca-certificates tar $firewall >/dev/null 2>&1
628 ) || exiterr3
629 elif [[ "$os" = "fedora" ]]; then
630 (
631 set -x
632 dnf install -y openvpn openssl ca-certificates tar $firewall >/dev/null
633 ) || exiterr "'dnf install' failed."
634 else
635 # Else, OS must be openSUSE
636 (
637 set -x
638 zypper install -y openvpn openssl ca-certificates tar $firewall >/dev/null
639 ) || exiterr4
640 fi
641 # If firewalld was just installed, enable it
642 if [[ "$firewall" == "firewalld" ]]; then
643 (
644 set -x
645 systemctl enable --now firewalld.service >/dev/null 2>&1
646 )
647 fi
648 # Get easy-rsa
649 easy_rsa_url='https://github.com/OpenVPN/easy-rsa/releases/download/v3.1.7/EasyRSA-3.1.7.tgz'
650 mkdir -p /etc/openvpn/server/easy-rsa/
651 { wget -t 3 -T 30 -qO- "$easy_rsa_url" 2>/dev/null || curl -m 30 -sL "$easy_rsa_url" ; } | tar xz -C /etc/openvpn/server/easy-rsa/ --strip-components 1
652 if [ ! -f /etc/openvpn/server/easy-rsa/easyrsa ]; then
653 exiterr "Failed to download EasyRSA from $easy_rsa_url."
654 fi
655 chown -R root:root /etc/openvpn/server/easy-rsa/
656 cd /etc/openvpn/server/easy-rsa/ || exit 1
657 (
658 set -x
659 # Create the PKI, set up the CA and the server and client certificates
660 ./easyrsa --batch init-pki >/dev/null
661 ./easyrsa --batch build-ca nopass >/dev/null 2>&1
662 ./easyrsa --batch --days=3650 build-server-full server nopass >/dev/null 2>&1
663 ./easyrsa --batch --days=3650 build-client-full "$client" nopass >/dev/null 2>&1
664 ./easyrsa --batch --days=3650 gen-crl >/dev/null 2>&1
665 )
666 # Move the stuff we need
667 cp pki/ca.crt pki/private/ca.key pki/issued/server.crt pki/private/server.key pki/crl.pem /etc/openvpn/server
668 # CRL is read with each client connection, while OpenVPN is dropped to nobody
669 chown nobody:"$group_name" /etc/openvpn/server/crl.pem
670 # Without +x in the directory, OpenVPN can't run a stat() on the CRL file
671 chmod o+x /etc/openvpn/server/
672 (
673 set -x
674 # Generate key for tls-crypt
675 openvpn --genkey --secret /etc/openvpn/server/tc.key >/dev/null
676 )
677 # Create the DH parameters file using the predefined ffdhe2048 group
678 echo '-----BEGIN DH PARAMETERS-----
679 MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
680 +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
681 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
682 YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
683 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
684 ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
685 -----END DH PARAMETERS-----' > /etc/openvpn/server/dh.pem
686 # Generate server.conf
687 echo "local $ip
688 port $port
689 proto $protocol
690 dev tun
691 ca ca.crt
692 cert server.crt
693 key server.key
694 dh dh.pem
695 auth SHA256
696 tls-crypt tc.key
697 topology subnet
698 server 10.8.0.0 255.255.255.0" > /etc/openvpn/server/server.conf
699 # IPv6
700 if [[ -z "$ip6" ]]; then
701 echo 'push "block-ipv6"' >> /etc/openvpn/server/server.conf
702 echo 'push "ifconfig-ipv6 fddd:1194:1194:1194::2/64 fddd:1194:1194:1194::1"' >> /etc/openvpn/server/server.conf
703 else
704 echo 'server-ipv6 fddd:1194:1194:1194::/64' >> /etc/openvpn/server/server.conf
705 fi
706 echo 'push "redirect-gateway def1 ipv6 bypass-dhcp"' >> /etc/openvpn/server/server.conf
707 echo 'ifconfig-pool-persist ipp.txt' >> /etc/openvpn/server/server.conf
708 # DNS
709 case "$dns" in
710 1)
711 # Locate the proper resolv.conf
712 # Needed for systems running systemd-resolved
713 if grep '^nameserver' "/etc/resolv.conf" | grep -qv '127.0.0.53' ; then
714 resolv_conf="/etc/resolv.conf"
715 else
716 resolv_conf="/run/systemd/resolve/resolv.conf"
717 fi
718 # Obtain the resolvers from resolv.conf and use them for OpenVPN
719 grep -v '^#\|^;' "$resolv_conf" | grep '^nameserver' | grep -v '127.0.0.53' | grep -oE '[0-9]{1,3}(\.[0-9]{1,3}){3}' | while read line; do
720 echo "push \"dhcp-option DNS $line\"" >> /etc/openvpn/server/server.conf
721 done
722 ;;
723 2|"")
724 echo 'push "dhcp-option DNS 8.8.8.8"' >> /etc/openvpn/server/server.conf
725 echo 'push "dhcp-option DNS 8.8.4.4"' >> /etc/openvpn/server/server.conf
726 ;;
727 3)
728 echo 'push "dhcp-option DNS 1.1.1.1"' >> /etc/openvpn/server/server.conf
729 echo 'push "dhcp-option DNS 1.0.0.1"' >> /etc/openvpn/server/server.conf
730 ;;
731 4)
732 echo 'push "dhcp-option DNS 208.67.222.222"' >> /etc/openvpn/server/server.conf
733 echo 'push "dhcp-option DNS 208.67.220.220"' >> /etc/openvpn/server/server.conf
734 ;;
735 5)
736 echo 'push "dhcp-option DNS 9.9.9.9"' >> /etc/openvpn/server/server.conf
737 echo 'push "dhcp-option DNS 149.112.112.112"' >> /etc/openvpn/server/server.conf
738 ;;
739 6)
740 echo 'push "dhcp-option DNS 94.140.14.14"' >> /etc/openvpn/server/server.conf
741 echo 'push "dhcp-option DNS 94.140.15.15"' >> /etc/openvpn/server/server.conf
742 ;;
743 7)
744 echo "push \"dhcp-option DNS $dns1\"" >> /etc/openvpn/server/server.conf
745 if [ -n "$dns2" ]; then
746 echo "push \"dhcp-option DNS $dns2\"" >> /etc/openvpn/server/server.conf
747 fi
748 ;;
749 esac
750 echo 'push "block-outside-dns"' >> /etc/openvpn/server/server.conf
751 echo "keepalive 10 120
752 cipher AES-128-GCM
753 user nobody
754 group $group_name
755 persist-key
756 persist-tun
757 verb 3
758 crl-verify crl.pem" >> /etc/openvpn/server/server.conf
759 if [[ "$protocol" = "udp" ]]; then
760 echo "explicit-exit-notify" >> /etc/openvpn/server/server.conf
761 fi
762 update_sysctl
763 if systemctl is-active --quiet firewalld.service; then
764 # Using both permanent and not permanent rules to avoid a firewalld
765 # reload.
766 # We don't use --add-service=openvpn because that would only work with
767 # the default port and protocol.
768 firewall-cmd -q --add-port="$port"/"$protocol"
769 firewall-cmd -q --zone=trusted --add-source=10.8.0.0/24
770 firewall-cmd -q --permanent --add-port="$port"/"$protocol"
771 firewall-cmd -q --permanent --zone=trusted --add-source=10.8.0.0/24
772 # Set NAT for the VPN subnet
773 firewall-cmd -q --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j MASQUERADE
774 firewall-cmd -q --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j MASQUERADE
775 if [[ -n "$ip6" ]]; then
776 firewall-cmd -q --zone=trusted --add-source=fddd:1194:1194:1194::/64
777 firewall-cmd -q --permanent --zone=trusted --add-source=fddd:1194:1194:1194::/64
778 firewall-cmd -q --direct --add-rule ipv6 nat POSTROUTING 0 -s fddd:1194:1194:1194::/64 ! -d fddd:1194:1194:1194::/64 -j MASQUERADE
779 firewall-cmd -q --permanent --direct --add-rule ipv6 nat POSTROUTING 0 -s fddd:1194:1194:1194::/64 ! -d fddd:1194:1194:1194::/64 -j MASQUERADE
780 fi
781 else
782 # Create a service to set up persistent iptables rules
783 iptables_path=$(command -v iptables)
784 ip6tables_path=$(command -v ip6tables)
785 # nf_tables is not available as standard in OVZ kernels. So use iptables-legacy
786 # if we are in OVZ, with a nf_tables backend and iptables-legacy is available.
787 if [[ $(systemd-detect-virt) == "openvz" ]] && readlink -f "$(command -v iptables)" | grep -q "nft" && hash iptables-legacy 2>/dev/null; then
788 iptables_path=$(command -v iptables-legacy)
789 ip6tables_path=$(command -v ip6tables-legacy)
790 fi
791 echo "[Unit]
792 Before=network.target
793 [Service]
794 Type=oneshot
795 ExecStart=$iptables_path -t nat -A POSTROUTING -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j MASQUERADE
796 ExecStart=$iptables_path -I INPUT -p $protocol --dport $port -j ACCEPT
797 ExecStart=$iptables_path -I FORWARD -s 10.8.0.0/24 -j ACCEPT
798 ExecStart=$iptables_path -I FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
799 ExecStop=$iptables_path -t nat -D POSTROUTING -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j MASQUERADE
800 ExecStop=$iptables_path -D INPUT -p $protocol --dport $port -j ACCEPT
801 ExecStop=$iptables_path -D FORWARD -s 10.8.0.0/24 -j ACCEPT
802 ExecStop=$iptables_path -D FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT" > /etc/systemd/system/openvpn-iptables.service
803 if [[ -n "$ip6" ]]; then
804 echo "ExecStart=$ip6tables_path -t nat -A POSTROUTING -s fddd:1194:1194:1194::/64 ! -d fddd:1194:1194:1194::/64 -j MASQUERADE
805 ExecStart=$ip6tables_path -I FORWARD -s fddd:1194:1194:1194::/64 -j ACCEPT
806 ExecStart=$ip6tables_path -I FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
807 ExecStop=$ip6tables_path -t nat -D POSTROUTING -s fddd:1194:1194:1194::/64 ! -d fddd:1194:1194:1194::/64 -j MASQUERADE
808 ExecStop=$ip6tables_path -D FORWARD -s fddd:1194:1194:1194::/64 -j ACCEPT
809 ExecStop=$ip6tables_path -D FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT" >> /etc/systemd/system/openvpn-iptables.service
810 fi
811 echo "RemainAfterExit=yes
812 [Install]
813 WantedBy=multi-user.target" >> /etc/systemd/system/openvpn-iptables.service
814 (
815 set -x
816 systemctl enable --now openvpn-iptables.service >/dev/null 2>&1
817 )
818 fi
819 if [ "$os" != "openSUSE" ]; then
820 update_rclocal
821 fi
822 # If SELinux is enabled and a custom port was selected, we need this
823 if sestatus 2>/dev/null | grep "Current mode" | grep -q "enforcing" && [[ "$port" != 1194 ]]; then
824 # Install semanage if not already present
825 if ! hash semanage 2>/dev/null; then
826 if [[ "$os_version" -eq 7 ]]; then
827 # Centos 7
828 (
829 set -x
830 yum -y -q install policycoreutils-python >/dev/null
831 ) || exiterr3
832 else
833 # CentOS 8/9 or Fedora
834 (
835 set -x
836 dnf install -y policycoreutils-python-utils >/dev/null
837 ) || exiterr "'dnf install' failed."
838 fi
839 fi
840 semanage port -a -t openvpn_port_t -p "$protocol" "$port"
841 fi
842 # If the server is behind NAT, use the correct IP address
843 [[ -n "$public_ip" ]] && ip="$public_ip"
844 # client-common.txt is created so we have a template to add further users later
845 echo "client
846 dev tun
847 proto $protocol
848 remote $ip $port
849 resolv-retry infinite
850 nobind
851 persist-key
852 persist-tun
853 remote-cert-tls server
854 auth SHA256
855 cipher AES-128-GCM
856 ignore-unknown-option block-outside-dns block-ipv6
857 verb 3" > /etc/openvpn/server/client-common.txt
858 # Enable and start the OpenVPN service
859 if [ "$os" != "openSUSE" ]; then
860 (
861 set -x
862 systemctl enable --now openvpn-server@server.service >/dev/null 2>&1
863 )
864 else
865 ln -s /etc/openvpn/server/* /etc/openvpn >/dev/null 2>&1
866 (
867 set -x
868 systemctl enable --now openvpn@server.service >/dev/null 2>&1
869 )
870 fi
871 # Generates the custom client.ovpn
872 new_client
873 echo
874 echo "Finished!"
875 echo
876 echo "The client configuration is available in: $export_dir$client.ovpn"
877 echo "New clients can be added by running this script again."
878 else
879 show_header
880 echo
881 echo "OpenVPN is already installed."
882 echo
883 echo "Select an option:"
884 echo " 1) Add a new client"
885 echo " 2) Export config for an existing client"
886 echo " 3) List existing clients"
887 echo " 4) Revoke an existing client"
888 echo " 5) Remove OpenVPN"
889 echo " 6) Exit"
890 read -rp "Option: " option
891 until [[ "$option" =~ ^[1-6]$ ]]; do
892 echo "$option: invalid selection."
893 read -rp "Option: " option
894 done
895 case "$option" in
896 1)
897 echo
898 echo "Provide a name for the client:"
899 read -rp "Name: " unsanitized_client
900 [ -z "$unsanitized_client" ] && abort_and_exit
901 set_client_name
902 while [[ -z "$client" || -e /etc/openvpn/server/easy-rsa/pki/issued/"$client".crt ]]; do
903 echo "$client: invalid name."
904 read -rp "Name: " unsanitized_client
905 [ -z "$unsanitized_client" ] && abort_and_exit
906 set_client_name
907 done
908 cd /etc/openvpn/server/easy-rsa/ || exit 1
909 (
910 set -x
911 ./easyrsa --batch --days=3650 build-client-full "$client" nopass >/dev/null 2>&1
912 )
913 # Generates the custom client.ovpn
914 new_client
915 echo
916 echo "$client added. Configuration available in: $export_dir$client.ovpn"
917 exit
918 ;;
919 2)
920 num_of_clients=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c "^V")
921 if [[ "$num_of_clients" = 0 ]]; then
922 echo
923 echo "There are no existing clients!"
924 exit
925 fi
926 echo
927 echo "Select the client to export:"
928 tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | nl -s ') '
929 read -rp "Client: " client_num
930 [ -z "$client_num" ] && abort_and_exit
931 until [[ "$client_num" =~ ^[0-9]+$ && "$client_num" -le "$num_of_clients" ]]; do
932 echo "$client_num: invalid selection."
933 read -rp "Client: " client_num
934 [ -z "$client_num" ] && abort_and_exit
935 done
936 client=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | sed -n "$client_num"p)
937 new_client
938 echo
939 echo "$client exported. Configuration available in: $export_dir$client.ovpn"
940 exit
941 ;;
942 3)
943 echo
944 echo "Checking for existing client(s)..."
945 num_of_clients=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c "^V")
946 if [[ "$num_of_clients" = 0 ]]; then
947 echo
948 echo "There are no existing clients!"
949 exit
950 fi
951 echo
952 tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | nl -s ') '
953 if [ "$num_of_clients" = 1 ]; then
954 printf '\n%s\n' "Total: 1 client"
955 elif [ -n "$num_of_clients" ]; then
956 printf '\n%s\n' "Total: $num_of_clients clients"
957 fi
958 exit
959 ;;
960 4)
961 num_of_clients=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c "^V")
962 if [[ "$num_of_clients" = 0 ]]; then
963 echo
964 echo "There are no existing clients!"
965 exit
966 fi
967 echo
968 echo "Select the client to revoke:"
969 tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | nl -s ') '
970 read -rp "Client: " client_num
971 [ -z "$client_num" ] && abort_and_exit
972 until [[ "$client_num" =~ ^[0-9]+$ && "$client_num" -le "$num_of_clients" ]]; do
973 echo "$client_num: invalid selection."
974 read -rp "Client: " client_num
975 [ -z "$client_num" ] && abort_and_exit
976 done
977 client=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | sed -n "$client_num"p)
978 echo
979 read -rp "Confirm $client revocation? [y/N]: " revoke
980 until [[ "$revoke" =~ ^[yYnN]*$ ]]; do
981 echo "$revoke: invalid selection."
982 read -rp "Confirm $client revocation? [y/N]: " revoke
983 done
984 if [[ "$revoke" =~ ^[yY]$ ]]; then
985 echo
986 echo "Revoking $client..."
987 cd /etc/openvpn/server/easy-rsa/ || exit 1
988 (
989 set -x
990 ./easyrsa --batch revoke "$client" >/dev/null 2>&1
991 ./easyrsa --batch --days=3650 gen-crl >/dev/null 2>&1
992 )
993 rm -f /etc/openvpn/server/crl.pem
994 cp /etc/openvpn/server/easy-rsa/pki/crl.pem /etc/openvpn/server/crl.pem
995 # CRL is read with each client connection, when OpenVPN is dropped to nobody
996 chown nobody:"$group_name" /etc/openvpn/server/crl.pem
997 get_export_dir
998 ovpn_file="$export_dir$client.ovpn"
999 if [ -f "$ovpn_file" ]; then
1000 echo "Removing $ovpn_file..."
1001 rm -f "$ovpn_file"
1002 fi
1003 echo
1004 echo "$client revoked!"
1005 else
1006 echo
1007 echo "$client revocation aborted!"
1008 fi
1009 exit
1010 ;;
1011 5)
1012 echo
1013 read -rp "Confirm OpenVPN removal? [y/N]: " remove
1014 until [[ "$remove" =~ ^[yYnN]*$ ]]; do
1015 echo "$remove: invalid selection."
1016 read -rp "Confirm OpenVPN removal? [y/N]: " remove
1017 done
1018 if [[ "$remove" =~ ^[yY]$ ]]; then
1019 echo
1020 echo "Removing OpenVPN, please wait..."
1021 port=$(grep '^port ' /etc/openvpn/server/server.conf | cut -d " " -f 2)
1022 protocol=$(grep '^proto ' /etc/openvpn/server/server.conf | cut -d " " -f 2)
1023 if systemctl is-active --quiet firewalld.service; then
1024 ip=$(firewall-cmd --direct --get-rules ipv4 nat POSTROUTING | grep '\-s 10.8.0.0/24 '"'"'!'"'"' -d 10.8.0.0/24' | grep -oE '[^ ]+$')
1025 # Using both permanent and not permanent rules to avoid a firewalld reload.
1026 firewall-cmd -q --remove-port="$port"/"$protocol"
1027 firewall-cmd -q --zone=trusted --remove-source=10.8.0.0/24
1028 firewall-cmd -q --permanent --remove-port="$port"/"$protocol"
1029 firewall-cmd -q --permanent --zone=trusted --remove-source=10.8.0.0/24
1030 firewall-cmd -q --direct --remove-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j MASQUERADE
1031 firewall-cmd -q --permanent --direct --remove-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j MASQUERADE
1032 if grep -qs "server-ipv6" /etc/openvpn/server/server.conf; then
1033 ip6=$(firewall-cmd --direct --get-rules ipv6 nat POSTROUTING | grep '\-s fddd:1194:1194:1194::/64 '"'"'!'"'"' -d fddd:1194:1194:1194::/64' | grep -oE '[^ ]+$')
1034 firewall-cmd -q --zone=trusted --remove-source=fddd:1194:1194:1194::/64
1035 firewall-cmd -q --permanent --zone=trusted --remove-source=fddd:1194:1194:1194::/64
1036 firewall-cmd -q --direct --remove-rule ipv6 nat POSTROUTING 0 -s fddd:1194:1194:1194::/64 ! -d fddd:1194:1194:1194::/64 -j MASQUERADE
1037 firewall-cmd -q --permanent --direct --remove-rule ipv6 nat POSTROUTING 0 -s fddd:1194:1194:1194::/64 ! -d fddd:1194:1194:1194::/64 -j MASQUERADE
1038 fi
1039 else
1040 systemctl disable --now openvpn-iptables.service
1041 rm -f /etc/systemd/system/openvpn-iptables.service
1042 fi
1043 if sestatus 2>/dev/null | grep "Current mode" | grep -q "enforcing" && [[ "$port" != 1194 ]]; then
1044 semanage port -d -t openvpn_port_t -p "$protocol" "$port"
1045 fi
1046 if [ "$os" != "openSUSE" ]; then
1047 systemctl disable --now openvpn-server@server.service
1048 else
1049 systemctl disable --now openvpn@server.service
1050 fi
1051 rm -f /etc/systemd/system/openvpn-server@server.service.d/disable-limitnproc.conf
1052 rm -f /etc/sysctl.d/99-openvpn-forward.conf /etc/sysctl.d/99-openvpn-optimize.conf
1053 if [ ! -f /usr/bin/wg-quick ] && [ ! -f /usr/sbin/ipsec ] \
1054 && [ ! -f /usr/local/sbin/ipsec ]; then
1055 echo 0 > /proc/sys/net/ipv4/ip_forward
1056 echo 0 > /proc/sys/net/ipv6/conf/all/forwarding
1057 fi
1058 ipt_cmd="systemctl restart openvpn-iptables.service"
1059 if grep -qs "$ipt_cmd" /etc/rc.local; then
1060 sed --follow-symlinks -i "/^$ipt_cmd/d" /etc/rc.local
1061 fi
1062 if [[ "$os" = "debian" || "$os" = "ubuntu" ]]; then
1063 (
1064 set -x
1065 rm -rf /etc/openvpn/server
1066 apt-get remove --purge -y openvpn >/dev/null
1067 )
1068 elif [[ "$os" = "openSUSE" ]]; then
1069 (
1070 set -x
1071 zypper remove -y openvpn >/dev/null
1072 rm -rf /etc/openvpn/server
1073 )
1074 rm -f /etc/openvpn/ipp.txt
1075 else
1076 # Else, OS must be CentOS or Fedora
1077 (
1078 set -x
1079 yum -y -q remove openvpn >/dev/null
1080 rm -rf /etc/openvpn/server
1081 )
1082 fi
1083 echo
1084 echo "OpenVPN removed!"
1085 else
1086 echo
1087 echo "OpenVPN removal aborted!"
1088 fi
1089 exit
1090 ;;
1091 6)
1092 exit
1093 ;;
1094 esac
1095 fi
1096 }
1097
1098 ## Defer setup until we have the complete script
1099 ovpnsetup "$@"
1100
1101 exit 0
备案号:苏ICP备2024087954号-2 | 渝公网安备50010402001513