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>
ModpackChecker — Pterodactyl Blueprint Extension
Version: 1.0.0
Author: Firefrost Gaming
License: Proprietary (Commercial product for BuiltByBit)
A Pterodactyl Panel extension that checks modpack versions across CurseForge, Modrinth, FTB, and Technic platforms. Shows update status on the dashboard and provides manual version checks from the server console.
Table of Contents
- Features
- Architecture Overview
- File Structure
- Installation
- Configuration
- Usage
- Development
- API Reference
- Troubleshooting
- Design Decisions
Features
Dashboard Badge
- Shows a colored dot next to each server name on the dashboard
- 🟠 Orange (Fire #FF6B35): Update available
- 🟢 Teal (Frost #4ECDC4): Up to date
- Hover for version details tooltip
- Single API call per page load (cached globally)
Console Widget
- "Check for Updates" button on each server's console page
- Real-time version check against platform API
- Shows modpack name, current version, and latest version
Admin Panel
- Configure CurseForge API key
- View extension status
- (Future: Rate limit settings, notification preferences)
Supported Platforms
| Platform | ID Type | API Key Required | Status |
|---|---|---|---|
| CurseForge | Numeric project ID | ✅ Yes | ✅ Working |
| Modrinth | Project ID or slug | ❌ No | ✅ Working |
| FTB | Numeric modpack ID | ❌ No | ✅ Working |
| Technic | URL slug | ❌ No | ✅ Working |
Note: Technic requires a dynamic build number parameter. The extension automatically fetches the current launcher build from Technic's API to avoid 401 errors.
Architecture Overview
┌─────────────────────────────────────────────────────────────────────────────┐
│ MODPACK VERSION CHECKER │
│ Architecture Diagram │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────┐
│ CRON JOB (runs every 4-6 hrs) │
│ php artisan modpackchecker:check │
│ │
│ • Finds servers with MODPACK_* │
│ • Calls platform APIs one by one │
│ • 2-second delay between calls │
│ • Stores results in database │
└──────────────────┬──────────────────┘
│
▼
┌─────────────────────────────────────┐
│ DATABASE CACHE │
│ modpackchecker_servers table │
│ │
│ • server_id, server_uuid │
│ • platform, modpack_id │
│ • current_version, latest_version │
│ • update_available (boolean) │
│ • last_checked timestamp │
└──────────────────┬──────────────────┘
│
┌──────────────────┴──────────────────┐
│ │
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────┐
│ DASHBOARD BADGE │ │ CONSOLE WIDGET │
│ (UpdateBadge.tsx) │ │ (wrapper.tsx) │
│ │ │ │
│ • Reads from cache ONLY │ │ • Manual "Check" button │
│ • Never calls external │ │ • LIVE API call │
│ • One API call per page │ │ • Single server only │
│ • Shows 🟠 or 🟢 dot │ │ • Shows full details │
└───────────────────────────┘ └───────────────────────────┘
Why This Architecture?
The Problem: A panel with 50 servers and 20 active users could generate thousands of API calls per day if each dashboard view triggered live checks.
The Solution:
- Cron job handles external API calls with rate limiting
- Dashboard reads from local cache only
- Console provides on-demand checks for specific servers
This was validated by Gemini AI during architectural review.
File Structure
blueprint-extension/
├── README.md # This file
├── conf.yml # Blueprint configuration
├── build.sh # Injection script (runs during blueprint -build)
├── icon.png # Extension icon (128x128, Gemini-designed)
│
├── app/ # Merges into Pterodactyl's app/ via requests.app
│ ├── Console/
│ │ └── Commands/
│ │ └── CheckModpackUpdates.php # Laravel cron command
│ └── Http/
│ └── Controllers/
│ └── ModpackAPIController.php # API endpoints (manualCheck, getStatus)
│
├── admin/
│ ├── controller.php # Admin panel logic
│ └── view.blade.php # Admin panel UI
│
├── database/
│ └── migrations/
│ └── 2026_04_06_000000_create_modpackchecker_servers_table.php
│
├── routes/
│ └── client.php # API route definitions
│
└── views/
├── server/
│ └── wrapper.tsx # Console "Check for Updates" widget
└── dashboard/
└── UpdateBadge.tsx # Dashboard status dot component
Why the app/ folder structure?
Blueprint's requests.app field merges the contents of your app/ folder directly into Pterodactyl's app/ directory. This means:
- PSR-4 Autoloading: Your classes are automatically found by Laravel's autoloader
- Correct Namespaces: Use
Pterodactyl\Http\Controllers(not custom Blueprint namespaces) - Case Sensitivity: Linux requires exact folder casing —
Controllers/notcontrollers/
This architecture was validated through painful debugging and Gemini AI consultation.
Installation
Prerequisites
- Pterodactyl Panel v1.11+
- Blueprint Framework (beta-2026-01 or newer)
- PHP 8.1+
- Node.js 18+
Steps
-
Copy extension to Blueprint directory:
cp -r blueprint-extension /var/www/pterodactyl/.blueprint/extensions/modpackchecker chown -R www-data:www-data /var/www/pterodactyl/.blueprint/extensions/modpackchecker -
Build the extension:
cd /var/www/pterodactyl blueprint -build -
Compile frontend assets:
export NODE_OPTIONS=--openssl-legacy-provider yarn build:production -
Run database migration:
php artisan migrate -
Restart PHP-FPM:
systemctl restart php8.3-fpm -
Set up cron job:
# Add to /etc/crontab or crontab -e 0 */6 * * * www-data cd /var/www/pterodactyl && php artisan modpackchecker:check >> /dev/null 2>&1
Configuration
Server Egg Variables
For modpack detection, set these variables in your server's egg:
| Variable | Description | Example |
|---|---|---|
MODPACK_PLATFORM |
Platform name | modrinth, curseforge, ftb, technic |
MODPACK_ID |
Platform-specific ID | adrenaserver (Modrinth slug) |
MODPACK_CURRENT_VERSION |
Installed version | 1.7.0 |
CurseForge API Key
CurseForge requires an API key. To configure:
- Apply for API access at https://docs.curseforge.com/
- Go to Admin Panel → Extensions → ModpackChecker
- Enter your API key and save
Usage
Dashboard Badge
No action needed — badges appear automatically for servers that:
- Have
MODPACK_PLATFORMegg variable set - Have been checked by the cron job at least once
Manual Check
- Go to any server's console page
- Click "Check for Updates" button
- View results showing modpack name and version status
Cron Command
Run manually for testing:
cd /var/www/pterodactyl
php artisan modpackchecker:check
Output:
Starting modpack update check...
Found 12 servers with modpack configuration
Checking: ATM9 Server (a1b2c3d4-...)
🟠 UPDATE AVAILABLE: All The Mods 9 - 0.2.60
Checking: Vanilla+ (e5f6g7h8-...)
🟢 Up to date: Vanilla+ - 1.2.0
Modpack update check complete!
Development
Local Development
- Enable Blueprint developer mode in admin panel
- Make changes in
.blueprint/dev/or.blueprint/extensions/modpackchecker/ - Run
blueprint -buildafter changes - Run
yarn build:productionfor frontend changes
Testing API Endpoints
# Manual check (requires auth token)
curl -X POST "https://panel.example.com/api/client/servers/{uuid}/ext/modpackchecker/check" \
-H "Authorization: Bearer {token}"
# Get all statuses (requires auth token)
curl "https://panel.example.com/api/client/extensions/modpackchecker/status" \
-H "Authorization: Bearer {token}"
Adding a New Platform
- Add check method to
ModpackAPIController.php(e.g.,checkNewPlatform()) - Add to the
match()statement incheckVersion() - Add same method to
CheckModpackUpdates.php - Update this README
API Reference
POST /api/client/servers/{server}/ext/modpackchecker/check
Manual version check for a specific server. Makes live API call.
Response:
{
"success": true,
"platform": "modrinth",
"modpack_id": "adrenaserver",
"modpack_name": "Adrenaserver",
"latest_version": "1.7.0+1.21.1.fabric",
"status": "checked"
}
GET /api/client/extensions/modpackchecker/status
Get cached status for all servers accessible to the authenticated user.
Response:
{
"a1b2c3d4-...": {
"update_available": true,
"modpack_name": "All The Mods 9",
"current_version": "0.2.51",
"latest_version": "0.2.60"
},
"e5f6g7h8-...": {
"update_available": false,
"modpack_name": "Adrenaserver",
"current_version": "1.7.0",
"latest_version": "1.7.0"
}
}
Troubleshooting
Badge not showing
- Check server has
MODPACK_PLATFORMvariable set - Run cron command manually:
php artisan modpackchecker:check - Check
modpackchecker_serverstable for entries
"CurseForge API key not configured"
- Go to Admin → Extensions → ModpackChecker
- Enter your CurseForge API key
- Key must have mod read permissions
500 errors on check
- Check PHP error log:
tail -f /var/log/php8.3-fpm.log - Verify controller namespace:
Pterodactyl\Http\Controllers - Restart PHP-FPM:
systemctl restart php8.3-fpm
Build.sh not running
- Ensure file is executable:
chmod +x build.sh - Check Blueprint version supports build scripts
- Run manually from panel root:
bash .blueprint/extensions/modpackchecker/build.sh
Design Decisions
Why cache instead of live checks?
Rate limits. CurseForge allows ~1000 requests/day for personal keys. A busy panel could exhaust that in hours without caching.
Why 2-second sleep in cron?
Prevents burst traffic to APIs. 50 servers × 2 seconds = ~2 minute runtime, which is acceptable for a background job.
Why inline styles in React?
The component is injected into Pterodactyl's build. Adding CSS classes would require modifying their build pipeline. Inline styles are self-contained.
Why separate console widget and dashboard badge?
Different use cases:
- Dashboard: Quick overview, needs to be fast → cached
- Console: User wants current info → live API call is acceptable
Credits
Developed by: Firefrost Gaming / Frostystyle
Contact: dev@firefrostgaming.com
Website: https://firefrostgaming.com
Part of Firefrost Gaming
Fire + Frost + Foundation = Where Love Builds Legacy 🔥❄️💙