diff --git a/extensions/Servers/Proxmox/Console/Commands/PollServerStatistics.php b/extensions/Servers/Proxmox/Console/Commands/PollServerStatistics.php index 528d64686..9d48d09ac 100644 --- a/extensions/Servers/Proxmox/Console/Commands/PollServerStatistics.php +++ b/extensions/Servers/Proxmox/Console/Commands/PollServerStatistics.php @@ -2,7 +2,9 @@ namespace Paymenter\Extensions\Servers\Proxmox\Console\Commands; +use Carbon\Carbon; use Illuminate\Console\Command; +use Illuminate\Support\Facades\Cache; use Paymenter\Extensions\Servers\Proxmox\Models\Server; use Paymenter\Extensions\Servers\Proxmox\Models\ServerStatistics; use Paymenter\Extensions\Servers\Proxmox\Proxmox; @@ -166,6 +168,145 @@ class PollServerStatistics extends Command } $this->info('Statistics polling completed. Success: ' . $successCount . ', Errors: ' . $errorCount); + + // Enforce bandwidth throttling for servers exceeding their limits + $this->info('Checking bandwidth limits...'); + $this->enforceBandwidthLimits(); + } + + protected function enforceBandwidthLimits() + { + $threshold = $this->calculateBandwidthThreshold(); + $this->info('Bandwidth threshold: ' . $threshold . ' GiB'); + + $throttledCount = 0; + $servers = Server::all(); + + foreach ($servers as $server) { + try { + $monthlyTraffic = $this->calculateServerMonthlyTraffic($server); + + if ($monthlyTraffic > $threshold) { + $this->warn('Server ID ' . $server->id . ' (' . $server->hostname . ') exceeded bandwidth: ' . + round($monthlyTraffic, 2) . ' GiB / ' . $threshold . ' GiB'); + $this->throttleServerBandwidth($server); + $throttledCount++; + } + } catch (\Throwable $e) { + $this->error('Error checking bandwidth for server ID ' . $server->id . ': ' . $e->getMessage()); + } + } + + $this->info('Bandwidth enforcement completed. Throttled: ' . $throttledCount . ' servers'); + } + + protected function calculateBandwidthThreshold() + { + return Cache::remember('bandwidth_threshold_95_' . Carbon::now()->format('Y-m'), 15, function () { + $startOfMonth = Carbon::now()->startOfMonth(); + $allServers = Server::all(); + + if ($allServers->isEmpty()) { + return 10; + } + + $totalTraffic = 0; + $serverCount = 0; + + foreach ($allServers as $server) { + $firstStat = ServerStatistics::where('server_id', $server->id) + ->where('DateTime', '>=', $startOfMonth) + ->orderBy('DateTime', 'asc') + ->first(); + + $latestStat = ServerStatistics::where('server_id', $server->id) + ->where('DateTime', '>=', $startOfMonth) + ->orderBy('DateTime', 'desc') + ->first(); + + if ($firstStat && $latestStat) { + $netinDiff = max(0, $latestStat->netin - $firstStat->netin); + $netoutDiff = max(0, $latestStat->netout - $firstStat->netout); + $totalBytes = $netinDiff + $netoutDiff; + $totalTraffic += $totalBytes / (1024 * 1024 * 1024); + $serverCount++; + } + } + + if ($serverCount === 0) { + return 10; + } + + $averageBandwidth = $totalTraffic / $serverCount; + $threshold95Percent = $averageBandwidth * 0.95; + + return round(max($threshold95Percent, 10), 2); + }); + } + + protected function calculateServerMonthlyTraffic(Server $server) + { + $startOfMonth = Carbon::now()->startOfMonth(); + + $firstStat = ServerStatistics::where('server_id', $server->id) + ->where('DateTime', '>=', $startOfMonth) + ->orderBy('DateTime', 'asc') + ->first(); + + $latestStat = ServerStatistics::where('server_id', $server->id) + ->where('DateTime', '>=', $startOfMonth) + ->orderBy('DateTime', 'desc') + ->first(); + + if (!$firstStat || !$latestStat) { + return 0; + } + + $netinDiff = max(0, $latestStat->netin - $firstStat->netin); + $netoutDiff = max(0, $latestStat->netout - $firstStat->netout); + $totalBytes = $netinDiff + $netoutDiff; + + return round($totalBytes / (1024 * 1024 * 1024), 2); + } + + protected function throttleServerBandwidth(Server $server) + { + try { + $proxmox = new Proxmox; + $config = $proxmox->request('/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/config')->json()['data']; + + $updates = []; + + foreach ($config as $key => $value) { + if (preg_match('/^net\d+$/', $key)) { + $parts = explode(',', $value); + $newParts = []; + $hasRate = false; + + foreach ($parts as $part) { + if (strpos($part, 'rate=') === 0) { + $newParts[] = 'rate=1'; + $hasRate = true; + } else { + $newParts[] = $part; + } + } + + if (!$hasRate) { + $newParts[] = 'rate=1'; + } + + $updates[$key] = implode(',', $newParts); + } + } + + if (!empty($updates)) { + $proxmox->request('/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/config', 'put', $updates); + $this->info('Throttled network interfaces for server ID ' . $server->id . ' to 1 MiB/s'); + } + } catch (\Throwable $e) { + $this->error('Failed to throttle bandwidth for server ID ' . $server->id . ': ' . $e->getMessage()); + } } } diff --git a/extensions/Servers/Proxmox/Livewire/Server.php b/extensions/Servers/Proxmox/Livewire/Server.php index 0d3f074f2..e62ca7f63 100644 --- a/extensions/Servers/Proxmox/Livewire/Server.php +++ b/extensions/Servers/Proxmox/Livewire/Server.php @@ -12,6 +12,9 @@ class Server extends Component public function mount(ServerModel $server): void { parent::mount($server); + + // Check and enforce bandwidth throttling if exceeded + $this->checkAndEnforceBandwidthLimit(); } public function getMonthlyTrafficProperty() @@ -97,6 +100,68 @@ class Server extends Component }); } + protected function checkAndEnforceBandwidthLimit() + { + $monthlyTraffic = $this->monthlyTraffic; + $threshold = $this->bandwidthThreshold95; + + // Check if bandwidth is exceeded + if ($monthlyTraffic > $threshold) { + // Throttle the server to 1 MiB/s + $this->throttleServerBandwidth(); + } + } + + protected function throttleServerBandwidth() + { + try { + $proxmox = new \Paymenter\Extensions\Servers\Proxmox\Proxmox; + + // Get current VM config + $config = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config')->json()['data']; + + $updates = []; + + // Update all network interfaces to rate limit of 1 MiB/s + foreach ($config as $key => $value) { + if (preg_match('/^net\d+$/', $key)) { + // Parse existing network config + $parts = explode(',', $value); + $newParts = []; + $hasRate = false; + + foreach ($parts as $part) { + if (strpos($part, 'rate=') === 0) { + // Replace existing rate + $newParts[] = 'rate=1'; + $hasRate = true; + } else { + $newParts[] = $part; + } + } + + // Add rate if not present + if (!$hasRate) { + $newParts[] = 'rate=1'; + } + + $updates[$key] = implode(',', $newParts); + } + } + + // Apply the updates if any network interfaces were found + if (!empty($updates)) { + $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', 'put', $updates); + } + } catch (\Throwable $e) { + // Log the error but don't fail the page load + \Log::error('Failed to throttle bandwidth for server ' . $this->server->id, [ + 'error' => $e->getMessage(), + 'server_id' => $this->server->id, + ]); + } + } + public function render() { return view('proxmox::server'); diff --git a/runner.sh b/runner.sh index 881c238a8..72b3275f9 100755 --- a/runner.sh +++ b/runner.sh @@ -48,7 +48,8 @@ EOF "$git_cache" ;; 5) - LOG_FILE="${SCRIPT_DIR}/storage/logs/laravel.log" + LOG_DATE=$(date +%Y-%m-%d) + LOG_FILE="${SCRIPT_DIR}/storage/logs/laravel-${LOG_DATE}.log" if [ -f "$LOG_FILE" ]; then > "$LOG_FILE" echo "Log file cleared successfully: $LOG_FILE"