Files
Virtnet.bond/extensions/Servers/Proxmox/Livewire/Backups/Index.php
Joseph.Rawlings 68043f0416 fix(proxmox): harden console sessions and firewall rollout safety
Reduce security and reliability risk across the Proxmox extension by removing password exposure from Livewire state, hardening console auth/cookie handling, and improving sanitized error paths. Improve firewall and router performance by adding safer lazy loading, bounded pagination, cross-server compatibility guards, and lower-overhead API workflows.

Made-with: Cursor
2026-03-28 19:34:46 -04:00

205 lines
6.2 KiB
PHP

<?php
namespace Paymenter\Extensions\Servers\Proxmox\Livewire\Backups;
use Livewire\Attributes\Locked;
use Paymenter\Extensions\Servers\Proxmox\Livewire\Component;
use Paymenter\Extensions\Servers\Proxmox\Models\Server;
use Paymenter\Extensions\Servers\Proxmox\Proxmox;
class Index extends Component
{
#[Locked]
public $backups = [];
public $showRestore = false;
public $confirmRestore = false;
public bool $showDelete = false;
public bool $confirmDelete = false;
public $volid;
public function promptRestore(string $volid): void
{
$this->volid = $volid;
$this->confirmRestore = false;
$this->showRestore = true;
}
public function mount(Server $server): void
{
parent::mount($server);
$settings = $this->server->service->product->settings()->where('key', 'backups')->first();
if ($settings?->value <= 0) {
$this->redirect(route('proxmox.server.overview', $this->server->id), true);
return;
}
// Check if backup_location is configured
if (empty($this->server->node->backup_location)) {
$this->backups = [];
return;
}
$this->loadBackups();
}
#[\Livewire\Attributes\On('backup-started')]
public function handleBackupStarted(): void
{
$this->loadBackups();
}
public function loadBackups(): void
{
if (empty($this->server->node->backup_location)) {
$this->backups = [];
return;
}
try {
$proxmox = new Proxmox;
$response = $proxmox->request('/nodes/' . $this->server->node->name . '/storage/' . $this->server->node->backup_location . '/content', data: [
'content' => 'backup',
'vmid' => $this->server->vm_id,
]);
$this->backups = $response->json()['data'] ?? [];
} catch (\Exception $e) {
$this->backups = [];
// Log the error for debugging
\Illuminate\Support\Facades\Log::error('Failed to fetch backups: ' . $e->getMessage());
}
}
public function restoreBackup()
{
$this->validate([
'volid' => 'required',
]);
if (!$this->confirmRestore) {
$this->notify('Please confirm the restore action', 'error');
return;
}
// Check if backup exists
$backup = collect($this->backups)->firstWhere('volid', $this->volid);
if (!$backup) {
$this->notify('Backup not found', 'error');
return;
}
$proxmox = new Proxmox;
// Stop the VM before restoring — Proxmox requires it
try {
$stopStatus = $proxmox->requestAndWait('/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/status/stop', 'post', [
'vmid' => $this->server->vm_id,
], $this->server->node->name);
if ($stopStatus !== 'stopped' && $stopStatus !== 'OK') {
$this->notify('Failed to stop VM before restore. Please try again.', 'error');
return;
}
} catch (\Exception $e) {
$msg = strtolower($e->getMessage());
// If VM is already stopped, continue with restore; otherwise abort
if (!str_contains($msg, 'not running')
&& !str_contains($msg, 'already stopped')
&& !str_contains($msg, 'is not running')) {
report($e);
$this->notify('Failed to stop VM before restore. Please try again.', 'error');
return;
}
}
try {
$restoreStatus = $proxmox->requestAndWait('/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType(), 'post', data: [
'vmid' => $this->server->vm_id,
'force' => true,
'archive' => $this->volid,
], node: $this->server->node->name);
if ($restoreStatus !== 'stopped' && $restoreStatus !== 'OK') {
$this->notify('Backup restore task failed to complete successfully.', 'error');
return;
}
} catch (\Throwable $e) {
report($e);
$this->notify('Backup restore failed. Please try again in a moment.', 'error');
return;
}
$this->notify('Backup restoration in progress', 'success');
$this->showRestore = false;
// Redirect to the server overview page
$this->redirect(route('proxmox.server.overview', $this->server->id), true);
}
public function promptDelete(string $volid): void
{
$this->volid = $volid;
$this->confirmDelete = false;
$this->showDelete = true;
}
public function deleteBackup(?string $volid = null)
{
$volid = $volid ?? $this->volid;
if (!$volid) {
$this->notify('Select a backup to delete.', 'error');
return;
}
if (!$this->confirmDelete) {
$this->notify('Please confirm the delete action', 'error');
return;
}
// Check if backup exists
$backup = collect($this->backups)->firstWhere('volid', $volid);
if (!$backup) {
$this->notify('Backup not found', 'error');
return;
}
$proxmox = new Proxmox;
try {
$response = $proxmox->request('/nodes/' . $this->server->node->name . '/storage/' . $this->server->node->backup_location . '/content/' . $volid, 'delete');
$this->notify('Backup deleted successfully', 'success');
$this->loadBackups();
$this->showDelete = false;
$this->confirmDelete = false;
$this->volid = null;
} catch (\Illuminate\Http\Client\RequestException $e) {
$errors = $e->response?->json()['errors'] ?? null;
$message = is_array($errors) ? implode(', ', array_values($errors)) : 'Unknown error';
$this->notify('Failed to delete backup: ' . $message, 'error');
}
}
public function render()
{
return view('proxmox::backups')->layoutData([
'sidebar' => true,
]);
}
}