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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user