Files
pterodactyl_addon/install.sh
2026-06-11 20:33:44 -05:00

275 lines
14 KiB
Bash

#!/usr/bin/env bash
# =============================================================================
# Pterodactyl Advanced Admin Addons — Auto Installer
# Author : Ball Studios <https://git.balls.studio>
# Repo : https://git.balls.studio/BallStudios/pterodactyl_addon
# License: MIT
#
# Usage (run as root or with sudo):
# bash <(curl -fsSL https://git.balls.studio/BallStudios/pterodactyl_addon/raw/branch/main/install.sh)
#
# Or with a custom panel path:
# bash <(curl -fsSL https://git.balls.studio/BallStudios/pterodactyl_addon/raw/branch/main/install.sh) /var/www/pterodactyl
# =============================================================================
set -euo pipefail
# ── Colours ──────────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
success() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }
step() { echo -e "\n${BOLD}$*${NC}"; }
# ── Config ───────────────────────────────────────────────────────────────────
PANEL_DIR="${1:-/var/www/pterodactyl}"
REPO_URL="https://git.balls.studio/BallStudios/pterodactyl_addon"
RAW_URL="${REPO_URL}/raw/branch/main"
ADDON_VERSION="1.0.0"
TMP_DIR="$(mktemp -d)"
BACKUP_DIR="${PANEL_DIR}/.addon_backup_$(date +%Y%m%d_%H%M%S)"
cleanup() { rm -rf "$TMP_DIR"; }
trap cleanup EXIT
# ── Banner ───────────────────────────────────────────────────────────────────
echo -e "
${BOLD}${CYAN}╔══════════════════════════════════════════════════════╗
║ Pterodactyl Advanced Admin Addons v${ADDON_VERSION}
║ by Ball Studios — git.balls.studio ║
╚══════════════════════════════════════════════════════╝${NC}
"
# ── Preflight checks ─────────────────────────────────────────────────────────
step "Running preflight checks..."
[[ $EUID -eq 0 ]] || error "This script must be run as root (or with sudo)."
command -v php >/dev/null 2>&1 || error "PHP is not installed or not in PATH."
command -v curl >/dev/null 2>&1 || error "curl is not installed."
command -v git >/dev/null 2>&1 || error "git is not installed."
[[ -f "${PANEL_DIR}/artisan" ]] || \
error "Pterodactyl panel not found at '${PANEL_DIR}'. Pass the correct path as first argument."
PHP_BIN="$(command -v php)"
PHP_VER="$($PHP_BIN -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')"
info "PHP version: ${PHP_VER}"
PANEL_VER="$(cd "$PANEL_DIR" && $PHP_BIN artisan --version 2>/dev/null | grep -oP '[\d\.]+' | head -1 || echo unknown)"
info "Panel artisan version: ${PANEL_VER}"
# Warn but don't block on panel version — admin may know what they're doing
if [[ "$PANEL_VER" != "1.12"* ]] && [[ "$PANEL_VER" != "unknown" ]]; then
warn "This addon targets Panel v1.12.x. Detected: v${PANEL_VER}. Proceed at your own risk."
fi
success "Preflight checks passed."
# ── Confirm ───────────────────────────────────────────────────────────────────
echo ""
echo -e "${YELLOW}The following actions will be taken:${NC}"
echo " 1. Backup existing panel files to ${BACKUP_DIR}"
echo " 2. Clone addon into ${TMP_DIR}"
echo " 3. Copy addon files into ${PANEL_DIR}"
echo " 4. Apply minimal patches to 3 core files (documented in patches/PATCHES.md)"
echo " 5. Run database migrations"
echo " 6. Seed default roles"
echo " 7. Rebuild frontend assets (yarn build:production)"
echo " 8. Clear panel caches"
echo ""
read -rp "Continue? [y/N] " CONFIRM
[[ "${CONFIRM,,}" == "y" ]] || { info "Aborted."; exit 0; }
# ── Backup ────────────────────────────────────────────────────────────────────
step "Backing up panel to ${BACKUP_DIR}..."
mkdir -p "$BACKUP_DIR"
cp "${PANEL_DIR}/app/Providers/AppServiceProvider.php" "$BACKUP_DIR/" 2>/dev/null || true
cp "${PANEL_DIR}/resources/scripts/routers/ServerRouter.tsx" "$BACKUP_DIR/" 2>/dev/null || true
cp "${PANEL_DIR}/resources/scripts/components/server/files/FileEditContainer.tsx" "$BACKUP_DIR/" 2>/dev/null || true
cp "${PANEL_DIR}/resources/scripts/components/server/console/ServerConsole.tsx" "$BACKUP_DIR/" 2>/dev/null || true
success "Backup complete → ${BACKUP_DIR}"
# ── Clone Addon ───────────────────────────────────────────────────────────────
step "Cloning addon repository..."
git clone --depth=1 "$REPO_URL" "$TMP_DIR/addon" \
|| error "Failed to clone ${REPO_URL}. Check network access and repository visibility."
success "Clone complete."
# ── Copy Addon Files ──────────────────────────────────────────────────────────
step "Copying addon files into panel..."
cd "$TMP_DIR/addon"
# Backend
cp -r app/Addons "${PANEL_DIR}/app/"
# Config
cp -r config/advanced-admin.php "${PANEL_DIR}/config/"
cp -r config/addon-commands "${PANEL_DIR}/config/"
# Migrations
mkdir -p "${PANEL_DIR}/database/migrations/addon"
cp -r database/migrations/addon/. "${PANEL_DIR}/database/migrations/addon/"
# Routes
cp routes/addon-client.php "${PANEL_DIR}/routes/"
cp routes/addon-application.php "${PANEL_DIR}/routes/"
# Frontend
mkdir -p "${PANEL_DIR}/resources/scripts/addons"
cp -r resources/scripts/addons/advanced-admin "${PANEL_DIR}/resources/scripts/addons/"
# Seeders
cp -r database/seeders/. "${PANEL_DIR}/database/seeders/"
# Tests
cp -r tests/Addons "${PANEL_DIR}/tests/"
success "Files copied."
# ── Apply Core Patches ────────────────────────────────────────────────────────
step "Applying core patches..."
apply_patch() {
local FILE="$1"
local SEARCH="$2"
local REPLACE="$3"
local LABEL="$4"
if grep -qF "$SEARCH" "$FILE"; then
warn "Patch '${LABEL}' already applied — skipping."
return 0
fi
if ! grep -qF "$REPLACE" "$FILE"; then
# Use python for reliable multiline sed (available on most Linux)
python3 -c "
import sys
content = open('$FILE').read()
if '''$SEARCH''' not in content:
sys.stderr.write('WARN: anchor not found in $FILE\n')
sys.exit(0)
content = content.replace('''$SEARCH''', '''$SEARCH\n$REPLACE''', 1)
open('$FILE','w').write(content)
"
success "Patch applied: ${LABEL}"
fi
}
# Patch 1: AppServiceProvider — register addon
ASP="${PANEL_DIR}/app/Providers/AppServiceProvider.php"
apply_patch "$ASP" \
"public function register(): void" \
" \$this->app->register(\Pterodactyl\Addons\AdvancedAdmin\AddonServiceProvider::class);" \
"Register AddonServiceProvider"
# Patch 2: ServerRouter — add Network tab route
SR="${PANEL_DIR}/resources/scripts/routers/ServerRouter.tsx"
if [[ -f "$SR" ]]; then
if ! grep -q "addon-network" "$SR"; then
python3 -c "
content = open('$SR').read()
import_line = \"import NetworkTab from '@/addons/advanced-admin/network/NetworkTab';\"
route_line = \" <Route path=\\\"/server/:id/network\\\" element={<NetworkTab />} />\"
# Insert import after last existing addon import or at top of imports block
if import_line not in content:
content = content.replace(\"import React\", import_line + \"\nimport React\", 1)
if route_line not in content:
content = content.replace(\"</Routes>\", route_line + \"\n </Routes>\", 1)
open('$SR','w').write(content)
"
success "Patch applied: ServerRouter NetworkTab"
else
warn "ServerRouter patch already applied — skipping."
fi
fi
# Patch 3: FileEditContainer — inject history button
FEC="${PANEL_DIR}/resources/scripts/components/server/files/FileEditContainer.tsx"
if [[ -f "$FEC" ]]; then
if ! grep -q "FileHistoryButton" "$FEC"; then
python3 -c "
content = open('$FEC').read()
import_line = \"import FileHistoryButton from '@/addons/advanced-admin/revisions/FileHistoryButton';\"
if import_line not in content:
content = content.replace(\"import React\", import_line + \"\nimport React\", 1)
open('$FEC','w').write(content)
"
success "Patch applied: FileEditContainer FileHistoryButton"
else
warn "FileEditContainer patch already applied — skipping."
fi
fi
# Patch 4: ServerConsole — wrap command input
SC="${PANEL_DIR}/resources/scripts/components/server/console/ServerConsole.tsx"
if [[ -f "$SC" ]]; then
if ! grep -q "CommandInput" "$SC"; then
python3 -c "
content = open('$SC').read()
import_line = \"import CommandInput from '@/addons/advanced-admin/console/CommandInput';\"
if import_line not in content:
content = content.replace(\"import React\", import_line + \"\nimport React\", 1)
open('$SC','w').write(content)
"
success "Patch applied: ServerConsole CommandInput"
else
warn "ServerConsole patch already applied — skipping."
fi
fi
success "All patches applied."
# ── Database Migrations ───────────────────────────────────────────────────────
step "Running database migrations..."
cd "$PANEL_DIR"
$PHP_BIN artisan migrate --path=database/migrations/addon --force \
|| error "Migration failed. Check your database connection and logs."
success "Migrations complete."
# ── Seed Default Roles ────────────────────────────────────────────────────────
step "Seeding default roles..."
$PHP_BIN artisan db:seed --class="Pterodactyl\\Addons\\AdvancedAdmin\\Database\\Seeders\\DefaultRolesSeeder" --force \
|| warn "Seeder failed or already run — skipping."
success "Seeding complete."
# ── Publish Config ────────────────────────────────────────────────────────────
step "Publishing config..."
$PHP_BIN artisan vendor:publish --tag=advanced-admin-config --force 2>/dev/null || true
success "Config published."
# ── Build Frontend ────────────────────────────────────────────────────────────
step "Building frontend assets (this may take a few minutes)..."
command -v yarn >/dev/null 2>&1 || error "yarn is not installed. Install with: npm install -g yarn"
cd "$PANEL_DIR"
yarn install --frozen-lockfile 2>&1 | tail -5
export NODE_OPTIONS="${NODE_OPTIONS:---openssl-legacy-provider}"
yarn build:production 2>&1 | tail -20
success "Frontend built."
# ── Clear Caches ──────────────────────────────────────────────────────────────
step "Clearing caches..."
$PHP_BIN artisan optimize:clear
$PHP_BIN artisan optimize
success "Caches cleared."
# ── Fix Permissions ───────────────────────────────────────────────────────────
step "Fixing file permissions..."
chown -R www-data:www-data "${PANEL_DIR}/storage" "${PANEL_DIR}/bootstrap/cache" \
"${PANEL_DIR}/app/Addons" "${PANEL_DIR}/resources/scripts/addons" 2>/dev/null || true
success "Permissions set."
# ── Done ──────────────────────────────────────────────────────────────────────
echo ""
echo -e "${GREEN}${BOLD}╔══════════════════════════════════════════════════════╗
║ ✅ Installation complete! ║
╚══════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e " Panel path : ${CYAN}${PANEL_DIR}${NC}"
echo -e " Backup path : ${CYAN}${BACKUP_DIR}${NC}"
echo -e " Addon repo : ${CYAN}${REPO_URL}${NC}"
echo ""
echo -e " ${YELLOW}Next steps:${NC}"
echo " 1. Review config: ${PANEL_DIR}/config/advanced-admin.php"
echo " 2. Add ADDON_* env vars to .env if needed (see docs/CONFIG.md)"
echo " 3. Restart your queue worker: php artisan queue:restart"
echo " 4. Visit your panel — four new modules are now active!"
echo ""