/
install.sh
executable file
·3714 lines (3235 loc) · 111 KB
/
install.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env bash
# PiVPN: Trivial OpenVPN or WireGuard setup and configuration
# Easiest setup and mangement of OpenVPN or WireGuard on Raspberry Pi
# https://pivpn.io
# Heavily adapted from the pi-hole.net project and...
# https://github.com/StarshipEngineer/OpenVPN-Setup/
#
# Install with this command (from your Pi):
#
# curl -sSfL https://install.pivpn.io | bash
# Make sure you have `curl` installed
######## VARIABLES #########
pivpnGitUrl="https://github.com/pivpn/pivpn.git"
# Uncomment to checkout a custom branch for local pivpn files
#pivpnGitBranch="custombranchtocheckout"
setupVarsFile="setupVars.conf"
setupConfigDir="/etc/pivpn"
tempsetupVarsFile="/tmp/setupVars.conf"
pivpnFilesDir="/usr/local/src/pivpn"
pivpnScriptDir="/opt/pivpn"
GITBIN="/usr/bin/git"
piholeSetupVars="/etc/pihole/setupVars.conf"
dnsmasqConfig="/etc/dnsmasq.d/02-pivpn.conf"
dhcpcdFile="/etc/dhcpcd.conf"
ovpnUserGroup="openvpn:openvpn"
######## PKG Vars ########
PKG_MANAGER="apt-get"
### FIXME: quoting UPDATE_PKG_CACHE and PKG_INSTALL hangs the script,
### shellcheck SC2086
UPDATE_PKG_CACHE="${PKG_MANAGER} update -y"
PKG_INSTALL="${PKG_MANAGER} --yes --no-install-recommends install"
PKG_COUNT="${PKG_MANAGER} -s -o Debug::NoLocking=true upgrade | grep -c ^Inst || true"
CHECK_PKG_INSTALLED='dpkg-query -s'
# Dependencies that are required by the script,
# regardless of the VPN protocol chosen
BASE_DEPS=(git tar curl grep dnsutils grepcidr whiptail net-tools)
BASE_DEPS+=(bsdmainutils bash-completion)
BASE_DEPS_ALPINE=(git grep bind-tools newt net-tools bash-completion coreutils)
BASE_DEPS_ALPINE+=(openssl util-linux openrc iptables ip6tables coreutils sed)
BASE_DEPS_ALPINE+=(perl libqrencode-tools)
# Dependencies that where actually installed by the script. For example if the
# script requires grep and dnsutils but dnsutils is already installed, we save
# grep here. This way when uninstalling PiVPN we won't prompt to remove packages
# that may have been installed by the user for other reasons
INSTALLED_PACKAGES=()
######## URLs ########
easyrsaVer="3.1.0"
easyrsaRel="https://github.com/OpenVPN/easy-rsa/releases/download/v${easyrsaVer}/EasyRSA-${easyrsaVer}.tgz"
######## Undocumented Flags. Shhh ########
runUnattended=false
skipSpaceCheck=false
reconfigure=false
showUnsupportedNICs=false
######## Some vars that might be empty
# but need to be defined for checks
pivpnPERSISTENTKEEPALIVE=""
pivpnDNS2=""
######## IPv6 related config
# cli parameter "--noipv6" allows to disable IPv6 which also prevents forced
# IPv6 route
# cli parameter "--ignoreipv6leak" allows to skip the forced IPv6 route if
# required (not recommended)
## Force IPv6 through VPN even if IPv6 is not supported by the server
## This will prevent an IPv6 leak on the client site but might cause
## issues on the client site accessing IPv6 addresses.
## This option is useless if routes are set manually.
## It's also irrelevant when IPv6 is (forced) enabled.
pivpnforceipv6route=1
## Enable or disable IPv6.
## Leaving it empty or set to "1" will trigger an IPv6 uplink check
pivpnenableipv6=""
## Enable to skip IPv6 connectivity check and also force client IPv6 traffic
## through wireguard regardless if there is a working IPv6 route on the server.
pivpnforceipv6=0
######## SCRIPT ########
# Find the rows and columns. Will default to 80x24 if it can not be detected.
screen_size="$(stty size 2> /dev/null || echo 24 80)"
rows="$(echo "${screen_size}" | awk '{print $1}')"
columns="$(echo "${screen_size}" | awk '{print $2}')"
# Divide by two so the dialogs take up half of the screen, which looks nice.
r=$((rows / 2))
c=$((columns / 2))
# Unless the screen is tiny
r=$((r < 20 ? 20 : r))
c=$((c < 70 ? 70 : c))
# Override localization settings so the output is in English language.
export LC_ALL=C
main() {
# Pre install checks and configs
distroCheck
rootCheck
flagsCheck "$@"
unattendedCheck
checkExistingInstall "$@"
checkHostname
# Verify there is enough disk space for the install
if [[ "${skipSpaceCheck}" == 'true' ]]; then
echo -n "::: --skip-space-check passed to script, "
echo "skipping free disk space verification!"
else
verifyFreeDiskSpace
fi
updatePackageCache
notifyPackageUpdatesAvailable
preconfigurePackages
if [[ "${PLAT}" == 'Alpine' ]]; then
installDependentPackages BASE_DEPS_ALPINE[@]
else
installDependentPackages BASE_DEPS[@]
fi
welcomeDialogs
if [[ "${pivpnforceipv6}" -eq 1 ]]; then
echo "::: Forced IPv6 config, skipping IPv6 uplink check!"
pivpnenableipv6=1
else
if [[ -z "${pivpnenableipv6}" ]] \
|| [[ "${pivpnenableipv6}" -eq 1 ]]; then
checkipv6uplink
fi
if [[ "${pivpnenableipv6}" -eq 0 ]] \
&& [[ "${pivpnforceipv6route}" -eq 1 ]]; then
askforcedipv6route
fi
fi
chooseInterface
if checkStaticIpSupported; then
getStaticIPv4Settings
if [[ -z "${dhcpReserv}" ]] \
|| [[ "${dhcpReserv}" -ne 1 ]]; then
setStaticIPv4
fi
else
staticIpNotSupported
fi
chooseUser
cloneOrUpdateRepos
# Install
if installPiVPN; then
echo "::: Install Complete..."
else
exit 1
fi
restartServices
# Ask if unattended-upgrades will be enabled
askUnattendedUpgrades
if [[ "${UNATTUPG}" -eq 1 ]]; then
confUnattendedUpgrades
fi
writeConfigFiles
installScripts
displayFinalMessage
echo ":::"
}
####### FUNCTIONS ##########
err() {
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2
}
rootCheck() {
######## FIRST CHECK ########
# Must be root to install
echo ":::"
if [[ "${EUID}" -eq 0 ]]; then
echo "::: You are root."
else
echo "::: sudo will be used for the install."
# Check if it is actually installed
# If it isn't, exit because the install cannot complete
if eval "${CHECK_PKG_INSTALLED} sudo" &> /dev/null; then
export SUDO="sudo"
export SUDOE="sudo -E"
else
err "::: Please install sudo or run this as root."
exit 1
fi
fi
}
flagsCheck() {
# Check arguments for the undocumented flags
for ((i = 1; i <= "$#"; i++)); do
j="$((i + 1))"
case "${!i}" in
"--skip-space-check")
skipSpaceCheck=true
;;
"--unattended")
runUnattended=true
unattendedConfig="${!j}"
;;
"--reconfigure")
reconfigure=true
;;
"--show-unsupported-nics")
showUnsupportedNICs=true
;;
"--giturl")
pivpnGitUrl="${!j}"
;;
"--gitbranch")
pivpnGitBranch="${!j}"
;;
"--noipv6")
pivpnforceipv6=0
pivpnenableipv6=0
pivpnforceipv6route=0
;;
"--ignoreipv6leak")
pivpnforceipv6route=0
;;
esac
done
}
unattendedCheck() {
if [[ "${runUnattended}" == 'true' ]]; then
echo -n "::: --unattended passed to install script, "
echo "no whiptail dialogs will be displayed"
if [[ -z "${unattendedConfig}" ]]; then
err "::: No configuration file passed"
exit 1
else
if [[ -r "${unattendedConfig}" ]]; then
# shellcheck disable=SC1090
. "${unattendedConfig}"
else
err "::: Can't open ${unattendedConfig}"
exit 1
fi
fi
fi
}
checkExistingInstall() {
# see which setup already exists
if [[ -r "${setupConfigDir}/wireguard/${setupVarsFile}" ]]; then
setupVars="${setupConfigDir}/wireguard/${setupVarsFile}"
elif [[ -r "${setupConfigDir}/openvpn/${setupVarsFile}" ]]; then
setupVars="${setupConfigDir}/openvpn/${setupVarsFile}"
fi
if [[ -r "${setupVars}" ]]; then
if [[ "${reconfigure}" == 'true' ]]; then
echo -n "::: --reconfigure passed to install script, "
echo "will reinstall PiVPN overwriting existing settings"
UpdateCmd="Reconfigure"
elif [[ "${runUnattended}" == 'true' ]]; then
### What should the script do when passing --unattended to
### an existing installation?
UpdateCmd="Reconfigure"
else
askAboutExistingInstall "${setupVars}"
fi
fi
if [[ -z "${UpdateCmd}" ]] \
|| [[ "${UpdateCmd}" == "Reconfigure" ]]; then
:
elif [[ "${UpdateCmd}" == "Update" ]]; then
${SUDO} "${pivpnScriptDir}/update.sh" "$@"
exit "$?"
elif [[ "${UpdateCmd}" == "Repair" ]]; then
# shellcheck disable=SC1090
. "${setupVars}"
runUnattended=true
fi
}
askAboutExistingInstall() {
opt1a="Update"
opt1b="Get the latest PiVPN scripts"
opt2a="Repair"
opt2b="Reinstall PiVPN using existing settings"
opt3a="Reconfigure"
opt3b="Reinstall PiVPN with new settings"
UpdateCmd="$(whiptail \
--title "Existing Install Detected!" \
--menu "
We have detected an existing install.
${1}
Please choose from the following options \
(Reconfigure can be used to add a second VPN type):" "${r}" "${c}" 3 \
"${opt1a}" "${opt1b}" \
"${opt2a}" "${opt2b}" \
"${opt3a}" "${opt3b}" \
3>&2 2>&1 1>&3)" \
|| {
err "::: Cancel selected. Exiting"
exit 1
}
echo "::: ${UpdateCmd} option selected."
}
distroCheck() {
# Check for supported distribution
# Compatibility, functions to check for supported OS
# distroCheck, maybeOSSupport, noOSSupport
# if lsb_release command is on their system
if command -v lsb_release > /dev/null; then
PLAT="$(lsb_release -si)"
OSCN="$(lsb_release -sc)"
else # else get info from os-release
. /etc/os-release
PLAT="$(awk '{print $1}' <<< "${NAME}")"
VER="${VERSION_ID}"
declare -A VER_MAP=(["10"]="buster"
["11"]="bullseye"
["12"]="bookworm"
["18.04"]="bionic"
["20.04"]="focal"
["22.04"]="jammy"
["23.04"]="lunar")
OSCN="${VER_MAP["${VER}"]}"
# Alpine support
if [[ -z "${OSCN}" ]]; then
OSCN="${VER}"
fi
fi
case "${PLAT}" in
Debian | Raspbian | Ubuntu)
case "${OSCN}" in
stretch | buster | bullseye | bookwork | xenial | bionic | focal | jammy | lunar)
:
;;
*)
maybeOSSupport
;;
esac
;;
Alpine)
PKG_MANAGER='apk'
UPDATE_PKG_CACHE="${PKG_MANAGER} update"
PKG_INSTALL="${PKG_MANAGER} --no-cache add"
PKG_COUNT="${PKG_MANAGER} list -u | wc -l || true"
CHECK_PKG_INSTALLED="${PKG_MANAGER} --no-cache info -e"
;;
*)
noOSSupport
;;
esac
{
echo "PLAT=${PLAT}"
echo "OSCN=${OSCN}"
} > "${tempsetupVarsFile}"
}
noOSSupport() {
if [[ "${runUnattended}" == 'true' ]]; then
err "::: Invalid OS detected"
err "::: We have not been able to detect a supported OS."
err "::: Currently this installer supports Raspbian, Debian and Ubuntu."
exit 1
fi
whiptail \
--backtitle "INVALID OS DETECTED" \
--title "Invalid OS" \
--msgbox "We have not been able to detect a supported OS.
Currently this installer supports Raspbian, Debian and Ubuntu.
For more details, check our documentation at \
https://github.com/pivpn/pivpn/wiki" "${r}" "${c}"
exit 1
}
maybeOSSupport() {
if [[ "${runUnattended}" == 'true' ]]; then
echo "::: OS Not Supported"
echo -n "::: You are on an OS that we have not tested but MAY work, "
echo "continuing anyway..."
return
fi
if whiptail \
--backtitle "Untested OS" \
--title "Untested OS" \
--yesno "You are on an OS that we have not tested but MAY work.
Currently this installer supports Raspbian, Debian and Ubuntu.
For more details about supported OS please check our documentation \
at https://github.com/pivpn/pivpn/wiki
Would you like to continue anyway?" "${r}" "${c}"; then
echo "::: Did not detect perfectly supported OS but,"
echo -n "::: Continuing installation at user's own "
echo "risk..."
else
err "::: Exiting due to untested OS"
exit 1
fi
}
checkHostname() {
# Checks for hostname Length
host_name="$(hostname -s)"
if [[ "${#host_name}" -gt 28 ]]; then
if [[ "${runUnattended}" == 'true' ]]; then
err "::: Your hostname is too long."
err "::: Use 'hostnamectl set-hostname YOURHOSTNAME' to set a new hostname"
err "::: It must be less then 28 characters long and it must not use special characters"
exit 1
fi
until [[ "${#host_name}" -le 28 ]] \
&& [[ "${host_name}" =~ ^[a-zA-Z0-9][a-zA-Z0-9-]{1,28}$ ]]; do
host_name="$(whiptail \
--title "Hostname too long" \
--inputbox "Your hostname is too long.
Enter new hostname with less then 28 characters
No special characters allowed." "${r}" "${c}" \
3>&1 1>&2 2>&3)"
${SUDO} hostnamectl set-hostname "${host_name}"
if [[ "${#host_name}" -le 28 ]] \
&& [[ "${host_name}" =~ ^[a-zA-Z0-9][a-zA-Z0-9-]{1,28}$ ]]; then
echo "::: Hostname valid and length OK, proceeding..."
fi
done
else
echo "::: Hostname length OK"
fi
}
spinner() {
local pid="${1}"
local delay=0.50
local spinstr='/-\|'
while ps a | awk '{print $1}' | grep -q "${pid}"; do
local temp="${spinstr#?}"
printf " [%c] " "${spinstr}"
local spinstr="${temp}${spinstr%"$temp"}"
sleep "${delay}"
printf "\\b\\b\\b\\b\\b\\b"
done
printf " \\b\\b\\b\\b"
}
verifyFreeDiskSpace() {
# If user installs unattended-upgrades we'd need about 60MB so
# will check for 75MB free
echo "::: Verifying free disk space..."
local required_free_kilobytes=76800
local existing_free_kilobytes
existing_free_kilobytes="$(df -Pk \
| grep -m1 '\/$' \
| awk '{print $4}')"
# - Unknown free disk space , not a integer
if [[ ! "${existing_free_kilobytes}" =~ ^([0-9])+$ ]]; then
echo "::: Unknown free disk space!"
echo -n "::: We were unable to determine available free disk space "
echo "on this system."
if [[ "${runUnattended}" == 'true' ]]; then
exit 1
fi
echo -n "::: You may continue with the installation, however, "
echo "it is not recommended."
echo -n "::: If you are sure you want to continue, "
echo -n "type YES and press enter :: "
read -r response
case "${response}" in
[Yy][Ee][Ss])
:
;;
*)
err "::: Confirmation not received, exiting..."
exit 1
;;
esac
# - Insufficient free disk space
elif [[ "${existing_free_kilobytes}" -lt "${required_free_kilobytes}" ]]; then
err "::: Insufficient Disk Space!"
err "::: Your system appears to be low on disk space. PiVPN recommends a minimum of ${required_free_kilobytes} KiloBytes."
err "::: You only have ${existing_free_kilobytes} KiloBytes free."
err "::: If this is a new install on a Raspberry Pi you may need to expand your disk."
err "::: Try running 'sudo raspi-config', and choose the 'expand file system option'"
err "::: After rebooting, run this installation again. (curl -sSfL https://install.pivpn.io | bash)"
err "Insufficient free space, exiting..."
exit 1
fi
}
updatePackageCache() {
# update package lists
echo ":::"
echo -e "::: Package Cache update is needed, running ${UPDATE_PKG_CACHE} ..."
# shellcheck disable=SC2086
${SUDO} ${UPDATE_PKG_CACHE} &> /dev/null &
spinner "$!"
echo " done!"
}
notifyPackageUpdatesAvailable() {
# Let user know if they have outdated packages on their system and
# advise them to run a package update at soonest possible.
echo ":::"
echo -n "::: Checking ${PKG_MANAGER} for upgraded packages...."
updatesToInstall="$(eval "${PKG_COUNT}")"
echo " done!"
echo ":::"
if [[ "${updatesToInstall}" -eq 0 ]]; then
echo "::: Your system is up to date! Continuing with PiVPN installation..."
else
echo "::: There are ${updatesToInstall} updates available for your system!"
echo "::: We recommend you update your OS after installing PiVPN! "
echo ":::"
fi
}
preconfigurePackages() {
# Install packages used by this installation script
# If apt is older than 1.5 we need to install an additional package to add
# support for https repositories that will be used later on
if [[ "${PKG_MANAGER}" == 'apt-get' ]] \
&& [[ -f /etc/apt/sources.list ]]; then
INSTALLED_APT="$(apt-cache policy apt \
| grep -m1 'Installed: ' \
| grep -v '(none)' \
| awk '{print $2}')"
if dpkg --compare-versions "${INSTALLED_APT}" lt 1.5; then
BASE_DEPS+=("apt-transport-https")
fi
fi
# We set static IP only on Raspberry Pi OS
if checkStaticIpSupported; then
BASE_DEPS+=(dhcpcd5)
fi
if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then
DPKG_ARCH="$(dpkg --print-architecture)"
elif [[ "${PKG_MANAGER}" == 'apk' ]]; then
DPKG_ARCH="$(apk --print-arch)"
fi
if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then
AVAILABLE_OPENVPN="$(apt-cache policy openvpn \
| grep -m1 'Candidate: ' \
| grep -v '(none)' \
| awk '{print $2}')"
elif [[ "${PKG_MANAGER}" == 'apk' ]]; then
AVAILABLE_OPENVPN="$(apk search -e openvpn \
| sed -E -e 's/openvpn\-(.*)/\1/')"
fi
OPENVPN_SUPPORT=0
NEED_OPENVPN_REPO=0
# We require OpenVPN 2.4 or later for ECC support. If not available in the
# repositories but we are running x86 Debian or Ubuntu, add the official repo
# which provides the updated package.
if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then
if [[ -n "${AVAILABLE_OPENVPN}" ]] \
&& dpkg --compare-versions "${AVAILABLE_OPENVPN}" ge 2.4; then
OPENVPN_SUPPORT=1
else
if [[ "${PLAT}" == "Debian" ]] \
|| [[ "${PLAT}" == "Ubuntu" ]]; then
if [[ "${DPKG_ARCH}" == "amd64" ]] \
|| [[ "${DPKG_ARCH}" == "i386" ]]; then
NEED_OPENVPN_REPO=1
OPENVPN_SUPPORT=1
else
OPENVPN_SUPPORT=0
fi
else
OPENVPN_SUPPORT=0
fi
fi
elif [[ "${PKG_MANAGER}" == 'apk' ]]; then
if [[ -n "${AVAILABLE_OPENVPN}" ]] \
&& [[ "$(apk version -t "${AVAILABLE_OPENVPN}" 2.4)" == '>' ]]; then
OPENVPN_SUPPORT=1
else
OPENVPN_SUPPORT=0
fi
fi
if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then
AVAILABLE_WIREGUARD="$(apt-cache policy wireguard \
| grep -m1 'Candidate: ' \
| grep -v '(none)' \
| awk '{print $2}')"
elif [[ "${PKG_MANAGER}" == 'apk' ]]; then
AVAILABLE_WIREGUARD="$(apk search -e wireguard-tools \
| sed -E -e 's/wireguard\-tools\-(.*)/\1/')"
fi
WIREGUARD_SUPPORT=0
# If a wireguard kernel object is found and is part of any installed package,
# then it has not been build via DKMS or manually (installing via
# wireguard-dkms does not make the module part of the package since the
# module itself is built at install time and not part of the .deb).
# Source: https://github.com/MichaIng/DietPi/blob/7bf5e1041f3b2972d7827c48215069d1c90eee07/dietpi/dietpi-software#L1807-L1815
WIREGUARD_BUILTIN=0
if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then
if dpkg-query -S '/lib/modules/*/wireguard.ko*' &> /dev/null \
|| modinfo wireguard 2> /dev/null \
| grep -q '^filename:[[:blank:]]*(builtin)$'; then
WIREGUARD_BUILTIN=1
fi
fi
# case 1: If the module is builtin and the package available,
# we only need to install wireguard-tools.
# case 2: If the package is not available, on Debian and
# Raspbian we can add it via Bullseye repository.
# case 3: If the module is not builtin, on Raspbian we know
# the headers package: raspberrypi-kernel-headers
# case 4: On Alpine, the kernel must be linux-lts or linux-virt
# if we want to load the kernel module
# case 5: On Alpine Docker Container, the responsibility to have
# a WireGuard module on the host system is at user side
# case 6: On Alpine container, wireguard-tools is available
# case 7: On Debian (and Ubuntu), we can only reliably assume the
# headers package for amd64: linux-image-amd64
# case 8: On Ubuntu, additionally the WireGuard package needs to
# be available, since we didn't test mixing Ubuntu repositories.
# case 9: Ubuntu focal has wireguard support
if [[ "${WIREGUARD_BUILTIN}" -eq 1 && -n "${AVAILABLE_WIREGUARD}" ]] \
|| [[ "${WIREGUARD_BUILTIN}" -eq 1 && ("${PLAT}" == 'Debian' || "${PLAT}" == 'Raspbian') ]] \
|| [[ "${PLAT}" == 'Raspbian' ]] \
|| [[ "${PLAT}" == 'Alpine' && ! -f /.dockerenv && "$(uname -mrs)" =~ ^Linux\ +[0-9\.\-]+\-((lts)|(virt))\ +.*$ ]] \
|| [[ "${PLAT}" == 'Alpine' && -f /.dockerenv ]] \
|| [[ "${PLAT}" == 'Alpine' && -n "${AVAILABLE_WIREGUARD}" ]] \
|| [[ "${PLAT}" == 'Debian' && "${DPKG_ARCH}" == 'amd64' ]] \
|| [[ "${PLAT}" == 'Ubuntu' && "${DPKG_ARCH}" == 'amd64' && -n "${AVAILABLE_WIREGUARD}" ]] \
|| [[ "${PLAT}" == 'Ubuntu' && "${DPKG_ARCH}" == 'arm64' && "${OSCN}" == 'focal' && -n "${AVAILABLE_WIREGUARD}" ]]; then
WIREGUARD_SUPPORT=1
fi
if [[ "${OPENVPN_SUPPORT}" -eq 0 ]] \
&& [[ "${WIREGUARD_SUPPORT}" -eq 0 ]]; then
err "::: Neither OpenVPN nor WireGuard are available to install by PiVPN, exiting..."
exit 1
fi
# if ufw is enabled, configure that.
# running as root because sometimes the executable is not in the user's $PATH
if ${SUDO} bash -c 'command -v ufw' > /dev/null; then
if ! ${SUDO} ufw status || ${SUDO} ufw status | grep -q inactive; then
USING_UFW=0
else
USING_UFW=1
fi
else
USING_UFW=0
fi
if [[ "${PKG_MANAGER}" == 'apt-get' ]] && [[ "${USING_UFW}" -eq 0 ]]; then
BASE_DEPS+=(iptables-persistent)
echo iptables-persistent iptables-persistent/autosave_v4 boolean true \
| ${SUDO} debconf-set-selections
echo iptables-persistent iptables-persistent/autosave_v6 boolean false \
| ${SUDO} debconf-set-selections
fi
if [[ "${PLAT}" == 'Alpine' ]] \
&& ! command -v grepcidr &> /dev/null; then
local down_dir
## install dependencies
# shellcheck disable=SC2086
${SUDO} ${PKG_INSTALL} build-base make curl tar
if ! down_dir="$(mktemp -d)"; then
err "::: Failed to create download directory for grepcidr!"
exit 1
fi
## download binaries
curl -fLo "${down_dir}/master.tar.gz" \
https://github.com/pivpn/grepcidr/archive/master.tar.gz
tar -xzC "${down_dir}" -f "${down_dir}/master.tar.gz"
(
cd "${down_dir}/grepcidr-master" || exit
## personalize binaries
sed -i -E -e 's/^PREFIX\=.*/PREFIX\=\/usr\nCC\=gcc/' Makefile
## install
make
${SUDO} make install
if ! command -v grepcidr &> /dev/null; then
err "::: Failed to compile and install grepcidr!"
exit
fi
) || exit 1
fi
echo "USING_UFW=${USING_UFW}" >> "${tempsetupVarsFile}"
}
installDependentPackages() {
# Install packages passed via argument array
# No spinner - conflicts with set -e
local FAILED=0
local APTLOGFILE
declare -a TO_INSTALL=()
declare -a argArray1=("${!1}")
for i in "${argArray1[@]}"; do
echo -n "::: Checking for ${i}..."
if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then
if dpkg-query -W -f='${Status}' "${i}" 2> /dev/null \
| grep -q "ok installed"; then
echo " already installed!"
else
echo " not installed!"
# Add this package to the list of packages in the argument array that
# need to be installed
TO_INSTALL+=("${i}")
fi
elif [[ "${PKG_MANAGER}" == 'apk' ]]; then
if eval "${SUDO} ${CHECK_PKG_INSTALLED} ${i}" &> /dev/null; then
echo " already installed!"
else
echo " not installed!"
# Add this package to the list of packages in the argument array that
# need to be installed
TO_INSTALL+=("${i}")
fi
fi
done
APTLOGFILE="$(${SUDO} mktemp)"
# shellcheck disable=SC2086
${SUDO} ${PKG_INSTALL} "${TO_INSTALL[@]}"
for i in "${TO_INSTALL[@]}"; do
if [[ "${PKG_MANAGER}" == 'apt-get' ]]; then
if dpkg-query -W -f='${Status}' "${i}" 2> /dev/null \
| grep -q "ok installed"; then
echo "::: Package ${i} successfully installed!"
# Add this package to the total list of packages that were actually
# installed by the script
INSTALLED_PACKAGES+=("${i}")
else
echo "::: Failed to install ${i}!"
((FAILED++))
fi
elif [[ "${PKG_MANAGER}" == 'apk' ]]; then
if eval "${SUDO} ${CHECK_PKG_INSTALLED} ${i}" &> /dev/null; then
echo "::: Package ${i} successfully installed!"
# Add this package to the total list of packages that were actually
# installed by the script
INSTALLED_PACKAGES+=("${i}")
else
echo "::: Failed to install ${i}!"
((FAILED++))
fi
fi
done
if [[ "${FAILED}" -gt 0 ]]; then
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]:" >&2
${SUDO} cat "${APTLOGFILE}" >&2
exit 1
fi
}
welcomeDialogs() {
if [[ "${runUnattended}" == 'true' ]]; then
echo "::: PiVPN Automated Installer"
echo -n "::: This installer will transform your ${PLAT} host into an "
echo "OpenVPN or WireGuard server!"
echo "::: Initiating network interface"
return
fi
# Display the welcome dialog
whiptail \
--backtitle "Welcome" \
--title "PiVPN Automated Installer" \
--msgbox "This installer will transform your Raspberry Pi into an \
OpenVPN or WireGuard server!" "${r}" "${c}"
# Explain the need for a static address
whiptail \
--backtitle "Initiating network interface" \
--title "Static IP Needed" \
--msgbox "The PiVPN is a SERVER so it needs a STATIC IP ADDRESS to \
function properly.
In the next section, you can choose to use your current network settings \
(DHCP) or to manually edit them." "${r}" "${c}"
}
chooseInterface() {
# Find interfaces and let the user choose one
# Turn the available interfaces into an array so it can be used with
# a whiptail dialog
local interfacesArray=()
# Number of available interfaces
local interfaceCount
# Whiptail variable storage
local chooseInterfaceCmd
# Temporary Whiptail options storage
local chooseInterfaceOptions
# Loop sentinel variable
local firstloop=1
availableInterfaces="$(ip -o link)"
if [[ "${showUnsupportedNICs}" == 'true' ]]; then
# Show every network interface, could be useful for those who
# install PiVPN inside virtual machines or on Raspberry Pis
# with USB adapters
availableInterfaces="$(echo "${availableInterfaces}" \
| awk '{print $2}')"
else
# Find network interfaces whose state is UP
availableInterfaces="$(echo "${availableInterfaces}" \
| awk '/state UP/ {print $2}')"
fi
# Skip virtual, loopback and docker interfaces
availableInterfaces="$(echo "${availableInterfaces}" \
| cut -d ':' -f 1 \
| cut -d '@' -f 1 \
| grep -v -w 'lo' \
| grep -v '^docker')"
if [[ -z "${availableInterfaces}" ]]; then
err "::: Could not find any active network interface, exiting"
exit 1
else
while read -r line; do
mode="OFF"
if [[ "${firstloop}" -eq 1 ]]; then
firstloop=0
mode="ON"
fi
interfacesArray+=("${line}" "available" "${mode}")
((interfaceCount++))
done <<< "${availableInterfaces}"
fi
if [[ "${runUnattended}" == 'true' ]]; then
if [[ -z "${IPv4dev}" ]]; then
if [[ "${interfaceCount}" -eq 1 ]]; then
IPv4dev="${availableInterfaces}"
echo -n "::: No interface specified for IPv4, but only ${IPv4dev} "
echo "is available, using it"
else
err "::: No interface specified for IPv4 and failed to determine one"
exit 1
fi
else
if ip -o link | grep -qw "${IPv4dev}"; then
echo "::: Using interface: ${IPv4dev} for IPv4"
else
err "::: Interface ${IPv4dev} for IPv4 does not exist"
exit 1
fi
fi
if [[ "${pivpnenableipv6}" -eq 1 ]]; then
if [[ -z "${IPv6dev}" ]]; then
if [[ "${interfaceCount}" -eq 1 ]]; then
IPv6dev="${availableInterfaces}"
echo -n "::: No interface specified for IPv6, but only ${IPv6dev} "
echo "is available, using it"
else
err "::: No interface specified for IPv6 and failed to determine one"
exit 1
fi
else
if ip -o link | grep -qw "${IPv6dev}"; then
echo "::: Using interface: ${IPv6dev} for IPv6"
else
err "::: Interface ${IPv6dev} for IPv6 does not exist"
exit 1
fi
fi
fi
{
echo "IPv4dev=${IPv4dev}"
if [[ "${pivpnenableipv6}" -eq 1 ]] \
&& [[ -z "${IPv6dev}" ]]; then
echo "IPv6dev=${IPv6dev}"
fi
} >> "${tempsetupVarsFile}"
return
else
if [[ "${interfaceCount}" -eq 1 ]]; then
IPv4dev="${availableInterfaces}"
{
echo "IPv4dev=${IPv4dev}"
if [[ "${pivpnenableipv6}" -eq 1 ]]; then
IPv6dev="${availableInterfaces}"
echo "IPv6dev=${IPv6dev}"
fi
} >> "${tempsetupVarsFile}"
return
fi
fi
chooseInterfaceCmd=(whiptail
--separate-output
--radiolist "Choose An interface for IPv4 \
(press space to select):" "${r}" "${c}" "${interfaceCount}")
if chooseInterfaceOptions="$("${chooseInterfaceCmd[@]}" \
"${interfacesArray[@]}" \
2>&1 > /dev/tty)"; then
for desiredInterface in ${chooseInterfaceOptions}; do
IPv4dev="${desiredInterface}"
echo "::: Using interface: ${IPv4dev}"
echo "IPv4dev=${IPv4dev}" >> "${tempsetupVarsFile}"
done
else
err "::: Cancel selected, exiting...."
exit 1
fi
if [[ "${pivpnenableipv6}" -eq 1 ]]; then
chooseInterfaceCmd=(whiptail
--separate-output
--radiolist "Choose An interface for IPv6, usually the same as used by \
IPv4 (press space to select):" "${r}" "${c}" "${interfaceCount}")
if chooseInterfaceOptions="$("${chooseInterfaceCmd[@]}" \
"${interfacesArray[@]}" \
2>&1 > /dev/tty)"; then
for desiredInterface in ${chooseInterfaceOptions}; do
IPv6dev="${desiredInterface}"
echo "::: Using interface: ${IPv6dev}"
echo "IPv6dev=${IPv6dev}" >> "${tempsetupVarsFile}"
done
else
err "::: Cancel selected, exiting...."
exit 1