# MODPACK VERSION CHECKER - IMPLEMENTATION PLAN

**Complete code implementation organized by component**

All code blocks provided by Gemini Pro research session (February 22, 2026)

---

## 📋 TABLE OF CONTENTS

1. Blueprint Configuration
2. Database Installation Scripts
3. Backend Services (PHP/Laravel)
4. Frontend Components (React/TypeScript)
5. API Provider Implementations
6. Caching & Detection Services
7. Configuration Files

---

## 1. BLUEPRINT CONFIGURATION

### blueprint.yml

**Location:** `/blueprint.yml`

```yaml
info:
  name: "Modpack Version Monitor"
  identifier: "modpackmonitor"
  description: "Automated version tracking for CurseForge, FTB, and Modrinth."
  version: "1.0.0"
  target: "alpha"

# UI Injection Configuration
injections:
  - target: resources/scripts/components/server/ServerDetailsBlock.tsx
    find: <p className={"text-xs text-neutral-400 font-mono overflow-hidden text-ellipsis"}>
    replace: |
      <ModpackStatusBadge serverId={server.id} />
      <p className={"text-xs text-neutral-400 font-mono overflow-hidden text-ellipsis"}>
```

**Purpose:** Defines extension metadata and UI injection points for Blueprint framework

---

## 2. DATABASE INSTALLATION SCRIPTS

### install.sh - Database Injection Script

**Location:** `/scripts/install.sh`

**Block 1: Initial Setup & Environment Parsing**

```bash
#!/bin/bash
# Blueprint Extension Install Script: Modpack Checker
PTERO_DIR="/var/www/pterodactyl"

if [ ! -f "$PTERO_DIR/.env" ]; then
    echo "❌ Error: Pterodactyl .env not found. Aborting."
    exit 1
fi

get_env() {
    grep -w "$1" $PTERO_DIR/.env | cut -d '=' -f2 | tr -d '"' | tr -d "'"
}
```

**Block 2: Variable Assignment**

```bash
echo "Loading database credentials..."
DB_HOST=$(get_env "DB_HOST")
DB_PORT=$(get_env "DB_PORT")
DB_NAME=$(get_env "DB_DATABASE")
DB_USER=$(get_env "DB_USERNAME")
DB_PASS=$(get_env "DB_PASSWORD")
```

**Block 3: SQL Payload for MODPACK_PLATFORM**

```bash
SQL_PLATFORM="INSERT INTO egg_variables 
(egg_id, name, description, env_variable, default_value, user_viewable, user_editable, rules, created_at, updated_at)
SELECT id, 'Modpack Platform', 'Detected platform (auto, curseforge, ftb, modrinth)', 'MODPACK_PLATFORM', 'auto', 1, 1, 'required|string|max:50', NOW(), NOW()
FROM eggs WHERE NOT EXISTS (
    SELECT 1 FROM egg_variables ev WHERE ev.egg_id = eggs.id AND ev.env_variable = 'MODPACK_PLATFORM'
);"
```

**Block 4: SQL Payload for MODPACK_ID**

```bash
SQL_ID="INSERT INTO egg_variables 
(egg_id, name, description, env_variable, default_value, user_viewable, user_editable, rules, created_at, updated_at)
SELECT id, 'Modpack ID', 'Project ID for the platform', 'MODPACK_ID', '', 1, 1, 'nullable|string|max:191', NOW(), NOW()
FROM eggs WHERE NOT EXISTS (
    SELECT 1 FROM egg_variables ev WHERE ev.egg_id = eggs.id AND ev.env_variable = 'MODPACK_ID'
);"
```

**Block 5: Execution & Error Handling**

```bash
echo "Injecting Modpack Platform variable..."
mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "$SQL_PLATFORM"
if [ $? -ne 0 ]; then
    echo "❌ Failed to inject MODPACK_PLATFORM. Aborting."
    exit 1
fi

echo "Injecting Modpack ID variable..."
mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "$SQL_ID"
echo "✅ Blueprint extension installed successfully! 🔥❄️"
```

---

### remove.sh - Clean Uninstall Script

**Location:** `/scripts/remove.sh`

**Block 1: Setup & Deletion**

```bash
#!/bin/bash
# Blueprint Extension Remove Script
PTERO_DIR="/var/www/pterodactyl"

get_env() {
    grep -w "$1" $PTERO_DIR/.env | cut -d '=' -f2 | tr -d '"' | tr -d "'"
}

DB_NAME=$(get_env "DB_DATABASE")
DB_USER=$(get_env "DB_USERNAME")
DB_PASS=$(get_env "DB_PASSWORD")
```

**Block 2: Safely Execute Removal**

```bash
echo "Removing database variables..."
SQL_REMOVE="DELETE FROM egg_variables WHERE env_variable IN ('MODPACK_PLATFORM', 'MODPACK_ID');"

mysql -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "$SQL_REMOVE"

if [ $? -eq 0 ]; then
    echo "🗑️ Modpack variables safely removed."
else
    echo "❌ Error removing database variables. Manual cleanup required."
fi
```

**Note:** CASCADE foreign keys automatically clean server_variables table

---

## 3. BACKEND SERVICES (PHP/LARAVEL)

### ModpackDetector.php - File Scanning & Platform Detection

**Location:** `/app/Services/Extensions/ModpackVersionChecker/ModpackDetector.php`

**Block 1: Service Setup**

```php
<?php
// app/Services/Extensions/ModpackDetector.php
namespace Pterodactyl\Services\Extensions;

class ModpackDetector {
    private const PLATFORMS = [
        'ftb' => 'instance.json',
        'modrinth' => 'modrinth.index.json',
        'curseforge' => 'manifest.json',
    ];
}
```

**Block 2: File Scanning Logic**

```php
public function detect(Server $server) {
    foreach (self::PLATFORMS as $type => $file) {
        // Check if file exists in server root
        if ($this->disk->exists($server->uuid . '/' . $file)) {
            return $this->processPlatform($type, $server, $file);
        }
    }
    return 'unknown';
}
```

**Block 3: CurseForge Extraction**

```php
private function parseCurseForge($content) {
    $data = json_decode($content, true);
    return [
        'id' => $data['projectID'] ?? null,
        'version' => $data['version'] ?? 'unknown'
    ];
}
```

**Block 4: FTB Extraction**

```php
private function parseFTB($content) {
    $data = json_decode($content, true);
    return [
        'id' => $data['modpackId'] ?? null,
        'version' => $data['versionId'] ?? 'unknown'
    ];
}
```

---

### ModpackCacheService.php - Egg Variable Caching

**Location:** `/app/Services/Extensions/ModpackVersionChecker/ModpackCacheService.php`

**Block 1: Service Setup**

```php
<?php
// app/Services/Extensions/ModpackCacheService.php
namespace Pterodactyl\Services\Extensions;

use Pterodactyl\Models\Server;

class ModpackCacheService {
    protected $detector;

    public function __construct(ModpackDetector $detector) {
        $this->detector = $detector;
    }
}
```

**Block 2: Reading the Cache**

```php
public function getCachedPlatform(Server $server) {
    $platformVar = $server->variables()
        ->whereHas('variable', function ($query) {
            $query->where('env_variable', 'MODPACK_PLATFORM');
        })->first();

    return $platformVar ? $platformVar->server_value : 'auto';
}
```

**Block 3: The Detection Trigger**

```php
public function resolvePlatform(Server $server) {
    $current = $this->getCachedPlatform($server);
    
    if ($current === 'auto') {
        $detected = $this->detector->detect($server);
        $this->updateCache($server, $detected['platform'], $detected['id']);
        return $detected['platform'];
    }

    return $current;
}
```

**Block 4: Writing to Database (Caching)**

```php
private function updateCache(Server $server, $platform, $id) {
    $server->variables()->whereHas('variable', fn($q) => 
        $q->where('env_variable', 'MODPACK_PLATFORM')
    )->update(['server_value' => $platform]);

    $server->variables()->whereHas('variable', fn($q) => 
        $q->where('env_variable', 'MODPACK_ID')
    )->update(['server_value' => $id]);
}
```

---

## 4. FRONTEND COMPONENTS (REACT/TYPESCRIPT)

### ModpackStatusBadge.tsx - React Component

**Location:** `/resources/scripts/components/server/ModpackStatusBadge.tsx`

**Block 1: Component Setup**

```tsx
// resources/scripts/components/server/ModpackStatusBadge.tsx
import React, { useState, useEffect } from 'react';
import getModpackStatus from '@/api/server/getModpackStatus';

export default ({ serverId }: { serverId: string }) => {
    const [status, setStatus] = useState('Checking...');
    const [color, setColor] = useState('bg-yellow-500');

    // Fetch logic goes here
};
```

**Block 2: Component Render**

```tsx
// resources/scripts/components/server/ModpackStatusBadge.tsx (continued)
    return (
        <div className="flex items-center mt-2">
            <span className={`px-2 py-1 rounded text-xs text-white ${color}`}>
                Modpack: {status}
            </span>
            <button onClick={forceRescan} className="ml-2 text-xs text-gray-400 hover:text-white">
                🔄 Re-scan
            </button>
        </div>
    );
```

**Note:** Complete implementation requires API endpoint for fetching status and re-scan trigger

---

## 5. API PROVIDER IMPLEMENTATIONS

### FtbApiService.php - FTB API Integration

**Location:** `/app/Services/Extensions/ModpackVersionChecker/Providers/FtbApiService.php`

```php
<?php
// app/Services/FtbApiService.php
namespace Pterodactyl\Services\Extensions;

use Illuminate\Support\Facades\Http;

class FtbApiService {
    protected $baseUrl = 'https://api.modpacks.ch/public';

    public function getLatestVersion($modpackId) {
        // Fetch modpack details
        $response = Http::get("{$this->baseUrl}/modpack/{$modpackId}");
        
        if ($response->successful()) {
            $versions = $response->json('versions');
            // FTB versions are usually ordered; grab the first (newest)
            return $versions[0]['name'] ?? 'Unknown';
        }
        return null;
    }
}
```

**API Endpoint:** `https://api.modpacks.ch/public/modpack/{modpackId}`

**Authentication:** None required (public API)

**Notes:**
- FTB returns version ID (integer) separately from version string
- Requires two API calls for full version info
- No rate limiting on public endpoints

---

### CurseForgeProvider.php - CurseForge API Integration

**Location:** `/app/Services/Extensions/ModpackVersionChecker/Providers/CurseForgeProvider.php`

**Structure:**

```php
<?php
namespace Pterodactyl\Services\Extensions\Providers;

class CurseForgeProvider implements ModpackProviderInterface {
    protected $apiKey;
    protected $baseUrl = 'https://api.curseforge.com/v1';

    public function __construct($apiKey) {
        $this->apiKey = $apiKey;
    }

    public function detectPlatform($serverPath) {
        // Check for manifest.json
    }

    public function getInstalledVersion($serverPath) {
        // Parse manifest.json
    }

    public function getLatestVersion($projectId) {
        // Query CurseForge API
    }

    public function compareVersions($installed, $latest) {
        // Semantic version comparison
    }
}
```

**API Requirements:**
- Requires API key (user-provided)
- Store globally in admin panel
- Graceful degradation if no key

---

### ModrinthProvider.php - Modrinth API Integration

**Location:** `/app/Services/Extensions/ModpackVersionChecker/Providers/ModrinthProvider.php`

**Structure:**

```php
<?php
namespace Pterodactyl\Services\Extensions\Providers;

class ModrinthProvider implements ModpackProviderInterface {
    protected $baseUrl = 'https://api.modrinth.com/v2';

    // No API key needed - public API

    public function detectPlatform($serverPath) {
        // Check for modrinth.index.json
    }

    public function getInstalledVersion($serverPath) {
        // Parse modrinth.index.json
    }

    public function getLatestVersion($projectId) {
        // Query Modrinth API (no auth)
    }

    public function compareVersions($installed, $latest) {
        // Version comparison
    }
}
```

**API Advantages:**
- No authentication required
- Fast and reliable
- Modern API design

---

## 6. PROVIDER INTERFACE

### ModpackProviderInterface.php

**Location:** `/app/Services/Extensions/ModpackVersionChecker/Providers/ModpackProviderInterface.php`

```php
<?php
namespace Pterodactyl\Services\Extensions\Providers;

interface ModpackProviderInterface {
    /**
     * Detect if this platform's files exist in server directory
     */
    public function detectPlatform(string $serverPath): bool;

    /**
     * Extract installed version from manifest file
     */
    public function getInstalledVersion(string $serverPath): ?string;

    /**
     * Query platform API for latest version
     */
    public function getLatestVersion(string $projectId): ?string;

    /**
     * Compare installed vs latest version
     * Returns: 'current', 'outdated', or 'unknown'
     */
    public function compareVersions(string $installed, string $latest): string;
}
```

**Purpose:** Ensures all providers implement consistent interface for detection, version extraction, and comparison

---

## 7. DIRECTORY STRUCTURE

**Complete Blueprint extension file structure:**

```
modpack-version-checker.blueprint/
├── blueprint.yml
├── scripts/
│   ├── install.sh
│   └── remove.sh
├── app/
│   └── Services/
│       └── Extensions/
│           └── ModpackVersionChecker/
│               ├── ModpackDetector.php
│               ├── ModpackCacheService.php
│               └── Providers/
│                   ├── ModpackProviderInterface.php
│                   ├── CurseForgeProvider.php
│                   ├── ModrinthProvider.php
│                   └── FtbApiService.php
├── resources/
│   └── scripts/
│       └── components/
│           └── server/
│               └── ModpackStatusBadge.tsx
└── README.md
```

---

## 8. IMPLEMENTATION WORKFLOW

### Step 1: Create Extension Skeleton

```bash
mkdir -p modpack-version-checker.blueprint
cd modpack-version-checker.blueprint
mkdir -p scripts app/Services/Extensions/ModpackVersionChecker/Providers
mkdir -p resources/scripts/components/server
```

### Step 2: Create Configuration Files

1. Copy `blueprint.yml` (from above)
2. Create `install.sh` (all 5 blocks)
3. Create `remove.sh` (all 2 blocks)

### Step 3: Implement Backend Services

1. Create `ModpackDetector.php` (all 4 blocks)
2. Create `ModpackCacheService.php` (all 4 blocks)
3. Create `ModpackProviderInterface.php`
4. Create `FtbApiService.php`
5. Create `CurseForgeProvider.php` (skeleton)
6. Create `ModrinthProvider.php` (skeleton)

### Step 4: Implement Frontend Component

1. Create `ModpackStatusBadge.tsx` (both blocks)
2. Add API endpoint for status fetching
3. Add re-scan trigger logic

### Step 5: Test Locally

1. Build Blueprint package: `blueprint -build`
2. Test on clean Pterodactyl VPS (NOT Firefrost nodes)
3. Verify all test cases pass

### Step 6: Polish & Launch

1. Complete documentation
2. Beta test with 3-5 users
3. Create BuiltByBit listing
4. Launch

---

## 9. CRITICAL IMPLEMENTATION NOTES

### Database Safety

**The install.sh script is IDEMPOTENT:**
- Uses `WHERE NOT EXISTS` clause
- Safe to run multiple times
- Won't duplicate variables
- Won't break existing servers

**CASCADE foreign keys:**
- Deleting `egg_variables` auto-deletes `server_variables`
- Clean uninstall with no orphaned data

### API Key Management

**CurseForge API Key Strategy:**
- Store globally in admin panel (one key for all users)
- Graceful degradation if no key (Modrinth/FTB still work)
- Show yellow badge: "CurseForge API Key Required"

**Never use shared key from developer:**
- Risk: Rate limiting affects ALL customers
- Risk: Negative BuiltByBit reviews
- Solution: User provides their own key

### React vs Blade

**CRITICAL:** Pterodactyl Client panel uses React, NOT Blade
- Admin panel = Blade (PHP)
- Client panel = React (TypeScript)
- UI injection must use React components

### Caching Performance

**Why Egg Variables?**
- Fast database reads vs slow file scans
- Pterodactyl-native (no custom tables)
- User-editable (manual override)
- Persistent across panel updates

**Cache invalidation:**
- User-controlled (force re-scan button)
- No automatic re-scanning (prevents infinite loops)
- Manual override in Startup tab

---

## 10. TESTING CHECKLIST

**Before BuiltByBit launch:**

- [ ] Clean install on test VPS (not Firefrost nodes)
- [ ] Database variables created correctly
- [ ] CurseForge detection works (manifest.json)
- [ ] Modrinth detection works (modrinth.index.json)
- [ ] FTB detection works (instance.json)
- [ ] API timeout handling (no crashes)
- [ ] Clean uninstall (restores UI, removes DB vars)
- [ ] Force re-scan button functional
- [ ] Manual override in Startup tab works
- [ ] API key admin panel functional
- [ ] Status badges display correctly (green/red/yellow)
- [ ] Beta testing complete (3-5 users, 1 week)

---

## 11. COMPLETION CRITERIA

**This implementation is COMPLETE when:**

✅ All code blocks assembled into working extension  
✅ All test cases pass  
✅ Beta testing validated  
✅ BuiltByBit listing published  
✅ Demo video recorded  
✅ Documentation complete  
✅ Support Discord created  
✅ First sale received  

---

**Fire + Frost + Foundation = Where Code Becomes Commerce** 💙🔥❄️💰

**Implementation guide created:** February 22, 2026  
**Created by:** The Chronicler #21  
**Total code blocks:** 20+ organized components  
**Ready for:** Assembly and testing
