feat: add LXC container support to Proxmox extension

- Add is_lxc boolean column to ext_proxmox_nodes (default false, existing nodes unaffected)
- Add LXC checkbox to admin node creation form (disabled on edit)
- Dynamic API path selection via Node::vmType() (returns 'lxc' or 'qemu')
- LXC deployment: syncLxcSettings() for cores, memory, swap, hostname, rootfs, net config
- LXC network uses name=eth0 format with applyLxcPoolSettings() for VLAN/firewall/MTU
- All lifecycle operations LXC-aware: install, suspend, unsuspend, terminate, upgrade, reinstall
- VNC/xterm console uses correct type (lxc vs kvm)
- All 20+ Livewire components use vmType() for Proxmox API paths
- LXC user interface shows basic-only view (no advanced toggle, no KVM-specific tabs)
- Basic tabs for LXC: Overview, Network Configuration, Backups, Console
This commit is contained in:
2026-03-02 07:11:25 -05:00
parent a98bbd6d82
commit 5f2e591c6e
21 changed files with 330 additions and 140 deletions

View File

@@ -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;
}