Files
firefrost-operations-manual/docs/tasks/modpack-version-checker/proxy/proxy.php
Claude (Chronicler #47) cfa838e86a feat: modpack version checker dashboard + PHP proxy (v1.0)
WHAT WAS DONE:
- Built browser dashboard (dashboard.html) showing installed vs latest version
  for all Pterodactyl game servers
- Built PHP proxy (proxy.php + config.php) for Billing VPS deployment
- Created isolated Nginx server block (version-proxy.conf)
- Created full deployment guide (DEPLOYMENT-GUIDE.md)

ARCHITECTURE:
- PHP proxy at /var/www/version-proxy on Billing VPS (38.68.14.188)
- Isolated from Paymenter/Laravel routing — separate directory + port
- API keys (Pterodactyl ptlc_, CurseForge) live server-side only
- FTB packs: fully automatic via .manifest.json + FTB public API
- CurseForge packs: reads manifest.json, needs CF Project ID + API key
- config.php blocked from direct web access via Nginx

PENDING AT DEPLOYMENT:
- Verify port 8080 is free (ss -tlnp) before enabling Nginx block
- Fill real API keys into config.php on server
- Enter CurseForge Project IDs for CF packs (saved in localStorage)

COLLABORATION:
- PHP proxy architecture designed by Gemini (consultation session 2026-03-29)
- Dashboard HTML and detection logic by Chronicler #47
- Gemini identified Laravel routing conflict and content-type gotcha

WHY:
- Interim solution before full Blueprint extension (post-launch)
- Hands-off modpack update monitoring for staff
- Zero manual checking required after initial CF Project ID setup

Signed-off-by: claude@firefrostgaming.com
2026-03-29 14:10:47 +00:00

72 lines
2.5 KiB
PHP

<?php
// proxy.php
// Deploy to /var/www/version-proxy/ on Billing VPS (38.68.14.188)
// Serves as CORS-safe API bridge between the dashboard HTML and:
// - Pterodactyl Panel API (panel.firefrostgaming.com)
// - CurseForge API (api.curseforge.com)
$config = require 'config.php';
$action = $_GET['action'] ?? '';
// Allow requests from any origin (dashboard can be hosted anywhere)
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET');
header('Access-Control-Allow-Headers: Content-Type');
function makeRequest($url, $headers) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
$result = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 400) {
http_response_code($httpCode);
echo json_encode(['error' => "HTTP $httpCode"]);
exit;
}
return $result;
}
$panelHeaders = [
"Authorization: Bearer {$config['panel_key']}",
"Accept: application/json"
];
if ($action === 'servers') {
// List all servers
header('Content-Type: application/json');
echo makeRequest("{$config['panel_url']}/api/client/servers", $panelHeaders);
} elseif ($action === 'files') {
// List files in server root
header('Content-Type: application/json');
$id = urlencode($_GET['server'] ?? '');
echo makeRequest("{$config['panel_url']}/api/client/servers/{$id}/files/list", $panelHeaders);
} elseif ($action === 'read') {
// Read a specific file from server filesystem
// Note: Pterodactyl returns raw text here, not JSON
header('Content-Type: text/plain');
$id = urlencode($_GET['server'] ?? '');
$file = urlencode($_GET['file'] ?? '');
echo makeRequest("{$config['panel_url']}/api/client/servers/{$id}/files/contents?file={$file}", $panelHeaders);
} elseif ($action === 'curseforge') {
// Get latest CurseForge mod files (API key stays server-side)
header('Content-Type: application/json');
$projectId = urlencode($_GET['project'] ?? '');
$cfHeaders = [
"x-api-key: {$config['cf_key']}",
"Accept: application/json"
];
echo makeRequest("https://api.curseforge.com/v1/mods/{$projectId}/files?pageSize=1&sortField=5&sortOrder=desc", $cfHeaders);
} else {
header('Content-Type: application/json');
http_response_code(400);
echo json_encode(['error' => 'Invalid action']);
}