Files
Virtnet.bond/extensions/Servers/Proxmox/Livewire/Backups/Create.php

203 lines
6.2 KiB
PHP

<?php
namespace Paymenter\Extensions\Servers\Proxmox\Livewire\Backups;
use Illuminate\Support\Facades\Cache;
use Paymenter\Extensions\Servers\Proxmox\Livewire\Component;
use Paymenter\Extensions\Servers\Proxmox\Proxmox;
class Create extends Component
{
private const BACKUP_RATE_LIMIT_MINUTES = 10;
public $visible;
public $name;
public $type = 'snapshot';
public $compression = 'zstd';
public function getCanCreateBackupProperty()
{
if ($this->isBackupRateLimited()) {
return false;
}
$backupSettings = $this->server->service->product->settings()->where('key', 'backups')->first();
$backupLimit = (int) ($backupSettings?->value ?? 0);
// If limit is 0 or not set, allow unlimited backups
if ($backupLimit <= 0) {
return true;
}
// Check if backup_location is configured
if (empty($this->server->node->backup_location)) {
return false;
}
try {
$proxmox = new Proxmox;
$currentBackups = $proxmox->request('/nodes/' . $this->server->node->name . '/storage/' . $this->server->node->backup_location . '/content', data: [
'content' => 'backup',
'vmid' => $this->server->vm_id,
])->json()['data'] ?? [];
$currentBackupCount = count($currentBackups);
return $currentBackupCount < $backupLimit;
} catch (\Exception $e) {
\Illuminate\Support\Facades\Log::error('Failed to check backup limit: ' . $e->getMessage());
return false;
}
}
public function backup()
{
$this->validate([
'name' => 'required|string|max:255',
'type' => 'required|in:snapshot,suspend,stop',
'compression' => 'required|in:gzip,lzo,zstd,none',
]);
if ($this->isBackupRateLimited()) {
$remainingSeconds = $this->secondsUntilRateLimitExpires();
$this->notify(
sprintf(
'You must wait %s before creating another backup.',
$this->formatDurationForHumans($remainingSeconds)
),
'error'
);
return;
}
// Check backup limit
$backupSettings = $this->server->service->product->settings()->where('key', 'backups')->first();
$backupLimit = (int) ($backupSettings?->value ?? 0);
if ($backupLimit > 0 && !empty($this->server->node->backup_location)) {
try {
$proxmox = new Proxmox;
$currentBackups = $proxmox->request('/nodes/' . $this->server->node->name . '/storage/' . $this->server->node->backup_location . '/content', data: [
'content' => 'backup',
'vmid' => $this->server->vm_id,
])->json()['data'] ?? [];
$currentBackupCount = count($currentBackups);
if ($currentBackupCount >= $backupLimit) {
$this->notify("Backup limit reached. You can only create {$backupLimit} backup(s). Please delete an existing backup before creating a new one.", 'error');
return;
}
} catch (\Exception $e) {
$this->notify('Failed to check backup limit: ' . $e->getMessage(), 'error');
return;
}
}
if (empty($this->server->node->backup_location)) {
$this->notify('Backup storage location is not configured.', 'error');
return;
}
$proxmox = new Proxmox;
try {
$proxmox->request('/nodes/' . $this->server->node->name . '/vzdump', 'post', data: [
'vmid' => $this->server->vm_id,
'storage' => $this->server->node->backup_location,
'mode' => $this->type,
'compress' => $this->compression == 'none' ? (int) false : $this->compression,
'notes-template' => $this->name,
]);
} catch (\Throwable $e) {
$this->notify('Failed to start backup: ' . $e->getMessage(), 'error');
return;
}
Cache::put(
$this->getBackupRateLimitCacheKey(),
now()->timestamp,
now()->addMinutes(self::BACKUP_RATE_LIMIT_MINUTES + 1)
);
$this->notify('Backup started', 'success');
$this->visible = false;
$this->reset([
'name',
'type',
'compression',
]);
}
public function render()
{
return view('proxmox::backups.create');
}
public function pollEligibility(): void
{
// Intentionally empty. Polling triggers Livewire to re-render,
// which re-evaluates canCreateBackup (rate limit + plan limits).
}
private function isBackupRateLimited(): bool
{
$lastTimestamp = Cache::get($this->getBackupRateLimitCacheKey());
if (!$lastTimestamp) {
return false;
}
$elapsed = now()->timestamp - (int) $lastTimestamp;
if ($elapsed >= self::BACKUP_RATE_LIMIT_MINUTES * 60) {
Cache::forget($this->getBackupRateLimitCacheKey());
return false;
}
return true;
}
private function secondsUntilRateLimitExpires(): int
{
if (!$this->isBackupRateLimited()) {
return 0;
}
$lastTimestamp = (int) Cache::get($this->getBackupRateLimitCacheKey());
$elapsed = now()->timestamp - $lastTimestamp;
return max(0, (self::BACKUP_RATE_LIMIT_MINUTES * 60) - $elapsed);
}
private function getBackupRateLimitCacheKey(): string
{
return 'proxmox-backup-last-create-' . $this->server->id;
}
private function formatDurationForHumans(int $seconds): string
{
if ($seconds <= 0) {
return 'a moment';
}
$minutes = intdiv($seconds, 60);
$remainingSeconds = $seconds % 60;
if ($minutes > 0 && $remainingSeconds > 0) {
return sprintf('%d minute(s) and %d second(s)', $minutes, $remainingSeconds);
}
if ($minutes > 0) {
return sprintf('%d minute(s)', $minutes);
}
return sprintf('%d second(s)', $remainingSeconds);
}
}