-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsetup_payram.sh
More file actions
executable file
·3960 lines (3468 loc) · 142 KB
/
setup_payram.sh
File metadata and controls
executable file
·3960 lines (3468 loc) · 142 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
set -euo pipefail
# =============================================================================
# PayRam Universal Setup Script v3
# =============================================================================
# A universal, cross-platform setup script for PayRam crypto payment gateway
# Supports: Ubuntu, Debian, CentOS, RHEL, Fedora, Arch, Alpine
# =============================================================================
# --- GLOBAL SYSTEM INFORMATION ---
# Note: declare -g (bash 4.2+) is not used here; plain assignments work on bash 3.2+ (macOS default)
OS_FAMILY=""
OS_DISTRO=""
OS_VERSION=""
PACKAGE_MANAGER=""
# Initialize original user information early
if [[ -n "${SUDO_USER:-}" ]]; then
ORIGINAL_USER="$SUDO_USER"
ORIGINAL_HOME=$(eval echo "~$SUDO_USER")
else
ORIGINAL_USER="$(whoami)"
ORIGINAL_HOME="$HOME"
fi
SERVICE_MANAGER=""
INSTALL_METHOD=""
SCRIPT_DIR="${PWD}"
LOG_FILE="/tmp/payram-setup.log"
PAYRAM_REMOTE_URL="https://payram.com/setup_payram.sh"
# Initialize directory variables with defaults
PAYRAM_INFO_DIR="${HOME}/.payraminfo"
PAYRAM_CORE_DIR="${HOME}/.payram-core"
build_remote_command() {
local mode="${1:-sudo}"
shift || true
local args="$*"
if [[ "$mode" == "sudo" ]]; then
if [[ -n "$args" ]]; then
printf "sudo bash -c 'bash <(curl -fsSL %s) %s'" "$PAYRAM_REMOTE_URL" "$args"
else
printf "sudo bash -c 'bash <(curl -fsSL %s)'" "$PAYRAM_REMOTE_URL"
fi
else
if [[ -n "$args" ]]; then
printf "bash <(curl -fsSL %s) %s" "$PAYRAM_REMOTE_URL" "$args"
else
printf "bash <(curl -fsSL %s)" "$PAYRAM_REMOTE_URL"
fi
fi
}
# --- CORE UTILITY FUNCTIONS ---
# Enhanced logging with timestamps
log() {
local level="$1"
shift
local message="$*"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
case "$level" in
ERROR) print_color "red" "❌ $message" ;;
WARN) print_color "yellow" "⚠️ $message" ;;
INFO) print_color "blue" "ℹ️ $message" ;;
SUCCESS) print_color "green" "✅ $message" ;;
DEBUG) [[ "${DEBUG:-0}" == "1" ]] && print_color "gray" "🔍 $message" ;;
*) echo "$message" ;;
esac
}
# Progress indicator with improved spacing
show_progress() {
local current=$1
local total=$2
local description="$3"
local percent=$((current * 100 / total))
local completed=$((current * 50 / total))
local remaining=$((50 - completed))
# Add space above progress bar
echo
printf "🚀 [%s%s] %d%% - %s" \
"$(printf "%${completed}s" | tr ' ' '=')" \
"$(printf "%${remaining}s" | tr ' ' '-')" \
"$percent" \
"$description"
if [[ $current -eq $total ]]; then
echo
# Add space below when progress is complete
echo
else
echo
# Add space below for ongoing progress
echo
fi
}
# Check if script is run as root (with better UX)
check_privileges() {
if [ "$(id -u)" -ne 0 ]; then
local rerun_command
rerun_command="$(build_remote_command sudo "$@")"
log "ERROR" "This script requires root privileges for system modifications"
echo
print_color "yellow" "Please rerun with sudo at the beginning, for example:"
print_color "blue" " $rerun_command"
echo
exit 1
fi
# Update user info if running as root via sudo
if [[ "$(id -u)" -eq 0 && -n "${SUDO_USER:-}" ]]; then
ORIGINAL_USER="$SUDO_USER"
ORIGINAL_HOME=$(eval echo "~$SUDO_USER")
elif [[ "$(id -u)" -eq 0 ]]; then
ORIGINAL_USER="root"
ORIGINAL_HOME="/root"
fi
log "INFO" "Running as root, original user: $ORIGINAL_USER"
}
# --- SYSTEM DETECTION MODULE ---
# Comprehensive OS detection
detect_system_info() {
log "INFO" "Detecting system information..."
show_progress 1 10 "Analyzing operating system..."
# Initialize variables
OS_FAMILY=""
OS_DISTRO=""
OS_VERSION=""
PACKAGE_MANAGER=""
SERVICE_MANAGER=""
INSTALL_METHOD=""
# Detect OS type
if [[ "$OSTYPE" == "darwin"* ]]; then
OS_FAMILY="macos"
OS_DISTRO="macos"
OS_VERSION=$(sw_vers -productVersion 2>/dev/null || echo "unknown")
PACKAGE_MANAGER="brew"
SERVICE_MANAGER="launchctl"
INSTALL_METHOD="homebrew"
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
OS_FAMILY="windows"
OS_DISTRO="windows"
PACKAGE_MANAGER="none"
SERVICE_MANAGER="none"
INSTALL_METHOD="manual"
elif [[ -f /etc/os-release ]]; then
source /etc/os-release
OS_DISTRO="${ID:-unknown}"
OS_VERSION="${VERSION_ID:-unknown}"
# Determine OS family and capabilities
case "$OS_DISTRO" in
ubuntu|debian|raspbian|mint|pop|elementary|kali|linuxmint)
OS_FAMILY="debian"
PACKAGE_MANAGER="apt"
INSTALL_METHOD="official"
;;
centos|rhel|rocky|almalinux|ol|amzn)
OS_FAMILY="rhel"
PACKAGE_MANAGER="yum"
[[ -x "$(command -v dnf)" ]] && PACKAGE_MANAGER="dnf"
INSTALL_METHOD="official"
;;
fedora)
OS_FAMILY="fedora"
PACKAGE_MANAGER="dnf"
INSTALL_METHOD="official"
;;
opensuse*|sles)
OS_FAMILY="opensuse"
PACKAGE_MANAGER="zypper"
INSTALL_METHOD="official"
;;
arch|manjaro|endeavouros|artix)
OS_FAMILY="arch"
PACKAGE_MANAGER="pacman"
INSTALL_METHOD="official"
;;
alpine)
OS_FAMILY="alpine"
PACKAGE_MANAGER="apk"
INSTALL_METHOD="official"
;;
void)
OS_FAMILY="void"
PACKAGE_MANAGER="xbps"
INSTALL_METHOD="fallback"
;;
*)
# Fallback: check ID_LIKE for derivative distros (e.g. raspbian has ID_LIKE=debian)
local id_like="${ID_LIKE:-}"
if [[ "$id_like" == *"debian"* || "$id_like" == *"ubuntu"* ]]; then
OS_FAMILY="debian"
PACKAGE_MANAGER="apt"
INSTALL_METHOD="official"
elif [[ "$id_like" == *"rhel"* || "$id_like" == *"centos"* || "$id_like" == *"fedora"* ]]; then
OS_FAMILY="rhel"
PACKAGE_MANAGER="dnf"
[[ -x "$(command -v yum)" ]] && PACKAGE_MANAGER="yum"
[[ -x "$(command -v dnf)" ]] && PACKAGE_MANAGER="dnf"
INSTALL_METHOD="official"
else
OS_FAMILY="linux"
PACKAGE_MANAGER="unknown"
INSTALL_METHOD="fallback"
fi
;;
esac
# Detect service manager
if [[ -x "$(command -v systemctl)" ]] && systemctl --version &>/dev/null; then
SERVICE_MANAGER="systemd"
elif [[ -x "$(command -v service)" ]]; then
SERVICE_MANAGER="sysvinit"
elif [[ -x "$(command -v rc-service)" ]]; then
SERVICE_MANAGER="openrc"
else
SERVICE_MANAGER="unknown"
fi
else
# Fallback detection
if command -v uname &>/dev/null; then
local uname_s=$(uname -s)
case "$uname_s" in
Linux) OS_FAMILY="linux"; INSTALL_METHOD="fallback" ;;
Darwin) OS_FAMILY="macos"; INSTALL_METHOD="homebrew" ;;
*) OS_FAMILY="unknown"; INSTALL_METHOD="manual" ;;
esac
else
OS_FAMILY="unknown"
INSTALL_METHOD="manual"
fi
fi
show_progress 3 10 "System detection complete"
# macOS: root can't reach Colima's user-owned socket without DOCKER_HOST
if [[ "$OS_FAMILY" == "macos" ]]; then
local colima_socket="$ORIGINAL_HOME/.colima/default/docker.sock"
if [[ -S "$colima_socket" ]]; then
export DOCKER_HOST="unix://$colima_socket"
log "INFO" "macOS: DOCKER_HOST set to Colima socket ($colima_socket)"
else
log "WARN" "macOS: Colima socket not found at $colima_socket — Docker commands may fail if Colima is not running"
fi
fi
# Display results
log "SUCCESS" "System Detection Results:"
log "INFO" " OS Family: $OS_FAMILY"
log "INFO" " Distribution: $OS_DISTRO"
log "INFO" " Version: $OS_VERSION"
log "INFO" " Package Manager: $PACKAGE_MANAGER"
log "INFO" " Service Manager: $SERVICE_MANAGER"
log "INFO" " Install Method: $INSTALL_METHOD"
# Validate compatibility
validate_system_compatibility
}
validate_system_compatibility() {
show_progress 4 10 "Validating system compatibility..."
case "$OS_FAMILY" in
macos|debian|rhel|fedora|arch|alpine)
log "SUCCESS" "System is fully supported"
;;
linux)
log "WARN" "Limited support - will attempt fallback installation"
;;
windows)
log "ERROR" "Windows is not directly supported. Please use WSL2 or Docker Desktop"
print_color "yellow" "Setup instructions:"
print_color "blue" " 1. Install WSL2: https://docs.microsoft.com/en-us/windows/wsl/install"
print_color "blue" " 2. Install Ubuntu in WSL2"
print_color "blue" " 3. Run this script inside WSL2"
exit 1
;;
unknown)
log "ERROR" "Unsupported operating system detected"
print_color "yellow" "Please install Docker and PostgreSQL manually, then run:"
print_color "blue" " curl -fsSL https://get.docker.com | sh"
exit 1
;;
esac
}
# --- UNIVERSAL PACKAGE MANAGEMENT MODULE ---
# Universal package manager wrapper
pkg_update() {
log "INFO" "Updating package lists for $PACKAGE_MANAGER..."
case "$PACKAGE_MANAGER" in
apt)
DEBIAN_FRONTEND=noninteractive apt update -qq
;;
yum)
yum check-update || true
;;
dnf)
dnf check-update || true
;;
zypper)
zypper refresh -q
;;
pacman)
pacman -Sy --noconfirm
;;
apk)
apk update -q
;;
xbps)
xbps-install -S
;;
brew)
su - "$ORIGINAL_USER" -c "brew update" || true
;;
*)
log "ERROR" "Unknown package manager: $PACKAGE_MANAGER"
return 1
;;
esac
}
pkg_install() {
local packages=("$@")
log "INFO" "Installing packages: ${packages[*]}"
case "$PACKAGE_MANAGER" in
apt)
DEBIAN_FRONTEND=noninteractive apt install -y "${packages[@]}"
;;
yum)
yum install -y "${packages[@]}"
;;
dnf)
dnf install -y "${packages[@]}"
;;
zypper)
zypper install -y "${packages[@]}"
;;
pacman)
pacman -S --noconfirm "${packages[@]}"
;;
apk)
apk add "${packages[@]}"
;;
xbps)
xbps-install -y "${packages[@]}"
;;
brew)
for package in "${packages[@]}"; do
su - "$ORIGINAL_USER" -c "brew install $package" || true
done
;;
*)
log "ERROR" "Cannot install packages with $PACKAGE_MANAGER"
return 1
;;
esac
}
# Universal service management
service_start() {
local service="$1"
log "INFO" "Starting service: $service"
case "$SERVICE_MANAGER" in
systemd)
systemctl start "$service"
;;
sysvinit)
service "$service" start
;;
openrc)
rc-service "$service" start
;;
launchctl)
log "INFO" "Service management on macOS is automatic"
;;
*)
log "WARN" "Cannot start service $service with $SERVICE_MANAGER"
;;
esac
}
service_enable() {
local service="$1"
log "INFO" "Enabling service: $service"
case "$SERVICE_MANAGER" in
systemd)
systemctl enable "$service"
;;
openrc)
rc-update add "$service" boot
;;
sysvinit|launchctl)
log "INFO" "Service auto-enable not required for $SERVICE_MANAGER"
;;
*)
log "WARN" "Cannot enable service $service with $SERVICE_MANAGER"
;;
esac
}
# Get appropriate package names for different systems
get_docker_prerequisites() {
case "$OS_FAMILY" in
debian)
echo "ca-certificates curl gnupg lsb-release apt-transport-https"
;;
rhel|fedora)
echo "curl yum-utils device-mapper-persistent-data lvm2"
;;
arch)
echo "" # No prerequisites needed
;;
alpine)
echo "" # No prerequisites needed
;;
macos)
echo "" # Homebrew handles dependencies
;;
*)
echo ""
;;
esac
}
get_postgresql_client_package() {
case "$OS_FAMILY" in
debian) echo "postgresql-client" ;;
rhel|fedora) echo "postgresql" ;;
arch) echo "postgresql" ;;
alpine) echo "postgresql-client" ;;
macos) echo "postgresql" ;;
*) echo "postgresql-client" ;;
esac
}
# --- DEPENDENCY MANAGEMENT MODULE ---
# Universal Docker installation
install_docker() {
log "INFO" "Installing Docker using $INSTALL_METHOD method..."
show_progress 5 10 "Installing Docker..."
# Check if Docker is already installed and working
if command -v docker &>/dev/null && docker info &>/dev/null; then
log "SUCCESS" "Docker is already installed and running"
docker --version
return 0
fi
case "$INSTALL_METHOD" in
official)
install_docker_official_repo
;;
homebrew)
install_docker_homebrew
;;
fallback)
install_docker_distribution_packages
;;
manual)
log "ERROR" "Manual Docker installation required for $OS_FAMILY"
print_color "yellow" "Please visit: https://docs.docker.com/get-docker/"
return 1
;;
esac
configure_docker_post_install
verify_docker_installation
}
install_docker_official_repo() {
local prereq_packages
prereq_packages=$(get_docker_prerequisites)
# Install prerequisites
if [[ -n "$prereq_packages" ]]; then
pkg_install $prereq_packages
fi
case "$OS_FAMILY" in
debian)
# Add Docker's official GPG key
curl -fsSL "https://download.docker.com/linux/$OS_DISTRO/gpg" | \
gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# Add repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/$OS_DISTRO $(lsb_release -cs) stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
pkg_update
pkg_install docker-ce docker-ce-cli containerd.io docker-compose-plugin
;;
rhel|fedora)
# Add Docker repository
local repo_url="https://download.docker.com/linux/centos/docker-ce.repo"
[[ "$OS_FAMILY" == "fedora" ]] && repo_url="https://download.docker.com/linux/fedora/docker-ce.repo"
if [[ "$PACKAGE_MANAGER" == "dnf" ]]; then
pkg_install dnf-plugins-core
dnf config-manager --add-repo "$repo_url"
else
# yum has config-manager via yum-utils
$PACKAGE_MANAGER config-manager --add-repo "$repo_url"
fi
pkg_install docker-ce docker-ce-cli containerd.io docker-compose-plugin
;;
arch)
pkg_install docker docker-compose
;;
alpine)
pkg_install docker docker-compose
;;
esac
}
install_docker_homebrew() {
# Check if Homebrew is installed
if ! su - "$ORIGINAL_USER" -c "command -v brew" &>/dev/null; then
log "INFO" "Installing Homebrew first..."
su - "$ORIGINAL_USER" -c '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
fi
# Install Colima (lightweight headless Docker runtime - no GUI needed) and Docker CLI
log "INFO" "Installing Colima and Docker CLI..."
su - "$ORIGINAL_USER" -c "brew install colima docker"
# Start Colima - this blocks until the Docker daemon is fully ready (no manual steps needed)
log "INFO" "Starting Colima Docker runtime..."
su - "$ORIGINAL_USER" -c "colima start"
# Enable Colima to auto-start on login
su - "$ORIGINAL_USER" -c "brew services start colima" || true
# Homebrew installs to /opt/homebrew/bin (Apple Silicon) or /usr/local/bin (Intel).
# Root's PATH doesn't include these — prepend both so docker/colima are findable.
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
log "SUCCESS" "Colima is running and Docker daemon is ready"
}
install_docker_distribution_packages() {
log "WARN" "Falling back to distribution packages..."
case "$OS_FAMILY" in
debian)
pkg_install docker.io docker-compose
;;
rhel|fedora)
pkg_install docker docker-compose
;;
*)
log "ERROR" "No fallback package available for $OS_FAMILY"
return 1
;;
esac
}
configure_docker_post_install() {
log "INFO" "Configuring Docker post-installation..."
# Skip service management for macOS
if [[ "$OS_FAMILY" != "macos" ]]; then
service_start docker
service_enable docker
# Add original user to docker group
if [[ "$ORIGINAL_USER" != "root" ]]; then
usermod -aG docker "$ORIGINAL_USER"
log "SUCCESS" "User $ORIGINAL_USER added to docker group"
log "WARN" "Please log out and back in for group changes to take effect"
fi
fi
}
wait_for_docker_macos() {
local max_attempts=30
local attempt=0
log "INFO" "Waiting for Docker Desktop to start..."
while ! docker info &>/dev/null; do
if [ $attempt -ge $max_attempts ]; then
log "ERROR" "Docker Desktop did not start within 5 minutes"
return 1
fi
sleep 10
((attempt++))
echo -n "."
done
echo
log "SUCCESS" "Docker Desktop is ready"
}
verify_docker_installation() {
log "INFO" "Verifying Docker installation..."
# Test Docker daemon
if ! docker info &>/dev/null; then
log "ERROR" "Docker is not running or not accessible"
return 1
fi
# Test with hello-world
if docker run --rm hello-world &>/dev/null; then
log "SUCCESS" "Docker is working correctly"
docker --version
return 0
else
log "ERROR" "Docker test failed"
return 1
fi
}
# PostgreSQL client installation
install_postgresql_client() {
show_progress 6 10 "Installing PostgreSQL client..."
local pg_package
pg_package=$(get_postgresql_client_package)
if command -v psql &>/dev/null; then
log "SUCCESS" "PostgreSQL client is already installed"
psql --version
else
log "INFO" "Installing PostgreSQL client..."
pkg_install "$pg_package"
if command -v psql &>/dev/null; then
log "SUCCESS" "PostgreSQL client installed successfully"
psql --version
else
log "ERROR" "PostgreSQL client installation failed"
return 1
fi
fi
}
# Main dependency installation orchestrator
install_all_dependencies() {
log "INFO" "Installing system dependencies..."
show_progress 4 10 "Preparing dependency installation..."
# Update package lists
pkg_update
# Install Docker
install_docker
# Install PostgreSQL client
install_postgresql_client
show_progress 7 10 "All dependencies installed successfully"
log "SUCCESS" "All system dependencies are ready"
}
# --- CONFIGURATION MANAGEMENT MODULE ---
# Fetch latest PayRam version from Docker Hub
fetch_latest_payram_version() {
local arch latest_version all_tags
arch=$(uname -m)
if command -v curl >/dev/null 2>&1; then
all_tags=$(curl -s --connect-timeout 5 --max-time 10 \
"https://registry.hub.docker.com/v2/repositories/payramapp/payram/tags/?page_size=100" 2>/dev/null \
| grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' \
| sed 's/.*"\([^"]*\)".*/\1/')
if [[ "$arch" == "arm64" || "$arch" == "aarch64" ]]; then
# arm64: strip -arm64, sort numerically (portable), re-append suffix
local base
base=$(echo "$all_tags" \
| grep -E '^[0-9]+\.[0-9]+\.[0-9]+-arm64$' \
| sed 's/-arm64$//' \
| sort -t. -k1,1n -k2,2n -k3,3n \
| tail -1)
[[ -n "$base" ]] && latest_version="${base}-arm64" || latest_version="latest-arm64"
else
# amd64: sort numerically (portable)
latest_version=$(echo "$all_tags" \
| grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' \
| sort -t. -k1,1n -k2,2n -k3,3n \
| tail -1)
[[ -z "$latest_version" ]] && latest_version="latest"
fi
else
[[ "$arch" == "arm64" || "$arch" == "aarch64" ]] && latest_version="latest-arm64" || latest_version="latest"
fi
echo "$latest_version"
}
# Configuration defaults
set_configuration_defaults() {
DEFAULT_IMAGE_TAG=$(fetch_latest_payram_version)
NETWORK_TYPE="mainnet"
SERVER="PRODUCTION"
IMAGE_TAG=""
POSTGRES_SSLMODE="prefer"
SSL_CERT_PATH=""
SSL_MODE=""
DOMAIN_NAME=""
AES_KEY=""
: "${NETWORK_CHOICE:=}"
}
# Initialize defaults early
set_configuration_defaults
# Dynamic directory paths based on original user
get_payram_directories() {
if [[ "$ORIGINAL_USER" == "root" ]]; then
PAYRAM_HOME="/root"
else
PAYRAM_HOME="$ORIGINAL_HOME"
fi
PAYRAM_INFO_DIR="$PAYRAM_HOME/.payraminfo"
PAYRAM_CORE_DIR="$PAYRAM_HOME/.payram-core"
log "INFO" "PayRam directories:"
log "INFO" " Home: $PAYRAM_HOME"
log "INFO" " Config: $PAYRAM_INFO_DIR"
log "INFO" " Data: $PAYRAM_CORE_DIR"
# Check disk space availability
check_disk_space_requirements
}
# Check disk space requirements with user choice
check_disk_space_requirements() {
local minimum_space_gb=5
local recommended_space_gb=10
local minimum_space_kb=$((minimum_space_gb * 1024 * 1024))
local recommended_space_kb=$((recommended_space_gb * 1024 * 1024))
log "INFO" "Checking disk space requirements..."
# Get available space in KB for the target directory
local available_space_kb
if command -v df >/dev/null 2>&1; then
# Try different approaches to get disk space
available_space_kb=$(df "$PAYRAM_HOME" 2>/dev/null | awk 'NR==2 {print $4}')
if [[ -z "$available_space_kb" || "$available_space_kb" == "0" ]]; then
# Fallback to root filesystem if PAYRAM_HOME fails
available_space_kb=$(df / 2>/dev/null | awk 'NR==2 {print $4}')
fi
if [[ -z "$available_space_kb" ]]; then
available_space_kb=0
fi
local available_space_gb=$((available_space_kb / 1024 / 1024))
echo
print_color "blue" "💾 Disk Space Requirements:"
print_color "gray" " • Minimum: ${minimum_space_gb}GB required"
print_color "gray" " • Recommended: ${recommended_space_gb}GB for optimal performance"
print_color "gray" " • Available: ${available_space_gb}GB"
if [[ $available_space_kb -lt $minimum_space_kb ]]; then
print_color "red" " ❌ Insufficient disk space!"
echo
print_color "red" "❌ CRITICAL: You have ${available_space_gb}GB available, but ${minimum_space_gb}GB minimum is required."
print_color "yellow" " PayRam requires space for:"
print_color "gray" " • Docker images and containers (~3GB)"
print_color "gray" " • Database storage (~1GB)"
print_color "gray" " • Logs and temporary files (~1GB)"
echo
print_color "blue" "💡 Note: You can increase disk space after installation if needed."
echo
while true; do
print_color "yellow" "Do you want to continue anyway? (y/N): "
read -r response
case $response in
[Yy]|[Yy][Ee][Ss])
print_color "yellow" "⚠️ Proceeding with insufficient disk space - installation may fail..."
echo
return 0
;;
[Nn]|[Nn][Oo]|"")
print_color "red" "Installation cancelled. Please free up disk space and try again."
echo
print_color "blue" "💡 Tips to free up space:"
print_color "gray" " • Remove unused Docker images: docker system prune -a"
print_color "gray" " • Clean package cache: sudo apt clean (Ubuntu/Debian)"
print_color "gray" " • Remove old log files: sudo journalctl --vacuum-time=7d"
print_color "gray" " • Use a different installation directory with more space"
echo
return 1
;;
*)
print_color "red" "Please answer 'y' for yes or 'n' for no."
;;
esac
done
elif [[ $available_space_kb -lt $recommended_space_kb ]]; then
print_color "yellow" " ⚠️ Limited disk space available"
echo
print_color "yellow" "⚠️ WARNING: You have ${available_space_gb}GB available. ${recommended_space_gb}GB is recommended for optimal performance."
print_color "yellow" " With ${available_space_gb}GB you may experience:"
print_color "gray" " • Slower performance during heavy usage"
print_color "gray" " • Limited log retention"
print_color "gray" " • Potential space issues with large transactions"
echo
print_color "blue" "💡 Note: Installation will proceed but monitor disk usage closely."
print_color "yellow" "⚠️ Continuing with limited disk space..."
echo
return 0
else
print_color "green" " ✅ Sufficient disk space available"
fi
echo
else
print_color "yellow" "⚠️ Could not check disk space. Ensure ${minimum_space_gb}GB minimum (${recommended_space_gb}GB recommended) available"
echo
fi
return 0
}
# Non-interactive disk space check for validation purposes
check_disk_space_requirements_silent() {
local minimum_space_gb=5
local minimum_space_kb=$((minimum_space_gb * 1024 * 1024))
# Get available space in KB for the target directory
local available_space_kb
if command -v df >/dev/null 2>&1; then
# Try different approaches to get disk space
available_space_kb=$(df "$PAYRAM_HOME" 2>/dev/null | awk 'NR==2 {print $4}')
if [[ -z "$available_space_kb" || "$available_space_kb" == "0" ]]; then
# Fallback to root filesystem if PAYRAM_HOME fails
available_space_kb=$(df / 2>/dev/null | awk 'NR==2 {print $4}')
fi
if [[ -z "$available_space_kb" ]]; then
available_space_kb=0
fi
if [[ $available_space_kb -lt $minimum_space_kb ]]; then
return 1 # Insufficient space
else
return 0 # Sufficient space
fi
else
return 0 # Can't check, assume OK
fi
}
# Portable TCP connectivity check: nc if available, bash /dev/tcp fallback
tcp_check() {
local host="$1" port="$2" timeout="${3:-5}"
if command -v nc >/dev/null 2>&1; then
nc -z -w"$timeout" "$host" "$port" >/dev/null 2>&1
elif command -v curl >/dev/null 2>&1; then
# curl telnet:// is curl's own built-in TCP probe — does NOT need the telnet command
curl -s --connect-timeout "$timeout" --max-time "$timeout" \
"telnet://$host:$port" >/dev/null 2>&1
else
return 1
fi
}
# Check required ports for PayRam installation
check_required_ports() {
local ports=(5432 80 443 8080 8443)
local port_in_use=false
log "INFO" "Checking required ports for PayRam..."
if [[ "$OS_FAMILY" == "macos" ]]; then
# macOS: use nc (netcat, pre-installed on macOS) to attempt a real TCP connection
# on each port. This catches everything — OS listeners, Colima SSH tunnels,
# existing Docker containers — regardless of who owns the process or socket.
# lsof/docker ps both fail here because Colima uses per-user SSH tunnels that
# root cannot see, and the Docker socket is not at /var/run/docker.sock.
for port in "${ports[@]}"; do
if nc -z -w1 127.0.0.1 "$port" >/dev/null 2>&1; then
log "ERROR" "Port $port is already in use"
print_color "red" "❌ Port $port is already in use by another service"
port_in_use=true
else
log "INFO" "Port $port is available"
fi
done
else
# Linux: use ss, fallback to netstat
local check_cmd=()
if command -v ss >/dev/null 2>&1; then
check_cmd=(ss -tuln)
elif command -v netstat >/dev/null 2>&1; then
check_cmd=(netstat -tuln)
else
log "WARN" "Neither 'ss' nor 'netstat' available - skipping port check"
return 0
fi
for port in "${ports[@]}"; do
if "${check_cmd[@]}" 2>/dev/null | grep -E ":$port[[:space:]]|:$port$" >/dev/null 2>&1; then
log "ERROR" "Port $port is already in use"
print_color "red" "❌ Port $port is already in use by another service"
port_in_use=true
else
log "INFO" "Port $port is available"
fi
done
fi
if [[ "$port_in_use" == true ]]; then
echo
print_color "red" "❌ CRITICAL: Required ports are in use. Please free them or modify the script to use different ports."
print_color "yellow" "💡 To check what's using a port:"
print_color "gray" " sudo lsof -i :PORT"
echo
exit 1
fi
log "SUCCESS" "All required ports are available"
}
# Enhanced database configuration with better UX
configure_database() {
show_progress 8 10 "Configuring database connection..."
echo
print_color "bold" "📊 Database Configuration"
echo
print_color "yellow" "PayRam stores business data in PostgreSQL:"
print_color "gray" " • 📈 Transaction history and status"
print_color "gray" " • ⚙️ System configurations and settings"
print_color "gray" " • 👥 Merchant accounts and API credentials"
print_color "gray" " • 📊 Analytics and reporting data"
echo
print_color "green" "� Security: Private keys are NOT stored in database"
print_color "gray" " • Cold wallet keys: Never on server"
print_color "gray" " • Deposit keys: Not stored, smart sweep used"
print_color "gray" " • Hot wallet keys: Encrypted separately with AES-256"
echo
print_color "blue" "1) External PostgreSQL Database (Recommended for Production)"
print_color "gray" " • Your database runs on a separate server or cloud service"
print_color "gray" " • Better uptime, professional backups, and easy scaling"
print_color "gray" " • Ideal for live businesses and production environments"
print_color "gray" " • Requires: Existing PostgreSQL server with credentials"
echo
print_color "blue" "2) Containerized PostgreSQL (Quick Setup for Development)"
print_color "gray" " • Database runs inside a Docker container on this server"
print_color "gray" " • Fast setup with automatic configuration"
print_color "gray" " • ⚠️ Risk: Container loss = data loss (backup regularly!)"
print_color "gray" " • Best for: Testing, development, and small-scale usage"
echo
while true; do
read -e -p "Select option (1-2): " choice
case $choice in
1)
configure_external_database
break
;;
2)
configure_internal_database
break
;;
*)
print_color "red" "Invalid option. Please select 1 or 2."
;;
esac
done
}
configure_external_database() {
echo
print_color "bold" "🔗 External PostgreSQL Configuration"
print_color "yellow" "You'll need an existing PostgreSQL database with:"
print_color "gray" " • Database server accessible from this machine"