forked from Acekorneya/Ark-Survival-Ascended-Server
-
Notifications
You must be signed in to change notification settings - Fork 0
/
POK-manager.sh
2110 lines (1891 loc) · 74.7 KB
/
POK-manager.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
#!/bin/bash
BASE_DIR="$(dirname "$(realpath "$0")")/"
MAIN_DIR="$BASE_DIR"
SERVER_FILES_DIR="./ServerFiles/arkserver"
CLUSTER_DIR="./Cluster"
instance_dir="./Instance_${instance_name}"
# Set PUID and PGID to match the container's expected values
PUID=1000
PGID=1000
# Define the order in which the settings should be displayed
declare -a config_order=(
"Memory Limit"
"BattleEye"
"RCON Enabled"
"POK Monitor Message"
"Update Server"
"Update Interval"
"Update Window Start"
"Update Window End"
"Restart Notice"
"MOTD Enabled"
"MOTD"
"MOTD Duration"
"Map Name"
"Session Name"
"Admin Password"
"Server Password"
"ASA Port"
"RCON Port"
"Max Players"
"Show Admin Commands In Chat"
"Cluster ID"
"Mod IDs"
"Passive Mods"
"Custom Server Args"
)
# Global associative array for default configuration values
declare -A default_config_values=(
["TZ"]="America/New_York, America/Los_Angeles"
["Memory Limit"]="16G"
["BattleEye"]="FALSE"
["RCON Enabled"]="TRUE"
["POK Monitor Message"]="FALSE"
["Update Server"]="TRUE"
["Update Interval"]="24"
["Update Window Start"]="12:00 AM"
["Update Window End"]="11:59 PM"
["Restart Notice"]="30"
["MOTD Enabled"]="FALSE"
["MOTD"]="Welcome To my Server"
["MOTD Duration"]="30"
["Map Name"]="TheIsland"
["Session Name"]="MyServer"
["Admin Password"]="myadminpassword"
["Server Password"]=
["ASA Port"]=""7777""
["RCON Port"]="27020"
["Max Players"]="70"
["Show Admin Commands In Chat"]="FALSE"
["Cluster ID"]="cluster"
["Mod IDs"]=
["Passive Mods"]=
["Custom Server Args"]="-UseDynamicConfig"
# Add other default values here
)
# Validation functions
validate_boolean() {
local input=$1
local key=$2 # Added key parameter for custom message
# Convert input to uppercase for case-insensitive comparison
input="${input^^}"
while ! [[ "$input" =~ ^(TRUE|FALSE)$ ]]; do
read -rp "Invalid input for $key. Please enter TRUE or FALSE: " input
input="${input^^}" # Convert again after re-prompting
done
echo "$input" # Already uppercase
}
validate_time() {
local input=$1
while ! [[ "$input" =~ ^(1[0-2]|0?[1-9]):[0-5][0-9]\ (AM|PM)$ ]]; do
read -rp "Invalid input. Please enter a time in the format HH:MM AM/PM: " input
done
echo "$input"
}
validate_number() {
local input=$1
while ! [[ "$input" =~ ^[0-9]+$ ]]; do
read -rp "Invalid input. Please enter a number: " input
done
echo "$input"
}
validate_memory_limit() {
local input=$1
# Check if the input already ends with 'G' or is a numeric value without 'G'
while ! [[ "$input" =~ ^[0-9]+G$ ]] && ! [[ "$input" =~ ^[0-9]+$ ]]; do
read -rp "Invalid input. Please enter memory limit in the format [number]G or [number] for GB: " input
done
# If the input is numeric without 'G', append 'G' to it
if [[ "$input" =~ ^[0-9]+$ ]]; then
input="${input}G"
fi
echo "$input"
}
validate_mod_ids() {
local input="$1"
# Check if input is 'None' or 'NONE', and return an empty string if so
if [[ "$input" =~ ^(none|NONE|None)$ ]]; then
echo ""
return
fi
# Continue with the regular validation if input is not 'None' or 'NONE'
while ! [[ "$input" =~ ^([0-9]+,)*[0-9]+$ ]]; do
read -rp "Invalid input. Please enter mod IDs in the format 12345,67890, or 'NONE' for blank: " input
# Allow immediate exit from the loop if 'None' or 'NONE' is entered
if [[ "$input" =~ ^(none|NONE|None)$ ]]; then
echo ""
return
fi
done
echo "$input"
}
validate_simple_password() {
local input="$1"
# Loop until the input is alphanumeric (letters and numbers only)
while ! [[ "$input" =~ ^[a-zA-Z0-9]+$ ]]; do
read -rp "Invalid input. Please enter a password with numbers or letters only, no special characters: " input
done
echo "$input"
}
validate_admin_password() {
local input="$1"
while [[ "$input" =~ [\"\'] ]]; do
read -rp "Invalid input. The password cannot contain double quotes (\") or single quotes ('). Please enter a valid password: " input
done
echo "$input"
}
validate_session_name() {
local input="$1"
# Allow any characters except double quotes and single quotes
while [[ "$input" =~ [\"\'] ]]; do
read -rp "Invalid input. The session name cannot contain double quotes (\") or single quotes ('). Please enter a valid session name: " input
done
echo "$input"
}
validate_generic() {
local input="$1"
# This function can be expanded to escape special characters or check for injection patterns
echo "$input"
}
prompt_for_input() {
local config_key="$1"
local user_input
local prompt_message="Enter new value for $config_key [Current: ${config_values[$config_key]}]:"
local prompt_suffix=" (Enter to keep current/Type to change):"
# Adjust the prompt suffix for fields that can be set to blank with 'NONE'
if [[ "$config_key" =~ ^(Cluster ID|Mod IDs|Passive Mods|Custom Server Args|Server Password|MOTD)$ ]]; then
prompt_suffix=" (Enter to keep current/'NONE' for blank/Type to change):"
fi
echo -n "$prompt_message$prompt_suffix"
read user_input
# Handle 'NONE' for special fields, and empty input to use current values
if [[ -z "$user_input" ]]; then
return # Keep the current value
elif [[ "$user_input" =~ ^(none|NONE|None)$ ]] && [[ "$config_key" =~ ^(Cluster ID|Mod IDs|Passive Mods|Custom Server Args|Server Password|MOTD)$ ]]; then
config_values[$config_key]=""
return
fi
# Proceed with specific validation based on the config key
case $config_key in
"BattleEye"|"RCON Enabled"|"POK Monitor Message"|"Update Server"|"MOTD Enabled"|"Show Admin Commands In Chat")
config_values[$config_key]=$(validate_boolean "$user_input" "$config_key")
;;
"Update Window Start"|"Update Window End")
config_values[$config_key]=$(validate_time "$user_input")
;;
"Update Interval"|"Max Players"|"Restart Notice"|"MOTD Duration"|"ASA Port"|"RCON Port")
config_values[$config_key]=$(validate_number "$user_input")
;;
"Memory Limit")
config_values[$config_key]=$(validate_memory_limit "$user_input")
;;
"Mod IDs"|"Passive Mods")
config_values[$config_key]=$(validate_mod_ids "$user_input")
;;
"Session Name")
config_values[$config_key]=$(validate_session_name "$user_input")
;;
"Server Password")
config_values[$config_key]=$(validate_simple_password "$user_input")
;;
"Admin Password")
config_values[$config_key]=$(validate_admin_password "$user_input")
;;
"MOTD")
config_values[$config_key]="$user_input"
;;
"Custom Server Args")
config_values[$config_key]="$user_input"
;;
*)
config_values[$config_key]="$user_input"
;;
esac
}
install_jq() {
if ! command -v jq &>/dev/null; then
echo "jq is not installed. Attempting to install jq..."
if [ -f /etc/debian_version ]; then
# Debian or Ubuntu
sudo apt-get update
sudo apt-get install -y jq
elif [ -f /etc/redhat-release ]; then
# Red Hat, CentOS, or Fedora
if command -v dnf &>/dev/null; then
sudo dnf install -y jq
else
sudo yum install -y jq
fi
elif [ -f /etc/arch-release ]; then
# Arch Linux
sudo pacman -Sy --noconfirm jq
else
echo "Unsupported Linux distribution. Please install jq manually and run the setup again."
return 1
fi
if command -v jq &>/dev/null; then
echo "jq has been successfully installed."
else
echo "Failed to install jq. Please install it manually and run the setup again."
return 1
fi
else
echo "jq is already installed."
fi
}
check_dependencies() {
# Check if Docker is installed
if ! command -v docker &>/dev/null; then
echo "Docker is not installed on your system."
read -p "Do you want to install Docker? [y/N]: " install_docker
if [[ "$install_docker" =~ ^[Yy]$ ]]; then
# Detect the OS and install Docker accordingly
if command -v apt-get &>/dev/null; then
# Debian/Ubuntu
sudo apt-get update
sudo apt-get install -y docker.io
elif command -v dnf &>/dev/null; then
# Fedora
sudo dnf install -y docker
elif command -v yum &>/dev/null; then
# CentOS/RHEL
sudo yum install -y docker
else
echo "Unsupported Linux distribution. Please install Docker manually and run the script again."
exit 1
fi
sudo usermod -aG docker $USER
echo "Docker has been installed. Please log out and log back in for the changes to take effect."
else
echo "Docker installation declined. Please install Docker manually to proceed."
exit 1
fi
fi
# Initialize Docker Compose command variable
local docker_compose_version_command
# Check for the Docker Compose V2 command availability ('docker compose')
if docker compose version &>/dev/null; then
docker_compose_version_command="docker compose version"
DOCKER_COMPOSE_CMD="docker compose"
elif docker-compose --version &>/dev/null; then
# Fallback to Docker Compose V1 command if V2 is not available
docker_compose_version_command="docker-compose --version"
DOCKER_COMPOSE_CMD="docker-compose"
else
echo "Neither 'docker compose' (V2) nor 'docker-compose' (V1) command is available."
read -p "Do you want to install Docker Compose? [y/N]: " install_compose
if [[ "$install_compose" =~ ^[Yy]$ ]]; then
# Detect the OS and install Docker Compose accordingly
if command -v apt-get &>/dev/null; then
# Debian/Ubuntu
sudo apt-get update
sudo apt-get install -y docker-compose
elif command -v dnf &>/dev/null; then
# Fedora
sudo dnf install -y docker-compose
elif command -v yum &>/dev/null; then
# CentOS/RHEL
sudo yum install -y docker-compose
else
echo "Unsupported Linux distribution. Please install Docker Compose manually and run the script again."
exit 1
fi
DOCKER_COMPOSE_CMD="docker-compose"
else
echo "Docker Compose installation declined. Please install Docker Compose manually to proceed."
exit 1
fi
fi
# Extract the version number using the appropriate command
local compose_version=$($docker_compose_version_command | grep -oE '([0-9]+\.[0-9]+\.[0-9]+)')
local major_version=$(echo $compose_version | cut -d. -f1)
# Ensure we use 'docker compose' for version 2 and above
if [[ $major_version -ge 2 ]]; then
DOCKER_COMPOSE_CMD="docker compose"
else
DOCKER_COMPOSE_CMD="docker-compose"
fi
echo "$DOCKER_COMPOSE_CMD" > ./config/POK-manager/docker_compose_cmd
echo "Using Docker Compose command: '$DOCKER_COMPOSE_CMD'."
}
get_docker_compose_cmd() {
local cmd_file="./config/POK-manager/docker_compose_cmd"
local config_dir="./config/POK-manager"
mkdir -p "$config_dir"
if [ ! -f "$cmd_file" ]; then
touch "$cmd_file"
fi
if [ -f "$cmd_file" ]; then
DOCKER_COMPOSE_CMD=$(cat "$cmd_file")
echo "Using Docker Compose command: '$DOCKER_COMPOSE_CMD' (read from file)."
elif [ -z "$DOCKER_COMPOSE_CMD" ]; then
# Check for the Docker Compose V2 command availability ('docker compose')
if docker compose version &>/dev/null; then
DOCKER_COMPOSE_CMD="docker compose"
elif docker-compose --version &>/dev/null; then
# Fallback to Docker Compose V1 command if V2 is not available
DOCKER_COMPOSE_CMD="docker-compose"
else
echo "Neither 'docker compose' (V2) nor 'docker-compose' (V1) command is available."
echo "Please ensure Docker Compose is correctly installed."
exit 1
fi
echo "Using Docker Compose command: '$DOCKER_COMPOSE_CMD'."
fi
}
get_config_file_path() {
local config_dir="./config/POK-manager"
mkdir -p "$config_dir"
echo "$config_dir/config.txt"
}
prompt_change_host_timezone() {
# Get the current host timezone
local current_tz=$(timedatectl show -p Timezone --value)
read -p "Do you want to change the host's timezone? Current timezone: $current_tz (y/N): " change_tz
if [[ "$change_tz" =~ ^[Yy]$ ]]; then
read -p "Enter the desired timezone (e.g., America/New_York): " new_tz
if timedatectl set-timezone "$new_tz"; then
echo "Host timezone set to $new_tz"
else
echo "Failed to set the host timezone to $new_tz"
read -p "Do you want to use the default UTC timezone instead? (Y/n): " use_default
if [[ ! "$use_default" =~ ^[Nn]$ ]]; then
if timedatectl set-timezone "UTC"; then
echo "Host timezone set to the default UTC"
else
echo "Failed to set the host timezone to the default UTC"
fi
fi
fi
else
echo "Host timezone change skipped."
fi
echo "You can always run './POK-manager.sh -setup' again to change the host's timezone later."
}
# Set timezone
set_timezone() {
# Try to read the current timezone from /etc/timezone or equivalent
local current_tz
if [ -f "/etc/timezone" ]; then
current_tz=$(cat /etc/timezone)
elif [ -h "/etc/localtime" ]; then
# For systems where /etc/localtime is a symlink to the timezone in /usr/share/zoneinfo
current_tz=$(readlink /etc/localtime | sed "s#/usr/share/zoneinfo/##")
else
current_tz="UTC" # Default to UTC if unable to determine the timezone
fi
echo "Detected Host Timezone: $current_tz"
read -rp "Press Enter to accept the host default for the container timezone ($current_tz) or type to change: " user_tz
TZ="${user_tz:-$current_tz}" # Use user input or fall back to detected timezone
# Export the TZ variable for use in other functions
export TZ
export USER_TIMEZONE="$TZ"
# Add TZ environment variable to the Docker Compose file for the instance
echo "Configured Timezone: $TZ"
echo "TZ=$TZ" >> "${instance_dir}/docker-compose-${instance_name}.yaml"
}
# Adjust file ownership and permissions on the host
adjust_ownership_and_permissions() {
local dir="$1"
if [ -z "$dir" ]; then
echo "Error: No directory provided."
return 1
fi
# Create the directory if it doesn't exist
if [ ! -d "$dir" ]; then
echo "Creating directory: $dir"
mkdir -p "$dir"
chown 1000:1000 "$dir"
chmod 755 "$dir"
fi
echo "Checking and adjusting ownership and permissions for $dir..."
find "$dir" -type d -exec chown 1000:1000 {} \;
find "$dir" -type d -exec chmod 755 {} \;
find "$dir" -type f -exec chown 1000:1000 {} \;
find "$dir" -type f -exec chmod 644 {} \;
# Set executable bit for POK-manager.sh
chmod +x "$(dirname "$(realpath "$0")")/POK-manager.sh"
echo "Ownership and permissions adjustment on $dir completed."
}
# Check vm.max_map_count
check_vm_max_map_count() {
local required_map_count=262144
local current_map_count=$(cat /proc/sys/vm/max_map_count)
if [ "$current_map_count" -lt "$required_map_count" ]; then
echo "ERROR: vm.max_map_count is too low ($current_map_count). Needs to be at least $required_map_count."
echo "Please run the following command to temporarily set the value:"
echo " sudo sysctl -w vm.max_map_count=262144"
echo "To set the value permanently, add the following line to /etc/sysctl.conf and run 'sudo sysctl -p':"
echo " vm.max_map_count=262144"
exit 1
fi
}
check_puid_pgid_user() {
local puid="$1"
local pgid="$2"
# Check if the script is run with sudo (EUID is 0)
if is_sudo; then
echo "Running with sudo privileges. Skipping PUID and PGID check."
return
fi
local current_uid=$(id -u)
local current_gid=$(id -g)
local current_user=$(id -un)
if [ "${current_uid}" -ne "${puid}" ] || [ "${current_gid}" -ne "${pgid}" ]; then
echo "You are not running the script as the user with the correct PUID (${puid}) and PGID (${pgid})."
echo "Your current user '${current_user}' has UID ${current_uid} and GID ${current_gid}."
echo "Please switch to the correct user or update your current user's UID and GID to match the required values."
echo "Alternatively, you can run the script with sudo to bypass this check: sudo ./POK-manager.sh <commands>"
exit 1
fi
}
copy_default_configs() {
# Define the directory where the configuration files will be stored
local config_dir="${base_dir}/Instance_${instance_name}/Saved/Config/WindowsServer"
local base_dir=$(dirname "$(realpath "$0")")
# Ensure the configuration directory exists
mkdir -p "$config_dir"
# Copy GameUserSettings.ini if it does not exist
if [ ! -f "${config_dir}/GameUserSettings.ini" ]; then
echo "Copying default GameUserSettings.ini"
cp ./defaults/GameUserSettings.ini "$config_dir"
chown 1000:1000 "${config_dir}/GameUserSettings.ini"
fi
# Copy Game.ini if it does not exist
if [ ! -f "${config_dir}/Game.ini" ]; then
echo "Copying default Game.ini"
cp ./defaults/Game.ini "$config_dir"
chown 1000:1000 "${config_dir}/Game.ini"
fi
}
install_yq() {
echo "Checking for yq..."
if ! command -v yq &>/dev/null; then
echo "yq not found. Attempting to install Mike Farah's yq..."
# Define the version of yq to install
YQ_VERSION="v4.9.8" # Check https://github.com/mikefarah/yq for the latest version
# Determine OS and architecture
os=""
case "$(uname -s)" in
Linux) os="linux" ;;
Darwin) os="darwin" ;;
*) echo "Unsupported OS."; exit 1 ;;
esac
arch=""
case "$(uname -m)" in
x86_64) arch="amd64" ;;
arm64) arch="arm64" ;;
aarch64) arch="arm64" ;;
*) echo "Unsupported architecture."; exit 1 ;;
esac
YQ_BINARY="yq_${os}_${arch}"
# Check for wget or curl and install if not present
if ! command -v wget &>/dev/null && ! command -v curl &>/dev/null; then
echo "Neither wget nor curl found. Attempting to install wget..."
if command -v apt-get &>/dev/null; then
sudo apt-get update && sudo apt-get install -y wget
elif command -v yum &>/dev/null; then
sudo yum install -y wget
elif command -v pacman &>/dev/null; then
sudo pacman -Sy wget
elif command -v dnf &>/dev/null; then
sudo dnf install -y wget
else
echo "Package manager not detected. Please manually install wget or curl."
exit 1
fi
fi
# Download and install yq
if command -v wget &>/dev/null; then
wget "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${YQ_BINARY}" -O /usr/local/bin/yq && chmod +x /usr/local/bin/yq
elif command -v curl &>/dev/null; then
curl -L "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${YQ_BINARY}" -o /usr/local/bin/yq && chmod +x /usr/local/bin/yq
fi
# Verify installation
if ! command -v yq &>/dev/null; then
echo "Failed to install Mike Farah's yq."
exit 1
else
echo "Mike Farah's yq installed successfully."
fi
else
echo "yq is already installed."
fi
}
# Root tasks
root_tasks() {
local base_dir=$(dirname "$(realpath "$0")")
check_vm_max_map_count
check_puid_pgid_user "$PUID" "$PGID"
check_dependencies
install_jq
install_yq
install_steamcmd
adjust_ownership_and_permissions "${base_dir}/ServerFiles/arkserver"
adjust_ownership_and_permissions "${base_dir}/ServerFiles/arkserver/ShooterGame"
adjust_ownership_and_permissions "${base_dir}/Cluster"
prompt_change_host_timezone
echo "Root tasks completed. You're now ready to create an instance."
}
pull_docker_image() {
local image_name="acekorneya/asa_server:2_0_latest"
echo "Pulling Docker image: $image_name"
sudo docker pull "$image_name"
}
read_docker_compose_config() {
local instance_name="$1"
local base_dir=$(dirname "$(realpath "$0")")
local docker_compose_file="${base_dir}/Instance_${instance_name}/docker-compose-${instance_name}.yaml"
if [ ! -f "$docker_compose_file" ]; then
echo "Docker compose file for ${instance_name} does not exist."
exit 1
fi
# Parse the environment section
local env_vars
mapfile -t env_vars < <(yq e '.services.asaserver.environment[]' "$docker_compose_file")
for env_var in "${env_vars[@]}"; do
# Splitting each line into key and value
IFS='=' read -r key value <<< "${env_var}"
key="${key//-/_}" # Replace hyphens with underscores to match your script's keys
key="${key^^}" # Convert to uppercase to match your associative array keys
# Map environment variable keys to your script's config keys if needed
case "$key" in
"TZ") config_key="TZ" ;;
"BATTLEEYE") config_key="BattleEye" ;;
"RCON_ENABLED") config_key="RCON Enabled" ;;
"DISPLAY_POK_MONITOR_MESSAGE") config_key="POK Monitor Message" ;;
"UPDATE_SERVER") config_key="Update Server" ;;
"CHECK_FOR_UPDATE_INTERVAL") config_key="Update Interval" ;;
"UPDATE_WINDOW_MINIMUM_TIME") config_key="Update Window Start" ;;
"UPDATE_WINDOW_MAXIMUM_TIME") config_key="Update Window End" ;;
"RESTART_NOTICE_MINUTES") config_key="Restart Notice" ;;
"ENABLE_MOTD") config_key="MOTD Enabled" ;;
"MOTD") config_key="MOTD" ;;
"MOTD_DURATION") config_key="MOTD Duration" ;;
"MAP_NAME") config_key="Map Name" ;;
"SESSION_NAME") config_key="Session Name" ;;
"SERVER_ADMIN_PASSWORD") config_key="Admin Password" ;;
"SERVER_PASSWORD") config_key="Server Password" ;;
"ASA_PORT") config_key="ASA Port" ;;
"RCON_PORT") config_key="RCON Port" ;;
"MAX_PLAYERS") config_key="Max Players" ;;
"SHOW_ADMIN_COMMANDS_IN_CHAT") config_key="Show Admin Commands In Chat" ;;
"CLUSTER_ID") config_key="Cluster ID" ;;
"MOD_IDS") config_key="Mod IDs" ;;
"PASSIVE_MODS") config_key="Passive Mods" ;;
"CUSTOM_SERVER_ARGS") config_key="Custom Server Args" ;;
*) config_key="$key" ;; # For any not explicitly mapped
esac
# Populate config_values
config_values[$config_key]="$value"
done
# Separately parse the mem_limit
local mem_limit
mem_limit=$(yq e '.services.asaserver.mem_limit' "$docker_compose_file")
if [ ! -z "$mem_limit" ]; then
# Assuming you want to strip the last character (G) and store just the numeric part
# If you want to keep the 'G', remove the `${mem_limit%?}` manipulation
config_values["Memory Limit"]="${mem_limit}"
fi
}
# Function to write Docker Compose file
write_docker_compose_file() {
local instance_name="$1"
local base_dir=$(dirname "$(realpath "$0")")
local instance_dir="${base_dir}/Instance_${instance_name}"
local docker_compose_file="${instance_dir}/docker-compose-${instance_name}.yaml"
# Ensure the instance directory exists
mkdir -p "${instance_dir}"
# Start writing the Docker Compose configuration
cat > "$docker_compose_file" <<-EOF
version: '2.4'
services:
asaserver:
build: .
image: acekorneya/asa_server:2_0_latest
container_name: asa_${instance_name}
restart: unless-stopped
environment:
- INSTANCE_NAME=${instance_name}
- TZ=$TZ
EOF
# Iterate over the config_order to maintain the order in Docker Compose
for key in "${config_order[@]}"; do
# Convert the friendly name to the actual environment variable key
case "$key" in
"BattleEye") env_key="BATTLEEYE" ;;
"RCON Enabled") env_key="RCON_ENABLED" ;;
"POK Monitor Message") env_key="DISPLAY_POK_MONITOR_MESSAGE" ;;
"Update Server") env_key="UPDATE_SERVER" ;;
"Update Interval") env_key="CHECK_FOR_UPDATE_INTERVAL" ;;
"Update Window Start") env_key="UPDATE_WINDOW_MINIMUM_TIME" ;;
"Update Window End") env_key="UPDATE_WINDOW_MAXIMUM_TIME" ;;
"Restart Notice") env_key="RESTART_NOTICE_MINUTES" ;;
"MOTD Enabled") env_key="ENABLE_MOTD" ;;
"MOTD") env_key="MOTD" ;;
"MOTD Duration") env_key="MOTD_DURATION" ;;
"Map Name") env_key="MAP_NAME" ;;
"Session Name") env_key="SESSION_NAME" ;;
"Admin Password") env_key="SERVER_ADMIN_PASSWORD" ;;
"Server Password") env_key="SERVER_PASSWORD" ;;
"ASA Port") env_key="ASA_PORT" ;;
"RCON Port") env_key="RCON_PORT" ;;
"Show Admin Commands In Chat") env_key="SHOW_ADMIN_COMMANDS_IN_CHAT" ;;
"Max Players") env_key="MAX_PLAYERS" ;;
"Cluster ID") env_key="CLUSTER_ID" ;;
"Mod IDs") env_key="MOD_IDS" ;;
"Passive Mods") env_key="PASSIVE_MODS" ;;
"Custom Server Args") env_key="CUSTOM_SERVER_ARGS" ;;
*) env_key="$key" ;; # Default case if the mapping is direct
esac
# Write the environment variable to the Docker Compose file, skipping Memory Limit
if [[ "$key" != "Memory Limit" ]]; then
echo " - $env_key=${config_values[$key]}" >> "$docker_compose_file"
fi
done
# Continue writing the rest of the Docker Compose configuration
cat >> "$docker_compose_file" <<-EOF
ports:
- "${config_values[ASA Port]}:${config_values[ASA Port]}/tcp"
- "${config_values[ASA Port]}:${config_values[ASA Port]}/udp"
- "${config_values[RCON Port]}:${config_values[RCON Port]}/tcp"
volumes:
- "${base_dir}/ServerFiles/arkserver:/home/pok/arkserver"
- "${instance_dir}/Saved:/home/pok/arkserver/ShooterGame/Saved"
$(if [ -n "${config_values[Cluster ID]}" ]; then echo " - \"${base_dir}/Cluster:/home/pok/arkserver/ShooterGame/Saved/clusters\"" ; fi)
mem_limit: ${config_values[Memory Limit]}
EOF
}
# Function to check and optionally adjust Docker command permissions
adjust_docker_permissions() {
local config_file=$(get_config_file_path)
if [ -f "$config_file" ]; then
local use_sudo
use_sudo=$(cat "$config_file")
if [ "$use_sudo" = "false" ]; then
echo "User has chosen to run Docker commands without 'sudo'."
return
fi
else
if groups $USER | grep -q '\bdocker\b'; then
echo "User $USER is already in the docker group."
read -r -p "Would you like to run Docker commands without 'sudo'? [y/N] " response
if [[ "$response" =~ ^[Yy]$ ]]; then
echo "Changing ownership of /var/run/docker.sock to $USER..."
sudo chown $USER /var/run/docker.sock
echo "false" > "$config_file"
echo "User preference saved. You can now run Docker commands without 'sudo'."
return
fi
else
read -r -p "Config file not found. Do you want to add user $USER to the 'docker' group? [y/N] " add_to_group
if [[ "$add_to_group" =~ ^[Yy]$ ]]; then
echo "Adding user $USER to the 'docker' group..."
sudo usermod -aG docker $USER
echo "User $USER has been added to the 'docker' group."
echo "Changing ownership of /var/run/docker.sock to $USER..."
sudo chown $USER /var/run/docker.sock
echo "You can now run Docker commands without 'sudo'."
echo "false" > "$config_file"
return
else
echo "true" > "$config_file"
echo "Please ensure to use 'sudo' for Docker commands or run this script with 'sudo'."
return
fi
fi
fi
echo "true" > "$config_file"
echo "Please ensure to use 'sudo' for Docker commands or run this script with 'sudo'."
}
get_docker_preference() {
local config_file=$(get_config_file_path)
if [ -f "$config_file" ]; then
local use_sudo
use_sudo=$(cat "$config_file")
echo "$use_sudo"
else
echo "true"
fi
}
prompt_for_instance_name() {
local provided_name="$1"
if [ -z "$provided_name" ]; then
read -rp "Please enter an instance name: " instance_name
if [ -z "$instance_name" ]; then
echo "Instance name is required to proceed."
exit 1 # Now exits if no instance name is provided
fi
else
instance_name="$provided_name"
fi
echo "$instance_name" # Return the determined instance name
}
# Function to perform an action on all instances
perform_action_on_all_instances() {
local action=$1
echo "Performing '${action}' on all instances..."
# Find all instance directories
local instance_dirs=($(find ./Instance_* -maxdepth 0 -type d))
for instance_dir in "${instance_dirs[@]}"; do
# Extract instance name from directory
local instance_name=$(basename "$instance_dir" | sed -E 's/Instance_(.*)/\1/')
echo "Performing '${action}' on instance: $instance_name"
case $action in
-start)
start_instance "$instance_name"
;;
-stop)
stop_instance "$instance_name"
;;
*)
echo "Unsupported action '${action}' for all instances."
;;
esac
done
}
# Helper function to prompt for instance copy
prompt_for_instance_copy() {
local instance_name="$1"
local instances=($(list_instances))
if [ ${#instances[@]} -gt 0 ]; then
echo "Existing instances found. Would you like to copy settings from another instance? (y/N)"
read answer
if [[ "$answer" =~ ^[Yy]$ ]]; then
echo "Select the instance you want to copy settings from:"
select instance in "${instances[@]}"; do
if [ -n "$instance" ] && [ "$instance" != "$instance_name" ]; then
echo "Copying settings from $instance..."
read_docker_compose_config "$instance"
break
else
echo "Invalid selection."
fi
done
else
echo "Proceeding with default settings."
# Initially populate config_values with defaults if not copying
for key in "${!default_config_values[@]}"; do
config_values[$key]=${default_config_values[$key]}
done
fi
else
echo "No existing instances found. Proceeding with default settings."
# Initially populate config_values with defaults if no existing instances
for key in "${!default_config_values[@]}"; do
config_values[$key]=${default_config_values[$key]}
done
fi
}
# Function to review and modify configuration before finalizing
review_and_modify_configuration() {
local repeat=true
while $repeat; do
echo "Current Configuration:"
for key in "${config_order[@]}"; do
echo "$key: ${config_values[$key]}"
done
echo "If you need to modify any setting, enter the setting name. Type 'confirm' to proceed with the current configuration."
local modify
read -rp "Modify setting (or 'confirm'): " modify
if [[ $modify == "confirm" ]]; then
repeat=false
elif [[ ${config_values[$modify]+_} ]]; then
prompt_for_input "$modify"
else
echo "Invalid setting name. Please try again."
fi
done
}
edit_instance() {
local instances=($(list_instances))
echo "Select the instance you wish to edit:"
select instance in "${instances[@]}"; do
if [ -n "$instance" ]; then
local editor=$(find_editor)
local docker_compose_file="./Instance_$instance/docker-compose-$instance.yaml"
echo "Opening $docker_compose_file for editing with $editor..."
$editor "$docker_compose_file"
break
else
echo "Invalid selection."
fi
done
}
# Function to generate or update Docker Compose file for an instance
generate_docker_compose() {
check_puid_pgid_user "$PUID" "$PGID"
local instance_name="$1"
# Assuming TZ is set or defaults to UTC
local tz="${TZ:-UTC}"
declare -A config_values
# Prompt for copying settings from an existing instance
prompt_for_instance_copy "$instance_name"
# Configuration review and modification loop
review_and_modify_configuration
# Path where Docker Compose files are located
local base_dir=$(dirname "$(realpath "$0")")
local instance_dir="${base_dir}/Instance_${instance_name}"
local docker_compose_file="${instance_dir}/docker-compose-${instance_name}.yaml"
# Check if the Docker Compose file already exists
if [ -f "$docker_compose_file" ]; then
echo "Docker Compose file for ${instance_name} already exists. Extracting and updating configuration..."
read_docker_compose_config "$instance_name"
else
echo "Creating new Docker Compose configuration for ${instance_name}."
mkdir -p "${instance_dir}"
mkdir -p "${instance_dir}/Saved" # Ensure Saved directory is created
copy_default_configs
adjust_ownership_and_permissions "${instance_dir}" # Adjust permissions right after creation
fi
# Set the timezone for the container
set_timezone
# Generate or update Docker Compose file with the confirmed settings
write_docker_compose_file "$instance_name"
# Prompt user for any final edits before saving
prompt_for_final_edit "$docker_compose_file"
echo "Docker Compose configuration for ${instance_name} has been finalized."
}
# Function to prompt user for final edits before saving the Docker Compose file
prompt_for_final_edit() {
local docker_compose_file="$1"
echo "Would you like to review and edit the Docker Compose configuration before finalizing? [y/N]"
read -r response
if [[ "$response" =~ ^[Yy]$ ]]; then
local editor=$(find_editor) # Ensure find_editor function returns a valid editor command
"$editor" "$docker_compose_file"
fi
}
list_instances() {
local compose_files=($(find ./Instance_* -name 'docker-compose-*.yaml'))
local instances=()
for file in "${compose_files[@]}"; do
local instance_name=$(echo "$file" | sed -E 's|.*/Instance_([^/]+)/docker-compose-.*\.yaml|\1|')
instances+=("$instance_name")
done
echo "${instances[@]}"
}
find_editor() {
# List of common text editors, ordered by preference
local editors=("nano" "vim" "vi" "emacs")
for editor in "${editors[@]}"; do
if command -v "$editor" &> /dev/null; then
echo "$editor"
return
fi
done
# No editor found, ask the user to specify one
echo "No text editor found in your system. Please install 'nano', 'vim', or similar."
echo "Alternatively, you can specify the path to your preferred text editor."
read -rp "Enter the command or path for your text editor: " user_editor
if [ -n "$user_editor" ]; then
if command -v "$user_editor" &> /dev/null; then
echo "$user_editor"
return
else
echo "The specified editor could not be found. Please ensure the command or path is correct."
exit 1
fi
else
echo "No editor specified. Exiting..."
exit 1
fi
}
# Function to start an instance
start_instance() {
local instance_name=$1
local docker_compose_file="./Instance_${instance_name}/docker-compose-${instance_name}.yaml"
echo "-----Starting ${instance_name} Server-----"
if [ -f "$docker_compose_file" ]; then