Files
firefrost-services/services/modpack-version-checker/blueprint-extension/app/Console/Commands/CheckModpackUpdates.php
Claude (Chronicler #63) 8e37120289 refactor(modpackchecker): Batch 2 fixes - centralized service, rate limiting, schema fixes
NEW: app/Services/ModpackApiService.php
- Centralized API logic for all 4 platforms
- Technic build number cached for 12 hours (RV-Ready)
- Single source of truth for API calls

Controller (ModpackAPIController.php):
- Now uses injected ModpackApiService instead of duplicated code
- Added RateLimiter: 2 requests/minute per server on manualCheck()
- Returns 429 with countdown when rate limited
- Removed 400+ lines of duplicated API code

Console Command (CheckModpackUpdates.php):
- FIXED: updateDatabase() now uses server_uuid (not server_id)
- FIXED: status column uses strings ('update_available', 'up_to_date', 'error')
- FIXED: Technic API now uses dynamic build via service
- Now uses injected ModpackApiService

SECURITY:
- Rate limiting prevents API key abuse via button spam
- Technic build caching reduces external API calls

Reviewed by: Gemini AI (Architecture Consultant)
Signed-off-by: Claude (Chronicler #63) <claude@firefrostgaming.com>
2026-04-06 11:33:11 +00:00

160 lines
5.5 KiB
PHP

<?php
/**
* =============================================================================
* MODPACK VERSION CHECKER - CRON COMMAND
* =============================================================================
*
* Laravel Artisan command that checks all servers for modpack updates.
* This is the "brain" that populates the cache used by the dashboard badges.
*
* USAGE:
* php artisan modpackchecker:check
*
* RECOMMENDED CRON SCHEDULE:
* # Check for updates every 6 hours (adjust based on your server count)
* 0 */6 * * * cd /var/www/pterodactyl && php artisan modpackchecker:check >> /dev/null 2>&1
*
* HOW IT WORKS:
* 1. Finds all servers with MODPACK_PLATFORM egg variable set
* 2. Loops through each server, checking via ModpackApiService
* 3. Stores results in modpackchecker_servers database table
* 4. Dashboard badges read from this table (never calling APIs directly)
*
* RATE LIMITING:
* Each API call is followed by a 2-second sleep to avoid rate limits.
* For 50 servers, a full check takes ~2 minutes.
*
* @package Pterodactyl\Console\Commands
* @author Firefrost Gaming / Frostystyle <dev@firefrostgaming.com>
* @version 1.0.0
* @see ModpackApiService.php (centralized API logic)
* @see ModpackAPIController.php (provides getStatus endpoint for badges)
* =============================================================================
*/
namespace Pterodactyl\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Pterodactyl\Models\Server;
use Pterodactyl\Services\ModpackApiService;
class CheckModpackUpdates extends Command
{
protected $signature = 'modpackchecker:check';
protected $description = 'Check all servers for modpack updates';
public function __construct(private ModpackApiService $apiService)
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int Exit code (0 = success)
*/
public function handle(): int
{
$this->info('Starting modpack update check...');
// Get all servers that have modpack variables set
$servers = Server::whereHas('variables', function ($q) {
$q->where('env_variable', 'MODPACK_PLATFORM');
})->get();
$this->info("Found {$servers->count()} servers with modpack configuration");
foreach ($servers as $server) {
$this->checkServer($server);
// Rate limiting - sleep between checks
sleep(2);
}
$this->info('Modpack update check complete!');
return 0;
}
/**
* Check a single server for modpack updates.
*
* @param Server $server The server to check
* @return void
*/
private function checkServer(Server $server): void
{
$this->line("Checking: {$server->name} ({$server->uuid})");
try {
$platform = $this->getVariable($server, 'MODPACK_PLATFORM');
$modpackId = $this->getVariable($server, 'MODPACK_ID');
if (!$platform || !$modpackId) {
$this->warn(" Skipping - missing platform or modpack ID");
return;
}
// Centralized API Call via Service
$latestData = $this->apiService->fetchLatestVersion($platform, $modpackId);
$currentVersion = $this->getVariable($server, 'MODPACK_CURRENT_VERSION');
$updateAvailable = $currentVersion && $currentVersion !== $latestData['version'];
$this->updateDatabase($server, [
'platform' => $platform,
'modpack_id' => $modpackId,
'modpack_name' => $latestData['name'],
'current_version' => $currentVersion,
'latest_version' => $latestData['version'],
'status' => $updateAvailable ? 'update_available' : 'up_to_date',
'error_message' => null,
'last_checked' => now(),
]);
$statusIcon = $updateAvailable ? '🟠 UPDATE AVAILABLE' : '🟢 Up to date';
$this->info(" {$statusIcon}: {$latestData['name']} - {$latestData['version']}");
} catch (\Exception $e) {
$this->error(" Error: {$e->getMessage()}");
$this->updateDatabase($server, [
'status' => 'error',
'error_message' => $e->getMessage(),
'last_checked' => now(),
]);
}
}
/**
* Get an egg variable value from a server.
*
* @param Server $server The server to query
* @param string $name The variable name
* @return string|null The variable value, or null if not set
*/
private function getVariable(Server $server, string $name): ?string
{
$variable = $server->variables()
->where('env_variable', $name)
->first();
return $variable?->server_value;
}
/**
* Store or update the modpack check results in the database.
*
* Uses updateOrInsert for upsert behavior.
* The server_uuid column is the unique key for matching.
*
* @param Server $server The server being checked
* @param array $data The data to store
* @return void
*/
private function updateDatabase(Server $server, array $data): void
{
DB::table('modpackchecker_servers')->updateOrInsert(
['server_uuid' => $server->uuid],
array_merge($data, ['updated_at' => now()])
);
}
}