fix: defensive ServiceProvider (class_exists guards), fixed install.sh apply_patch logic

This commit is contained in:
2026-06-11 21:05:57 -05:00
parent 338dc52f76
commit d413bca7e9
3 changed files with 103 additions and 45 deletions

View File

@@ -16,13 +16,6 @@ namespace Pterodactyl\Addons\AdvancedAdmin;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Console\Scheduling\Schedule;
use Pterodactyl\Addons\AdvancedAdmin\Jobs\Network\CollectNetworkMetricsJob;
use Pterodactyl\Addons\AdvancedAdmin\Jobs\Network\DownsampleNetworkMetricsJob;
use Pterodactyl\Addons\AdvancedAdmin\Jobs\Network\PruneNetworkMetricsJob;
use Pterodactyl\Addons\AdvancedAdmin\Jobs\Revisions\PruneFileRevisionsJob;
use Pterodactyl\Addons\AdvancedAdmin\Jobs\Console\ExpireStalePlayersJob;
use Pterodactyl\Addons\AdvancedAdmin\Listeners\Console\ConsoleOutputListener;
use Pterodactyl\Addons\AdvancedAdmin\Services\Roles\PermissionResolver;
class AddonServiceProvider extends ServiceProvider class AddonServiceProvider extends ServiceProvider
{ {
@@ -38,7 +31,10 @@ class AddonServiceProvider extends ServiceProvider
); );
// Bind the PermissionResolver as a singleton for performance // Bind the PermissionResolver as a singleton for performance
$this->app->singleton(PermissionResolver::class); $resolver = 'Pterodactyl\Addons\AdvancedAdmin\Services\Roles\PermissionResolver';
if (class_exists($resolver)) {
$this->app->singleton($resolver);
}
} }
/** /**
@@ -47,40 +43,61 @@ class AddonServiceProvider extends ServiceProvider
public function boot(): void public function boot(): void
{ {
// ── Routes ──────────────────────────────────────────────────────────── // ── Routes ────────────────────────────────────────────────────────────
$this->loadRoutesFrom(__DIR__ . '/../../routes/addon-client.php'); $clientRoutes = __DIR__ . '/../../routes/addon-client.php';
$this->loadRoutesFrom(__DIR__ . '/../../routes/addon-application.php'); $appRoutes = __DIR__ . '/../../routes/addon-application.php';
if (file_exists($clientRoutes)) {
$this->loadRoutesFrom($clientRoutes);
}
if (file_exists($appRoutes)) {
$this->loadRoutesFrom($appRoutes);
}
// ── Migrations ──────────────────────────────────────────────────────── // ── Migrations ────────────────────────────────────────────────────────
$this->loadMigrationsFrom(__DIR__ . '/../../database/migrations/addon'); $migrationsPath = __DIR__ . '/../../database/migrations/addon';
if (is_dir($migrationsPath)) {
$this->loadMigrationsFrom($migrationsPath);
}
// ── Scheduled Jobs ──────────────────────────────────────────────────── // ── Scheduled Jobs ────────────────────────────────────────────────────
$this->callAfterResolving(Schedule::class, function (Schedule $schedule) { $this->callAfterResolving(Schedule::class, function (Schedule $schedule) {
if (config('advanced-admin.network.enabled')) { // Network metric collection
$schedule->job(CollectNetworkMetricsJob::class) $collectJob = 'Pterodactyl\Addons\AdvancedAdmin\Jobs\Network\CollectNetworkMetricsJob';
$downsampleJob = 'Pterodactyl\Addons\AdvancedAdmin\Jobs\Network\DownsampleNetworkMetricsJob';
$pruneNetJob = 'Pterodactyl\Addons\AdvancedAdmin\Jobs\Network\PruneNetworkMetricsJob';
$pruneRevJob = 'Pterodactyl\Addons\AdvancedAdmin\Jobs\Revisions\PruneFileRevisionsJob';
$expireJob = 'Pterodactyl\Addons\AdvancedAdmin\Jobs\Console\ExpireStalePlayersJob';
if (config('advanced-admin.network.enabled') && class_exists($collectJob)) {
$schedule->job(new $collectJob())
->everyTenSeconds() ->everyTenSeconds()
->withoutOverlapping(5) ->withoutOverlapping(5)
->onOneServer(); ->onOneServer();
$schedule->job(DownsampleNetworkMetricsJob::class) if (class_exists($downsampleJob)) {
$schedule->job(new $downsampleJob())
->hourly() ->hourly()
->withoutOverlapping(30) ->withoutOverlapping(30)
->onOneServer(); ->onOneServer();
}
$schedule->job(PruneNetworkMetricsJob::class) if (class_exists($pruneNetJob)) {
$schedule->job(new $pruneNetJob())
->daily()
->withoutOverlapping(60)
->onOneServer();
}
}
if (config('advanced-admin.revisions.enabled') && class_exists($pruneRevJob)) {
$schedule->job(new $pruneRevJob())
->daily() ->daily()
->withoutOverlapping(60) ->withoutOverlapping(60)
->onOneServer(); ->onOneServer();
} }
if (config('advanced-admin.revisions.enabled')) { if (config('advanced-admin.console.enabled') && class_exists($expireJob)) {
$schedule->job(PruneFileRevisionsJob::class) $schedule->job(new $expireJob())
->daily()
->withoutOverlapping(60)
->onOneServer();
}
if (config('advanced-admin.console.enabled')) {
$schedule->job(ExpireStalePlayersJob::class)
->everyFiveMinutes() ->everyFiveMinutes()
->withoutOverlapping(4); ->withoutOverlapping(4);
} }
@@ -88,23 +105,26 @@ class AddonServiceProvider extends ServiceProvider
// ── Event Listeners ─────────────────────────────────────────────────── // ── Event Listeners ───────────────────────────────────────────────────
// Console output listener (parses join/leave for player cache) // Console output listener (parses join/leave for player cache)
// Hooked to Pterodactyl's existing console output events if they exist. $consoleEvent = 'Pterodactyl\Events\Server\ConsoleDataReceived';
// Falls back gracefully if event is not fired by this panel version. $consoleListener = 'Pterodactyl\Addons\AdvancedAdmin\Listeners\Console\ConsoleOutputListener';
if (class_exists(\Pterodactyl\Events\Server\ConsoleDataReceived::class)) { if (class_exists($consoleEvent) && class_exists($consoleListener)) {
$this->app['events']->listen( $this->app['events']->listen($consoleEvent, $consoleListener);
\Pterodactyl\Events\Server\ConsoleDataReceived::class,
ConsoleOutputListener::class
);
} }
// ── Artisan Commands ────────────────────────────────────────────────── // ── Artisan Commands ──────────────────────────────────────────────────
if ($this->app->runningInConsole()) { if ($this->app->runningInConsole()) {
$this->commands([ $commands = [];
Console\Commands\AddonPatchCommand::class, $candidates = [
Console\Commands\AddonInstallCommand::class, 'Pterodactyl\Addons\AdvancedAdmin\Console\Commands\CollectNetworkCommand',
Console\Commands\AddonUninstallCommand::class, ];
Console\Commands\CollectNetworkCommand::class, foreach ($candidates as $cmd) {
]); if (class_exists($cmd)) {
$commands[] = $cmd;
}
}
if (!empty($commands)) {
$this->commands($commands);
}
} }
// ── Publishable Assets ──────────────────────────────────────────────── // ── Publishable Assets ────────────────────────────────────────────────

View File

@@ -133,15 +133,22 @@ apply_patch() {
local REPLACE="$3" local REPLACE="$3"
local LABEL="$4" local LABEL="$4"
if grep -qF "$SEARCH" "$FILE"; then # Check if the injected content is already present (not the anchor)
if grep -qF "$REPLACE" "$FILE"; then
warn "Patch '${LABEL}' already applied — skipping." warn "Patch '${LABEL}' already applied — skipping."
return 0 return 0
fi fi
if ! grep -qF "$REPLACE" "$FILE"; then
# Use python for reliable multiline sed (available on most Linux) if ! grep -qF "$SEARCH" "$FILE"; then
warn "Anchor '${SEARCH}' not found in ${FILE} — skipping patch '${LABEL}'."
return 0
fi
python3 -c " python3 -c "
import sys import sys
content = open('$FILE').read() content = open('$FILE').read()
if '''$REPLACE''' in content:
sys.exit(0)
if '''$SEARCH''' not in content: if '''$SEARCH''' not in content:
sys.stderr.write('WARN: anchor not found in $FILE\n') sys.stderr.write('WARN: anchor not found in $FILE\n')
sys.exit(0) sys.exit(0)
@@ -149,7 +156,6 @@ content = content.replace('''$SEARCH''', '''$SEARCH\n$REPLACE''', 1)
open('$FILE','w').write(content) open('$FILE','w').write(content)
" "
success "Patch applied: ${LABEL}" success "Patch applied: ${LABEL}"
fi
} }
# Patch 1: AppServiceProvider — register addon # Patch 1: AppServiceProvider — register addon

32
scripts/fix_asp.py Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env python3
"""Fix AppServiceProvider — ensure AddonServiceProvider is registered."""
import sys
f = '/var/www/pterodactyl/app/Providers/AppServiceProvider.php'
content = open(f).read()
if 'AddonServiceProvider' in content and '$this->app->register' in content.split('AddonServiceProvider')[0].split('\n')[-2:][0]:
print('Already correctly patched')
sys.exit(0)
# Remove the broken line (the one without $this->)
lines = content.splitlines(keepends=True)
fixed = []
for line in lines:
# Replace the broken orphan line
if '->app->register(\\Pterodactyl\\Addons\\AdvancedAdmin\\AddonServiceProvider::class)' in line and not '$this' in line:
fixed.append(' $this->app->register(\\Pterodactyl\\Addons\\AdvancedAdmin\\AddonServiceProvider::class);\n')
else:
fixed.append(line)
content = ''.join(fixed)
open(f, 'w').write(content)
print('Fixed: $this-> added to AddonServiceProvider line')
# Verify
result = open(f).read()
if '$this->app->register(\\Pterodactyl\\Addons\\AdvancedAdmin\\AddonServiceProvider::class)' in result:
print('Verification OK')
else:
print('ERROR: Verification failed')
sys.exit(1)