diff --git a/extensions/Servers/Proxmox/Admin/Resources/NodeResource.php b/extensions/Servers/Proxmox/Admin/Resources/NodeResource.php index a49d03d66..2e4d0a72f 100644 --- a/extensions/Servers/Proxmox/Admin/Resources/NodeResource.php +++ b/extensions/Servers/Proxmox/Admin/Resources/NodeResource.php @@ -113,6 +113,11 @@ class NodeResource extends Resource ->default(true) ->helperText('Enable or disable VNC access for this node. (this requires the Proxmox instance to be on the same domain as the Paymenter installation)') ->required(), + Forms\Components\Checkbox::make('is_lxc') + ->label('LXC Container Node') + ->default(false) + ->helperText('Check to deploy LXC containers instead of KVM virtual machines on this node. Existing KVM nodes are unaffected.') + ->disabled(fn (string $operation): bool => $operation === 'edit'), ]); } @@ -141,6 +146,15 @@ class NodeResource extends Resource ->sortable() ->searchable() ->toggleable(), + Tables\Columns\IconColumn::make('is_lxc') + ->label('Type') + ->boolean() + ->trueIcon('ri-checkbox-circle-fill') + ->falseIcon('ri-close-circle-fill') + ->trueColor('info') + ->falseColor('gray') + ->formatStateUsing(fn (bool $state): string => $state ? 'LXC' : 'KVM') + ->toggleable(), ]) ->filters([ // diff --git a/extensions/Servers/Proxmox/Console/Commands/PollServerStatistics.php b/extensions/Servers/Proxmox/Console/Commands/PollServerStatistics.php index 751538e76..5de39892b 100644 --- a/extensions/Servers/Proxmox/Console/Commands/PollServerStatistics.php +++ b/extensions/Servers/Proxmox/Console/Commands/PollServerStatistics.php @@ -48,7 +48,7 @@ class PollServerStatistics extends Command $proxmox = new Proxmox; $status = $proxmox->request( - '/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/status/current' + '/nodes/' . $server->node->name . '/' . $server->node->vmType() . '/' . $server->vm_id . '/status/current' )->json()['data'] ?? null; if ($status) { @@ -60,7 +60,7 @@ class PollServerStatistics extends Command try { $rrdData = $proxmox->request( - '/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/rrddata', + '/nodes/' . $server->node->name . '/' . $server->node->vmType() . '/' . $server->vm_id . '/rrddata', 'get', ['timeframe' => 'hour', 'cf' => 'AVERAGE'] )->json()['data'] ?? null; @@ -78,7 +78,7 @@ class PollServerStatistics extends Command // The status endpoint may not return maxdisk, especially if guest agent is not enabled try { $config = $proxmox->request( - '/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/config' + '/nodes/' . $server->node->name . '/' . $server->node->vmType() . '/' . $server->vm_id . '/config' )->json()['data'] ?? null; if ($config) { @@ -277,7 +277,7 @@ class PollServerStatistics extends Command { try { $proxmox = new Proxmox; - $config = $proxmox->request('/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/config')->json()['data'] ?? []; + $config = $proxmox->request('/nodes/' . $server->node->name . '/' . $server->node->vmType() . '/' . $server->vm_id . '/config')->json()['data'] ?? []; $updates = []; @@ -305,7 +305,7 @@ class PollServerStatistics extends Command } if (!empty($updates)) { - $proxmox->request('/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/config', 'put', $updates); + $proxmox->request('/nodes/' . $server->node->name . '/' . $server->node->vmType() . '/' . $server->vm_id . '/config', 'put', $updates); $this->info('Throttled network interfaces for server ID ' . $server->id . ' to 1 MiB/s'); } } catch (\Throwable $e) { diff --git a/extensions/Servers/Proxmox/Http/Controllers/LiveTrafficController.php b/extensions/Servers/Proxmox/Http/Controllers/LiveTrafficController.php index d01573639..6e04b54da 100644 --- a/extensions/Servers/Proxmox/Http/Controllers/LiveTrafficController.php +++ b/extensions/Servers/Proxmox/Http/Controllers/LiveTrafficController.php @@ -21,7 +21,7 @@ class LiveTrafficController extends Controller // First, get the VM configuration to identify network interfaces $configResponse = $proxmox->request( - '/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/config' + '/nodes/' . $server->node->name . '/' . $server->node->vmType() . '/' . $server->vm_id . '/config' ); $config = $configResponse->json()['data'] ?? []; @@ -48,7 +48,7 @@ class LiveTrafficController extends Controller $guestAgentInterfaces = []; try { $guestResponse = $proxmox->request( - '/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/agent/network-get-interfaces' + '/nodes/' . $server->node->name . '/' . $server->node->vmType() . '/' . $server->vm_id . '/agent/network-get-interfaces' ); $guestJson = $guestResponse->json(); $guestAgentInterfaces = $guestJson['data']['result'] ?? []; @@ -74,7 +74,7 @@ class LiveTrafficController extends Controller if (count($interfaces) > 0) { try { $rrdResponse = $proxmox->request( - '/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/rrddata', + '/nodes/' . $server->node->name . '/' . $server->node->vmType() . '/' . $server->vm_id . '/rrddata', 'get', [ 'timeframe' => 'hour', @@ -239,7 +239,7 @@ class LiveTrafficController extends Controller // If no interface-specific data, fall back to general RRD data if (empty($interfaceData)) { $response = $proxmox->request( - '/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/rrddata', + '/nodes/' . $server->node->name . '/' . $server->node->vmType() . '/' . $server->vm_id . '/rrddata', 'get', [ 'timeframe' => 'hour', @@ -317,7 +317,7 @@ class LiveTrafficController extends Controller // Get total statistics from status endpoint $statusResponse = $proxmox->request( - '/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/status/current' + '/nodes/' . $server->node->name . '/' . $server->node->vmType() . '/' . $server->vm_id . '/status/current' ); $status = $statusResponse->json()['data'] ?? []; diff --git a/extensions/Servers/Proxmox/Jobs/ReinstallJob.php b/extensions/Servers/Proxmox/Jobs/ReinstallJob.php index 9ecc51240..2bccbfda0 100644 --- a/extensions/Servers/Proxmox/Jobs/ReinstallJob.php +++ b/extensions/Servers/Proxmox/Jobs/ReinstallJob.php @@ -32,18 +32,19 @@ class ReinstallJob implements ShouldQueue public function handle(): void { $proxmox = new Proxmox; + $vmType = $this->server->node->vmType(); - // Stop and delete existing VM if it exists + // Stop and delete existing VM/CT if it exists try { $proxmox->requestAndWait( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/status/stop', + '/nodes/' . $this->server->node->name . '/' . $vmType . '/' . $this->server->vm_id . '/status/stop', 'post', ['vmid' => $this->server->vm_id], $this->server->node->name ); $proxmox->requestAndWait( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id, + '/nodes/' . $this->server->node->name . '/' . $vmType . '/' . $this->server->vm_id, 'delete', [], $this->server->node->name diff --git a/extensions/Servers/Proxmox/Livewire/Backups/Index.php b/extensions/Servers/Proxmox/Livewire/Backups/Index.php index 2d37f2bb8..0a68c95dd 100644 --- a/extensions/Servers/Proxmox/Livewire/Backups/Index.php +++ b/extensions/Servers/Proxmox/Livewire/Backups/Index.php @@ -78,7 +78,7 @@ class Index extends Component // Stop the VM before restoring — Proxmox requires it try { - $proxmox->requestAndWait('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/status/stop', 'post', [ + $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); } catch (\Exception $e) { diff --git a/extensions/Servers/Proxmox/Livewire/BootOrder.php b/extensions/Servers/Proxmox/Livewire/BootOrder.php index ad5ab2a48..f18c3b5c7 100644 --- a/extensions/Servers/Proxmox/Livewire/BootOrder.php +++ b/extensions/Servers/Proxmox/Livewire/BootOrder.php @@ -102,7 +102,7 @@ class BootOrder extends Component if (empty($this->order)) { // Delete the boot order parameter to reset to default $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ 'delete' => 'boot', @@ -111,7 +111,7 @@ class BootOrder extends Component $this->notify('Boot order reset to default. The new sequence will be used on the next power event.', 'success'); } else { $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ 'boot' => 'order=' . implode(';', $this->order), @@ -140,7 +140,7 @@ class BootOrder extends Component // Fetch current VM config to check slot occupancy (rawConfig is protected and not persisted between requests) try { - $currentConfig = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config')->json()['data'] ?? []; + $currentConfig = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config')->json()['data'] ?? []; } catch (\Exception $e) { $this->notify('Failed to fetch VM configuration: ' . $e->getMessage(), 'error'); return; @@ -169,7 +169,7 @@ class BootOrder extends Component // Try to create the drive on this slot try { $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ $ideSlot => 'none,media=cdrom', @@ -206,7 +206,7 @@ class BootOrder extends Component // Try to create the drive on this slot try { $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ $sataSlot => 'none,media=cdrom', @@ -251,7 +251,7 @@ class BootOrder extends Component try { $proxmox = new Proxmox; $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ 'delete' => $drive, @@ -308,7 +308,7 @@ class BootOrder extends Component try { $proxmox = new Proxmox; $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ $targetDrive => $value, @@ -361,7 +361,7 @@ class BootOrder extends Component { try { $proxmox = new Proxmox; - $config = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config')->json()['data'] ?? []; + $config = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config')->json()['data'] ?? []; $this->rawConfig = $config; $this->availableDevices = $this->extractBootableDevices($config); diff --git a/extensions/Servers/Proxmox/Livewire/CloudInit.php b/extensions/Servers/Proxmox/Livewire/CloudInit.php index 486cb9968..62f346391 100644 --- a/extensions/Servers/Proxmox/Livewire/CloudInit.php +++ b/extensions/Servers/Proxmox/Livewire/CloudInit.php @@ -159,7 +159,7 @@ class CloudInit extends Component try { $proxmox = new Proxmox; $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', $payload ); @@ -170,7 +170,7 @@ class CloudInit extends Component // Regenerate the cloud-init ISO so changes are picked up on next boot. try { $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/cloudinit/cmd', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/cloudinit/cmd', 'post', ['command' => 'generate'] ); @@ -198,7 +198,7 @@ class CloudInit extends Component { try { $proxmox = new Proxmox; - $response = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config'); + $response = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config'); $config = $response->json()['data'] ?? []; $this->rawConfig = $config; @@ -491,7 +491,7 @@ class CloudInit extends Component $proxmox = new Proxmox; $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ 'cipassword' => $this->password, diff --git a/extensions/Servers/Proxmox/Livewire/Component.php b/extensions/Servers/Proxmox/Livewire/Component.php index 20b2ae83e..b0c67dbb3 100644 --- a/extensions/Servers/Proxmox/Livewire/Component.php +++ b/extensions/Servers/Proxmox/Livewire/Component.php @@ -48,8 +48,8 @@ class Component extends BaseComponent return; } - // Check storage quota before starting the VM - if ($action === 'start') { + // Check storage quota before starting the VM (KVM only) + if ($action === 'start' && !$this->server->node->is_lxc) { $quotaCheck = $this->checkStorageQuota(); if ($quotaCheck !== true) { $this->notify($quotaCheck, 'error'); @@ -59,8 +59,9 @@ class Component extends BaseComponent try { $proxmox = new Proxmox; + $vmType = $this->server->node->vmType(); $response = $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/status/' . $action, + '/nodes/' . $this->server->node->name . '/' . $vmType . '/' . $this->server->vm_id . '/status/' . $action, 'post', ['vmid' => $this->server->vm_id] ); @@ -97,8 +98,9 @@ class Component extends BaseComponent // Get VM config to calculate total storage $proxmox = new Proxmox; + $vmType = $this->server->node->vmType(); $configResponse = $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config' + '/nodes/' . $this->server->node->name . '/' . $vmType . '/' . $this->server->vm_id . '/config' ); // Note: request() calls ->throw(), so response is always successful here @@ -251,8 +253,9 @@ class Component extends BaseComponent { try { $proxmox = new Proxmox; + $vmType = $this->server->node->vmType(); $response = $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/status/current' + '/nodes/' . $this->server->node->name . '/' . $vmType . '/' . $this->server->vm_id . '/status/current' ); $data = $response->json()['data'] ?? null; diff --git a/extensions/Servers/Proxmox/Livewire/Display.php b/extensions/Servers/Proxmox/Livewire/Display.php index db9fe7d57..f122815f2 100644 --- a/extensions/Servers/Proxmox/Livewire/Display.php +++ b/extensions/Servers/Proxmox/Livewire/Display.php @@ -69,7 +69,7 @@ class Display extends Component try { $proxmox = new Proxmox; $config = $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config' + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config' )->json()['data'] ?? []; [$type, $memory] = $this->parseDisplayString($config['vga'] ?? null); @@ -111,7 +111,7 @@ class Display extends Component try { $proxmox = new Proxmox; $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ 'vga' => $payload, diff --git a/extensions/Servers/Proxmox/Livewire/Firewall.php b/extensions/Servers/Proxmox/Livewire/Firewall.php index 50c8cc48d..8d2e44d59 100644 --- a/extensions/Servers/Proxmox/Livewire/Firewall.php +++ b/extensions/Servers/Proxmox/Livewire/Firewall.php @@ -363,7 +363,7 @@ class Firewall extends Component { try { $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config' + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config' ); if ($response->successful()) { @@ -456,7 +456,7 @@ class Firewall extends Component // Send the full modified configuration back to Proxmox $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [$adapter => $newConfigString] ); @@ -483,7 +483,7 @@ class Firewall extends Component { try { $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/options' + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/options' ); if ($response->successful()) { @@ -506,7 +506,7 @@ class Firewall extends Component // Always force DHCP disabled and NDP enabled $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/options', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/options', 'put', [ 'enable' => $newState ? 1 : 0, @@ -551,7 +551,7 @@ class Firewall extends Component } $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/options', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/options', 'put', [ 'policy_in' => $this->policyIn, @@ -608,7 +608,7 @@ class Firewall extends Component { try { $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/rules' + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/rules' ); if ($response->successful()) { @@ -731,13 +731,13 @@ class Firewall extends Component if ($this->editingRuleIndex !== null && isset($this->rules[$this->editingRuleIndex])) { $pos = $this->rules[$this->editingRuleIndex]['pos']; $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/rules/' . $pos, + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/rules/' . $pos, 'put', $payload ); } else { $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/rules', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/rules', 'post', $payload ); @@ -777,7 +777,7 @@ class Firewall extends Component $newEnable = $currentEnable == 0 ? 1 : 0; $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/rules/' . $pos, + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/rules/' . $pos, 'put', ['enable' => $newEnable] ); @@ -818,7 +818,7 @@ class Firewall extends Component try { $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/rules/' . $pos, + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/rules/' . $pos, 'delete' ); @@ -848,7 +848,7 @@ class Firewall extends Component { try { $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/rules/' . $fromPos, + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/rules/' . $fromPos, 'put', ['moveto' => $toPos] ); @@ -895,7 +895,7 @@ class Firewall extends Component { try { $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/aliases' + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/aliases' ); if ($response->successful()) { @@ -953,13 +953,13 @@ class Firewall extends Component return; } $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/aliases/' . $this->editingAliasName, + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/aliases/' . $this->editingAliasName, 'put', $payload ); } else { $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/aliases', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/aliases', 'post', $payload ); @@ -1010,7 +1010,7 @@ class Firewall extends Component try { $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/aliases/' . $this->deletingAliasName, + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/aliases/' . $this->deletingAliasName, 'delete' ); @@ -1056,7 +1056,7 @@ class Firewall extends Component { try { $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/ipset' + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/ipset' ); if ($response->successful()) { @@ -1092,7 +1092,7 @@ class Firewall extends Component } $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/ipset', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/ipset', 'post', $payload ); @@ -1141,7 +1141,7 @@ class Firewall extends Component try { $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/ipset/' . $this->deletingIPSetName, + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/ipset/' . $this->deletingIPSetName, 'delete' ); @@ -1205,7 +1205,7 @@ class Firewall extends Component { try { $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/ipset/' . $name + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/ipset/' . $name ); if ($response->successful()) { @@ -1256,7 +1256,7 @@ class Firewall extends Component return; } $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/ipset/' . $this->selectedIPSetName, + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/ipset/' . $this->selectedIPSetName, 'post', $payload ); @@ -1309,7 +1309,7 @@ class Firewall extends Component $encodedCidr = urlencode($this->deletingMemberCidr); $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/ipset/' . $this->selectedIPSetName . '/' . $encodedCidr, + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/ipset/' . $this->selectedIPSetName . '/' . $encodedCidr, 'delete' ); @@ -1353,7 +1353,7 @@ class Firewall extends Component // Proxmox returns logs with newest first when using start=0 // Fetch a larger batch to ensure we get the most recent logs $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/log', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/log', 'get', [ 'limit' => 5000, // Fetch more logs to get the recent ones @@ -2180,7 +2180,7 @@ class Firewall extends Component $this->policyOut = $config->outgoing_policy; $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/options', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/options', 'put', [ 'policy_in' => $this->policyIn, @@ -2198,7 +2198,7 @@ class Firewall extends Component foreach (array_reverse($this->rules) as $rule) { if (isset($rule['pos'])) { $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/rules/' . $rule['pos'], + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/rules/' . $rule['pos'], 'delete' ); } @@ -2267,7 +2267,7 @@ class Firewall extends Component // Create the rule $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/rules', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/firewall/rules', 'post', $rulePayload ); @@ -2695,10 +2695,11 @@ class Firewall extends Component $savedRules = $config['firewall_rules'] ?? []; $incomingPolicy = $config['incoming_policy'] ?? 'DROP'; $outgoingPolicy = $config['outgoing_policy'] ?? 'ACCEPT'; + $vmType = $server->node->vmType(); // Step 1: Update policies $response = $this->firewallRequest( - '/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/firewall/options', + '/nodes/' . $server->node->name . '/' . $vmType . '/' . $server->vm_id . '/firewall/options', 'put', [ 'policy_in' => $incomingPolicy, @@ -2714,7 +2715,7 @@ class Firewall extends Component // Step 2: Get existing rules and delete them $existingRulesResponse = $this->firewallRequest( - '/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/firewall/rules' + '/nodes/' . $server->node->name . '/' . $vmType . '/' . $server->vm_id . '/firewall/rules' ); if ($existingRulesResponse->successful()) { @@ -2722,7 +2723,7 @@ class Firewall extends Component foreach (array_reverse($existingRules) as $rule) { if (isset($rule['pos'])) { $this->firewallRequest( - '/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/firewall/rules/' . $rule['pos'], + '/nodes/' . $server->node->name . '/' . $vmType . '/' . $server->vm_id . '/firewall/rules/' . $rule['pos'], 'delete' ); } @@ -2749,7 +2750,7 @@ class Firewall extends Component // Skip iface as it may not exist on other servers $this->firewallRequest( - '/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/firewall/rules', + '/nodes/' . $server->node->name . '/' . $vmType . '/' . $server->vm_id . '/firewall/rules', 'post', $rulePayload ); diff --git a/extensions/Servers/Proxmox/Livewire/Network.php b/extensions/Servers/Proxmox/Livewire/Network.php index 08c389621..4eaac58b4 100644 --- a/extensions/Servers/Proxmox/Livewire/Network.php +++ b/extensions/Servers/Proxmox/Livewire/Network.php @@ -73,7 +73,7 @@ class Network extends Component { try { $proxmox = new Proxmox; - $config = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config')->json()['data'] ?? []; + $config = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config')->json()['data'] ?? []; $interfaces = []; $configs = []; @@ -204,7 +204,7 @@ class Network extends Component { try { $proxmox = new Proxmox; - $response = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/agent/network-get-interfaces'); + $response = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/agent/network-get-interfaces'); $json = $response->json(); // Check if the response indicates an error (VM not running, guest agent not available, etc.) @@ -290,7 +290,7 @@ class Network extends Component { try { $proxmox = new Proxmox; - $config = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config')->json()['data'] ?? []; + $config = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config')->json()['data'] ?? []; // CPU cores can be specified with 'cores' or 'cpus' in Proxmox config $cores = (int) ($config['cores'] ?? $config['cpus'] ?? 1); @@ -434,7 +434,7 @@ class Network extends Component } $proxmox = new Proxmox; - $currentConfig = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config')->json()['data'] ?? []; + $currentConfig = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config')->json()['data'] ?? []; $currentValue = (string) ($currentConfig[$this->selectedInterface] ?? ''); $segments = array_filter(array_map('trim', explode(',', $currentValue))); @@ -476,7 +476,7 @@ class Network extends Component try { $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [$this->selectedInterface => $newValue] ); @@ -536,7 +536,7 @@ class Network extends Component try { $proxmox = new Proxmox; $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', ['delete' => $target] ); @@ -595,7 +595,7 @@ class Network extends Component try { $proxmox = new Proxmox; - $currentConfig = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config')->json()['data'] ?? []; + $currentConfig = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config')->json()['data'] ?? []; $currentValue = (string) ($currentConfig[$name] ?? ''); if (empty($currentValue)) { @@ -632,7 +632,7 @@ class Network extends Component $newValue = implode(',', $newSegments); $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [$name => $newValue] ); @@ -728,7 +728,7 @@ class Network extends Component $proxmox = new Proxmox; $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [$newInterfaceName => $newConfig] ); @@ -751,7 +751,7 @@ class Network extends Component try { $proxmox = new Proxmox; - $currentConfig = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config')->json()['data'] ?? []; + $currentConfig = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config')->json()['data'] ?? []; $currentValue = (string) ($currentConfig[$name] ?? ''); if (empty($currentValue)) { @@ -808,7 +808,7 @@ class Network extends Component $newValue = implode(',', $newSegments); $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [$name => $newValue] ); diff --git a/extensions/Servers/Proxmox/Livewire/Overview.php b/extensions/Servers/Proxmox/Livewire/Overview.php index cf743189f..fc5ede60f 100644 --- a/extensions/Servers/Proxmox/Livewire/Overview.php +++ b/extensions/Servers/Proxmox/Livewire/Overview.php @@ -51,7 +51,8 @@ class Overview extends Component // Load notes from Proxmox VM description try { $proxmox = new Proxmox; - $config = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config')->json()['data'] ?? []; + $vmType = $this->server->node->vmType(); + $config = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $vmType . '/' . $this->server->vm_id . '/config')->json()['data'] ?? []; $this->notes = $config['description'] ?? ''; } catch (\Exception $e) { $this->notes = ''; @@ -87,7 +88,8 @@ class Overview extends Component $cacheKey = 'proxmox-stats-' . $this->server->id . '-' . $graphType . '-' . $timeRange . '-' . $aggregation; $data = Cache::remember($cacheKey, 60, function () use ($timeRange, $aggregation) { $proxmox = new Proxmox; - $stats = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/rrddata', data: [ + $vmType = $this->server->node->vmType(); + $stats = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $vmType . '/' . $this->server->vm_id . '/rrddata', data: [ 'timeframe' => $timeRange, 'cf' => $aggregation === 'maximum' ? 'MAX' : 'AVERAGE', ]); @@ -367,8 +369,9 @@ class Overview extends Component // Update VM name - Proxmox uses the VM name as the hostname for cloud-init // Cloud-init will pick up the new VM name on the next boot - $response = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', 'put', [ - 'name' => $this->hostname, + $vmType = $this->server->node->vmType(); + $response = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $vmType . '/' . $this->server->vm_id . '/config', 'put', [ + $this->server->node->is_lxc ? 'hostname' : 'name' => $this->hostname, ]); // The request() method throws on error, so if we get here, it was successful @@ -377,7 +380,7 @@ class Overview extends Component $this->hostnameError = ''; // Refresh status to get updated name - $this->status = (object) ($proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/status/current')->json()['data'] ?? []); + $this->status = (object) ($proxmox->request('/nodes/' . $this->server->node->name . '/' . $vmType . '/' . $this->server->vm_id . '/status/current')->json()['data'] ?? []); } catch (\Exception $e) { $this->hostnameError = 'Failed to apply hostname: ' . $e->getMessage(); $this->notify('Failed to apply hostname to Proxmox', 'error'); @@ -469,10 +472,13 @@ class Overview extends Component // Apply password via Proxmox API using cloud-init $proxmox = new Proxmox; + $vmType = $this->server->node->vmType(); - // The cipassword parameter sets the password that will be applied via cloud-init on next boot - $response = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', 'put', [ - 'cipassword' => $this->password, + // For KVM: cipassword sets the password via cloud-init on next boot + // For LXC: password sets the root password directly + $passwordKey = $this->server->node->is_lxc ? 'password' : 'cipassword'; + $response = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $vmType . '/' . $this->server->vm_id . '/config', 'put', [ + $passwordKey => $this->password, ]); // The request() method throws on error, so if we get here, it was successful @@ -501,7 +507,8 @@ class Overview extends Component // Reload notes from Proxmox try { $proxmox = new Proxmox; - $config = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config')->json()['data'] ?? []; + $vmType = $this->server->node->vmType(); + $config = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $vmType . '/' . $this->server->vm_id . '/config')->json()['data'] ?? []; $this->notes = $config['description'] ?? ''; } catch (\Exception $e) { $this->notes = ''; @@ -512,10 +519,11 @@ class Overview extends Component { try { $proxmox = new Proxmox; + $vmType = $this->server->node->vmType(); // Save notes to Proxmox VM description (limit to 8KB to avoid bloating Proxmox config) $notes = mb_substr($this->notes ?? '', 0, 8192); - $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', 'put', [ + $proxmox->request('/nodes/' . $this->server->node->name . '/' . $vmType . '/' . $this->server->vm_id . '/config', 'put', [ 'description' => $notes, ]); diff --git a/extensions/Servers/Proxmox/Livewire/Reinstall.php b/extensions/Servers/Proxmox/Livewire/Reinstall.php index c0eba9dcb..7e8604c38 100644 --- a/extensions/Servers/Proxmox/Livewire/Reinstall.php +++ b/extensions/Servers/Proxmox/Livewire/Reinstall.php @@ -27,7 +27,7 @@ class Reinstall extends Component { try { $proxmox = new Proxmox; - $config = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config')->json()['data'] ?? []; + $config = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config')->json()['data'] ?? []; // Protected can be 1, '1', true, or missing/null $this->isProtected = isset($config['protection']) && ($config['protection'] == 1 || $config['protection'] === '1' || $config['protection'] === true); } catch (\Throwable $e) { diff --git a/extensions/Servers/Proxmox/Livewire/Server.php b/extensions/Servers/Proxmox/Livewire/Server.php index aacf7338c..b80ff244a 100644 --- a/extensions/Servers/Proxmox/Livewire/Server.php +++ b/extensions/Servers/Proxmox/Livewire/Server.php @@ -122,7 +122,7 @@ class Server extends Component $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'] ?? []; + $config = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config')->json()['data'] ?? []; $updates = []; @@ -155,7 +155,7 @@ class Server extends Component // 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); + $proxmox->request('/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', $updates); } } catch (\Throwable $e) { // Log the error but don't fail the page load diff --git a/extensions/Servers/Proxmox/Livewire/Statistics.php b/extensions/Servers/Proxmox/Livewire/Statistics.php index ae9fa007f..5da3998f1 100644 --- a/extensions/Servers/Proxmox/Livewire/Statistics.php +++ b/extensions/Servers/Proxmox/Livewire/Statistics.php @@ -895,7 +895,7 @@ class Statistics extends Component // Reduced cache TTL from 60 to 30 seconds for fresher data and faster updates $data = Cache::remember($cacheKey, 30, function () use ($timeRange, $aggregation) { $proxmox = new Proxmox; - $stats = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/rrddata', data: [ + $stats = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/rrddata', data: [ 'timeframe' => $timeRange, 'cf' => $aggregation === 'maximum' ? 'MAX' : 'AVERAGE', ]); @@ -1028,7 +1028,7 @@ class Statistics extends Component $proxmox = new Proxmox; $status = $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/status/current' + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/status/current' )->json()['data'] ?? null; if ($status) { @@ -1040,7 +1040,7 @@ class Statistics extends Component try { $rrdData = $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/rrddata', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/rrddata', 'get', ['timeframe' => 'hour', 'cf' => 'AVERAGE'] )->json()['data'] ?? null; @@ -1058,7 +1058,7 @@ class Statistics extends Component // The status endpoint may not return maxdisk, especially if guest agent is not enabled try { $config = $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config' + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config' )->json()['data'] ?? null; if ($config) { diff --git a/extensions/Servers/Proxmox/Livewire/Storage.php b/extensions/Servers/Proxmox/Livewire/Storage.php index 0f7ebc4fb..d08f3971f 100644 --- a/extensions/Servers/Proxmox/Livewire/Storage.php +++ b/extensions/Servers/Proxmox/Livewire/Storage.php @@ -169,7 +169,7 @@ class Storage extends Component try { $proxmox = new Proxmox; $config = $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config' + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config' )->json()['data'] ?? []; $this->parseStorageConfiguration($config); @@ -573,7 +573,7 @@ class Storage extends Component $driveKey = $this->newDriveBus . $index; $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ $driveKey => $this->storagePool . ':' . $this->newDriveSize . ($driveConfig ? ',' . $driveConfig : ''), @@ -724,7 +724,7 @@ class Storage extends Component try { $proxmox = new Proxmox; $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ $this->editingDriveKey => $newConfig, @@ -816,7 +816,7 @@ class Storage extends Component // Get current config to find next unused slot $config = $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config' + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config' ); $configData = $config->json()['data'] ?? []; @@ -829,7 +829,7 @@ class Storage extends Component // First, assign the volume to an unused slot to preserve it $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ 'unused' . $unusedIndex => $drive['volume'], @@ -839,7 +839,7 @@ class Storage extends Component // Then remove the drive configuration using delete (not 'none' which creates CD-ROM on SCSI) // The volume is already safe in the unused slot $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ 'delete' => $driveKey, @@ -893,7 +893,7 @@ class Storage extends Component // Get current config to check actual slot availability $config = $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config' + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config' ); $configData = $config->json()['data'] ?? []; @@ -925,7 +925,7 @@ class Storage extends Component // Step 1: Detach - assign the volume to an unused slot to preserve it $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ 'unused' . $unusedIndex => $drive['volume'], @@ -934,7 +934,7 @@ class Storage extends Component // Step 2: Remove the old drive configuration using delete (not 'none' which creates CD-ROM) $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ 'delete' => $this->movingDriveKey, @@ -967,7 +967,7 @@ class Storage extends Component // Attach to the new bus $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ $targetKey => $diskConfig, @@ -977,7 +977,7 @@ class Storage extends Component // Step 4: Clean up the unused entry try { $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ 'delete' => 'unused' . $unusedIndex, @@ -1044,7 +1044,7 @@ class Storage extends Component // First detach the disk, then delete the volume $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ 'delete' => $driveKey, @@ -1054,14 +1054,14 @@ class Storage extends Component // Now find and delete the unused disk // Reload config to get the unused disk reference $config = $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config' + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config' )->json()['data'] ?? []; // Find unused disks and delete the one we just detached foreach ($config as $key => $value) { if (str_starts_with($key, 'unused') && trim($value) === $this->drives[$driveKey]['volume']) { $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ 'delete' => $key, @@ -1140,7 +1140,7 @@ class Storage extends Component // First, get the current config to check actual slot availability and retrieve the exact unused disk value $config = $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config' + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config' ); $configData = $config->json()['data'] ?? []; @@ -1190,7 +1190,7 @@ class Storage extends Component // First, set the disk on the new bus (this attaches the volume) $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ $targetKey => $diskConfig, @@ -1201,7 +1201,7 @@ class Storage extends Component // This is safe because the volume is now attached to the new bus try { $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ 'delete' => $this->reattachingDriveKey, @@ -1238,7 +1238,7 @@ class Storage extends Component try { $proxmox = new Proxmox; $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ 'delete' => $unusedKey, @@ -1278,7 +1278,7 @@ class Storage extends Component try { $proxmox = new Proxmox; $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [ 'scsihw' => $this->scsiControllerType, diff --git a/extensions/Servers/Proxmox/Livewire/System.php b/extensions/Servers/Proxmox/Livewire/System.php index ef3a9e935..02954ee37 100644 --- a/extensions/Servers/Proxmox/Livewire/System.php +++ b/extensions/Servers/Proxmox/Livewire/System.php @@ -80,7 +80,7 @@ class System extends Component { try { $proxmox = new Proxmox; - $config = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config')->json()['data'] ?? []; + $config = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config')->json()['data'] ?? []; $this->machineType = (string) ($config['machine'] ?? ''); $this->biosType = (string) ($config['bios'] ?? $this->biosType); @@ -350,7 +350,7 @@ class System extends Component try { $proxmox = new Proxmox; $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', $payload ); @@ -476,7 +476,7 @@ class System extends Component // This will log "update VM {vmid}: -delete {deviceKey}" in Proxmox // Note: This may create an "unused" entry if the volume still exists $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', ['delete' => $deviceKey] ); @@ -538,7 +538,7 @@ class System extends Component try { // Get current VM config to check for unused entries $config = $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config' + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config' )->json()['data'] ?? []; // Extract just the volume name part (e.g., "vm-202500014-efidisk0" from "cephpool:vm-202500014-efidisk0") @@ -556,7 +556,7 @@ class System extends Component // Delete the unused entry try { $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', ['delete' => $key] ); @@ -686,7 +686,7 @@ class System extends Component try { $proxmox = new Proxmox; $proxmox->request( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + '/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', [$slot => $definition] ); diff --git a/extensions/Servers/Proxmox/Models/Node.php b/extensions/Servers/Proxmox/Models/Node.php index c1cf9f431..7094833cd 100644 --- a/extensions/Servers/Proxmox/Models/Node.php +++ b/extensions/Servers/Proxmox/Models/Node.php @@ -19,8 +19,21 @@ class Node extends Model 'storage_location', 'backup_location', 'vnc_enabled', + 'is_lxc', ]; + protected $casts = [ + 'is_lxc' => 'boolean', + ]; + + /** + * Get the Proxmox API type path segment for this node (qemu or lxc). + */ + public function vmType(): string + { + return $this->is_lxc ? 'lxc' : 'qemu'; + } + public function servers() { return $this->hasMany(Server::class); diff --git a/extensions/Servers/Proxmox/Proxmox.php b/extensions/Servers/Proxmox/Proxmox.php index 2a84ae668..d0c51fc0c 100644 --- a/extensions/Servers/Proxmox/Proxmox.php +++ b/extensions/Servers/Proxmox/Proxmox.php @@ -470,21 +470,24 @@ class Proxmox extends ClassServer $server->save(); - // Create the VM - $cloneResult = $this->requestAndWait('/nodes/' . $server->node->name . '/qemu/' . $server->os->vm_id . '/clone', 'post', [ + $vmType = $server->node->vmType(); + + // Create the VM/CT + $cloneResult = $this->requestAndWait('/nodes/' . $server->node->name . '/' . $vmType . '/' . $server->os->vm_id . '/clone', 'post', [ 'newid' => $server->vm_id, - 'name' => $server->hostname, + 'hostname' => $server->hostname, 'target' => $server->node->name, 'full' => 1, 'storage' => $server->node->storage_location, - 'pool' => $settings['pool'], + 'pool' => $settings['pool'] ?? null, ], $server->node->name); if ($cloneResult !== 'stopped' && $cloneResult !== 'OK') { - \Log::error('Proxmox VM clone failed for server ' . $server->id, [ + \Log::error('Proxmox VM/CT clone failed for server ' . $server->id, [ 'result' => $cloneResult, 'vm_id' => $server->vm_id, 'node' => $server->node->name, + 'type' => $vmType, ]); $server->status = 'error'; $server->save(); @@ -493,7 +496,11 @@ class Proxmox extends ClassServer } try { - $this->syncServerSettings($server, $settings, $password); + if ($server->node->is_lxc) { + $this->syncLxcSettings($server, $settings, $password); + } else { + $this->syncServerSettings($server, $settings, $password); + } } catch (\Throwable $e) { \Log::error('Proxmox VM sync settings failed for server ' . $server->id, [ 'error' => $e->getMessage(), @@ -591,6 +598,87 @@ class Proxmox extends ClassServer $this->request('/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/config', 'put', $config); } + /** + * Sync LXC container settings after cloning. + */ + private function syncLxcSettings(Server $server, $settings, $password = null) + { + $ipv4 = $server->primaryIpv4; + $ipv6 = $server->primaryIpv6; + + $data = [ + 'cores' => $settings['cores'], + 'memory' => $settings['memory'], + 'swap' => $settings['swap'] ?? 512, + 'hostname' => $server->hostname, + ]; + + if ($password) { + $data['password'] = $password; + } + + // Resize rootfs disk + $this->requestAndWait('/nodes/' . $server->node->name . '/lxc/' . $server->vm_id . '/resize', 'put', [ + 'disk' => 'rootfs', + 'size' => $settings['disk'] . 'G', + ], $server->node->name); + + // Build network config for LXC + // LXC net format: name=eth0,bridge=vmbr0,ip=x.x.x.x/24,gw=x.x.x.x[,tag=VLAN][,firewall=1][,rate=X] + if ($ipv4 && $ipv6 && $ipv4->ip_pool_id == $ipv6->ip_pool_id) { + $location = $ipv4->ipPool; + $ipv4Cidr = $ipv4->netmask ?: '24'; + $ipv6Cidr = $ipv6->netmask ?: '64'; + $net = 'name=eth0,bridge=' . $location->network_interface; + $net .= ',ip=' . $ipv4->ip . '/' . $ipv4Cidr . ',gw=' . $ipv4->gateway; + $net .= ',ip6=' . $ipv6->ip . '/' . $ipv6Cidr . ',gw6=' . $ipv6->gateway; + $net = $this->applyLxcPoolSettings($location, $net); + if (isset($settings['ratelimit'])) { + $net .= ',rate=' . $settings['ratelimit']; + } + $data['net0'] = $net; + } else { + if ($ipv4) { + $ipv4Cidr = $ipv4->netmask ?: '24'; + $net = 'name=eth0,bridge=' . $ipv4->ipPool->network_interface; + $net .= ',ip=' . $ipv4->ip . '/' . $ipv4Cidr . ',gw=' . $ipv4->gateway; + $net = $this->applyLxcPoolSettings($ipv4->ipPool, $net); + if (isset($settings['ratelimit'])) { + $net .= ',rate=' . $settings['ratelimit']; + } + $data['net0'] = $net; + } + if ($ipv6) { + $ipv6Cidr = $ipv6->netmask ?: '64'; + $net = 'name=eth1,bridge=' . $ipv6->ipPool->network_interface; + $net .= ',ip6=' . $ipv6->ip . '/' . $ipv6Cidr . ',gw6=' . $ipv6->gateway; + $net = $this->applyLxcPoolSettings($ipv6->ipPool, $net); + if (isset($settings['ratelimit'])) { + $net .= ',rate=' . $settings['ratelimit']; + } + $data['net1'] = $net; + } + } + + // Update container config + $this->request('/nodes/' . $server->node->name . '/lxc/' . $server->vm_id . '/config', 'put', $data); + } + + private function applyLxcPoolSettings(IPPool $ipPool, $string) + { + if ($ipPool->vlan) { + $string .= ',tag=' . $ipPool->vlan; + } + if ($ipPool->firewall) { + $string .= ',firewall=1'; + } + if ($ipPool->mtu) { + $string .= ',mtu=' . $ipPool->mtu; + } + + return $string; + } + private function applyPoolSettings(IPPool $ipPool, $string) { if ($ipPool->vlan) { @@ -796,7 +884,7 @@ class Proxmox extends ClassServer ->withHeader('CSRFPreventionToken', $response['CSRFPreventionToken']) ->withOptions([ 'verify' => $this->config('verify_ssl') ? true : false, - ])->post(rtrim($this->config('host'), '/') . '/api2/json/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/vncproxy', [ + ])->post(rtrim($this->config('host'), '/') . '/api2/json/nodes/' . $server->node->name . '/' . $server->node->vmType() . '/' . $server->vm_id . '/vncproxy', [ 'websocket' => 1, ])->throw()->json()['data'] ?? []; @@ -811,13 +899,16 @@ class Proxmox extends ClassServer } setcookie('PVEAuthCookie', $response['ticket'], 0, '/', $paymenterDomain, true, true); + $consoleType = $server->node->is_lxc ? 'lxc' : 'kvm'; + $vmType = $server->node->vmType(); + return rtrim($this->config('host'), '/') . - '/?console=kvm' . + '/?console=' . $consoleType . '&novnc=1' . '&node=' . $server->node->name . '&resize=1' . '&vmid=' . $server->vm_id . - '&path=/api2/json/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/vncwebsocket'; + '&path=/api2/json/nodes/' . $server->node->name . '/' . $vmType . '/' . $server->vm_id . '/vncwebsocket'; } public function xterm(Server $server) @@ -851,8 +942,10 @@ class Proxmox extends ClassServer } setcookie('PVEAuthCookie', $response['ticket'], 0, '/', $paymenterDomain, true, true); + $consoleType = $server->node->is_lxc ? 'lxc' : 'kvm'; + return rtrim($this->config('host'), '/') . - '/?console=kvm' . + '/?console=' . $consoleType . '&xtermjs=1' . '&node=' . $server->node->name . '&vmid=' . $server->vm_id; @@ -958,7 +1051,8 @@ class Proxmox extends ClassServer } // Stop server and wait for completion - $this->requestAndWait('/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/status/stop', 'post', [ + $vmType = $server->node->vmType(); + $this->requestAndWait('/nodes/' . $server->node->name . '/' . $vmType . '/' . $server->vm_id . '/status/stop', 'post', [ 'vmid' => $server->vm_id, ], $server->node->name); @@ -986,7 +1080,8 @@ class Proxmox extends ClassServer if ($server->node) { try { - $this->requestAndWait('/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/status/start', 'post', [ + $vmType = $server->node->vmType(); + $this->requestAndWait('/nodes/' . $server->node->name . '/' . $vmType . '/' . $server->vm_id . '/status/start', 'post', [ 'vmid' => $server->vm_id, ], $server->node->name); } catch (\Throwable $e) { @@ -1023,12 +1118,13 @@ class Proxmox extends ClassServer return true; } - // Stop the VM and wait for completion before deleting - $this->requestAndWait('/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/status/stop', 'post', [ + // Stop the VM/CT and wait for completion before deleting + $vmType = $server->node->vmType(); + $this->requestAndWait('/nodes/' . $server->node->name . '/' . $vmType . '/' . $server->vm_id . '/status/stop', 'post', [ 'vmid' => $server->vm_id, ], $server->node->name); // Delete proxmox server - $this->request('/nodes/' . $server->node->name . '/qemu/' . $server->vm_id, 'delete'); + $this->request('/nodes/' . $server->node->name . '/' . $vmType . '/' . $server->vm_id, 'delete'); // Release IP addresses so they can be reallocated $server->ipAddresses()->update(['server_id' => null]); // Delete server @@ -1058,7 +1154,11 @@ class Proxmox extends ClassServer } // Update the server settings - $this->syncServerSettings($server, $settings, null); + if ($server->node->is_lxc) { + $this->syncLxcSettings($server, $settings, null); + } else { + $this->syncServerSettings($server, $settings, null); + } return true; } diff --git a/extensions/Servers/Proxmox/database/migrations/2026_03_02_000001_add_is_lxc_to_ext_proxmox_nodes_table.php b/extensions/Servers/Proxmox/database/migrations/2026_03_02_000001_add_is_lxc_to_ext_proxmox_nodes_table.php new file mode 100644 index 000000000..fcc4fe462 --- /dev/null +++ b/extensions/Servers/Proxmox/database/migrations/2026_03_02_000001_add_is_lxc_to_ext_proxmox_nodes_table.php @@ -0,0 +1,22 @@ +boolean('is_lxc')->default(false)->after('vnc_enabled'); + }); + } + + public function down(): void + { + Schema::table('ext_proxmox_nodes', function (Blueprint $table) { + $table->dropColumn('is_lxc'); + }); + } +}; diff --git a/extensions/Servers/Proxmox/views/components/layout.blade.php b/extensions/Servers/Proxmox/views/components/layout.blade.php index f5a09cb01..92f7dc770 100644 --- a/extensions/Servers/Proxmox/views/components/layout.blade.php +++ b/extensions/Servers/Proxmox/views/components/layout.blade.php @@ -10,16 +10,18 @@ $vmStatus = optional($status)->status; $pollSuffix = $statusPollingInterval ? '.' . $statusPollingInterval : ''; $pollAttribute = $statusPolling ? 'wire:poll' . $pollSuffix . '="loadVmStatus"' : ''; + $isLxc = $server->node?->is_lxc ?? false; @endphp
@if($server->status === 'active')
@@ -1631,6 +1633,29 @@ ]; } $tabs[] = ['label' => 'Help', 'view' => 'help', 'route' => route('proxmox.server.help', ['server' => $server->id]), 'advanced' => true]; + + // For LXC containers, remove all advanced-only and KVM-specific tabs + if ($isLxc) { + $tabs = array_values(array_filter($tabs, function ($tab) { + // Remove top-level advanced tabs + if (!empty($tab['advanced'])) return false; + // Remove Hardware dropdown entirely (Boot, Display, Storage, System are KVM-only) + if (($tab['label'] ?? '') === 'Hardware') return false; + // For Networking dropdown, keep only Configuration (remove Firewall, Subdomain) + if (isset($tab['dropdown']) && $tab['dropdown'] && isset($tab['items'])) { + $tab['items'] = array_values(array_filter($tab['items'], fn($item) => empty($item['advanced']))); + return count($tab['items']) > 0; + } + return true; + })); + // Re-filter Networking dropdown items in-place + foreach ($tabs as &$tab) { + if (isset($tab['dropdown']) && $tab['dropdown'] && isset($tab['items'])) { + $tab['items'] = array_values(array_filter($tab['items'], fn($item) => empty($item['advanced']))); + } + } + unset($tab); + } @endphp
@@ -2124,7 +2149,8 @@
+ :class="isAdvancedOnlyView ? 'opacity-50 cursor-not-allowed pointer-events-none' : ''" + x-show="!isLxc"> Standard @@ -2145,10 +2171,11 @@
{ if (open) { isAdvancedView = localStorage.getItem('proxmox-view-mode') === 'advanced'; @@ -2333,7 +2360,8 @@
+ :class="isAdvancedOnlyView ? 'opacity-50 pointer-events-none' : ''" + x-show="!isLxc"> Std