MAJOR SESSION ACCOMPLISHMENTS (Chronicler #62, April 5, 2026): Task #26 — ModpackChecker Extension: - Full Dev Panel deployed (64.50.188.128) - Pterodactyl 1.12.2 + Wings Local Node + Blueprint beta-2026-01 - Extension scaffolded with real structure verified - 4-platform support from Day 1 (CurseForge, Modrinth, Technic, FTB) - FTB now accessible via modpacks.ch API (game changer) - Pricing locked: $14.99 Standard / $24.99 Professional - Gemini consultation: 20+ gaps filled with concrete details Implementation Guide includes: - Real Blueprint file structure (verified from scaffold) - All 4 API endpoints with response schemas - Database schema (modpack_settings, server_modpack_data) - Admin controller boilerplate (Blade) - Client API controller boilerplate (PHP) - React component boilerplate (TSX) - Detection strategy (Egg Variables → File Fingerprinting → Manual) - Error handling patterns - Cron job chunking for rate limit protection Other session work: - Complete task audit (#2-94) - BACKLOG.md full rewrite (30 active tasks) - Fabric egg fix (eclipse-temurin:21-jdk-jammy) - Gemini consultation procedure updated with template TODO before Phase 2: - Create dev@firefrostgaming.com in Mailcow - Begin admin settings UI (Blade) - Implement Modrinth API first (easiest) Signed-off-by: Claude (Chronicler #62) <claude@firefrostgaming.com>
608 lines
17 KiB
Markdown
608 lines
17 KiB
Markdown
# ModpackChecker Blueprint Extension — Implementation Guide
|
|
|
|
**Document ID:** FFG-TASK-026-IMPL
|
|
**Created:** April 5, 2026
|
|
**Created By:** Chronicler #62 with Gemini consultation
|
|
**Status:** VERIFIED — Based on real Blueprint scaffolding
|
|
**Dev Panel:** 64.50.188.128
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
This guide contains the **verified, real-world implementation details** for the ModpackChecker Blueprint extension. Unlike theoretical documentation, this is based on:
|
|
- Actual Blueprint beta-2026-01 scaffolding output
|
|
- Real Pterodactyl 1.12.2 installation
|
|
- Gemini architectural consultation (April 5, 2026)
|
|
|
|
---
|
|
|
|
## 1. Product Specification
|
|
|
|
| Attribute | Value |
|
|
|-----------|-------|
|
|
| Name | ModpackChecker |
|
|
| Identifier | modpackchecker |
|
|
| Target | Pterodactyl 1.12.x |
|
|
| Blueprint | beta-2026-01 |
|
|
| Pricing | $14.99 Standard / $24.99 Professional |
|
|
| Distribution | BuiltByBit (two separate listings) |
|
|
|
|
### Platform Support (Day 1)
|
|
|
|
| Platform | API Endpoint | Auth |
|
|
|----------|--------------|------|
|
|
| CurseForge | `https://api.curseforge.com/v1/mods/{modId}` | BYOK (`x-api-key`) |
|
|
| Modrinth | `https://api.modrinth.com/v2/project/{id}/version` | None |
|
|
| Technic | `https://api.technicpack.net/modpack/{slug}?build=1` | None |
|
|
| FTB | `https://api.modpacks.ch/public/modpack/{id}` | None |
|
|
|
|
### Tier Features
|
|
|
|
| Feature | Standard | Professional |
|
|
|---------|----------|--------------|
|
|
| 4-Platform Support | ✅ | ✅ |
|
|
| Manual Checks | ✅ | ✅ |
|
|
| Egg Variable Caching | ✅ | ✅ |
|
|
| Automated Cron Checks | ❌ | ✅ |
|
|
| Discord Webhook Alerts | ❌ | ✅ |
|
|
| Global Dashboard | ❌ | ✅ |
|
|
|
|
---
|
|
|
|
## 2. Dev Environment
|
|
|
|
**Dev Panel Credentials:**
|
|
|
|
| Item | Value |
|
|
|------|-------|
|
|
| URL | http://64.50.188.128 |
|
|
| Admin User | frostystyle |
|
|
| Admin Password | FFG-Dev-2026! |
|
|
| DB Name | panel |
|
|
| DB User | pterodactyl |
|
|
| DB Password | FFG-Dev-Panel-2026! |
|
|
| Node | Dev-Local (127.0.0.1) |
|
|
| Wings Port | 8080 |
|
|
| SFTP Port | 2022 |
|
|
| Allocations | 127.0.0.1:25500-25510 |
|
|
|
|
---
|
|
|
|
## 3. Blueprint Extension Structure
|
|
|
|
**Location:** `/var/www/pterodactyl/.blueprint/dev/`
|
|
|
|
```
|
|
modpackchecker/
|
|
├── conf.yml # Extension manifest
|
|
├── admin/
|
|
│ ├── controller.php # Admin settings controller (Blade)
|
|
│ └── view.blade.php # Admin settings UI
|
|
├── controllers/
|
|
│ └── ModpackAPIController.php # Client API controller
|
|
├── routes/
|
|
│ ├── admin.php # Admin routes
|
|
│ └── client.php # Client API routes
|
|
├── views/
|
|
│ └── server/
|
|
│ └── wrapper.tsx # Per-server React component
|
|
├── database/
|
|
│ └── migrations/
|
|
│ └── 2026_04_05_000000_create_modpack_settings.php
|
|
└── .dist/ # Build output (auto-generated)
|
|
```
|
|
|
|
---
|
|
|
|
## 4. conf.yml (Extension Manifest)
|
|
|
|
```yaml
|
|
info:
|
|
name: "ModpackChecker"
|
|
identifier: "modpackchecker"
|
|
description: "4-platform modpack version checker for Pterodactyl - supports CurseForge, Modrinth, Technic, and FTB"
|
|
flags: ""
|
|
version: "1.0.0"
|
|
target: "beta-2026-01"
|
|
author: "Firefrost Gaming <dev@firefrostgaming.com>"
|
|
icon: ""
|
|
website: "https://firefrostgaming.com"
|
|
|
|
admin:
|
|
view: "admin/view.blade.php"
|
|
controller: "admin/controller.php"
|
|
css: ""
|
|
wrapper: ""
|
|
|
|
dashboard:
|
|
css: ""
|
|
wrapper: "views/server/wrapper.tsx"
|
|
components: ""
|
|
|
|
data:
|
|
directory: "modpackchecker"
|
|
public: ""
|
|
console: ""
|
|
|
|
requests:
|
|
views: "views"
|
|
app: ""
|
|
routers:
|
|
application: ""
|
|
client: "routes/client.php"
|
|
web: ""
|
|
|
|
database:
|
|
migrations: "database/migrations"
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Database Schema
|
|
|
|
### modpack_settings (Global Config)
|
|
|
|
```php
|
|
// database/migrations/2026_04_05_000000_create_modpack_settings.php
|
|
Schema::create('modpack_settings', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('key')->unique();
|
|
$table->text('value')->nullable();
|
|
$table->timestamps();
|
|
});
|
|
|
|
// Default records:
|
|
// - curseforge_api_key (BYOK)
|
|
// - discord_webhook_url (Professional)
|
|
// - check_interval (daily/12h/6h)
|
|
// - tier (standard/professional)
|
|
```
|
|
|
|
### server_modpack_data (Per-Server)
|
|
|
|
```php
|
|
Schema::create('server_modpack_data', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('server_uuid')->unique();
|
|
$table->string('platform')->nullable(); // curseforge, modrinth, technic, ftb
|
|
$table->string('modpack_id')->nullable();
|
|
$table->string('modpack_name')->nullable();
|
|
$table->string('current_version')->nullable();
|
|
$table->string('latest_version')->nullable();
|
|
$table->enum('status', ['up_to_date', 'update_available', 'error', 'unknown'])->default('unknown');
|
|
$table->timestamp('last_checked')->nullable();
|
|
$table->text('error_message')->nullable();
|
|
$table->timestamps();
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Backend Controllers
|
|
|
|
### Admin Controller (Blade)
|
|
|
|
**Location:** `admin/controller.php`
|
|
|
|
```php
|
|
<?php
|
|
namespace Pterodactyl\Http\Controllers\Admin\Extensions\modpackchecker;
|
|
|
|
use Illuminate\View\View;
|
|
use Illuminate\View\Factory as ViewFactory;
|
|
use Pterodactyl\Http\Controllers\Controller;
|
|
use Pterodactyl\BlueprintFramework\Libraries\ExtensionLibrary\Admin\BlueprintAdminLibrary as BlueprintExtensionLibrary;
|
|
use Pterodactyl\Http\Requests\Admin\AdminFormRequest;
|
|
use Illuminate\Http\RedirectResponse;
|
|
|
|
class modpackcheckerExtensionController extends Controller
|
|
{
|
|
public function __construct(
|
|
private ViewFactory $view,
|
|
private BlueprintExtensionLibrary $blueprint,
|
|
) {}
|
|
|
|
public function index(): View
|
|
{
|
|
// Database access via Blueprint helper
|
|
$curseforge_key = $this->blueprint->dbGet('modpackchecker', 'curseforge_api_key');
|
|
$webhook_url = $this->blueprint->dbGet('modpackchecker', 'discord_webhook_url');
|
|
$check_interval = $this->blueprint->dbGet('modpackchecker', 'check_interval') ?: 'daily';
|
|
|
|
return $this->view->make(
|
|
'admin.extensions.modpackchecker.index', [
|
|
'curseforge_key' => $curseforge_key,
|
|
'webhook_url' => $webhook_url,
|
|
'check_interval' => $check_interval,
|
|
'root' => '/admin/extensions/modpackchecker',
|
|
'blueprint' => $this->blueprint,
|
|
]
|
|
);
|
|
}
|
|
|
|
public function update(modpackcheckerSettingsFormRequest $request): RedirectResponse
|
|
{
|
|
$this->blueprint->dbSet('modpackchecker', 'curseforge_api_key', $request->input('curseforge_api_key'));
|
|
$this->blueprint->dbSet('modpackchecker', 'discord_webhook_url', $request->input('discord_webhook_url'));
|
|
$this->blueprint->dbSet('modpackchecker', 'check_interval', $request->input('check_interval'));
|
|
|
|
return redirect()->route('admin.extensions.modpackchecker.index');
|
|
}
|
|
}
|
|
|
|
class modpackcheckerSettingsFormRequest extends AdminFormRequest
|
|
{
|
|
public function rules(): array
|
|
{
|
|
return [
|
|
'curseforge_api_key' => 'nullable|string|max:500',
|
|
'discord_webhook_url' => 'nullable|url|max:500',
|
|
'check_interval' => 'required|in:daily,12h,6h',
|
|
];
|
|
}
|
|
}
|
|
```
|
|
|
|
### Client API Controller
|
|
|
|
**Location:** `controllers/ModpackAPIController.php`
|
|
|
|
```php
|
|
<?php
|
|
namespace Pterodactyl\BlueprintFramework\Extensions\modpackchecker\controllers;
|
|
|
|
use Pterodactyl\Http\Controllers\Controller;
|
|
use Pterodactyl\Models\Server;
|
|
use Pterodactyl\Repositories\Wings\DaemonFileRepository;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\JsonResponse;
|
|
|
|
class ModpackAPIController extends Controller
|
|
{
|
|
public function __construct(
|
|
private DaemonFileRepository $fileRepository
|
|
) {}
|
|
|
|
/**
|
|
* Manual version check triggered from React frontend
|
|
*/
|
|
public function manualCheck(Request $request, Server $server): JsonResponse
|
|
{
|
|
// 1. Try Egg Variables first (most reliable)
|
|
$platform = $this->getEggVariable($server, 'MODPACK_PLATFORM');
|
|
$modpackId = $this->getEggVariable($server, 'MODPACK_ID');
|
|
|
|
// 2. Fallback to file fingerprinting if not set
|
|
if (empty($platform) || empty($modpackId)) {
|
|
$detected = $this->detectFromFiles($server);
|
|
$platform = $detected['platform'] ?? null;
|
|
$modpackId = $detected['id'] ?? null;
|
|
}
|
|
|
|
// 3. If still nothing, return unknown
|
|
if (empty($platform) || empty($modpackId)) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'status' => 'unknown',
|
|
'message' => 'Could not detect modpack. Please set platform and ID manually.',
|
|
]);
|
|
}
|
|
|
|
// 4. Query the appropriate API
|
|
$versionData = $this->checkVersion($platform, $modpackId);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'server_uuid' => $server->uuid,
|
|
'platform' => $platform,
|
|
'modpack_id' => $modpackId,
|
|
'current_version' => $versionData['current'] ?? 'Unknown',
|
|
'latest_version' => $versionData['latest'] ?? 'Unknown',
|
|
'status' => $versionData['status'] ?? 'unknown',
|
|
]);
|
|
}
|
|
|
|
private function getEggVariable(Server $server, string $name): ?string
|
|
{
|
|
$variable = $server->variables()->where('env_variable', $name)->first();
|
|
return $variable?->server_value;
|
|
}
|
|
|
|
private function detectFromFiles(Server $server): array
|
|
{
|
|
try {
|
|
// Try CurseForge manifest.json
|
|
$content = $this->fileRepository->setServer($server)->getContent('/manifest.json');
|
|
$json = json_decode($content, true);
|
|
if (isset($json['projectID'])) {
|
|
return ['platform' => 'curseforge', 'id' => $json['projectID']];
|
|
}
|
|
} catch (\Exception $e) {
|
|
// File doesn't exist, try next
|
|
}
|
|
|
|
try {
|
|
// Try Modrinth modrinth.index.json
|
|
$content = $this->fileRepository->setServer($server)->getContent('/modrinth.index.json');
|
|
$json = json_decode($content, true);
|
|
if (isset($json['dependencies'])) {
|
|
// Modrinth detection logic
|
|
return ['platform' => 'modrinth', 'id' => null]; // Needs manual ID
|
|
}
|
|
} catch (\Exception $e) {
|
|
// File doesn't exist
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
private function checkVersion(string $platform, string $modpackId): array
|
|
{
|
|
// Dispatch to appropriate provider
|
|
return match($platform) {
|
|
'curseforge' => $this->checkCurseForge($modpackId),
|
|
'modrinth' => $this->checkModrinth($modpackId),
|
|
'technic' => $this->checkTechnic($modpackId),
|
|
'ftb' => $this->checkFTB($modpackId),
|
|
default => ['status' => 'error', 'message' => 'Unknown platform'],
|
|
};
|
|
}
|
|
|
|
private function checkCurseForge(string $modpackId): array
|
|
{
|
|
// Implementation with BYOK API key
|
|
// Header: x-api-key: {key}
|
|
// Endpoint: https://api.curseforge.com/v1/mods/{modId}
|
|
return ['status' => 'unknown']; // TODO
|
|
}
|
|
|
|
private function checkModrinth(string $modpackId): array
|
|
{
|
|
// Endpoint: https://api.modrinth.com/v2/project/{id}/version
|
|
return ['status' => 'unknown']; // TODO
|
|
}
|
|
|
|
private function checkTechnic(string $modpackId): array
|
|
{
|
|
// Endpoint: https://api.technicpack.net/modpack/{slug}?build=1
|
|
return ['status' => 'unknown']; // TODO
|
|
}
|
|
|
|
private function checkFTB(string $modpackId): array
|
|
{
|
|
// Endpoint: https://api.modpacks.ch/public/modpack/{id}
|
|
return ['status' => 'unknown']; // TODO
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Client Routes
|
|
|
|
**Location:** `routes/client.php`
|
|
|
|
```php
|
|
<?php
|
|
use Illuminate\Support\Facades\Route;
|
|
use Pterodactyl\BlueprintFramework\Extensions\modpackchecker\controllers\ModpackAPIController;
|
|
|
|
Route::post('/servers/{server}/ext/modpackchecker/check', [ModpackAPIController::class, 'manualCheck']);
|
|
```
|
|
|
|
---
|
|
|
|
## 8. React Component (Per-Server View)
|
|
|
|
**Location:** `views/server/wrapper.tsx`
|
|
|
|
```tsx
|
|
import React, { useState } from 'react';
|
|
import { ServerContext } from '@/state/server';
|
|
import http from '@/api/http';
|
|
|
|
const ModpackVersionCard: React.FC = () => {
|
|
const uuid = ServerContext.useStoreState(state => state.server.data?.uuid);
|
|
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
|
|
const [data, setData] = useState<any>(null);
|
|
|
|
const checkForUpdates = async () => {
|
|
if (!uuid) return;
|
|
|
|
setStatus('loading');
|
|
try {
|
|
const response = await http.post(`/api/client/servers/${uuid}/ext/modpackchecker/check`);
|
|
setData(response.data);
|
|
setStatus('success');
|
|
} catch (error) {
|
|
setStatus('error');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="bg-gray-700 rounded p-4 mb-4">
|
|
<h3 className="text-lg font-semibold mb-2">Modpack Version</h3>
|
|
|
|
{status === 'idle' && (
|
|
<button
|
|
onClick={checkForUpdates}
|
|
className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded"
|
|
>
|
|
Check for Updates
|
|
</button>
|
|
)}
|
|
|
|
{status === 'loading' && (
|
|
<p className="text-gray-400">Checking...</p>
|
|
)}
|
|
|
|
{status === 'success' && data && (
|
|
<div>
|
|
<p><strong>Platform:</strong> {data.platform}</p>
|
|
<p><strong>Current:</strong> {data.current_version}</p>
|
|
<p><strong>Latest:</strong> {data.latest_version}</p>
|
|
<p className={data.status === 'update_available' ? 'text-yellow-400' : 'text-green-400'}>
|
|
{data.status === 'update_available' ? '⚠️ Update Available' : '✅ Up to Date'}
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{status === 'error' && (
|
|
<p className="text-red-400">Error checking version</p>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ModpackVersionCard;
|
|
```
|
|
|
|
---
|
|
|
|
## 9. API Response Schemas
|
|
|
|
### CurseForge
|
|
```json
|
|
{
|
|
"data": {
|
|
"id": 123456,
|
|
"name": "All the Mods 9",
|
|
"latestFiles": [
|
|
{
|
|
"id": 9876543,
|
|
"displayName": "All the Mods 9 - 0.2.51",
|
|
"releaseType": 1
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
### Modrinth
|
|
```json
|
|
[
|
|
{
|
|
"id": "IIxjBpdz",
|
|
"project_id": "P7dR8mSH",
|
|
"name": "Fabulously Optimized 5.12.0",
|
|
"version_number": "5.12.0",
|
|
"version_type": "release"
|
|
}
|
|
]
|
|
```
|
|
|
|
### Technic
|
|
```json
|
|
{
|
|
"name": "tekkitmain",
|
|
"version": "1.2.9k",
|
|
"minecraft": "1.6.4"
|
|
}
|
|
```
|
|
|
|
### FTB (modpacks.ch)
|
|
```json
|
|
{
|
|
"name": "FTB Omnia",
|
|
"versions": [
|
|
{
|
|
"id": 45,
|
|
"name": "1.0.1",
|
|
"type": "Release",
|
|
"updated": 1585968036,
|
|
"targets": [
|
|
{"name": "minecraft", "version": "1.15.2"},
|
|
{"name": "forge", "version": "31.1.35"}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Detection Strategy
|
|
|
|
**Priority Order:**
|
|
1. **Egg Variables** (most reliable) — Check `MODPACK_PLATFORM` and `MODPACK_ID`
|
|
2. **File Fingerprinting** (fallback) — Read manifest files via DaemonFileRepository
|
|
3. **Manual Override** (last resort) — User sets in admin UI
|
|
|
|
**Egg Variable Names:**
|
|
- `CURSEFORGE_ID`
|
|
- `MODRINTH_PROJECT_ID`
|
|
- `FTB_MODPACK_ID`
|
|
- `TECHNIC_SLUG`
|
|
|
|
---
|
|
|
|
## 11. Error Handling
|
|
|
|
| Error | HTTP Code | User Message |
|
|
|-------|-----------|--------------|
|
|
| Pack delisted | 404 | "Pack not found or delisted from platform." |
|
|
| Invalid BYOK | 401/403 | "Invalid CurseForge API Key. Check Extension Settings." |
|
|
| No versions | 200 (empty) | "No release versions found." |
|
|
| Timeout | 500 | Show last known version with ⚠️ warning |
|
|
|
|
---
|
|
|
|
## 12. Cron Job (Professional Only)
|
|
|
|
```php
|
|
// In Laravel scheduler
|
|
Server::chunk(20, function ($servers) {
|
|
foreach ($servers as $server) {
|
|
// Check version via Egg Variables only (NOT file reads)
|
|
}
|
|
sleep(2); // Rate limit protection
|
|
});
|
|
```
|
|
|
|
**Frequency options:** Daily (default), Every 12 Hours, Every 6 Hours
|
|
|
|
---
|
|
|
|
## 13. Build & Deploy Commands
|
|
|
|
```bash
|
|
# Build extension
|
|
cd /var/www/pterodactyl
|
|
blueprint -build
|
|
|
|
# Export for distribution
|
|
blueprint -export
|
|
|
|
# Install on customer panel
|
|
blueprint -install modpackchecker
|
|
php artisan migrate
|
|
```
|
|
|
|
---
|
|
|
|
## 14. Documentation Files (for BuiltByBit)
|
|
|
|
- `README.md` — Installation, BYOK guide, webhook setup
|
|
- `CHANGELOG.md` — Version history
|
|
- `LICENSE` — Commercial license
|
|
|
|
---
|
|
|
|
## 15. TODO Before Production
|
|
|
|
- [ ] Create `dev@firefrostgaming.com` in Mailcow
|
|
- [ ] Implement all 4 API providers
|
|
- [ ] Build admin settings UI (Blade)
|
|
- [ ] Build per-server component (React)
|
|
- [ ] Implement Discord webhook
|
|
- [ ] Implement cron scheduler
|
|
- [ ] Build global dashboard (Professional)
|
|
- [ ] Test on Dev Panel
|
|
- [ ] Create BuiltByBit listings
|
|
- [ ] Write user documentation
|
|
|
|
---
|
|
|
|
**Fire + Frost + Foundation = Where Love Builds Legacy** 💙🔥❄️
|