# 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 " 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 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 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 { const uuid = ServerContext.useStoreState(state => state.server.data?.uuid); const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle'); const [data, setData] = useState(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 (

Modpack Version

{status === 'idle' && ( )} {status === 'loading' && (

Checking...

)} {status === 'success' && data && (

Platform: {data.platform}

Current: {data.current_version}

Latest: {data.latest_version}

{data.status === 'update_available' ? '⚠️ Update Available' : '✅ Up to Date'}

)} {status === 'error' && (

Error checking version

)}
); }; 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** 💙🔥❄️