-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLinuxSetupHelperScript.sh
More file actions
1240 lines (1047 loc) · 42.2 KB
/
LinuxSetupHelperScript.sh
File metadata and controls
1240 lines (1047 loc) · 42.2 KB
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
#!/bin/bash
##############################################
#################### Notes ###################
##############################################
# This script automates a series of setup tasks on a Brand New Debian install, typically desktop installs, but can be used on server installs in a pinch
# It installs packages, configures services, sets up security tools, and more.
# 1) Welcomes user and provides some script info
# 2) Installs basic tools I expect on every linux box I use
# 3) access a menu to either pull in and install functions/apps via a markdown file, or setup some special installs / configurations I frequent but are best separate from a "bulk install"
# Important - ensure to run file using "Sudo bash <script location>", script will also need bulk install files in the correct pathing to work
# Function files installs are expecting one string per line and have limited ability to execute anything more complex
# This script does have logging, please see "Vars" for path
##############################################
############## Troubleshooting ###############
##############################################
# uncomment the below line to Enable verbose mode for debugging
#set -x
##############################################
#################### vars ####################
##############################################
#Defaults are set
BaseBulkInstallFileDir="$PWD/HelperScriptFiles/InstallFiles"
ScriptLogFilePath="/var/log/Linux_Setup_Helper_Script.log"
##############################################
################ Pre - Reqs ##################
##############################################
#Ensure script running as Root
echo -e "\e[1mChecking root privileges…\e[0m"
sleep 2
if [[ "$(id -u)" -ne 0 ]]; then
echo "Not Root Permissions, Exiting...."
exit 2
fi
sleep 2
#reset colour output
NC='\e[0m'
##############################################
################## Functions #################
##############################################
#Function: Logging
LogMessage() {
local LogMessageLevel="$1" # INFO, WARN, ERROR
local LogMessageText="$2"
local TimeStamp
TimeStamp="$(date '+%Y-%m-%d %H:%M:%S')"
# Colourise console output (optional, works on most terminals)
case "$LogMessageLevel" in
INFO) Colour="\e[32m";; # green
WARN) Colour="\e[33m";; # yellow
ERROR) Colour="\e[31m";; # red
*) Colour="\e[0m";;
esac
printf "${Colour}[%s] %s${NC}\n" "$TimeStamp" "$LogMessageText"
printf "[%s] %s\n" "$TimeStamp" "$LogMessageText" >> "$ScriptLogFilePath"
}
#Function: Start Logging
Log_Script_Start() {
#Ensure Log File exist and is clear a new excution is happening by appending information on script start
LogMessage "INFO" "------------------------------------------------------------------"
LogMessage "INFO" "Starting script"
LogMessage "INFO" "$(date '+%Y-%m-%d %H:%M:%S')"
LogMessage "INFO" "------------------------------------------------------------------"
clear
echo "Starting script..."
sleep 2
}
#Function: welcome message to user with info on script
Welcome_Message() {
clear
echo 'Welcome to your personal Linux Install Helper!'
echo
echo '**IMPORTANT** this script will complete several sensitive privileged actions that could compromise your system.'
echo
echo 'much of this script is 'Interactive' which will stall part of the configuration, without human input. This is intended design.'
echo
echo 'This script is expected to be used on a brand new Debian based install with minium modifications.'
echo 'The goal would be to automate as much as the setup as possible given my knowledge at this time, as well as'
echo ' having much of the process in one place to ensure not overlooked and standardized.'
echo
echo 'While ansible, terraform and packer, might be used in homelab, I love to have a setup helper for desktops'
echo ' or in the event of needing to setup a server quickly.'
echo '================================================================================================================='
echo ' [exit by hitting ctrl+c]'
sleep 16
}
#Function: This function setups some core basic functionality I expect explicitly on all linux systems I use
Baseline() {
clear
echo "checking core system functionality requirements"
sleep 2
Install_All_Updates
Install_Vim
Install_SUDO
Install_Curl
}
#Function: ensure system and packages all fully updated
Install_All_Updates() {
echo "Updating and upgrading the system..."
sleep 1.2
apt update -y && apt upgrade -y
apt dist-upgrade -y
apt autoremove -y
echo "============================="
LogMessage "WARN" "updates attempted, status unclear."
echo "updates complete."
sleep 1.2
clear
}
#Function: install Vim
Install_Vim() {
echo "Installing Vim..."
sleep 1.2
apt install -y vim
echo "============================="
LogMessage "WARN" "Vim install attempted, status unclear."
echo "Vim install complete."
sleep 1.2
clear
}
#Function: install SUDO
Install_SUDO() {
echo "Installing SUDO..."
sleep 1.2
apt install -y sudo
echo "============================="
LogMessage "WARN" "SUDO install attempted, status unclear."
echo "Sudo install complete."
sleep 1.2
clear
}
#Function: install Curl
Install_Curl() {
echo "Installing curl..."
sleep 2
apt install -y curl
echo "============================="
LogMessage "WARN" "Curl install attempted, status unclear."
echo "Curl install complete."
sleep 5
clear
}
# Main function to handle user input and execute selected options
Main_Menu() {
while true; do
Display_Menu
read -p "Enter your selection(s): " user_input
# Convert user input into an array
options=($user_input)
# Iterate over the options and call respective functions
for option in "${options[@]}"; do
#Make sure option is a number
if ! [[ "$option" =~ ^[0-9]+$ ]]; then
echo "Invalid input: '$option' is not a number."
continue
fi
case $option in
1) Install_Base_Security ;;
2) Install_Functions_From_File_Menu ;;
3) Lockdown_SSH ;;
4) Install_Docker_Portainer ;;
5) Setup_NFS_Share ;;
6) Install_Powertop ;;
7) Install_Wazuh_Agent ;;
8) Add_Users_Groups ;;
9) Add_User_To_SUDO ;;
10) Install_Jellyfin ;;
11) Install_Intel_Drivers ;;
12) Install_Nvidia_Drivers ;;
13) Install_Qemu_Guest_Agent ;;
14) Setup_Template_For_PVE ;;
15) echo "Exiting..."; Closeout_System ;;
*) echo "Invalid option: $option" ;;
esac
done
# Ask the user if they want to return to the menu
read -p "Would you like to return to the menu? (y/n): " return_choice
if [[ "$return_choice" != "y" && "$return_choice" != "Y" ]]; then
echo "Goodbye!"
Closeout_System
fi
done
}
# Function: pretty display menu for end user
Display_Menu() {
clear
echo "================================"
echo " Welcome to the function menu"
echo "================================"
echo "1) Install and Configure Base Security Standards"
echo "2) Install Bulk Functions From File (Optional Requirement - Docker)"
echo "3) Lockdown/Disable SSH"
echo "4) Install and config Docker with option for Portainer (Requirement - UFW)"
echo "5) Install and setup NFS share client (Requirement - UFW)"
echo "6) install and run Powertops"
echo "7) install and configure Wazuh Agent (Requirement - UFW)"
echo "8) Add User and Groups"
echo "9) Add User to SUDO"
echo "10) JellyFin Media Server Install for Linux (Requirement - UFW)"
echo "11) Intel GPU Driver Install"
echo "12) Nvidia GPU Driver Install"
echo "13) Install QEMU guest agent (Requirement - VM Env)"
echo "14) "Template" Machine for proxmox (Requirement - VM Env)"
echo "15) Exit"
echo "================================"
echo "Please select one or more options (e.g., 1 3 5): "
}
# Function: install my standards for a baseline security posture
Install_Base_Security(){
clear
Install_AppArmor
Install_UFW #This needs to happen before Crowdsec!
Install_Crowdsec
Install_Unattended_Upgrades
Install_ClamAV
}
#Function: install apparmor (should already be installed, nothing configured beyond basic)
Install_AppArmor() {
# AppArmor
echo "Ensuring AppArmor is installed..."
sleep 2
apt install -y apparmor apparmor-utils
systemctl enable apparmor
systemctl start apparmor
echo "============================="
echo "AppArmor install complete."
sleep 5
clear
}
#Function: installed UFW and configure
Install_UFW() {
# UFW
echo "Installing UFW..."
sleep 2
apt install -y ufw
ufw default deny incoming
ufw default deny outgoing
ufw reject 22 comment 'reject default ssh'
ufw allow out 443/tcp comment 'https for updates and browsing'
ufw allow out 53 comment 'dns'
ufw allow out 80/tcp comment 'http'
ufw allow out 123/udp comment 'ntp'
read -p "Would you like to add any custom (incoming or outgoing) ports? (y/n): " ConfirmedPortCustomAddChoice
if [[ $ConfirmedPortCustomAddChoice == "y" || $ConfirmedPortCustomAddChoice == "Y" ]]; then
UFWPortAddLoop=true
while [ $UFWPortAddLoop == true ]; do
echo "What (incoming) port would you like to add?"
echo "please only type in one port, prompt will loop"
read -p "(port or [enter]): " PortCustomAddChoice
ufw allow in $PortCustomAddChoice
read -p "Would you like to add any more custom (incoming) ports? (y/n): " MorePortCustomAddChoice
if [[ $MorePortCustomAddChoice == "n" || $MorePortCustomAddChoice == "N" ]]; then
echo "Not adding any additional (incoming) custom ports"
UFWPortAddLoop=false
fi
done
UFWPortAddLoop=true
while [ $UFWPortAddLoop == true ]; do
echo "What (outgoing) port would you like to add?"
echo "please only type in one port, prompt will loop"
read -p "(port or [enter]): " PortCustomAddChoice
ufw allow out $PortCustomAddChoice
read -p "Would you like to add any more custom (outgoing) ports? (y/n): " MorePortCustomAddChoice
if [[ $MorePortCustomAddChoice == "n" || $MorePortCustomAddChoice == "N" ]]; then
echo "Not adding any additional (outgoing) custom ports"
UFWPortAddLoop=false
fi
done
fi
ufw reload
ufw enable
clear
echo "============================="
ufw status
echo "============================="
echo "UFW install and basic config complete."
sleep 15
clear
}
#Function: for installing Crowdsec to act as bad ip bouncer
Install_Crowdsec() {
# Crowdsec
echo "Installing Crowdsec..."
sleep 2
#setup repo - typically prepackaged on many linux flavors now
#curl -s https://install.crowdsec.net | bash
#install sec engine
apt install -y crowdsec
# Install CrowdSec's firewall integration to block IPs (only if UFW or firewalld is installed)
apt install -y crowdsec-firewall-bouncer-iptables
echo "please capture API key, you will not be able to get this again"
sleep 20
read -p "Press any key to continue... " -n1 -s
# Turn on logs for UFW for crowdsec to work in ideal state
ufw logging on
#enable and start sec engine
systemctl enable crowdsec
systemctl start crowdsec
echo "============================="
echo "Crowdsec basic package installed, and firewall bouncer."
echo "Please install additional bouncers as you need, some of these can be facilitated via integrations on the service side."
echo "IMPORTANT - Crowdsec Not enrolled with CrowdSec Console"
echo "Note - this is not required"
sleep 5
clear
}
#Function: install and do intial scan for baseline of system for ClammAVd
Install_ClamAV() {
#ClamAV
echo "Installing ClamAV..."
sleep 2
apt-get install -y clamav-daemon
#Enabling and starting service
systemctl enable clamav-daemon
systemctl start clamav-daemon
#ensure service starts
sleep 60
#make wuarantine space
mkdir -p /var/lib/clamav/quarantine
chown clamav:clamav
chmod 660 /var/lib/clamav/quarantine
# Custom add cron runtime
if [[ $CustomizeSettings == "y" || $CustomizeSettings == "Y" ]]; then
read -p "What time would you like to run the clamAV scan at everyday? " CronTimeClamAVChoice
CronTimeClamAV=$CronTimeClamAVChoice
echo "Creating cron to run at $CronTimeClamAV"
else
CronTimeClamAV=1
fi
# Schedule cron job for full system scan every Sunday at 1 AM
echo "0 $CronTimeClamAV * * 0 clamav /usr/bin/clamdscan --fdpass --log=/var/log/clamav/clamdscan.log --move=/var/lib/clamav/quarantine /" | tee /etc/cron.d/clamdscan
# Commenting Out from scan several system default directories that contain false positives
printf "ExcludePath ^/proc\nExcludePath ^/sys\nExcludePath ^/run\nExcludePath ^/dev\nExcludePath ^/snap\nExcludePath ^/root/quarantine\n" | tee -a /etc/clamav/clamd.conf
#restart service
systemctl restart clamav-daemon
echo "============================="
echo "Running Intial ClamAV Baseline scan."
# Run initial scan to form baseline
/usr/bin/clamdscan --fdpass --log=/var/log/clamav/clamdscan.log --move=/root/quarantine /
echo "============================="
echo "ClamAV install complete."
sleep 5
clear
}
# Function: call which type of bulk install you would like to complete, currently supports debian package manager, docker images or flathub
Install_Functions_From_File_Menu() {
clear
echo "=========================================="
echo " Select the installation method"
echo "=========================================="
echo "1) apt – Debian/Ubuntu package manager"
echo "2) flatpak – Flathub universal packages"
echo "3) docker – Pull container images"
echo
echo "Enter the number of your choice, or press ENTER for the default (apt)."
echo
while true; do
read -rp "Choice [1/2/3]: " MenuChoice
case "${MenuChoice}" in
1|"") # apt selected (empty input defaults to apt)
InstallType="apt"
LogMessage "INFO" "User selected apt as the install method."
# Build the file path for apt – you can replace the filename if you keep a separate apt list
FunctionFilePath="${BaseBulkInstallFileDir}/PackageInstalls.md"
break
;;
2)
InstallType="flatpak"
echo "calling install for flatpak and setup flathub..."
sleep 1.2
Install_Flathub
LogMessage "INFO" "User selected flatpak as the install method."
# Build the file path for apt – you can replace the filename if you keep a separate apt list
FunctionFilePath="${BaseBulkInstallFileDir}/FlatpakInstalls.md"
break
;;
3)
InstallType="docker"
echo "calling install for docker..."
sleep 1.2
Install_Docker_Portainer
LogMessage "INFO" "User selected docker as the install method."
# Build the file path for apt – you can replace the filename if you keep a separate apt list
FunctionFilePath="${BaseBulkInstallFileDir}/DockerInstalls.md"
break
;;
*)
LogMessage "WARN" "Invalid menu selection: $MenuChoice"
echo "Please enter 1 for apt, 2 for flatpak, or 3 for docker."
;;
esac
done
# Confirm the choice before proceeding
echo
echo "You have chosen: ${InstallType^^}"
read -rp "Proceed with this install method? (y/n): " ConfirmChoice
case "${ConfirmChoice,,}" in
y|yes)
LogMessage "INFO" "Proceeding with $InstallType installation."
;;
*)
LogMessage "INFO" "User aborted after menu selection."
echo "Exiting as requested."
return 0
;;
esac
#Call the main installer function now that InstallType is selected
Install_Functions_From_File
}
# Function: Setup FlatHub for flatpak installs
Install_Flathub() {
echo "============================="
echo "Installing Flatpak..."
sleep 1.2
apt install flatpak
echo "============================="
echo "Flatpak is installed"
sleep 1.2
echo "Setting up Flathub repository..."
sleep 1.2
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
echo "============================="
echo "Flathub repository is setup for flatpak installs."
sleep 1.2
echo "============================="
}
Install_Functions_From_File() {
# Validate InstallType
case "${InstallType,,}" in
apt|flatpak|docker) ;; # supported
*)
LogMessage "ERROR" "Unsupported InstallType: \"$InstallType\". Use \"apt\", \"flatpak\" or \"docker\"."
return 1
;;
esac
# Load the markdown file into an array (ignore blanks, trim spaces)
if [[ ! -f "$FunctionFilePath" ]]; then
LogMessage "ERROR" "File not found: $FunctionFilePath"
return 1
fi
mapfile -t RawLines <"$FunctionFilePath"
declare -a ItemsArray=()
for Line in "${RawLines[@]}"; do
# Trim leading/trailing whitespace
Line="${Line#"${Line%%[![:space:]]*}"}"
Line="${Line%"${Line##*[![:space:]]}"}"
[[ -z "$Line" ]] && continue
ItemsArray+=("$Line")
done
TotalItems=${#ItemsArray[@]}
if (( TotalItems == 0 )); then
LogMessage "WARN" "No install entries found in $FunctionFilePath"
return 0
fi
# Pre‑flight validation: pretty two‑column bullet list
clear
echo "=== PRE‑FLIGHT VALIDATION ==="
echo "File : $FunctionFilePath"
echo "Install method: ${InstallType^^}"
echo "Entries found : $TotalItems"
echo
TermWidth=$(tput cols)
ColumnWidth=$(( TermWidth / 2 ))
(( ColumnWidth < 30 )) && ColumnWidth=30
for (( i=0; i<TotalItems; i++ )); do
LeftIdx=$i
LeftItem="${ItemsArray[$LeftIdx]}"
printf " • %-*s" "$ColumnWidth" "$LeftItem"
RightIdx=$(( i + TotalItems/2 + TotalItems%2 ))
if (( RightIdx < TotalItems )); then
RightItem="${ItemsArray[$RightIdx]}"
printf " • %s" "$RightItem"
fi
echo
done
echo
echo "Review the list above."
echo "Type 'confirm', 'c' or 'confirmed' to continue,"
echo "or type 'exit' to abort, or 'manual' for step‑by‑step mode."
echo
# Confirmation loop
while true; do
read -rp "Your choice [confirm/exit/manual]: " UserChoice
case "${UserChoice,,}" in
confirm|c|confirmed)
Mode="Auto"
break
;;
exit)
LogMessage "INFO" "User chose to exit before installation."
echo "Exiting as requested."
return 0
;;
manual)
Mode="Manual"
break
;;
*)
LogMessage "WARN" "Invalid confirmation input: $UserChoice"
echo "Please type 'confirm', 'exit' or 'manual'."
;;
esac
done
# Begin installation
echo "Starting to install......"
LogMessage "INFO" "Installation started (Mode=$Mode, InstallType=$InstallType)."
sleep 5
InstalledCount=0
SkippedCount=0
FailedCount=0
for (( Idx=0; Idx<TotalItems; Idx++ )); do
CurrentItem="${ItemsArray[$Idx]}"
clear
echo "🔧 Installing [$((Idx+1))/$TotalItems]: $CurrentItem"
LogMessage "INFO" "Attempting install of \"$CurrentItem\" (index $((Idx+1)))"
# Manual mode prompt
if [[ "$Mode" == "Manual" ]]; then
while true; do
read -rp "Install \"$CurrentItem\"? (y=install, s=skip, e=exit): " Action
case "${Action,,}" in
y|yes|install)
SelectedAction="Install"
break
;;
s|skip)
SelectedAction="Skip"
break
;;
e|exit)
LogMessage "INFO" "User exited during manual install (item=\"$CurrentItem\")."
echo "Exiting as requested."
# final summary
echo ""
echo "=== INSTALL SUMMARY ==="
echo "Installed : $InstalledCount"
echo "Skipped : $SkippedCount"
echo "Failed : $FailedCount"
echo "Log file : $ScriptLogFilePath"
return 0
;;
*)
LogMessage "WARN" "Unexpected manual response: $Action"
echo "Please answer with 'y', 's' or 'e'."
;;
esac
done
else
SelectedAction="Install"
fi
if [[ "$SelectedAction" == "Skip" ]]; then
echo "Skipping $CurrentItem"
LogMessage "INFO" "SKIPPED – $CurrentItem"
((SkippedCount++))
sleep 1.5
continue
fi
# Perform the bulk install
case "${InstallType,,}" in
apt)
if apt-get install -y "$CurrentItem" >/dev/null 2>&1; then
echo "Successfully installed $CurrentItem (apt)"
LogMessage "INFO" "SUCCESS – apt – $CurrentItem"
((InstalledCount++))
else
ErrMsg=$(apt-get install -y "$CurrentItem" 2>&1 | head -n 3)
echo "Failed to install $CurrentItem (apt)"
echo " Error: $ErrMsg"
LogMessage "ERROR" "FAILURE – apt – $CurrentItem – $ErrMsg"
((FailedCount++))
fi
;;
flatpak)
if flatpak install -y flathub "$CurrentItem" >/dev/null 2>&1; then
echo "Successfully installed $CurrentItem (flatpak)"
LogMessage "INFO" "SUCCESS – flatpak – $CurrentItem"
((InstalledCount++))
else
ErrMsg=$(flatpak install -y flathub "$CurrentItem" 2>&1 | head -n 3)
echo "Failed to install $CurrentItem (flatpak)"
echo " Error: $ErrMsg"
LogMessage "ERROR" "FAILURE – flatpak – $CurrentItem – $ErrMsg"
((FailedCount++))
fi
;;
docker)
if docker pull "$CurrentItem" >/dev/null 2>&1; then
echo "Successfully pulled $CurrentItem (docker)"
LogMessage "INFO" "SUCCESS – docker – $CurrentItem"
((InstalledCount++))
else
ErrMsg=$(docker pull "$CurrentItem" 2>&1 | head -n 3)
echo "Failed to pull $CurrentItem (docker)"
echo " Error: $ErrMsg"
LogMessage "ERROR" "FAILURE – docker – $CurrentItem – $ErrMsg"
((FailedCount++))
fi
;;
esac
sleep 1.5
done
# Post‑install cleanup (apt only)
if [[ "${InstallType,,}" == "apt" ]]; then
echo "Running apt clean & autoremove..."
LogMessage "INFO" "Running 'apt clean && apt autoremove -y'"
apt-get clean >/dev/null 2>&1
apt-get autoremove -y >/dev/null 2>&1
LogMessage "INFO" "APT cleanup completed."
else
echo "No additional cleanup required for ${InstallType^^}."
LogMessage "INFO" "${InstallType^^} mode – skipped apt cleanup."
fi
# Final summary (terminal + log)
echo ""
echo "=== FINAL SUMMARY ==="
echo "Installed : $InstalledCount"
echo "Skipped : $SkippedCount"
echo "Failed : $FailedCount"
echo "Log file : $ScriptLogFilePath"
LogMessage "INFO" "INSTALLATION SUMMARY – Installed=$InstalledCount, Skipped=$SkippedCount, Failed=$FailedCount"
LogMessage "INFO" "Log file location: $ScriptLogFilePath"
}
# Function: lockdown ssh
Lockdown_SSH() {
clear
echo "locking down ssh via UFW, files and service..."
sleep 1.2
# shutdown ssh service
systemctl stop ssh
systemctl disable ssh
systemctl sta!= true =tus ssh
## Remove host keys
rm /etc/ssh/ssh_host_*
## block ufw port
ufw reject ssh
ufw reject 22
echo "============================="
clear
}
# Function: Ask if the user wants to install Docker and Portainer
Install_Docker_Portainer() {
clear
# Install Docker
echo "Installing docker using community script method..."
sleep 1.2
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
systemctl enable docker
systemctl start docker
# Remove Docker sh
rm get-docker.sh
mkdir /opt/docker
sudo chown root:docker /opt/docker
echo "============================="
echo "Docker install complete."
sleep 1.2
echo "============================="
echo "Would you like to install Portainer?"
read -p "please type "yes" to continue: " choice
if [[ "$choice" == "yes" ]]; then
##creating portainer data volume
docker volume create portainer_data
# Change Portainer default port to custom port
read -p "What port would you like to run HTTPS Portainer on? (default is 9443): " PortainerHTTPSPortChoice
echo "Creating a custom Portainer container on port $PortainerHTTPSPortChoice..."
read -p "What name would you like to run this Portainer instance under?: " PortainerNameChoice
echo "Creating a custom Portainer container on port $PortainerNameChoice..."
docker run -d -p $PortainerHTTPSPortChoice:9443 --name $PortainerNameChoice --restart always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest
# Allow Portainer through UFW
ufw allow $PortainerHTTPPortChoice
ufw allow $PortainerHTTPSPortChoice
echo "============================="
echo "Portainer install complete."
sleep 1.2
fi
clear
}
# Function: Install Unattended Upgrades
Install_Unattended_Upgrades() {
echo "Unattended upgrades is best for servers, as its expected to run outside of normal hours and reboot."
sleep 2
echo "Installing unattended-upgrades..."
sleep 1.2
apt install -y unattended-upgrades
# Configure unattended-upgrades
echo "Enabling unattended upgrades..."
sleep 1.2
dpkg-reconfigure --priority=low unattended-upgrades
# Enable and start the service
systemctl enable unattended-upgrades
systemctl start unattended-upgrades
# Service will not only do base updates for security related patches
# Note - typically enabled, but sometimes 20auto-upgrades will not have either item enabled via boolen values, these need to be set to '1' (enabled)
# copy file and make it a higher priority, this does 2 things. 1) ensure with updates to unattended update the base config file does not change itself 2) ensure my config is the one that is actioned on.
cp /etc/apt/apt.conf.d/50unattended-upgrades /etc/apt/apt.conf.d/90unattended-upgrades
# Add lines of configurations to unattened-upgrades, normally you would uncomment these, however we will just append them which works as well.
# Remove old unsued packages and dependencies (as part of, or existed before, unattended upgrades ran upgrades)
sudo echo 'Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";' >> /etc/apt/apt.conf.d/90unattended-upgrades
sudo echo 'Unattended-Upgrade::Remove-New-Unused-Dependencies "true";' >> /etc/apt/apt.conf.d/90unattended-upgrades
sudo echo 'Unattended-Upgrade::Remove-Unused-Dependencies "true";' >> /etc/apt/apt.conf.d/90unattended-upgrades
# Will self reboot system if reboot is required for update (checked by if file exist - /var/run/reboot-required)
sudoecho 'Unattended-Upgrade::Automatic-Reboot "false";' >> /etc/apt/apt.conf.d/90unattended-upgrades
# Will self reboot system even if a user is logged in
sudo echo 'Unattended-Upgrade::Automatic-Reboot-WithUsers "true";' >> /etc/apt/apt.conf.d/90unattended-upgrades
# schedule time task for when to "REBOOT", please note that some other defaults for times happen between 1am-3am and 6am-7am local time
sudo echo 'Unattended-Upgrade::Automatic-Reboot-Time "07:30";' >> /etc/apt/apt.conf.d/90unattended-upgrades
echo "Running dry-run install of unattended upgrades"
sleep 1.2
unattended-upgrade --dry-run --debug
echo "============================="
echo "Unattended-Upgrades install complete."
echo "Unattended-Upgrades will run between default time of 6am-7am and reboot at 7:30am local time"
sleep 10
clear
}
# Function: Setup NFS Share Mount and fstab
Setup_NFS_Share() {
clear
echo "Setting up NFS shares using NFSv3..."
# Install NFS client if not installed
apt install -y nfs-common
# Custom add NFS mount to fstab
NFSAddLoop=true
while [ $NFSAddLoop == true ]; do
echo "============================="
echo "please only type in one option at a time, the prompts will loop"
read -p "Enter NFS server IP address: " nfs_ip
read -p "Enter the shared directory path: " shared_path
read -p "Enter the mount point on this server: " mount_point
echo "Ensuring mount point exist for "$mount_point"..."
sleep 1.2
# Create the mount point directory
mkdir -p "$mount_point"
echo "Adding NFS share to fstab..."
sleep 1.2
# Add the NFS mount to fstab
echo "$nfs_ip:$shared_path "$mount_point" nfs defaults 0 0" >> /etc/fstab
read -p "Would you like to add any more NFS mounts to fstab? (y/n): " MoreNFSAddChoice
if [[ $MoreNFSAddChoice == "n" || $MoreNFSAddChoice == "N" ]]; then
echo "Not adding any additional mounts"
NFSAddLoop=false
fi
done
echo "============================="
echo "Allowing out default NFSv3 port..."
sleep 1.2
# Allow out the port
ufw allow out 2049
echo "============================="
echo "Attempting to mount shares..."
sleep 1.2
# Mount all shares in fstab
mount -av
echo "============================="
echo "nfs setup complete."
sleep 1.2
clear
}
# Function: install and offer to run powertops
Install_Powertop() {
clear
# Install powertop
echo "Installing powertop..."
apt install -y powertop
# Check if installation was successful
if [ $? -eq 0 ]; then
echo "powertop installation completed successfully."
else
echo "Installation failed. Please check your package manager configuration."
exit 1
fi
# Ask user if they want to run the initial setup
read -p "Do you want to run the initial setup for powertop? (y/n): " setup_choice
# Run powertop setup if user chooses 'y'
if [[ $setup_choice == "y" || $setup_choice == "Y" ]]; then
echo "Running powertop's initial setup..."
sleep 2
# Run powertop in calibration mode for initial setup (this requires root)
powertop --calibrate
else
echo "Skipping powertop initial setup."
fi
echo "============================="
echo "Powertop install and config complete."
sleep 5
clear
}
# Function: Install and Configure Wazuh Agent
Install_Wazuh_Agent() {
clear
echo "Installing and configuring Wazuh Agent..."
echo "please ensure firewall rules are allowed at all levels, you have 2 minutes, or cancel this script and retry"
sleep 120
apt install -y gnupg2
curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | gpg --no-default-keyring --keyring gnupg-ring:/usr/share/keyrings/wazuh.gpg --import && chmod 644 /usr/share/keyrings/wazuh.gpg
echo "deb [signed-by=/usr/share/keyrings/wazuh.gpg] https://packages.wazuh.com/4.x/apt/ stable main" | tee -a /etc/apt/sources.list.d/wazuh.list
apt-get update
# Configure Wazuh agent to connect to remote manager
read -p "Enter Wazuh Manager IP address: " wazuh_manager_ip
WAZUH_MANAGER="$wazuh_manager_ip" apt-get install wazuh-agent
# Allow Wazuh Manager through firewall
ufw allow out 1514
ufw allow out 1515
# Enable and start Wazuh agent
systemctl daemon-reload
systemctl enable wazuh-agent
systemctl start wazuh-agent
echo "letting Wazuh agent register, then cleaning up firewall..."
sleep 90
ufw reject 1515
echo "============================="
echo "Wazuh Agent install complete."
sleep 1.2
clear
}
# Function: Add Users and Groups
Add_Users_Groups() {
clear
read -p "Would you like to setup a group and user? (y/n): " choice
if [[ "$choice" == "y" ]]; then
echo "Adding users and groups..."
read -p "Enter the group name to create: " group_name
groupadd "$group_name"
read -p "Enter the username to create: " username
useradd -m -g "$group_name" "$username"
read -p "Enter GID for the group: " gid
groupmod -g "$gid" "$group_name"
read -p "Enter UID for the user: " uid
usermod -u "$uid" "$username"
echo "============================="
echo "nfs install complete."
sleep 1.2
else
echo "Exiting function..."
sleep 1.2
fi
clear
}
# Function: Add custom user to sudo group
Add_User_To_SUDO() {
clear
#ask for a username, validate, add to sudo, loop back
while true; do
clear
echo -e "\e[1mAdd a user to the sudo group\e[0m"
read -rp "Enter the username to grant sudo rights (or press ENTER to quit): " UserName
# Allow the user to abort by hitting ENTER with no input
[[ -z "$UserName" ]] && { LogMessage "WARN" "User aborted the operation."; break; }
# Validate that the user exists on the system
if ! id "$UserName" >/dev/null 2>&1; then
LogMessage "WARN" "User \"$UserName\" does not exist."
echo -e "\e[33mUser \"$UserName\" does not exist. Please try again.\e[0m"