-
-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·1848 lines (1586 loc) · 61.5 KB
/
install.sh
File metadata and controls
executable file
·1848 lines (1586 loc) · 61.5 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
################################################################################
# Liberu Control Panel - Unified Installation Script
#
# This script provides a unified installation interface for:
# - Kubernetes deployment (recommended for production)
# - Docker Compose deployment (for development/small-scale)
# - Standalone deployment (traditional server setup)
#
# Supported Operating Systems:
# - Ubuntu LTS (20.04, 22.04, 24.04)
# - Debian (11, 12)
# - AlmaLinux / RHEL 8/9/10
# - Rocky Linux 8/9/10
# - CloudLinux 8/9/10 (Standalone only)
################################################################################
set -euo pipefail
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
MAGENTA='\033[0;35m'
NC='\033[0m' # No Color
BOLD='\033[1m'
# Script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Logging functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_header() {
echo ""
echo -e "${CYAN}${BOLD}=========================================${NC}"
echo -e "${CYAN}${BOLD}$1${NC}"
echo -e "${CYAN}${BOLD}=========================================${NC}"
echo ""
}
# Display banner
display_banner() {
clear
echo -e "${MAGENTA}${BOLD}"
cat << "EOF"
╔═══════════════════════════════════════════════════════════════╗
║ ║
║ Liberu Control Panel - Installation Wizard ║
║ ║
║ A modern web hosting control panel for: ║
║ - Virtual Hosts (NGINX) ║
║ - DNS Management (BIND/PowerDNS) ║
║ - Mail Services (Postfix/Dovecot) ║
║ - Database Management (MySQL/PostgreSQL) ║
║ - Docker/Kubernetes Orchestration ║
║ ║
╚═══════════════════════════════════════════════════════════════╝
EOF
echo -e "${NC}"
echo ""
}
# Check if running as root
check_root() {
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root (use sudo)"
exit 1
fi
}
# Detect operating system
detect_os() {
log_info "Detecting operating system..."
if [[ -f /etc/os-release ]]; then
. /etc/os-release
OS=$ID
OS_VERSION=$VERSION_ID
OS_NAME=$NAME
case $OS in
ubuntu)
if [[ ! "$OS_VERSION" =~ ^(20\.04|22\.04|24\.04) ]]; then
log_warning "Ubuntu version $OS_VERSION detected. Supported versions: 20.04, 22.04, 24.04"
read -p "Continue anyway? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
;;
debian)
if [[ ! "$OS_VERSION" =~ ^(11|12) ]]; then
log_warning "Debian version $OS_VERSION detected. Supported versions: 11 (Bullseye), 12 (Bookworm)"
read -p "Continue anyway? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
;;
almalinux|rhel|rocky|cloudlinux)
if [[ ! "$OS_VERSION" =~ ^[8-9]|^10 ]]; then
log_warning "$OS_NAME version $OS_VERSION detected. Supported versions: 8, 9, 10"
read -p "Continue anyway? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
;;
*)
log_error "Unsupported operating system: $OS_NAME"
log_info "Supported systems: Ubuntu LTS, Debian, AlmaLinux, RHEL, Rocky Linux, CloudLinux"
exit 1
;;
esac
log_success "Detected: $OS_NAME $OS_VERSION"
else
log_error "Cannot detect OS. /etc/os-release not found."
exit 1
fi
}
# Display installation menu
display_menu() {
log_header "Installation Method Selection"
echo -e "${BOLD}Choose your installation method:${NC}"
echo ""
echo -e "${GREEN}1)${NC} ${BOLD}Kubernetes${NC} (Recommended for Production)"
echo " - Full container orchestration"
echo " - Auto-scaling and load balancing"
echo " - High availability and self-healing"
echo " - Supports: Self-managed, AWS EKS, Azure AKS, GCP GKE, DigitalOcean"
echo ""
echo -e "${GREEN}2)${NC} ${BOLD}Docker Compose${NC} (Development/Small-Scale)"
echo " - Container-based deployment"
echo " - Easy local development"
echo " - Simple multi-container setup"
echo " - Best for single-server deployments"
echo ""
echo -e "${GREEN}3)${NC} ${BOLD}Standalone${NC} (Traditional Server)"
echo " - Direct server installation"
echo " - Traditional NGINX/Apache setup"
echo " - Standard Linux services"
echo " - No container overhead"
echo ""
echo -e "${GREEN}4)${NC} ${BOLD}Standalone DNS Only${NC} (DNS Cluster Node)"
echo " - DNS server only installation"
echo " - BIND9 or PowerDNS"
echo " - Ready for DNS cluster"
echo " - Lightweight nameserver setup"
echo ""
echo -e "${GREEN}5)${NC} ${BOLD}Exit${NC}"
echo ""
}
# Install prerequisites for all methods
install_common_prerequisites() {
log_info "Installing common prerequisites..."
case $OS in
ubuntu)
apt-get update
apt-get install -y \
curl \
wget \
git \
ca-certificates \
gnupg \
lsb-release \
software-properties-common \
apt-transport-https
;;
almalinux|rhel|rocky|cloudlinux)
dnf install -y \
curl \
wget \
git \
ca-certificates \
gnupg \
yum-utils
;;
esac
# Ensure /usr/local/bin is in PATH (needed for composer and other tools)
export PATH="$PATH:/usr/local/bin"
log_success "Common prerequisites installed"
}
# Kubernetes installation
install_kubernetes() {
log_header "Kubernetes Installation"
echo "This will install:"
echo " - Kubernetes cluster (if not using managed K8s)"
echo " - NGINX Ingress Controller"
echo " - cert-manager for SSL certificates"
echo " - Control Panel with all services"
echo " - MariaDB cluster"
echo " - Redis cache"
echo " - Mail services (Postfix/Dovecot)"
echo " - DNS cluster (PowerDNS)"
echo " - PHP multi-version support"
echo ""
read -p "Continue with Kubernetes installation? (Y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Nn]$ ]]; then
return
fi
# Check if install-k8s.sh exists
if [[ ! -f "$SCRIPT_DIR/install-k8s.sh" ]]; then
log_error "install-k8s.sh not found in $SCRIPT_DIR"
exit 1
fi
# Make scripts executable
chmod +x "$SCRIPT_DIR/install-k8s.sh"
chmod +x "$SCRIPT_DIR/install-control-panel.sh"
# Run Kubernetes installation
log_info "Running Kubernetes cluster setup..."
"$SCRIPT_DIR/install-k8s.sh"
# Check if K8s installation succeeded
if [[ $? -eq 0 ]]; then
log_success "Kubernetes cluster setup completed"
# Ask if user wants to install control panel now
echo ""
read -p "Install Control Panel and services now? (Y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
log_info "Running Control Panel installation..."
"$SCRIPT_DIR/install-control-panel.sh"
if [[ $? -eq 0 ]]; then
log_success "Control Panel installation completed!"
display_kubernetes_next_steps
else
log_error "Control Panel installation failed. Check logs above."
exit 1
fi
else
log_info "You can install the Control Panel later by running:"
log_info " sudo ./install-control-panel.sh"
fi
else
log_error "Kubernetes installation failed. Check logs above."
exit 1
fi
}
# Docker Compose installation
install_docker() {
log_header "Docker Compose Installation"
echo "This will install:"
echo " - Docker Engine"
echo " - Docker Compose"
echo " - Control Panel application"
echo " - MariaDB/PostgreSQL database"
echo " - NGINX reverse proxy"
echo " - Mail services (Postfix/Dovecot)"
echo " - DNS server (BIND9)"
echo " - Let's Encrypt SSL automation"
echo ""
read -p "Continue with Docker Compose installation? (Y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Nn]$ ]]; then
return
fi
# Install Docker
install_docker_engine
# Check if setup.sh exists
if [[ ! -f "$SCRIPT_DIR/setup.sh" ]]; then
log_error "setup.sh not found in $SCRIPT_DIR"
exit 1
fi
# Check if docker-compose.yml exists
if [[ ! -f "$SCRIPT_DIR/docker-compose.yml" ]]; then
log_error "docker-compose.yml not found in $SCRIPT_DIR"
exit 1
fi
# Create secrets directory and files if they don't exist
setup_docker_secrets
# Setup .env file
setup_env_file
# Run setup script
log_info "Running Docker Compose setup..."
chmod +x "$SCRIPT_DIR/setup.sh"
# Run setup with default answers for automation
cd "$SCRIPT_DIR"
log_info "Building and starting Docker containers..."
docker-compose build
docker-compose up -d
log_success "Docker Compose setup completed!"
display_docker_next_steps
}
# Install Docker Engine
install_docker_engine() {
log_info "Installing Docker Engine..."
# Check if Docker is already installed
if command -v docker &> /dev/null; then
log_success "Docker is already installed: $(docker --version)"
return
fi
case $OS in
ubuntu)
# Add Docker's official GPG key
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
# Set up the repository
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker Engine
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
;;
debian)
# Add Docker's official GPG key
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
# Set up the repository
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
$(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker Engine
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
;;
almalinux|rhel|rocky)
# Add Docker repository
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# Install Docker Engine
dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
;;
esac
# Start and enable Docker
systemctl start docker
systemctl enable docker
log_success "Docker Engine installed successfully"
}
# Setup Docker secrets
setup_docker_secrets() {
log_info "Setting up Docker secrets..."
mkdir -p "$SCRIPT_DIR/secrets"
# Generate database root password if not exists
if [[ ! -f "$SCRIPT_DIR/secrets/db_root_password.txt" ]]; then
openssl rand -base64 32 > "$SCRIPT_DIR/secrets/db_root_password.txt"
log_success "Generated database root password"
fi
# Generate database password if not exists
if [[ ! -f "$SCRIPT_DIR/secrets/db_password.txt" ]]; then
openssl rand -base64 32 > "$SCRIPT_DIR/secrets/db_password.txt"
log_success "Generated database password"
fi
chmod 600 "$SCRIPT_DIR/secrets"/*.txt
}
# Setup .env file
setup_env_file() {
log_info "Setting up environment file..."
if [[ ! -f "$SCRIPT_DIR/.env" ]]; then
if [[ -f "$SCRIPT_DIR/.env.example" ]]; then
cp "$SCRIPT_DIR/.env.example" "$SCRIPT_DIR/.env"
# Generate APP_KEY
if command -v php &> /dev/null; then
cd "$SCRIPT_DIR"
# Try to generate key using artisan
if [[ -f "artisan" ]]; then
APP_KEY=$(php artisan key:generate --show 2>/dev/null || echo "base64:$(openssl rand -base64 32)")
else
APP_KEY="base64:$(openssl rand -base64 32)"
fi
else
APP_KEY="base64:$(openssl rand -base64 32)"
fi
# Update .env with generated key
sed -i "s|APP_KEY=.*|APP_KEY=$APP_KEY|" "$SCRIPT_DIR/.env"
log_success "Created .env file from .env.example"
else
log_error ".env.example not found"
exit 1
fi
else
log_info ".env file already exists"
fi
# Prompt for domain name
echo ""
read -p "Enter your domain name (e.g., control.example.com): " DOMAIN
DOMAIN=${DOMAIN:-localhost}
read -p "Enter your email for Let's Encrypt (e.g., admin@example.com): " EMAIL
EMAIL=${EMAIL:-admin@example.com}
# Update .env file
sed -i "s|CONTROL_PANEL_DOMAIN=.*|CONTROL_PANEL_DOMAIN=$DOMAIN|" "$SCRIPT_DIR/.env"
sed -i "s|LETSENCRYPT_EMAIL=.*|LETSENCRYPT_EMAIL=$EMAIL|" "$SCRIPT_DIR/.env"
log_success "Environment file configured"
}
# Standalone installation
install_standalone() {
log_header "Standalone Installation"
echo "This will install directly on your server:"
echo " - NGINX web server"
echo " - PHP-FPM (latest version)"
echo " - MariaDB/MySQL database"
echo " - Redis cache"
echo " - Postfix mail server"
echo " - Dovecot IMAP/POP3 server"
echo " - BIND9 DNS server"
echo " - Control Panel application"
echo " - Certbot for SSL certificates"
echo ""
read -p "Continue with standalone installation? (Y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Nn]$ ]]; then
return
fi
# Install services based on OS
case $OS in
ubuntu|debian)
install_standalone_ubuntu
;;
almalinux|rhel|rocky|cloudlinux)
install_standalone_rhel
;;
esac
# Setup the control panel application
setup_standalone_app
log_success "Standalone installation completed!"
display_standalone_next_steps
}
# Install standalone services on Ubuntu
install_standalone_ubuntu() {
log_info "Installing services on Ubuntu/Debian..."
# Add PHP repository
if [[ "$OS" == "ubuntu" ]]; then
add-apt-repository -y ppa:ondrej/php
elif [[ "$OS" == "debian" ]]; then
# Add Sury PHP repository for Debian
apt-get install -y lsb-release apt-transport-https ca-certificates wget
wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg
echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/php.list
fi
apt-get update
# Install NGINX
log_info "Installing NGINX..."
apt-get install -y nginx
# Install PHP 8.2, 8.3, 8.4, and 8.5 with commonly used extensions
log_info "Installing PHP 8.2, 8.3, 8.4, and 8.5 with extensions..."
for PHP_VER in 8.2 8.3 8.4 8.5; do
apt-get install -y \
php${PHP_VER}-fpm \
php${PHP_VER}-cli \
php${PHP_VER}-common \
php${PHP_VER}-mysql \
php${PHP_VER}-zip \
php${PHP_VER}-gd \
php${PHP_VER}-mbstring \
php${PHP_VER}-curl \
php${PHP_VER}-xml \
php${PHP_VER}-bcmath \
php${PHP_VER}-redis \
php${PHP_VER}-intl \
php${PHP_VER}-opcache \
php${PHP_VER}-sqlite3 || log_warning "Some PHP ${PHP_VER} packages may not be available"
done
# Install MariaDB
log_info "Installing MariaDB..."
apt-get install -y mariadb-server mariadb-client
# Install Redis
log_info "Installing Redis..."
apt-get install -y redis-server
# Install Composer (latest)
log_info "Installing Composer..."
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Install Node.js (latest LTS)
log_info "Installing Node.js (latest LTS)..."
curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
apt-get install -y nodejs
# Install Certbot
log_info "Installing Certbot..."
apt-get install -y certbot python3-certbot-nginx
# Install mail services
log_info "Installing mail services..."
apt-get install -y postfix dovecot-core dovecot-imapd dovecot-pop3d
# Install BIND9
log_info "Installing BIND9..."
apt-get install -y bind9 bind9utils bind9-doc
# Start and enable services
systemctl start nginx
systemctl enable nginx
# Start all installed PHP-FPM versions; PHP 8.3 is the primary for the control panel
for PHP_VER in 8.2 8.3 8.4 8.5; do
systemctl start php${PHP_VER}-fpm 2>/dev/null || true
systemctl enable php${PHP_VER}-fpm 2>/dev/null || true
done
systemctl start mariadb
systemctl enable mariadb
systemctl start redis-server
systemctl enable redis-server
log_success "All services installed and started"
}
# Install standalone services on RHEL/AlmaLinux/Rocky/CloudLinux
install_standalone_rhel() {
log_info "Installing services on RHEL/AlmaLinux/Rocky/CloudLinux..."
# Determine major OS version for conditional package selection
local OS_MAJOR_VERSION
OS_MAJOR_VERSION=$(echo "$OS_VERSION" | cut -d'.' -f1)
# Enable EPEL repository
dnf install -y epel-release
# Add Remi repository for PHP
dnf install -y https://rpms.remirepo.net/enterprise/remi-release-${OS_MAJOR_VERSION}.rpm
# Install PHP 8.3 as the primary system PHP via module (used by control panel)
dnf module reset php -y || true
dnf module enable php:remi-8.3 -y || true
log_info "Installing PHP 8.3 (primary) and extensions..."
dnf install -y \
php \
php-fpm \
php-cli \
php-common \
php-mysqlnd \
php-zip \
php-gd \
php-mbstring \
php-curl \
php-xml \
php-bcmath \
php-redis \
php-intl \
php-opcache \
php-pdo \
php-json
# Install PHP 8.2, 8.4, and 8.5 as additional versions via Remi SCL packages
for PHP_SHORT_VER in 82 84 85; do
PHP_DOT_VER="${PHP_SHORT_VER:0:1}.${PHP_SHORT_VER:1:1}"
log_info "Installing PHP ${PHP_DOT_VER} (additional) and extensions..."
dnf install -y --enablerepo=remi \
php${PHP_SHORT_VER} \
php${PHP_SHORT_VER}-php-fpm \
php${PHP_SHORT_VER}-php-cli \
php${PHP_SHORT_VER}-php-common \
php${PHP_SHORT_VER}-php-mysqlnd \
php${PHP_SHORT_VER}-php-zip \
php${PHP_SHORT_VER}-php-gd \
php${PHP_SHORT_VER}-php-mbstring \
php${PHP_SHORT_VER}-php-curl \
php${PHP_SHORT_VER}-php-xml \
php${PHP_SHORT_VER}-php-bcmath \
php${PHP_SHORT_VER}-php-redis \
php${PHP_SHORT_VER}-php-intl \
php${PHP_SHORT_VER}-php-opcache \
php${PHP_SHORT_VER}-php-pdo || \
log_warning "Some PHP ${PHP_DOT_VER} packages may not be available"
done
# Install NGINX
log_info "Installing NGINX..."
dnf install -y nginx
# Install MariaDB
log_info "Installing MariaDB..."
dnf install -y mariadb-server mariadb
# Install Redis or Valkey (Redis-compatible) depending on OS version
# RHEL/AlmaLinux/Rocky/CloudLinux 10+ ships Valkey as the Redis replacement
log_info "Installing Redis/Valkey cache..."
if [[ "$OS_MAJOR_VERSION" -ge 10 ]]; then
log_info "Installing Valkey (Redis-compatible replacement) for version ${OS_MAJOR_VERSION}..."
dnf install -y valkey
systemctl start valkey
systemctl enable valkey
# Create a systemd alias so applications expecting the 'redis' unit still work
local VALKEY_UNIT
VALKEY_UNIT=$(systemctl cat valkey 2>/dev/null | awk '/^# /{print $2; exit}') || true
if [[ -n "$VALKEY_UNIT" && -f "$VALKEY_UNIT" ]]; then
ln -sf "$VALKEY_UNIT" /etc/systemd/system/redis.service 2>/dev/null || true
systemctl daemon-reload 2>/dev/null || true
fi
else
dnf install -y redis
systemctl start redis
systemctl enable redis
fi
# Install Composer (latest)
log_info "Installing Composer..."
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Install Node.js (latest LTS) via NodeSource
log_info "Installing Node.js (latest LTS)..."
curl -fsSL https://rpm.nodesource.com/setup_lts.x | bash -
dnf install -y nodejs
# Install Certbot
log_info "Installing Certbot..."
dnf install -y certbot python3-certbot-nginx
# Install mail services
log_info "Installing mail services..."
dnf install -y postfix dovecot
# Install BIND
log_info "Installing BIND..."
dnf install -y bind bind-utils
# Configure SELinux for web services
setsebool -P httpd_can_network_connect 1
setsebool -P httpd_can_network_connect_db 1
# Start and enable services
systemctl start nginx
systemctl enable nginx
# PHP 8.3 is the primary FPM for the control panel
systemctl start php-fpm
systemctl enable php-fpm
# Start additional PHP-FPM versions installed via Remi SCL
for PHP_SHORT_VER in 82 84 85; do
systemctl start php${PHP_SHORT_VER}-php-fpm 2>/dev/null || true
systemctl enable php${PHP_SHORT_VER}-php-fpm 2>/dev/null || true
done
systemctl start mariadb
systemctl enable mariadb
log_success "All services installed and started"
}
# Create the dedicated Control Panel service account
# This is the ONLY account that receives sudo privileges.
# Virtual host site users run PHP-FPM as their own accounts but have NO sudo.
create_control_panel_service_user() {
log_info "Creating Control Panel service account (cp-panel)..."
local CP_SERVICE_USER="cp-panel"
if ! id "$CP_SERVICE_USER" &>/dev/null; then
useradd --system --no-create-home --shell /usr/sbin/nologin \
--comment "Liberu Control Panel Service Account" \
"$CP_SERVICE_USER"
log_success "Created service account: $CP_SERVICE_USER"
else
log_info "Service account '$CP_SERVICE_USER' already exists"
fi
# Add the service account to the web server group so it can read web files
case $OS in
ubuntu|debian)
usermod -aG www-data "$CP_SERVICE_USER"
;;
almalinux|rhel|rocky|cloudlinux)
usermod -aG nginx "$CP_SERVICE_USER"
;;
esac
echo "$CP_SERVICE_USER"
}
# Create a dedicated PHP-FPM pool for the Control Panel service account
# so the panel's own PHP code runs as cp-panel (which has sudo) rather
# than as the shared www-data/nginx user.
create_control_panel_php_fpm_pool() {
local CP_SERVICE_USER="cp-panel"
local PHP_VERSION="8.3"
log_info "Creating PHP-FPM pool for Control Panel service account..."
# Determine pool.d directory based on OS
local POOL_DIR
case $OS in
ubuntu|debian)
POOL_DIR="/etc/php/${PHP_VERSION}/fpm/pool.d"
;;
*)
POOL_DIR="/etc/php-fpm.d"
;;
esac
local POOL_FILE="${POOL_DIR}/${CP_SERVICE_USER}.conf"
local SOCKET_PATH="/run/php/php${PHP_VERSION}-fpm-${CP_SERVICE_USER}.sock"
# The socket must be readable by the nginx worker process user
local WEB_SERVER_USER="www-data"
case $OS in
almalinux|rhel|rocky|cloudlinux)
WEB_SERVER_USER="nginx"
;;
esac
cat > "$POOL_FILE" << EOF
; PHP-FPM pool for the Liberu Control Panel service account
; This pool runs the control panel application as $CP_SERVICE_USER so that
; sudo commands issued by the control panel are executed under that account.
[$CP_SERVICE_USER]
user = $CP_SERVICE_USER
group = $CP_SERVICE_USER
listen = $SOCKET_PATH
listen.owner = $WEB_SERVER_USER
listen.group = $WEB_SERVER_USER
listen.mode = 0660
pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 5
EOF
chmod 644 "$POOL_FILE"
# Reload PHP-FPM to activate the pool
case $OS in
ubuntu|debian)
systemctl reload "php${PHP_VERSION}-fpm" || true
;;
*)
systemctl reload php-fpm || true
;;
esac
log_success "PHP-FPM pool created at $POOL_FILE (socket: $SOCKET_PATH)"
# Return the socket path for use in the nginx config
echo "$SOCKET_PATH"
}
# Setup sudo access for the web server user
setup_sudo_access() {
log_info "Configuring sudo access for Control Panel user..."
# The dedicated service account is the ONLY account that receives sudo.
# The shared web server user (www-data/nginx) and per-site user accounts
# do NOT receive any sudo privileges.
local CP_USER="cp-panel"
local SUDOERS_FILE="/etc/sudoers.d/control-panel"
# Write a targeted sudoers file granting ONLY the cp-panel service account
# passwordless sudo access to the system commands used by the Control Panel.
# The shared web server user (www-data/nginx) and per-site user accounts
# deliberately receive NO sudo privileges.
cat > "$SUDOERS_FILE" << EOF
# Liberu Control Panel - passwordless sudo for $CP_USER
# Generated by install.sh on $(date -u '+%Y-%m-%dT%H:%M:%SZ')
#
# IMPORTANT: Only the $CP_USER service account receives these privileges.
# Per-site system users (created for each virtual host) and the shared
# www-data/nginx user do NOT appear in this file and have no sudo access.
# Service management - restricted to the services managed by Control Panel
$CP_USER ALL=(root) NOPASSWD: /bin/systemctl reload nginx, /bin/systemctl restart nginx, /bin/systemctl status nginx
$CP_USER ALL=(root) NOPASSWD: /usr/bin/systemctl reload nginx, /usr/bin/systemctl restart nginx, /usr/bin/systemctl status nginx
$CP_USER ALL=(root) NOPASSWD: /bin/systemctl reload php*, /bin/systemctl restart php*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/systemctl reload php*, /usr/bin/systemctl restart php*
$CP_USER ALL=(root) NOPASSWD: /bin/systemctl reload named, /bin/systemctl restart named, /bin/systemctl reload bind9, /bin/systemctl restart bind9
$CP_USER ALL=(root) NOPASSWD: /usr/bin/systemctl reload named, /usr/bin/systemctl restart named, /usr/bin/systemctl reload bind9, /usr/bin/systemctl restart bind9
$CP_USER ALL=(root) NOPASSWD: /bin/systemctl reload postfix, /bin/systemctl restart postfix, /bin/systemctl reload dovecot, /bin/systemctl restart dovecot
$CP_USER ALL=(root) NOPASSWD: /usr/bin/systemctl reload postfix, /usr/bin/systemctl restart postfix, /usr/bin/systemctl reload dovecot, /usr/bin/systemctl restart dovecot
$CP_USER ALL=(root) NOPASSWD: /bin/systemctl reload mariadb, /bin/systemctl restart mariadb, /bin/systemctl reload mysql, /bin/systemctl restart mysql
$CP_USER ALL=(root) NOPASSWD: /usr/bin/systemctl reload mariadb, /usr/bin/systemctl restart mariadb, /usr/bin/systemctl reload mysql, /usr/bin/systemctl restart mysql
$CP_USER ALL=(root) NOPASSWD: /bin/systemctl reload redis, /bin/systemctl restart redis, /bin/systemctl reload redis-server, /bin/systemctl restart redis-server
$CP_USER ALL=(root) NOPASSWD: /usr/bin/systemctl reload redis, /usr/bin/systemctl restart redis, /usr/bin/systemctl reload redis-server, /usr/bin/systemctl restart redis-server
# NGINX management
$CP_USER ALL=(root) NOPASSWD: /usr/sbin/nginx -t
$CP_USER ALL=(root) NOPASSWD: /usr/bin/nginx -t
# File operations restricted to directories managed by Control Panel
$CP_USER ALL=(root) NOPASSWD: /bin/mv /tmp/* /etc/nginx/sites-available/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/mv /tmp/* /etc/nginx/sites-available/*
$CP_USER ALL=(root) NOPASSWD: /bin/chmod 644 /etc/nginx/sites-available/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/chmod 644 /etc/nginx/sites-available/*
$CP_USER ALL=(root) NOPASSWD: /bin/ln -s /etc/nginx/sites-available/* /etc/nginx/sites-enabled/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/ln -s /etc/nginx/sites-available/* /etc/nginx/sites-enabled/*
$CP_USER ALL=(root) NOPASSWD: /bin/rm /etc/nginx/sites-available/*, /bin/rm /etc/nginx/sites-enabled/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/rm /etc/nginx/sites-available/*, /usr/bin/rm /etc/nginx/sites-enabled/*
$CP_USER ALL=(root) NOPASSWD: /bin/mkdir -p /var/www/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/mkdir -p /var/www/*
$CP_USER ALL=(root) NOPASSWD: /bin/chmod 755 /var/www/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/chmod 755 /var/www/*
# chown is restricted: the target user must match the cp-user-* naming pattern
# used for per-site accounts, preventing ownership changes to root or system users
$CP_USER ALL=(root) NOPASSWD: /bin/chown -R cp-user-* /var/www/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/chown -R cp-user-* /var/www/*
$CP_USER ALL=(root) NOPASSWD: /bin/chown -R cp-user-*\:cp-user-* /var/www/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/chown -R cp-user-*\:cp-user-* /var/www/*
# PHP-FPM per-user pool management (for virtual host isolation)
$CP_USER ALL=(root) NOPASSWD: /bin/mv /tmp/* /etc/php/*/fpm/pool.d/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/mv /tmp/* /etc/php/*/fpm/pool.d/*
$CP_USER ALL=(root) NOPASSWD: /bin/mv /tmp/* /etc/php-fpm.d/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/mv /tmp/* /etc/php-fpm.d/*
$CP_USER ALL=(root) NOPASSWD: /bin/chmod 644 /etc/php/*/fpm/pool.d/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/chmod 644 /etc/php/*/fpm/pool.d/*
$CP_USER ALL=(root) NOPASSWD: /bin/chmod 644 /etc/php-fpm.d/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/chmod 644 /etc/php-fpm.d/*
$CP_USER ALL=(root) NOPASSWD: /bin/rm -f /etc/php/*/fpm/pool.d/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/rm -f /etc/php/*/fpm/pool.d/*
$CP_USER ALL=(root) NOPASSWD: /bin/rm -f /etc/php-fpm.d/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/rm -f /etc/php-fpm.d/*
# Per-site user account management: only no-login accounts matching the cp-user-* pattern
# are permitted, preventing creation of privileged or arbitrary system users
$CP_USER ALL=(root) NOPASSWD: /usr/sbin/useradd --no-create-home --shell /usr/sbin/nologin cp-user-*
$CP_USER ALL=(root) NOPASSWD: /usr/sbin/useradd -M -s /usr/sbin/nologin cp-user-*
# DNS zone file management
$CP_USER ALL=(root) NOPASSWD: /bin/mkdir -p /etc/bind/zones
$CP_USER ALL=(root) NOPASSWD: /usr/bin/mkdir -p /etc/bind/zones
$CP_USER ALL=(root) NOPASSWD: /bin/mv /tmp/* /etc/bind/zones/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/mv /tmp/* /etc/bind/zones/*
$CP_USER ALL=(root) NOPASSWD: /bin/chown bind\:bind /etc/bind/zones/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/chown bind\:bind /etc/bind/zones/*
$CP_USER ALL=(root) NOPASSWD: /bin/chmod 644 /etc/bind/zones/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/chmod 644 /etc/bind/zones/*
$CP_USER ALL=(root) NOPASSWD: /bin/rm -f /etc/bind/zones/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/rm -f /etc/bind/zones/*
$CP_USER ALL=(root) NOPASSWD: /bin/sed -i * /etc/bind/named.conf*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/sed -i * /etc/bind/named.conf*
$CP_USER ALL=(root) NOPASSWD: /bin/bash -c echo * >> /etc/bind/named.conf*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/bash -c echo * >> /etc/bind/named.conf*
# DNS validation
$CP_USER ALL=(root) NOPASSWD: /usr/sbin/named-checkconf
$CP_USER ALL=(root) NOPASSWD: /usr/sbin/named-checkzone
# SSL certificate management
$CP_USER ALL=(root) NOPASSWD: /usr/bin/certbot
# Mail service management
$CP_USER ALL=(root) NOPASSWD: /usr/sbin/postmap /etc/postfix/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/postmap /etc/postfix/*
$CP_USER ALL=(root) NOPASSWD: /bin/mkdir -p /var/mail/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/mkdir -p /var/mail/*
$CP_USER ALL=(root) NOPASSWD: /bin/chown -R vmail\:vmail /var/mail/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/chown -R vmail\:vmail /var/mail/*
$CP_USER ALL=(root) NOPASSWD: /bin/chmod -R 700 /var/mail/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/chmod -R 700 /var/mail/*
$CP_USER ALL=(root) NOPASSWD: /bin/rm -rf /var/mail/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/rm -rf /var/mail/*
$CP_USER ALL=(root) NOPASSWD: /bin/cp /etc/postfix/* /etc/postfix/*.bak
$CP_USER ALL=(root) NOPASSWD: /usr/bin/cp /etc/postfix/* /etc/postfix/*.bak
$CP_USER ALL=(root) NOPASSWD: /bin/sed -i * /etc/postfix/*
$CP_USER ALL=(root) NOPASSWD: /usr/bin/sed -i * /etc/postfix/*
# PostgreSQL management
$CP_USER ALL=(postgres) NOPASSWD: /usr/bin/psql
$CP_USER ALL=(postgres) NOPASSWD: /usr/bin/createdb
$CP_USER ALL=(postgres) NOPASSWD: /usr/bin/dropdb
EOF
# Restrict permissions on the sudoers file (required by sudo)
chmod 440 "$SUDOERS_FILE"
# Validate the sudoers file syntax before leaving it in place
if visudo -c -f "$SUDOERS_FILE" &>/dev/null; then
log_success "sudo access configured for '$CP_USER' (sudoers: $SUDOERS_FILE)"
else
log_error "Generated sudoers file failed validation - removing to avoid lockout"
rm -f "$SUDOERS_FILE"
return 1
fi
}
# Setup standalone application
setup_standalone_app() {
log_info "Setting up Control Panel application..."
# Create web directory
WEB_DIR="/var/www/control-panel"
mkdir -p "$WEB_DIR"
# Copy application files
log_info "Copying application files..."
rsync -av --exclude='.git' --exclude='node_modules' --exclude='vendor' "$SCRIPT_DIR/" "$WEB_DIR/"
# Create the dedicated Control Panel service account and its PHP-FPM pool.
# This is done before setting file permissions so we can use it as the owner.
local CP_SERVICE_USER
CP_SERVICE_USER=$(create_control_panel_service_user)
# Set permissions - owned by the service account, readable by the web server group
chown -R "$CP_SERVICE_USER:$CP_SERVICE_USER" "$WEB_DIR"
chmod -R 755 "$WEB_DIR"
chmod -R 775 "$WEB_DIR/storage"
chmod -R 775 "$WEB_DIR/bootstrap/cache"
# Create the dedicated PHP-FPM pool that runs the control panel as cp-panel
local CP_FPM_SOCKET
CP_FPM_SOCKET=$(create_control_panel_php_fpm_pool)
# Grant the cp-panel service account the sudo access it needs to manage
# system services. Per-site users and www-data/nginx do NOT get sudo.
setup_sudo_access
# Setup .env file
if [[ ! -f "$WEB_DIR/.env" ]]; then
cp "$WEB_DIR/.env.example" "$WEB_DIR/.env"
# Generate APP_KEY
cd "$WEB_DIR"
php artisan key:generate
fi
# Install Composer dependencies
log_info "Installing Composer dependencies..."
cd "$WEB_DIR"
composer install --no-dev --optimize-autoloader
# Install NPM dependencies and build assets
log_info "Building frontend assets..."
npm install
npm run build
# Setup database
setup_standalone_database