From 3bb4b323e76848a636b2532a529575e8313af4f4 Mon Sep 17 00:00:00 2001 From: BaileyCodes Date: Thu, 11 Jun 2026 21:38:06 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20rewrite=20install.sh=20=E2=80=94=20upda?= =?UTF-8?q?te=20mode,=20dep=20checks,=20Blueprint=20detection,=20all=204?= =?UTF-8?q?=20modules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install.sh | 683 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 468 insertions(+), 215 deletions(-) diff --git a/install.sh b/install.sh index c0229e7..115ed8e 100644 --- a/install.sh +++ b/install.sh @@ -1,20 +1,23 @@ #!/usr/bin/env bash # ============================================================================= -# Pterodactyl Advanced Admin Addons — Auto Installer +# Pterodactyl Advanced Admin Addons — Installer / Updater # Author : Ball Studios # Repo : https://git.balls.studio/BallStudios/pterodactyl_addon # License: MIT # -# Usage (run as root or with sudo): +# Fresh install: # 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 +# Update existing install: +# bash <(curl -fsSL .../install.sh) --update +# +# Custom panel path: +# bash <(curl -fsSL .../install.sh) /var/www/pterodactyl # ============================================================================= set -euo pipefail -# ── Colours ────────────────────────────────────────────────────────────────── +# ── Colours ─────────────────────────────────────────────────────────────────── RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m' @@ -23,287 +26,537 @@ 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}"; } +check() { echo -e " ${GREEN}✔${NC} $*"; } +fail() { echo -e " ${RED}✘${NC} $*"; } -# ── Config ─────────────────────────────────────────────────────────────────── -PANEL_DIR="${1:-/var/www/pterodactyl}" +# ── Args ────────────────────────────────────────────────────────────────────── +PANEL_DIR="/var/www/pterodactyl" +IS_UPDATE=false + +for arg in "$@"; do + case "$arg" in + --update|-u) IS_UPDATE=true ;; + /*) PANEL_DIR="$arg" ;; + esac +done + +# ── Config ──────────────────────────────────────────────────────────────────── 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)" +ADDON_MARKER="${PANEL_DIR}/.addon_installed" cleanup() { rm -rf "$TMP_DIR"; } trap cleanup EXIT -# ── Banner ─────────────────────────────────────────────────────────────────── +# ── Banner ──────────────────────────────────────────────────────────────────── echo -e " -${BOLD}${CYAN}╔══════════════════════════════════════════════════════╗ -║ Pterodactyl Advanced Admin Addons v${ADDON_VERSION} ║ -║ by Ball Studios — git.balls.studio ║ -╚══════════════════════════════════════════════════════╝${NC} +${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." +if [[ -f "$ADDON_MARKER" ]] && [[ "$IS_UPDATE" == "false" ]]; then + INSTALLED_VER="$(cat "$ADDON_MARKER" 2>/dev/null || echo unknown)" + echo -e "${YELLOW}[!] Addon v${INSTALLED_VER} is already installed.${NC}" + echo -e " Run with ${CYAN}--update${NC} to update, or re-run without to reinstall." + echo "" + read -rp "Reinstall/update anyway? [y/N] " CONFIRM + [[ "${CONFIRM,,}" == "y" ]] || { info "Aborted."; exit 0; } + IS_UPDATE=true fi -success "Preflight checks passed." +# ═════════════════════════════════════════════════════════════════════════════ +# STEP 1: DEPENDENCY CHECKS +# ═════════════════════════════════════════════════════════════════════════════ +step "Checking dependencies..." + +DEPS_OK=true + +# Root check +if [[ $EUID -ne 0 ]]; then + fail "Must run as root (or with sudo)." + DEPS_OK=false +else + check "Running as root" +fi + +# Panel directory +if [[ -f "${PANEL_DIR}/artisan" ]]; then + check "Pterodactyl panel found at ${PANEL_DIR}" +else + fail "Panel not found at ${PANEL_DIR}" + DEPS_OK=false +fi + +# Blueprint check (warn if installed — may conflict) +if [[ -f "${PANEL_DIR}/BlueprintFramework" ]] || \ + [[ -d "${PANEL_DIR}/blueprint" ]] || \ + command -v blueprint >/dev/null 2>&1; then + warn "Blueprint Framework detected. This addon patches core files directly" + warn "and may conflict with Blueprint extensions. Proceed with caution." +fi + +# PHP +if command -v php >/dev/null 2>&1; then + PHP_BIN="$(command -v php)" + PHP_VER="$($PHP_BIN -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;' 2>/dev/null || echo 0)" + PHP_MAJOR="${PHP_VER%%.*}" + if [[ "$PHP_MAJOR" -ge 8 ]]; then + check "PHP ${PHP_VER}" + else + fail "PHP ${PHP_VER} is too old (need >= 8.0)" + DEPS_OK=false + fi +else + fail "PHP not found" + DEPS_OK=false +fi + +# Required PHP extensions +for EXT in pdo_mysql mbstring openssl tokenizer xml ctype json bcmath; do + if $PHP_BIN -m 2>/dev/null | grep -qi "^${EXT}$"; then + check "PHP ext: ${EXT}" + else + # Try to install automatically + PKG="php${PHP_VER}-${EXT//_/-}" + if command -v apt-get >/dev/null 2>&1; then + info "Installing missing PHP extension: ${EXT}..." + DEBIAN_FRONTEND=noninteractive apt-get install -y "$PKG" -q >/dev/null 2>&1 \ + && check "PHP ext: ${EXT} (auto-installed)" \ + || warn "PHP ext: ${EXT} missing — install manually: apt install ${PKG}" + else + warn "PHP ext: ${EXT} missing — install it manually" + fi + fi +done + +# Composer +if command -v composer >/dev/null 2>&1; then + COMPOSER_BIN="$(command -v composer)" + check "Composer found at ${COMPOSER_BIN}" +elif [[ -f /usr/local/bin/composer ]]; then + COMPOSER_BIN="/usr/local/bin/composer" + check "Composer found at ${COMPOSER_BIN}" +else + warn "Composer not found — attempting auto-install..." + curl -fsSL https://getcomposer.org/installer | $PHP_BIN -- --install-dir=/usr/local/bin --filename=composer >/dev/null 2>&1 \ + && COMPOSER_BIN="/usr/local/bin/composer" \ + && check "Composer installed" \ + || { fail "Composer not found — install from https://getcomposer.org"; DEPS_OK=false; } +fi + +# Git +if command -v git >/dev/null 2>&1; then + check "Git: $(git --version | head -1)" +else + fail "git not installed — installing..." + apt-get install -y git -q >/dev/null 2>&1 \ + && check "git installed" \ + || { fail "Could not install git"; DEPS_OK=false; } +fi + +# curl +command -v curl >/dev/null 2>&1 && check "curl available" || { fail "curl not found"; DEPS_OK=false; } + +# Node.js +if command -v node >/dev/null 2>&1; then + NODE_VER="$(node --version 2>/dev/null)" + NODE_MAJ="${NODE_VER//v/}"; NODE_MAJ="${NODE_MAJ%%.*}" + if [[ "$NODE_MAJ" -ge 22 ]]; then + check "Node.js ${NODE_VER}" + else + warn "Node ${NODE_VER} is too old (panel requires >= 22). Will auto-upgrade." + # Upgrade via n + npm install -g n >/dev/null 2>&1 && n 22 >/dev/null 2>&1 && hash -r 2>/dev/null || true + NEW_VER="$(node --version 2>/dev/null)" + info "Node upgraded to ${NEW_VER}" + check "Node.js ${NEW_VER} (upgraded)" + fi +else + fail "Node.js not installed — installing via NodeSource..." + curl -fsSL https://deb.nodesource.com/setup_22.x | bash - >/dev/null 2>&1 + apt-get install -y nodejs >/dev/null 2>&1 + check "Node.js $(node --version)" +fi + +# yarn +if command -v yarn >/dev/null 2>&1; then + check "yarn: $(yarn --version)" +else + warn "yarn not found — installing..." + npm install -g yarn >/dev/null 2>&1 \ + && check "yarn installed" \ + || { fail "Could not install yarn"; DEPS_OK=false; } +fi + +# python3 (needed for patching) +if command -v python3 >/dev/null 2>&1; then + check "python3: $(python3 --version)" +else + apt-get install -y python3 -q >/dev/null 2>&1 \ + && check "python3 installed" \ + || { fail "python3 not available — needed for file patching"; DEPS_OK=false; } +fi + +[[ "$DEPS_OK" == "true" ]] || error "Dependency checks failed. Fix the issues above and re-run." +success "All dependencies satisfied." + +# ═════════════════════════════════════════════════════════════════════════════ +# STEP 2: CONFIRM +# ═════════════════════════════════════════════════════════════════════════════ +PHP_BIN="$(command -v php)" +PANEL_VER="$(cd "$PANEL_DIR" && $PHP_BIN artisan --version 2>/dev/null | grep -oP '[\d\.]+' | head -1 || echo unknown)" +info "Panel version: ${PANEL_VER}" + +[[ "$PANEL_VER" != "1.12"* ]] && [[ "$PANEL_VER" != "unknown" ]] && \ + warn "Addon targets v1.12.x. Detected: v${PANEL_VER}. May still work." -# ── 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" +if [[ "$IS_UPDATE" == "true" ]]; then + echo -e "${YELLOW}Update mode — will:${NC}" + echo " 1. Pull latest addon files from ${REPO_URL}" + echo " 2. Re-apply all patches (idempotent — skips already-applied ones)" + echo " 3. Run any new migrations" + echo " 4. Ensure recharts is added to package.json" + echo " 5. Rebuild frontend" + echo " 6. Clear caches" +else + echo -e "${YELLOW}Fresh install — will:${NC}" + echo " 1. Backup core panel files → ${BACKUP_DIR}" + echo " 2. Clone addon → ${TMP_DIR}" + echo " 3. Copy addon files into panel" + echo " 4. Apply patches (AppServiceProvider, routes.ts, FileEditContainer, Console)" + echo " 5. Run database migrations + seed default roles" + echo " 6. Add recharts dependency + rebuild frontend" + echo " 7. Clear caches" +fi echo "" read -rp "Continue? [y/N] " CONFIRM [[ "${CONFIRM,,}" == "y" ]] || { info "Aborted."; exit 0; } -# ── Backup ──────────────────────────────────────────────────────────────────── -step "Backing up panel to ${BACKUP_DIR}..." +# ═════════════════════════════════════════════════════════════════════════════ +# STEP 3: BACKUP +# ═════════════════════════════════════════════════════════════════════════════ +step "Backing up patched files..." 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}" +for F in \ + "app/Providers/AppServiceProvider.php" \ + "resources/scripts/routers/routes.ts" \ + "resources/scripts/routers/ServerRouter.tsx" \ + "resources/scripts/components/server/files/FileEditContainer.tsx" \ + "resources/scripts/components/server/console/Console.tsx"; do + [[ -f "${PANEL_DIR}/${F}" ]] && cp "${PANEL_DIR}/${F}" "$BACKUP_DIR/" 2>/dev/null || true +done +success "Backup → ${BACKUP_DIR}" -# ── Clone Addon ─────────────────────────────────────────────────────────────── -step "Cloning addon repository..." +# ═════════════════════════════════════════════════════════════════════════════ +# STEP 4: CLONE / PULL ADDON +# ═════════════════════════════════════════════════════════════════════════════ +step "Fetching addon from 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." + || error "Failed to clone ${REPO_URL}. Check network and repo visibility." +success "Addon fetched." -# ── Copy Addon Files ────────────────────────────────────────────────────────── -step "Copying addon files into panel..." +# ═════════════════════════════════════════════════════════════════════════════ +# STEP 5: COPY FILES +# ═════════════════════════════════════════════════════════════════════════════ +step "Copying addon files..." 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 +cp -r app/Addons "${PANEL_DIR}/app/" +cp config/advanced-admin.php "${PANEL_DIR}/config/" +cp -r config/addon-commands "${PANEL_DIR}/config/" 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 +cp -r database/migrations/addon/. "${PANEL_DIR}/database/migrations/addon/" +cp routes/addon-client.php "${PANEL_DIR}/routes/" +cp routes/addon-application.php "${PANEL_DIR}/routes/" 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/" +cp -r resources/scripts/addons/advanced-admin \ + "${PANEL_DIR}/resources/scripts/addons/" +cp -r database/seeders/. "${PANEL_DIR}/database/seeders/" +[[ -d tests/Addons ]] && cp -r tests/Addons "${PANEL_DIR}/tests/" || true success "Files copied." -# ── Apply Core Patches ──────────────────────────────────────────────────────── -step "Applying core patches..." +# ═════════════════════════════════════════════════════════════════════════════ +# STEP 6: APPLY ALL PATCHES (idempotent Python script) +# ═════════════════════════════════════════════════════════════════════════════ +step "Applying patches..." -apply_patch() { - local FILE="$1" - local SEARCH="$2" - local REPLACE="$3" - local LABEL="$4" +PATCH_SCRIPT="$(mktemp /tmp/addon_patch_XXXXXX.py)" +cat > "$PATCH_SCRIPT" << 'PYEOF' +import sys, re, os - # Check if the injected content is already present (not the anchor) - if grep -qF "$REPLACE" "$FILE"; then - warn "Patch '${LABEL}' already applied — skipping." - return 0 - fi +PANEL = sys.argv[1] - if ! grep -qF "$SEARCH" "$FILE"; then - warn "Anchor '${SEARCH}' not found in ${FILE} — skipping patch '${LABEL}'." - return 0 - fi +def read(p): return open(p).read() +def write(p, c): open(p, 'w').write(c) +def patch(path, label, check, anchor, inject): + c = read(path) + if check in c: + print(f" [skip] {label} — already applied") + return + if anchor not in c: + print(f" [warn] {label} — anchor not found, skipping") + return + write(path, c.replace(anchor, anchor + '\n' + inject, 1)) + print(f" [ ok ] {label}") - python3 -c " -import sys -content = open('$FILE').read() -if '''$REPLACE''' in content: - sys.exit(0) -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}" -} +# 1. AppServiceProvider +patch( + f'{PANEL}/app/Providers/AppServiceProvider.php', + 'Register AddonServiceProvider', + 'AddonServiceProvider::class', + ' public function register(): void\n {', + " $this->app->register(\\Pterodactyl\\Addons\\AdvancedAdmin\\AddonServiceProvider::class);" +) -# 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" +# 2. routes.ts — imports +rts = f'{PANEL}/resources/scripts/routers/routes.ts' +c = read(rts) +lines = c.splitlines(keepends=True) +last_import = max((i for i, l in enumerate(lines) if l.strip().startswith('import ')), default=0) +added = [] +for check, imp in [ + ('NetworkTab', "import NetworkTab from '@/addons/advanced-admin/network/NetworkTab';"), + ('RolesPage', "import RolesPage from '@/addons/advanced-admin/roles/RolesPage';"), +]: + if check not in c: + lines.insert(last_import + 1, imp + '\n') + last_import += 1 + added.append(check) + print(f" [ ok ] routes.ts import: {check}") + else: + print(f" [skip] routes.ts import: {check}") +c = ''.join(lines) -# 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 = \" } />\" -# 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(\"\", route_line + \"\n \", 1) -open('$SR','w').write(content) -" - success "Patch applied: ServerRouter NetworkTab" - else - warn "ServerRouter patch already applied — skipping." - fi -fi +# 2b. routes.ts — route entries +for rpath, block in [ + ('/addon-network', """, + { + path: '/addon-network', + permission: null, + name: 'Live Network', + component: NetworkTab, + }"""), + ('/addon-roles', """, + { + path: '/addon-roles', + permission: null, + name: 'Roles', + component: RolesPage, + }"""), +]: + if rpath in c: + print(f" [skip] routes.ts route: {rpath}") + else: + target = " ],\n} as Routes;" + if target in c: + c = c.replace(target, f"{block},\n ],\n}} as Routes;") + print(f" [ ok ] routes.ts route: {rpath}") + else: + print(f" [warn] routes.ts: closing not found for {rpath}") +write(rts, c) -# 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 +# 3. FileEditContainer — import +fec = f'{PANEL}/resources/scripts/components/server/files/FileEditContainer.tsx' +patch(fec, 'FileHistoryButton import', 'FileHistoryButton', + "import { encodePathSegments, hashToPath } from '@/helpers';", + "import FileHistoryButton from '@/addons/advanced-admin/revisions/FileHistoryButton';" +) +# 3b. FileEditContainer — JSX button +patch(fec, 'FileHistoryButton JSX', '", + " {action === 'edit' && }" +) -# 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 +# 4. Console.tsx — CommandInput +con = f'{PANEL}/resources/scripts/components/server/console/Console.tsx' +patch(con, 'CommandInput import', 'CommandInput', + "import { ServerContext } from '@/state/server';", + "import CommandInput from '@/addons/advanced-admin/console/CommandInput';" +) +# 4b. Inject CommandInput (replace native input) +c = read(con) +if ' { + setHistory((prev: string[]) => [cmd, ...(prev || [])].slice(0, 32)); + instance && instance.send('send command', cmd); + }} + disabled={!connected} + /> + middleware\('throttle:addon-[^']+'\)", "", c) + cleaned = re.sub(r",?\s*'addon\.ratelimit'", "", cleaned) + if cleaned != c: + write(path, cleaned) + print(f" [ ok ] Cleaned middleware: {rf}") + else: + print(f" [skip] Middleware clean: {rf}") + +print("\nAll patches done.") +PYEOF + +python3 "$PATCH_SCRIPT" "$PANEL_DIR" +rm -f "$PATCH_SCRIPT" +success "Patches applied." + +# ═════════════════════════════════════════════════════════════════════════════ +# STEP 7: 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." + || warn "Migration issues — check logs. May be already run." success "Migrations complete." -# ── Seed Default Roles ──────────────────────────────────────────────────────── +# ═════════════════════════════════════════════════════════════════════════════ +# STEP 8: SEED DEFAULT ROLES +# ═════════════════════════════════════════════════════════════════════════════ step "Seeding default roles..." cd "$PANEL_DIR" -# Write seeder bootstrap to a temp file (heredoc doesn't work when script is piped) SEED_SCRIPT="$(mktemp /tmp/addon_seed_XXXXXX.php)" -cat > "$SEED_SCRIPT" <<'PHPEOF' +PANEL_ESC="${PANEL_DIR//\'/\'}" +cat > "$SEED_SCRIPT" << PHPEOF make(Illuminate\Contracts\Console\Kernel::class); -$kernel->bootstrap(); -require_once __DIR__ . '/database/seeders/DefaultRolesSeeder.php'; -$seeder = new Database\Seeders\DefaultRolesSeeder(); -$seeder->setContainer($app); -$seeder->run(); -echo "Roles seeded successfully.\n"; +require '${PANEL_ESC}/vendor/autoload.php'; +\$app = require_once '${PANEL_ESC}/bootstrap/app.php'; +\$kernel = \$app->make(Illuminate\Contracts\Console\Kernel::class); +\$kernel->bootstrap(); +require_once '${PANEL_ESC}/database/seeders/DefaultRolesSeeder.php'; +\$seeder = new Database\Seeders\DefaultRolesSeeder(); +\$seeder->setContainer(\$app); +\$seeder->run(); +echo "Roles seeded.\n"; PHPEOF -# Replace __DIR__ placeholder with the actual panel dir -sed -i "s|__DIR__|'${PANEL_DIR}'|g" "$SEED_SCRIPT" $PHP_BIN "$SEED_SCRIPT" 2>&1 || warn "Seeder failed or already run — skipping." rm -f "$SEED_SCRIPT" 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." +# ═════════════════════════════════════════════════════════════════════════════ +# STEP 9: ADD ENV VARS (if not present) +# ═════════════════════════════════════════════════════════════════════════════ +step "Checking .env addon vars..." +ENV_FILE="${PANEL_DIR}/.env" +if [[ -f "$ENV_FILE" ]] && ! grep -q "ADDON_NETWORK_ENABLED" "$ENV_FILE"; then + cat >> "$ENV_FILE" << 'ENVEOF' -# ── 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" - -# Ensure Node >=22 (panel requires it; upgrade via 'n' if needed) -NODE_MAJ="$(node --version 2>/dev/null | sed 's/v//;s/\..*//' || echo 0)" -if [[ "${NODE_MAJ}" -lt 22 ]]; then - info "Node ${NODE_MAJ} is too old (need >=22). Upgrading via 'n'..." - npm install -g n >/dev/null 2>&1 \ - && n 22 >/dev/null 2>&1 \ - && hash -r 2>/dev/null || true - info "Node version now: $(node --version 2>/dev/null || echo unknown)" +# ── Pterodactyl Advanced Admin Addons ───────────────────────────────────────── +ADDON_NETWORK_ENABLED=true +ADDON_NETWORK_INTERVAL=10 +ADDON_NETWORK_IPTABLES=false +ADDON_NETWORK_RETENTION_RAW=1 +ADDON_NETWORK_RETENTION_5M=7 +ADDON_NETWORK_RETENTION_1H=30 +ADDON_NETWORK_RETENTION_1D=365 +ADDON_ROLES_ENABLED=true +ADDON_REVISIONS_ENABLED=true +ADDON_REVISIONS_MAX_FILE_SIZE=10485760 +ADDON_REVISIONS_MAX_PER_FILE=50 +ADDON_REVISIONS_RETENTION_DAYS=90 +ADDON_CONSOLE_ENABLED=true +ADDON_CONSOLE_RATE_LIMIT=60 +ADDON_CONSOLE_PLAYER_TTL=30 +ENVEOF + success ".env vars added." +else + info ".env addon vars already present — skipping." fi -export NODE_OPTIONS="${NODE_OPTIONS:---openssl-legacy-provider}" -yarn install --frozen-lockfile 2>&1 | tail -5 -yarn build:production 2>&1 | tail -30 +# ═════════════════════════════════════════════════════════════════════════════ +# STEP 10: BUILD FRONTEND +# ═════════════════════════════════════════════════════════════════════════════ +step "Building frontend assets..." +cd "$PANEL_DIR" + +# Ensure recharts is in package.json (survives yarn install --frozen-lockfile) +if ! grep -q '"recharts"' package.json 2>/dev/null; then + info "Adding recharts@2.12.7 to package.json..." + yarn add recharts@2.12.7 --no-lockfile >/dev/null 2>&1 + success "recharts added." +else + info "recharts already in package.json." +fi + +yarn install 2>&1 | tail -3 +yarn build:production 2>&1 | tail -15 success "Frontend built." -# ── Clear Caches ────────────────────────────────────────────────────────────── -step "Clearing caches..." +# ═════════════════════════════════════════════════════════════════════════════ +# STEP 11: CACHES + PERMISSIONS +# ═════════════════════════════════════════════════════════════════════════════ +step "Clearing caches and fixing permissions..." +cd "$PANEL_DIR" $PHP_BIN artisan optimize:clear $PHP_BIN artisan optimize -success "Caches cleared." +$PHP_BIN artisan queue:restart -# ── 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." +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 "Caches cleared and permissions set." + +# ── Mark installation ───────────────────────────────────────────────────────── +echo "$ADDON_VERSION" > "$ADDON_MARKER" # ── Done ────────────────────────────────────────────────────────────────────── echo "" -echo -e "${GREEN}${BOLD}╔══════════════════════════════════════════════════════╗ -║ ✅ Installation complete! ║ -╚══════════════════════════════════════════════════════╝${NC}" +echo -e "${GREEN}${BOLD}╔══════════════════════════════════════════════════════════╗ +║ ✅ $([ "$IS_UPDATE" == "true" ] && echo "Update" || echo "Installation") complete! v${ADDON_VERSION} ║ +╚══════════════════════════════════════════════════════════╝${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 -e " ${BOLD}Active modules:${NC}" +echo " 🌐 Live Network — server nav tab → /addon-network" +echo " 🛡 Roles — server nav tab → /addon-roles" +echo " 🕐 File History — button in file editor toolbar" +echo " 🤖 Autocomplete — enhanced console command input" +echo "" +echo -e " ${YELLOW}To update in the future, run:${NC}" +echo " bash <(curl -fsSL ${RAW_URL}/install.sh) --update" echo ""