563 lines
26 KiB
Bash
563 lines
26 KiB
Bash
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# Pterodactyl Advanced Admin Addons — Installer / Updater
|
|
# Author : Ball Studios <https://git.balls.studio>
|
|
# Repo : https://git.balls.studio/BallStudios/pterodactyl_addon
|
|
# License: MIT
|
|
#
|
|
# Fresh install:
|
|
# bash <(curl -fsSL https://git.balls.studio/BallStudios/pterodactyl_addon/raw/branch/main/install.sh)
|
|
#
|
|
# Update existing install:
|
|
# bash <(curl -fsSL .../install.sh) --update
|
|
#
|
|
# Custom panel path:
|
|
# bash <(curl -fsSL .../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}"; }
|
|
check() { echo -e " ${GREEN}✔${NC} $*"; }
|
|
fail() { echo -e " ${RED}✘${NC} $*"; }
|
|
|
|
# ── 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 ────────────────────────────────────────────────────────────────────
|
|
echo -e "
|
|
${BOLD}${CYAN}╔══════════════════════════════════════════════════════════╗
|
|
║ Pterodactyl Advanced Admin Addons v${ADDON_VERSION} ║
|
|
║ by Ball Studios — git.balls.studio ║
|
|
╚══════════════════════════════════════════════════════════╝${NC}
|
|
"
|
|
|
|
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
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
# 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."
|
|
|
|
echo ""
|
|
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; }
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
# STEP 3: BACKUP
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
step "Backing up patched files..."
|
|
mkdir -p "$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}"
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
# 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 and repo visibility."
|
|
success "Addon fetched."
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
# STEP 5: COPY FILES
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
step "Copying addon files..."
|
|
cd "$TMP_DIR/addon"
|
|
|
|
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/"
|
|
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/"
|
|
cp -r database/seeders/. "${PANEL_DIR}/database/seeders/"
|
|
[[ -d tests/Addons ]] && cp -r tests/Addons "${PANEL_DIR}/tests/" || true
|
|
|
|
success "Files copied."
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
# STEP 6: APPLY ALL PATCHES (idempotent Python script)
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
step "Applying patches..."
|
|
|
|
PATCH_SCRIPT="$(mktemp /tmp/addon_patch_XXXXXX.py)"
|
|
cat > "$PATCH_SCRIPT" << 'PYEOF'
|
|
import sys, re, os
|
|
|
|
PANEL = sys.argv[1]
|
|
|
|
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}")
|
|
|
|
# 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);"
|
|
)
|
|
|
|
# 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)
|
|
|
|
# 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)
|
|
|
|
# 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', '<FileHistoryButton',
|
|
" <div css={tw`flex justify-end mt-4`}>",
|
|
" {action === 'edit' && <FileHistoryButton filePath={hash.replace(/^#/, '')} />}"
|
|
)
|
|
|
|
# 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 '<CommandInput' not in c:
|
|
old = " <input\n className={classNames('peer', styles.command_input)}"
|
|
new = """ <CommandInput
|
|
onSend={(cmd) => {
|
|
setHistory((prev: string[]) => [cmd, ...(prev || [])].slice(0, 32));
|
|
instance && instance.send('send command', cmd);
|
|
}}
|
|
disabled={!connected}
|
|
/>
|
|
<input
|
|
className={classNames('peer', styles.command_input)}
|
|
style={{ display: 'none' }}"""
|
|
if old in c:
|
|
write(con, c.replace(old, new, 1))
|
|
print(" [ ok ] Console.tsx CommandInput injected")
|
|
else:
|
|
print(" [warn] Console.tsx: native input anchor not found")
|
|
else:
|
|
print(" [skip] Console.tsx CommandInput — already injected")
|
|
|
|
# 5. Clean any stray imports from ServerRouter.tsx
|
|
sr = f'{PANEL}/resources/scripts/routers/ServerRouter.tsx'
|
|
c = read(sr)
|
|
changed = False
|
|
for stray in [
|
|
"import NetworkTab from '@/addons/advanced-admin/network/NetworkTab';\n",
|
|
"import RolesPage from '@/addons/advanced-admin/roles/RolesPage';\n",
|
|
]:
|
|
if stray in c:
|
|
c = c.replace(stray, '')
|
|
changed = True
|
|
print(f" [ ok ] Removed stray import from ServerRouter")
|
|
if changed:
|
|
write(sr, c)
|
|
else:
|
|
print(" [skip] ServerRouter.tsx — no stray imports")
|
|
|
|
# 6. Fix route middleware (remove unregistered throttle limiters)
|
|
for rf in ['addon-client.php', 'addon-application.php']:
|
|
path = f'{PANEL}/routes/{rf}'
|
|
if not os.path.exists(path): continue
|
|
c = read(path)
|
|
cleaned = re.sub(r"->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 \
|
|
|| warn "Migration issues — check logs. May be already run."
|
|
success "Migrations complete."
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
# STEP 8: SEED DEFAULT ROLES
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
step "Seeding default roles..."
|
|
cd "$PANEL_DIR"
|
|
SEED_SCRIPT="$(mktemp /tmp/addon_seed_XXXXXX.php)"
|
|
PANEL_ESC="${PANEL_DIR//\'/\'}"
|
|
cat > "$SEED_SCRIPT" << PHPEOF
|
|
<?php
|
|
define('LARAVEL_START', microtime(true));
|
|
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
|
|
$PHP_BIN "$SEED_SCRIPT" 2>&1 || warn "Seeder failed or already run — skipping."
|
|
rm -f "$SEED_SCRIPT"
|
|
success "Seeding complete."
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
# 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'
|
|
|
|
# ── 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
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
# 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."
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
# STEP 11: CACHES + PERMISSIONS
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
step "Clearing caches and fixing permissions..."
|
|
cd "$PANEL_DIR"
|
|
$PHP_BIN artisan optimize:clear
|
|
$PHP_BIN artisan optimize
|
|
$PHP_BIN artisan queue:restart
|
|
|
|
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}╔══════════════════════════════════════════════════════════╗
|
|
║ ✅ $([ "$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 " ${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 ""
|