#!/bin/bash # LXS - Pterodactyl Installation Script # Description: Install Pterodactyl Panel + Wings # Author: LXS # Date: 2025 # Load LXS common library (colors, separator, run_spinner, loggers, helpers) LXS_RAW_BASE="${LXS_RAW_BASE:-https://git.hyko.cx/hykocx/lxs/raw/branch/main}" _lib=$(curl -fsSL "${LXS_RAW_BASE}/lib/common.sh") || { echo "Failed to fetch lib/common.sh" >&2; exit 1; } eval "$_lib" unset _lib export LXS_LOG_FILE="/tmp/lxs_pterodactyl.log" require_root "$0" "$@" # ═══════════════════════════════════════════════════════════════════════════ # Configuration # ═══════════════════════════════════════════════════════════════════════════ PANEL_DIR="/var/www/pterodactyl" WINGS_DIR="/etc/pterodactyl" DB_NAME="panel" DB_USER="pterodactyl" DB_HOST="127.0.0.1" # ═══════════════════════════════════════════════════════════════════════════ # Helper Functions # ═══════════════════════════════════════════════════════════════════════════ is_installed() { [ -d "$PANEL_DIR" ] && systemctl list-unit-files | grep -q "pteroq.service" } # ═══════════════════════════════════════════════════════════════════════════ # Installation Functions # ═══════════════════════════════════════════════════════════════════════════ install_dependencies() { apt_noninteractive run_spinner "Updating system..." "apt update -qq" run_spinner "Installing dependencies..." "apt install -y -qq software-properties-common curl -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'" run_spinner "Adding PHP repository..." "LC_ALL=C.UTF-8 add-apt-repository -y ppa:ondrej/php && apt update -qq" run_spinner "Installing PHP and services..." "apt install -y -qq php8.3 php8.3-{cli,gd,mysql,mbstring,bcmath,xml,fpm,curl,zip} mariadb-server nginx tar unzip git redis-server -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'" } install_composer() { run_spinner "Installing Composer..." "curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --quiet" } setup_database() { DB_PASS=$(generate_password) echo -e "${PURPLE}[*] Configuring database...${NC}" systemctl start mariadb && systemctl enable mariadb mysql -u root < /tmp/lxs_composer.log 2>&1 cp .env.example .env php artisan key:generate --force sed -i "s/DB_DATABASE=.*/DB_DATABASE=${DB_NAME}/" .env sed -i "s/DB_USERNAME=.*/DB_USERNAME=${DB_USER}/" .env sed -i "s/DB_PASSWORD=.*/DB_PASSWORD=${DB_PASS}/" .env run_spinner "Running migrations..." "php artisan migrate --seed --force" echo "" echo -e "${WHITE}${BOLD}Admin Account Setup${NC}" read -p "Email: " ADMIN_EMAIL read -p "Username: " ADMIN_USERNAME read -p "First Name: " ADMIN_FIRSTNAME read -p "Last Name: " ADMIN_LASTNAME read -sp "Password: " ADMIN_PASSWORD echo "" php artisan p:user:make --email="$ADMIN_EMAIL" --username="$ADMIN_USERNAME" --name-first="$ADMIN_FIRSTNAME" --name-last="$ADMIN_LASTNAME" --password="$ADMIN_PASSWORD" --admin=1 --no-interaction chown -R www-data:www-data $PANEL_DIR } configure_nginx() { local domain=$1 cat > /etc/nginx/sites-available/pterodactyl.conf <<'EOF' server { listen 80; server_name DOMAIN_PLACEHOLDER; root /var/www/pterodactyl/public; index index.php; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass unix:/run/php/php8.3-fpm.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } } EOF sed -i "s/DOMAIN_PLACEHOLDER/$domain/g" /etc/nginx/sites-available/pterodactyl.conf ln -sf /etc/nginx/sites-available/pterodactyl.conf /etc/nginx/sites-enabled/ rm -f /etc/nginx/sites-enabled/default nginx -t && systemctl restart nginx php8.3-fpm } configure_ssl() { local domain=$1 local email=$2 run_spinner "Installing Certbot..." "apt install -y -qq certbot python3-certbot-nginx -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'" certbot --nginx -d $domain --non-interactive --agree-tos --email $email --redirect 2>/dev/null [ $? -eq 0 ] && cd $PANEL_DIR && sed -i "s|APP_URL=.*|APP_URL=https://${domain}|" .env } setup_services() { cat > /etc/systemd/system/pteroq.service <<'EOF' [Unit] Description=Pterodactyl Queue Worker After=redis-server.service [Service] User=www-data Group=www-data Restart=always ExecStart=/usr/bin/php /var/www/pterodactyl/artisan queue:work --queue=high,standard,low --sleep=3 --tries=3 [Install] WantedBy=multi-user.target EOF systemctl enable --now redis-server pteroq.service (crontab -l 2>/dev/null; echo "* * * * * php /var/www/pterodactyl/artisan schedule:run >> /dev/null 2>&1") | crontab - } install_docker() { run_spinner "Installing Docker..." "curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg && \ echo \"deb [arch=\$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \$(lsb_release -cs) stable\" | tee /etc/apt/sources.list.d/docker.list > /dev/null && \ apt update -qq && apt install -y -qq docker-ce docker-ce-cli containerd.io -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'" systemctl enable --now docker } install_wings() { mkdir -p $WINGS_DIR run_spinner "Installing Wings..." "curl -L -o /usr/local/bin/wings https://github.com/pterodactyl/wings/releases/latest/download/wings_linux_amd64 && chmod +x /usr/local/bin/wings" cat > /etc/systemd/system/wings.service <<'EOF' [Unit] Description=Pterodactyl Wings After=docker.service Requires=docker.service [Service] User=root WorkingDirectory=/etc/pterodactyl ExecStart=/usr/local/bin/wings Restart=on-failure [Install] WantedBy=multi-user.target EOF systemctl daemon-reload } # ═══════════════════════════════════════════════════════════════════════════ # Main Installation # ═══════════════════════════════════════════════════════════════════════════ install_pterodactyl() { local start_time=$(date +%s) # Configure non-interactive mode globally export DEBIAN_FRONTEND=noninteractive export NEEDRESTART_MODE=a export NEEDRESTART_SUSPEND=1 echo -e "${WHITE}${BOLD}PTERODACTYL INSTALLATION${NC}\n" if is_installed; then echo -e "${YELLOW}Pterodactyl is already installed!${NC}" return 0 fi echo "[1/10] Installing dependencies..." install_dependencies && install_composer echo "" echo "[2/10] Setting up database..." setup_database echo "" echo "[3/10] Installing Panel..." install_panel echo "" echo "[4/10] Configuring Panel..." configure_panel echo "" echo "[5/10] Domain configuration..." read -p "Panel domain (e.g., panel.example.com): " PANEL_DOMAIN read -p "Wings domain (e.g., wings.example.com): " WINGS_DOMAIN read -p "Email for SSL: " SSL_EMAIL echo "" echo "[6/10] Configuring Nginx..." configure_nginx "$PANEL_DOMAIN" echo "" echo "[7/10] Configuring SSL..." configure_ssl "$PANEL_DOMAIN" "$SSL_EMAIL" echo "" echo "[8/10] Setting up services..." setup_services echo "" echo "[9/10] Installing Wings..." install_docker && install_wings echo "" echo "[10/10] Wings configuration..." echo -e "${YELLOW}Configure Wings from Panel: Admin -> Nodes -> Create Node${NC}" echo -e "${GRAY}Node settings:${NC}" echo -e " FQDN: ${CYAN}$WINGS_DOMAIN${NC}" echo -e " SSL: ${CYAN}Yes${NC}" echo -e " Port: ${CYAN}8080${NC}" echo "" read -p "Press Enter when ready to paste Wings config..." echo -e "${CYAN}Paste config and press Ctrl+D:${NC}" cat > /etc/pterodactyl/config.yml [ -s /etc/pterodactyl/config.yml ] && systemctl enable --now wings ufw_allow 80/tcp "Pterodactyl HTTP" ufw_allow 443/tcp "Pterodactyl HTTPS" ufw_allow 8080/tcp "Pterodactyl Wings daemon" ufw_allow 2022/tcp "Pterodactyl Wings SFTP" local duration=$(( $(date +%s) - start_time )) echo "" echo -e "${GREEN}${BOLD}Installation Completed!${NC}" echo -e "${GRAY}Time: $((duration / 60))m $((duration % 60))s${NC}" echo "" echo -e "${CYAN}Panel: ${BOLD}https://$PANEL_DOMAIN${NC}" echo -e "${CYAN}Wings: ${BOLD}$WINGS_DOMAIN:8080${NC}" echo "" echo -e "${WHITE}Database Credentials:${NC}" echo -e " Host: $DB_HOST" echo -e " Database: $DB_NAME" echo -e " User: $DB_USER" echo -e " Password: $DB_PASS" } show_status() { echo -e "${WHITE}${BOLD}PTERODACTYL STATUS${NC}\n" if ! is_installed; then echo -e "${RED}Pterodactyl is not installed!${NC}" return 1 fi systemctl is-active --quiet pteroq && echo -e "${GREEN}[✓] Panel Queue${NC}" || echo -e "${RED}[✗] Panel Queue${NC}" systemctl is-active --quiet nginx && echo -e "${GREEN}[✓] Web Server${NC}" || echo -e "${RED}[✗] Web Server${NC}" systemctl is-active --quiet wings && echo -e "${GREEN}[✓] Wings${NC}" || echo -e "${YELLOW}[!] Wings (needs config)${NC}" return 0 } # ═══════════════════════════════════════════════════════════════════════════ # Main Menu # ═══════════════════════════════════════════════════════════════════════════ show_menu() { clear echo -e "${WHITE}${BOLD}PTERODACTYL MANAGEMENT${NC}\n" echo -e " ${GREEN}[1]${NC} Install Pterodactyl" echo -e " ${CYAN}[2]${NC} View Status" echo -e " ${RED}[0]${NC} Back to main menu" echo "" echo -n "Choice [0-2]: " } main() { while true; do show_menu read -r choice echo "" case $choice in 1) install_pterodactyl ;; 2) show_status ;; 0) return 0 ;; *) echo -e "${RED}Invalid option${NC}" ;; esac echo "" read -p "Press Enter to continue..." done } main