-
Notifications
You must be signed in to change notification settings - Fork 302
Expand file tree
/
Copy pathinstallTorrServerMac.sh
More file actions
executable file
·1338 lines (1182 loc) · 38.1 KB
/
installTorrServerMac.sh
File metadata and controls
executable file
·1338 lines (1182 loc) · 38.1 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
#!/usr/bin/env bash
set -euo pipefail
#############################################
# GLOBAL VARIABLES
#############################################
# Installation settings
DEFAULT_INSTALL_DIR="/Users/Shared/TorrServer"
DEFAULT_SERVICE_NAME="torrserver"
DEFAULT_PORT="8090"
# Runtime variables
dirInstall="${DEFAULT_INSTALL_DIR}"
serviceName="${DEFAULT_SERVICE_NAME}"
scriptname=$(basename "$(test -L "$0" && readlink "$0" || echo "$0")")
# Flags
SILENT_MODE=0
USE_USER_LAUNCHAGENT=0
USER_PROMPTED=0
# Command-line state
parsedCommand=""
specificVersion=""
downgradeRelease=""
# Service configuration
servicePort=""
isAuth=""
isRdb=""
isLog=""
isAuthUser=""
isAuthPass=""
sysPath=""
# Constants
readonly REPO_URL="https://github.com/YouROK/TorrServer"
readonly REPO_API_URL="https://api.github.com/repos/YouROK/TorrServer"
readonly VERSION_PREFIX="MatriX"
readonly BINARY_NAME_PREFIX="TorrServer-darwin"
# Color support
getColorCode() {
case "$1" in
black) echo 0 ;;
red) echo 1 ;;
green) echo 2 ;;
yellow) echo 3 ;;
blue) echo 4 ;;
magenta) echo 5 ;;
cyan) echo 6 ;;
white) echo 7 ;;
*) echo 7 ;; # default to white
esac
}
supports_color_output=0
if command -v tput >/dev/null 2>&1 && [[ -t 1 ]]; then
if [[ $(tput colors 2>/dev/null) -ge 8 ]]; then
supports_color_output=1
fi
fi
# Language
lang="en"
#############################################
# TRANSLATION SYSTEM
#############################################
# Message dictionary
# Message dictionary - bash 3.2 compatible (no associative arrays)
# English messages
MSG_EN_lang_choice="Choose Language:"
MSG_EN_lang_english="English"
MSG_EN_lang_russian="Русский"
MSG_EN_your_lang="Your language (Ваш язык): "
MSG_EN_have_fun="Have Fun!"
MSG_EN_script_title="TorrServer install and configuration script for macOS"
MSG_EN_unsupported_arch="Unsupported Arch. Can't continue."
MSG_EN_unsupported_os="It looks like you are running this installer on a system other than macOS."
MSG_EN_downloading="Downloading TorrServer"
MSG_EN_target_version="Target version:"
MSG_EN_installed_version="installed:"
MSG_EN_target_label="target:"
MSG_EN_version_not_found="ERROR: Version %s not found in releases"
MSG_EN_check_versions="Please check available versions at: $REPO_URL/releases"
MSG_EN_already_installed="You already have TorrServer %s installed"
MSG_EN_have_latest="You have latest TorrServer %s"
MSG_EN_update_found="TorrServer update found!"
MSG_EN_will_install="Will install TorrServer version %s"
MSG_EN_installing_packages="Installing missing packages…"
MSG_EN_install_configure="Install and configure TorrServer…"
MSG_EN_starting_service="Starting TorrServer…"
MSG_EN_install_complete="TorrServer %s installed to %s"
MSG_EN_access_web="You can now open your browser at http://%s:%s to access TorrServer web GUI."
MSG_EN_use_auth="Use user \"%s\" with password \"%s\" for authentication"
MSG_EN_want_update="Want to update TorrServer?"
MSG_EN_want_install="Want to install or configure TorrServer? Type Delete to uninstall."
MSG_EN_want_reconfigure="Do you want to reconfigure TorrServer settings?"
MSG_EN_change_port="Change TorrServer web-port?"
MSG_EN_enter_port="Enter port number: "
MSG_EN_enable_auth="Enable server authorization?"
MSG_EN_prompt_user="User: "
MSG_EN_prompt_password="Password: "
MSG_EN_change_auth_credentials="Change authentication username and password?"
MSG_EN_enable_rdb="Start TorrServer in public read-only mode?"
MSG_EN_enable_log="Enable TorrServer log output to file?"
MSG_EN_confirm_delete="Are you sure you want to delete TorrServer?"
MSG_EN_prompt_launchagent="Add autostart for current user (1) or all users (2)?"
MSG_EN_admin_password="System can ask your admin account password"
MSG_EN_install_dir_label="TorrServer install dir -"
MSG_EN_uninstall_warning="This action will delete TorrServer including all it's torrents, settings and files on path above!"
MSG_EN_uninstalled="TorrServer uninstalled!"
MSG_EN_found_in="TorrServer found in"
MSG_EN_not_found="TorrServer not found. It's not installed or have zero size."
MSG_EN_no_version_info="No version information available. Can be server issue."
MSG_EN_config_updated="Configuration updated successfully"
MSG_EN_store_auth="Store %s:%s to %s"
MSG_EN_use_existing_auth="Use existing auth from %s - %s"
MSG_EN_set_readonly="Set database to read-only mode…"
MSG_EN_readonly_hint="To change remove --rdb option from %s or rerun install script without parameters"
MSG_EN_log_location="TorrServer log stored at %s"
MSG_EN_service_added="Autostart service added to %s"
MSG_EN_launchctl_missing="launchctl is not available. Skipping service management commands."
MSG_EN_launchctl_failed="Warning: launchctl %s failed"
MSG_EN_service_start_failed="Warning: TorrServer service failed to start. Check launchctl list for details."
MSG_EN_error_version_required="Error: Version number required for downgrade"
MSG_EN_error_version_example="Example: %s -d 101"
MSG_EN_error_unknown_option="Unknown option: %s"
MSG_EN_installing_specific_version="Installing specific version: %s"
MSG_EN_install_first_required="Please install TorrServer first using: %s --install"
# Russian messages
MSG_RU_lang_choice="Choose Language:"
MSG_RU_lang_english="English"
MSG_RU_lang_russian="Русский"
MSG_RU_your_lang="Your language (Ваш язык): "
MSG_RU_have_fun="Have Fun!"
MSG_RU_script_title="Скрипт установки, удаления и настройки TorrServer для macOS"
MSG_RU_unsupported_arch="Не поддерживаемая архитектура. Продолжение невозможно."
MSG_RU_unsupported_os="Похоже, что вы запускаете этот установщик в системе отличной от macOS."
MSG_RU_downloading="Загружаем TorrServer"
MSG_RU_target_version="Устанавливаемая версия:"
MSG_RU_installed_version="установлен:"
MSG_RU_target_label="устанавливаемая:"
MSG_RU_version_not_found="ОШИБКА: Версия %s не найдена в релизах"
MSG_RU_check_versions="Проверьте доступные версии по адресу: $REPO_URL/releases"
MSG_RU_already_installed="TorrServer %s уже установлен"
MSG_RU_have_latest="Установлен TorrServer последней версии %s"
MSG_RU_update_found="Доступно обновление сервера"
MSG_RU_will_install="Будет установлена версия TorrServer %s"
MSG_RU_installing_packages="Устанавливаем недостающие пакеты…"
MSG_RU_install_configure="Устанавливаем и настраиваем TorrServer…"
MSG_RU_starting_service="Запускаем службу TorrServer…"
MSG_RU_install_complete="TorrServer %s установлен в директории %s"
MSG_RU_access_web="Теперь вы можете открыть браузер по адресу http://%s:%s для доступа к вебу TorrServer"
MSG_RU_use_auth="Для авторизации используйте пользователя «%s» с паролем «%s»"
MSG_RU_want_update="Хотите обновить TorrServer?"
MSG_RU_want_install="Хотите установить, обновить или настроить TorrServer? Для удаления введите «Delete»"
MSG_RU_want_reconfigure="Хотите перенастроить параметры TorrServer?"
MSG_RU_change_port="Хотите изменить порт для TorrServer?"
MSG_RU_enter_port="Введите номер порта: "
MSG_RU_enable_auth="Включить авторизацию на сервере?"
MSG_RU_prompt_user="Пользователь: "
MSG_RU_prompt_password="Пароль: "
MSG_RU_change_auth_credentials="Изменить имя пользователя и пароль для авторизации?"
MSG_RU_enable_rdb="Запускать TorrServer в публичном режиме без возможности изменения настроек через веб сервера?"
MSG_RU_enable_log="Включить запись журнала работы TorrServer в файл?"
MSG_RU_confirm_delete="Вы уверены что хотите удалить программу?"
MSG_RU_prompt_launchagent="Добавить автозагрузку для текущего пользователя (1) или для всех (2)?"
MSG_RU_admin_password="Система может запросить ваш пароль администратора"
MSG_RU_install_dir_label="Директория c TorrServer -"
MSG_RU_uninstall_warning="Это действие удалит все данные TorrServer включая базу данных торрентов и настройки по указанному выше пути!"
MSG_RU_uninstalled="TorrServer удален из системы!"
MSG_RU_found_in="TorrServer найден в директории"
MSG_RU_not_found="TorrServer не найден, возможно он не установлен или размер бинарника равен 0."
MSG_RU_no_version_info="Информация о версии недоступна. Возможно сервер не доступен."
MSG_RU_config_updated="Конфигурация успешно обновлена"
MSG_RU_store_auth="Сохраняем %s:%s в %s"
MSG_RU_use_existing_auth="Используйте реквизиты из %s для авторизации - %s"
MSG_RU_set_readonly="База данных устанавливается в режим «только для чтения»…"
MSG_RU_readonly_hint="Для изменения отредактируйте %s, убрав опцию --rdb или запустите интерактивную установку без параметров повторно"
MSG_RU_log_location="лог TorrServer располагается по пути %s"
MSG_RU_service_added="Сервис автозагрузки записан в %s"
MSG_RU_launchctl_missing="launchctl недоступен. Пропускаем команды управления службой."
MSG_RU_launchctl_failed="Предупреждение: команда launchctl %s завершилась ошибкой"
MSG_RU_service_start_failed="Предупреждение: служба TorrServer не запустилась. Проверьте launchctl list для деталей."
MSG_RU_error_version_required="Ошибка: Требуется номер версии для понижения версии"
MSG_RU_error_version_example="Пример: %s -d 101"
MSG_RU_error_unknown_option="Неизвестная опция: %s"
MSG_RU_installing_specific_version="Установка конкретной версии: %s"
MSG_RU_install_first_required="Пожалуйста, сначала установите TorrServer используя: %s --install"
# Translation function - bash 3.2 compatible
msg() {
local key="$1"
shift
local var_name
local message=""
if [[ $lang == "ru" ]]; then
var_name="MSG_RU_${key}"
else
var_name="MSG_EN_${key}"
fi
# Use eval to get the variable value (bash 3.2 compatible)
eval "message=\"\${${var_name}:-${key}}\""
# Apply printf formatting if additional arguments provided
if [[ $# -gt 0 ]]; then
# shellcheck disable=SC2059
printf "$message" "$@"
else
printf '%s\n' "$message"
fi
}
#############################################
# UTILITY FUNCTIONS
#############################################
colorize() {
if [[ $supports_color_output -eq 1 ]]; then
local color_code
color_code=$(getColorCode "$1")
printf "%s%s%s" "$(tput setaf "$color_code")" "$2" "$(tput op)"
else
printf "%s" "$2"
fi
}
# Highlight first letter of a word with specified color
highlightFirstLetter() {
local color="$1"
local word="$2"
local first_char="${word:0:1}"
local rest="${word:1}"
printf "%s%s" "$(colorize "$color" "$first_char")" "$rest"
}
getBinaryName() {
echo "${BINARY_NAME_PREFIX}-${architecture}"
}
getVersionTag() {
local version="$1"
echo "${VERSION_PREFIX}.${version}"
}
buildDownloadUrl() {
local target_version="$1"
local binary_name="$2"
if [[ "$target_version" == "latest" ]]; then
echo "${REPO_URL}/releases/latest/download/${binary_name}"
else
echo "${REPO_URL}/releases/download/${target_version}/${binary_name}"
fi
}
getLang() {
lang=$(locale | grep LANG | cut -d= -f2 | tr -d '"' | cut -d_ -f1)
if [[ $lang != "ru" ]]; then
lang="en"
fi
}
getIP() {
local ip="localhost"
# Try to get local IP address from network interfaces
# On macOS, try ipconfig first (most reliable)
if command -v ipconfig >/dev/null 2>&1; then
# Try common interfaces: en0 (Ethernet/WiFi), en1, etc.
for interface in en0 en1 eth0; do
ip=$(ipconfig getifaddr "$interface" 2>/dev/null || echo "")
if [[ -n "$ip" ]] && [[ "$ip" != "127.0.0.1" ]]; then
break
fi
done
fi
# Fallback to ifconfig if ipconfig didn't work
if [[ -z "$ip" ]] || [[ "$ip" == "127.0.0.1" ]]; then
if command -v ifconfig >/dev/null 2>&1; then
# Get the first non-loopback inet address
ip=$(ifconfig 2>/dev/null | grep -E "inet " | grep -v "127.0.0.1" | head -n1 | awk '{print $2}' | sed 's/addr://' || echo "")
fi
fi
# If still no valid IP, use localhost
if [[ -z "$ip" ]] || [[ "$ip" == "127.0.0.1" ]]; then
ip="localhost"
fi
serverIP="$ip"
}
promptYesNo() {
local prompt="$1"
local default="${2:-n}"
local recommended="${3:-$default}"
if [[ $SILENT_MODE -eq 1 ]]; then
if [[ "$default" == "y" ]]; then
return 0
else
return 1
fi
fi
# Determine colors based on recommendation
local yes_color no_color
if [[ "$recommended" == "y" ]]; then
yes_color="green"
no_color="red"
else
yes_color="red"
no_color="green"
fi
# Define localized Yes/No words
local yes_word no_word
if [[ $lang == "ru" ]]; then
yes_word="Да"
no_word="Нет"
else
yes_word="Yes"
no_word="No"
fi
# Highlight first letter of each word
local yes_text
local no_text
yes_text="$(highlightFirstLetter "$yes_color" "$yes_word")"
no_text="$(highlightFirstLetter "$no_color" "$no_word")"
local answer
IFS= read -r -p " $prompt ($yes_text/$no_text) " answer </dev/tty
# Support both English (Yy) and Russian (Дд) for Yes
if [[ "$answer" =~ ^[YyДд] ]]; then
return 0
else
return 1
fi
}
promptYesNoDelete() {
local prompt="$1"
local default="${2:-n}"
local recommended="${3:-$default}"
if [[ $SILENT_MODE -eq 1 ]]; then
if [[ "$default" == "y" ]]; then
echo "yes"
else
echo "no"
fi
return
fi
# Determine colors based on recommended answer
local yes_color no_color
if [[ "$recommended" == "y" ]]; then
yes_color="green"
no_color="red"
else
yes_color="red"
no_color="green"
fi
# Define localized Yes/No words
local yes_word no_word
if [[ $lang == "ru" ]]; then
yes_word="Да"
no_word="Нет"
else
yes_word="Yes"
no_word="No"
fi
# Highlight first letter of each word
local yes_text
local no_text
yes_text="$(highlightFirstLetter "$yes_color" "$yes_word")"
no_text="$(highlightFirstLetter "$no_color" "$no_word")"
local answer
IFS= read -r -p " $prompt ($yes_text/$no_text) " answer </dev/tty
answer=$(echo "$answer" | tr '[:upper:]' '[:lower:]' | xargs)
# Check for Delete (case-insensitive, supports both English and Russian)
if [[ "$answer" == "delete" ]] || [[ "$answer" == "удалить" ]] || [[ "$answer" == "удаление" ]]; then
echo "delete"
elif [[ "$answer" =~ ^[YyДд] ]]; then
echo "yes"
else
echo "no"
fi
}
promptInput() {
local prompt="$1"
local default="$2"
if [[ $SILENT_MODE -eq 1 ]]; then
echo "$default"
return
fi
local answer
IFS= read -r -p " $prompt " answer </dev/tty
echo "${answer:-$default}"
}
launchctlCmd() {
local quiet=0
if [[ ${1:-} == "--quiet" ]]; then
quiet=1
shift
fi
if ! command -v launchctl >/dev/null 2>&1; then
if [[ $quiet -eq 0 && $SILENT_MODE -eq 0 ]]; then
echo " - $(msg launchctl_missing)"
fi
return 1
fi
local rc
launchctl "$@" >/dev/null 2>&1
rc=$?
if [[ $rc -ne 0 ]]; then
if [[ $quiet -eq 0 && $SILENT_MODE -eq 0 ]]; then
printf " - %s\n" "$(msg launchctl_failed "$*")"
fi
return $rc
fi
return 0
}
killRunning() {
local self="$(basename "$0")"
local runningPid
runningPid=$(ps -ax | grep -i torrserver | grep -v grep | grep -v "$self" | awk '{print $1}' || echo "")
if [[ -n "$runningPid" ]]; then
sudo kill -9 "$runningPid" 2>/dev/null || true
fi
}
#############################################
# VERSION MANAGEMENT
#############################################
getLatestRelease() {
curl -s "${REPO_API_URL}/releases/latest" |
grep -iE '"tag_name":|"version":' |
sed -E 's/.*"([^"]+)".*/\1/' |
head -n1
}
getSpecificRelease() {
local version="$1"
local tag_name
tag_name=$(getVersionTag "$version")
local response
response=$(curl -s "${REPO_API_URL}/releases/tags/$tag_name")
if echo "$response" | grep -q '"tag_name"'; then
echo "$tag_name"
else
echo ""
fi
}
getTargetVersion() {
if [[ -n "$specificVersion" ]]; then
local target_release
target_release=$(getSpecificRelease "$specificVersion")
if [[ -z "$target_release" ]]; then
echo " - $(colorize red "$(msg version_not_found "$specificVersion")")"
echo " - $(msg check_versions)"
exit 1
fi
echo "$target_release"
else
getLatestRelease
fi
}
downloadBinary() {
local url="$1"
local destination="$2"
local version_info="$3"
local curl_args=(-L)
if [[ $SILENT_MODE -eq 0 ]]; then
echo " - $(msg downloading) $version_info..."
curl_args+=(--progress-bar -#)
else
curl_args+=(-s -S)
fi
curl "${curl_args[@]}" -o "$destination" "$url"
chmod +x "$destination"
xattr -r -d com.apple.quarantine "$destination" 2>/dev/null || true
}
#############################################
# OS DETECTION & ARCHITECTURE
#############################################
checkOS() {
if [[ "$(uname)" != "Darwin" ]]; then
echo " $(msg unsupported_os)"
exit 1
fi
}
checkArch() {
case $(uname -m) in
i386|i686) architecture="386" ;;
x86_64) architecture="amd64" ;;
aarch64|arm64) architecture="arm64" ;;
*)
echo " $(msg unsupported_arch)"
exit 1
;;
esac
}
initialCheck() {
checkOS
checkArch
}
#############################################
# INSTALLATION FUNCTIONS
#############################################
checkInstalled() {
local binName
binName=$(getBinaryName)
if [[ -f "$dirInstall/$binName" ]] && [[ $(stat -f%z "$dirInstall/$binName" 2>/dev/null) -ne 0 ]]; then
if [[ $SILENT_MODE -eq 0 ]]; then
echo " - $(msg found_in) $dirInstall"
fi
return 0
else
if [[ $SILENT_MODE -eq 0 ]]; then
echo " - $(msg not_found)"
fi
return 1
fi
}
checkInstalledVersion() {
local binName
binName=$(getBinaryName)
local target_version
target_version=$(getTargetVersion)
local installed_version
installed_version="$("$dirInstall/$binName" --version 2>/dev/null | awk '{print $2}')"
if [[ -z "$target_version" ]]; then
echo " - $(msg no_version_info)"
exit 1
fi
if [[ "$target_version" == "$installed_version" ]]; then
if [[ -n "$specificVersion" ]]; then
if [[ $SILENT_MODE -eq 0 ]]; then
echo " - $(msg already_installed "$target_version")"
fi
else
if [[ $SILENT_MODE -eq 0 ]]; then
echo " - $(msg have_latest "$target_version")"
fi
fi
return 0
else
if [[ $SILENT_MODE -eq 0 ]]; then
if [[ -n "$specificVersion" ]]; then
echo " - $(msg will_install "$target_version")"
else
echo " - $(msg update_found)"
fi
echo " $(msg installed_version) \"$installed_version\""
echo " $(msg target_label) \"$target_version\""
fi
return 1
fi
}
createPlistFile() {
local daemon_options="--port $servicePort --path $dirInstall"
if [[ $isRdb -eq 1 ]]; then
daemon_options="$daemon_options --rdb"
fi
if [[ $isLog -eq 1 ]]; then
daemon_options="$daemon_options --logpath $dirInstall/$serviceName.log"
fi
if [[ $isAuth -eq 1 ]]; then
daemon_options="$daemon_options --httpauth"
fi
# Convert daemon_options to plist array format
local plist_args=()
local arg
for arg in $daemon_options; do
plist_args+=(" <string>$arg</string>")
done
local plist_args_str
plist_args_str=$(printf '%s\n' "${plist_args[@]}")
cat << EOF > "$dirInstall/$serviceName.plist"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>${serviceName}</string>
<key>ServiceDescription</key>
<string>TorrServer service for macOS</string>
<key>ProgramArguments</key>
<array>
<string>${dirInstall}/$(getBinaryName)</string>
${plist_args_str}
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>ProcessType</key>
<string>Background</string>
<key>ThrottleInterval</key>
<integer>10</integer>
<key>AbandonProcessGroup</key>
<true/>
<key>StandardOutPath</key>
<string>${dirInstall}/torrserver.log</string>
<key>StandardErrorPath</key>
<string>${dirInstall}/torrserver.log</string>
<key>WorkingDirectory</key>
<string>${dirInstall}</string>
</dict>
</plist>
EOF
}
readExistingConfig() {
local plist_file="$dirInstall/$serviceName.plist"
if [[ -f "$plist_file" ]]; then
# Extract port
if grep -q "<string>--port</string>" "$plist_file"; then
servicePort=$(grep -A1 "<string>--port</string>" "$plist_file" | tail -n1 | sed 's/.*<string>\(.*\)<\/string>.*/\1/')
fi
# Check for auth
if grep -q "<string>--httpauth</string>" "$plist_file"; then
isAuth=1
else
isAuth=0
fi
# Check for rdb
if grep -q "<string>--rdb</string>" "$plist_file"; then
isRdb=1
else
isRdb=0
fi
# Check for log
if grep -q "<string>--logpath</string>" "$plist_file"; then
isLog=1
else
isLog=0
fi
fi
}
configureService() {
# Read existing config if available (for reconfiguration)
if [[ -f "$dirInstall/$serviceName.plist" ]]; then
readExistingConfig
fi
# Port configuration
if [[ -z "$servicePort" ]]; then
local inferred_default="$DEFAULT_PORT"
if promptYesNo "$(msg change_port)" "n" "y"; then
servicePort=$(promptInput "$(msg enter_port)" "$inferred_default")
else
servicePort="$inferred_default"
fi
else
# Port exists, ask if user wants to change it
if [[ $SILENT_MODE -eq 0 ]]; then
if promptYesNo "$(msg change_port)" "n" "y"; then
servicePort=$(promptInput "$(msg enter_port)" "$servicePort")
fi
fi
fi
# Auth configuration
if [[ -z "$isAuth" ]]; then
if promptYesNo "$(msg enable_auth)" "n" "y"; then
isAuth=1
else
isAuth=0
fi
else
# Auth setting exists, ask if user wants to change it
if [[ $SILENT_MODE -eq 0 ]]; then
local current_auth_default
current_auth_default="$([[ $isAuth -eq 1 ]] && echo 'y' || echo 'n')"
if promptYesNo "$(msg enable_auth)" "$current_auth_default" "y"; then
isAuth=1
else
isAuth=0
fi
fi
fi
# Setup auth if enabled
if [[ $isAuth -eq 1 ]]; then
if [[ ! -f "$dirInstall/accs.db" ]]; then
isAuthUser=$(promptInput "$(msg prompt_user)" "admin")
isAuthPass=$(promptInput "$(msg prompt_password)" "admin")
if [[ $SILENT_MODE -eq 0 ]]; then
printf ' %s\n' "$(msg store_auth "$isAuthUser" "$isAuthPass" "${dirInstall}/accs.db")"
fi
echo -e "{\n \"$isAuthUser\": \"$isAuthPass\"\n}" > "$dirInstall/accs.db"
else
local auth
auth=$(cat "$dirInstall/accs.db" | head -2 | tail -1 | tr -d '[:space:]' | tr -d '"')
if [[ $SILENT_MODE -eq 0 ]]; then
printf ' - %s\n' "$(msg use_existing_auth "${dirInstall}/accs.db" "$auth")"
# Ask if user wants to change credentials
if promptYesNo "$(msg change_auth_credentials)" "n" "n"; then
isAuthUser=$(promptInput "$(msg prompt_user)" "admin")
isAuthPass=$(promptInput "$(msg prompt_password)" "admin")
if [[ $SILENT_MODE -eq 0 ]]; then
printf ' %s\n' "$(msg store_auth "$isAuthUser" "$isAuthPass" "${dirInstall}/accs.db")"
fi
echo -e "{\n \"$isAuthUser\": \"$isAuthPass\"\n}" > "$dirInstall/accs.db"
fi
fi
fi
fi
# Read-only database configuration
if [[ -z "$isRdb" ]]; then
if promptYesNo "$(msg enable_rdb)" "n" "n"; then
isRdb=1
else
isRdb=0
fi
else
# RDB setting exists, ask if user wants to change it
if [[ $SILENT_MODE -eq 0 ]]; then
local current_rdb_default
current_rdb_default="$([[ $isRdb -eq 1 ]] && echo 'y' || echo 'n')"
if promptYesNo "$(msg enable_rdb)" "$current_rdb_default" "n"; then
isRdb=1
else
isRdb=0
fi
fi
fi
if [[ $isRdb -eq 1 ]] && [[ $SILENT_MODE -eq 0 ]]; then
echo " $(msg set_readonly)"
printf ' %s\n' "$(msg readonly_hint "$dirInstall/$serviceName.plist")"
fi
# Logging configuration
if [[ -z "$isLog" ]]; then
if promptYesNo "$(msg enable_log)" "n" "y"; then
isLog=1
else
isLog=0
fi
else
# Log setting exists, ask if user wants to change it
if [[ $SILENT_MODE -eq 0 ]]; then
local current_log_default
current_log_default="$([[ $isLog -eq 1 ]] && echo 'y' || echo 'n')"
if promptYesNo "$(msg enable_log)" "$current_log_default" "y"; then
isLog=1
else
isLog=0
fi
fi
fi
if [[ $isLog -eq 1 ]] && [[ $SILENT_MODE -eq 0 ]]; then
printf ' - %s\n' "$(msg log_location "$dirInstall/$serviceName.log")"
fi
# LaunchAgent/LaunchDaemon selection
if [[ $SILENT_MODE -eq 0 && $USER_PROMPTED -eq 0 ]]; then
local answer_cu
answer_cu=$(promptInput "$(msg prompt_launchagent)" "1")
if [[ "$answer_cu" == "1" ]]; then
USE_USER_LAUNCHAGENT=1
sysPath="${HOME}/Library/LaunchAgents"
else
USE_USER_LAUNCHAGENT=0
sysPath="/Library/LaunchDaemons"
fi
USER_PROMPTED=1
elif [[ $SILENT_MODE -eq 1 ]]; then
# Silent mode defaults to user LaunchAgent
USE_USER_LAUNCHAGENT=1
sysPath="${HOME}/Library/LaunchAgents"
fi
}
installTorrServer() {
if [[ $SILENT_MODE -eq 0 ]]; then
echo " $(msg install_configure)"
fi
# Get target version
local target_version
target_version=$(getTargetVersion)
if [[ $SILENT_MODE -eq 0 ]]; then
echo " - $(msg target_version) $target_version"
fi
# Check if already installed and up to date
if checkInstalled; then
if ! checkInstalledVersion; then
if promptYesNo "$(msg want_update)" "y" "y"; then
UpdateVersion
return
fi
else
# Already installed and up to date, allow reconfiguration
if [[ $SILENT_MODE -eq 0 ]]; then
echo ""
# Allow user to reconfigure settings
if promptYesNo "$(msg want_reconfigure)" "n" "n"; then
# Read existing config first
if [[ -f "$dirInstall/$serviceName.plist" ]]; then
readExistingConfig
fi
# Reconfigure service
configureService
# Update plist file
createPlistFile
# Reload and restart service
cleanup
installService
echo ""
echo " - $(msg config_updated)"
echo ""
fi
fi
return
fi
fi
# Create directories
if [[ ! -d "$dirInstall" ]]; then
mkdir -p "$dirInstall"
chmod a+rw "$dirInstall"
fi
# Download binary if needed
local binName
binName=$(getBinaryName)
if [[ ! -f "$dirInstall/$binName" ]] || [[ ! -x "$dirInstall/$binName" ]] || [[ $(stat -f%z "$dirInstall/$binName" 2>/dev/null) -eq 0 ]]; then
local urlBin
if [[ -n "$specificVersion" ]]; then
urlBin=$(buildDownloadUrl "$target_version" "$binName")
else
urlBin=$(buildDownloadUrl "latest" "$binName")
fi
downloadBinary "$urlBin" "$dirInstall/$binName" "$target_version"
fi
# Create plist and configure service
configureService
createPlistFile
# Install service
local service_started=0
if installService; then
service_started=1
fi
# Show completion message
getIP
local installed_version="$target_version"
if [[ $SILENT_MODE -eq 0 ]]; then
echo ""
printf ' %s\n' "$(msg install_complete "$installed_version" "$dirInstall")"
echo ""
printf ' %s\n' "$(msg access_web "$serverIP" "$servicePort")"
echo ""
if [[ $isAuth -eq 1 && -n "$isAuthUser" ]]; then
printf ' %s\n' "$(msg use_auth "$isAuthUser" "$isAuthPass")"
echo ""
fi
if [[ $service_started -eq 0 ]]; then
echo " $(colorize yellow "$(msg service_start_failed)")"
fi
echo ""
fi
if [[ $SILENT_MODE -eq 1 ]]; then
printf "%s\n" "$(msg install_complete "$installed_version" "$dirInstall")"
printf "%s\n" "$(msg access_web "$serverIP" "$servicePort")"
if [[ $isAuth -eq 1 && -n "$isAuthUser" ]]; then
printf "%s\n" "$(msg use_auth "$isAuthUser" "$isAuthPass")"
fi
fi
return 0
}
installService() {
# Cleanup existing services first
cleanup
if [[ $USE_USER_LAUNCHAGENT -eq 1 ]]; then
# User LaunchAgent
sysPath="${HOME}/Library/LaunchAgents"
[[ ! -d "$sysPath" ]] && mkdir -p "$sysPath"
cp "$dirInstall/$serviceName.plist" "$sysPath"
chmod 0644 "$sysPath/$serviceName.plist"
if launchctlCmd load -w "$sysPath/$serviceName.plist"; then
if [[ $SILENT_MODE -eq 0 ]]; then
printf ' %s\n' "$(msg service_added "$sysPath")"
fi
return 0
else
if [[ $SILENT_MODE -eq 0 ]]; then
printf ' %s\n' "$(msg service_added "$sysPath")"
fi
return 1
fi
else
# System LaunchDaemon
sysPath="/Library/LaunchDaemons"
[[ ! -d "$sysPath" ]] && sudo mkdir -p "$sysPath"
sudo cp "$dirInstall/$serviceName.plist" "$sysPath"
sudo chown root:wheel "$sysPath/$serviceName.plist"
sudo chmod 0644 "$sysPath/$serviceName.plist"
if sudo launchctl load -w "$sysPath/$serviceName.plist" >/dev/null 2>&1; then
if [[ $SILENT_MODE -eq 0 ]]; then
printf ' %s\n' "$(msg service_added "$sysPath")"
fi
return 0
else
if [[ $SILENT_MODE -eq 0 ]]; then
printf ' %s\n' "$(msg service_added "$sysPath")"
fi
return 1
fi
fi
}
# Common function to update/downgrade TorrServer version
updateTorrServerVersion() {
local target_version="$1"
local cancel_message="$2"
local use_latest_url="${3:-0}"
killRunning
local binName
binName=$(getBinaryName)
local urlBin
if [[ $use_latest_url -eq 1 && -z "$specificVersion" ]]; then
urlBin=$(buildDownloadUrl "latest" "$binName")
else
urlBin=$(buildDownloadUrl "$target_version" "$binName")
fi
downloadBinary "$urlBin" "$dirInstall/$binName" "$target_version"