Files
pterodactyl_addon/app/Addons/AdvancedAdmin/Services/Network/DockerStatsCollector.php
2026-06-11 20:33:44 -05:00

84 lines
2.5 KiB
PHP

<?php
/**
* Docker Stats Collector — queries Wings REST API for per-server network stats.
*
* @author Ball Studios <https://git.balls.studio>
* @license MIT
*/
namespace Pterodactyl\Addons\AdvancedAdmin\Services\Network;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Pterodactyl\Models\Node;
use Pterodactyl\Models\Server;
class DockerStatsCollector
{
/**
* Collect network stats for all running servers on a node.
* Returns array of ['server_id' => int, 'rx_bytes' => int, 'tx_bytes' => int]
*/
public function collectForNode(Node $node): array
{
$results = [];
$servers = Server::query()
->where('node_id', $node->id)
->whereNotNull('uuid')
->get();
foreach ($servers as $server) {
try {
$stats = $this->fetchStats($node, $server->uuid);
if ($stats !== null) {
$results[] = [
'server_id' => $server->id,
'node_id' => $node->id,
'rx_bytes' => $stats['network']['rx_bytes'] ?? 0,
'tx_bytes' => $stats['network']['tx_bytes'] ?? 0,
];
}
} catch (\Throwable $e) {
Log::warning("[AdvancedAdmin] Failed to collect stats for server {$server->uuid}: {$e->getMessage()}");
}
}
return $results;
}
/**
* Fetch stats from Wings for a single server UUID.
* Uses the Wings Application API (node-level auth token).
*
* SSRF Prevention: URL is constructed only from trusted Node model data,
* never from user input.
*/
private function fetchStats(Node $node, string $serverUuid): ?array
{
// Validate UUID format before use
if (!preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $serverUuid)) {
return null;
}
$scheme = $node->scheme;
$fqdn = $node->fqdn;
$port = $node->daemonListen;
// Allowlist: only known node FQDNs are used (from DB, not from user input)
$url = "{$scheme}://{$fqdn}:{$port}/api/servers/{$serverUuid}/resources";
$response = Http::withToken($node->daemon_token_id)
->withHeaders(['Authorization' => "Bearer {$node->daemon_token_id}"])
->timeout(5)
->get($url);
if (!$response->successful()) {
return null;
}
return $response->json('resources');
}
}