From 9736e098243592665bae311f2083b32fddbc511f Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Tue, 3 Jun 2025 17:29:04 +0100 Subject: [PATCH 01/10] MAC Address option and ZFS ref link --- .../dynamix.vm.manager/VMMachines.page | 4 +- .../include/libvirt_helpers.php | 94 +++++++++++++++++-- .../dynamix.vm.manager/scripts/VMClone.php | 2 +- emhttp/plugins/dynamix/DashStats.page | 4 +- 4 files changed, 94 insertions(+), 10 deletions(-) diff --git a/emhttp/plugins/dynamix.vm.manager/VMMachines.page b/emhttp/plugins/dynamix.vm.manager/VMMachines.page index 2cc691b9b..9c756e2d7 100644 --- a/emhttp/plugins/dynamix.vm.manager/VMMachines.page +++ b/emhttp/plugins/dynamix.vm.manager/VMMachines.page @@ -234,7 +234,8 @@ function VMClone(uuid, name){ var edit = box.find('#Edit').prop('checked') ? 'yes' : 'no'; var overwrite = box.find('#Overwrite').prop('checked') ? 'yes' : 'no'; var free = box.find('#Free').prop('checked') ? 'yes' : 'no'; - scripturl = "VMClone.php " + encodeURIComponent("/usr/local/emhttp/plugins/dynamix.vm.manager/include/VMClone.php&" + $.param({action:"clone", name:name, clone:clone, overwrite:overwrite, edit:edit, start:start, free:free})); + var regenmac = box.find('#Regenmac').prop('checked') ? 'yes' : 'no'; + scripturl = "VMClone.php " + encodeURIComponent("/usr/local/emhttp/plugins/dynamix.vm.manager/include/VMClone.php&" + $.param({action:"clone", name:name, clone:clone, overwrite:overwrite, edit:edit, start:start, free:free, regenmac:regenmac})); openVMAction((scripturl),"VM Clone", "dynamix.vm.manager", "loadlist"); box.dialog('close'); }, @@ -572,6 +573,7 @@ _(Snapshot Name)_: _(Start Cloned VM)_: _(Edit VM after clone)_: +_(Regenerate MAC addresses)_: _(Check free space)_: diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 35461b16a..fad119ed2 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1727,7 +1727,7 @@ class Array2XML { return $copypaste; } - function vm_clone($vm, $clone ,$overwrite,$start,$edit, $free, $waitID) { + function vm_clone($vm, $clone ,$overwrite,$start,$edit, $free, $waitID, $regenmac) { global $lv,$domain_cfg,$arrDisplayOptions; /* Clone. @@ -1800,9 +1800,12 @@ class Array2XML { $config["domain"]["name"] = $clone; $config["domain"]["uuid"] = $lv->domain_generate_uuid(); - foreach($config["nic"] as $index => $detail) { - $config["nic"][$index]["mac"] = $lv->generate_random_mac_addr(); - } + if ($regenmac == "yes") { + write("addLog\0".htmlspecialchars(_("Regenerate MACs"))); + foreach($config["nic"] as $index => $detail) { + $config["nic"][$index]["mac"] = $lv->generate_random_mac_addr(); + } + } else write("addLog\0".htmlspecialchars(_("Retain existing MACs"))); $config["domain"]["type"] = "kvm"; $usbs = getVMUSBs($vmxml); @@ -1831,7 +1834,7 @@ class Array2XML { if (!is_dir($clonedir)) { my_mkdir($clonedir,0777,true); } - write("addLog\0".htmlspecialchars("Checking for image files")); + write("addLog\0".htmlspecialchars(_("Checking for image files"))); if ($file_exists && $overwrite != "yes") { write("addLog\0".htmlspecialchars(_("New image file names exist and Overwrite is not allowed"))); return( false); } #Create duplicate files. @@ -1844,8 +1847,18 @@ class Array2XML { $reptgt = str_replace('/mnt/user/', "/mnt/$sourcerealdisk/", $target); $repsrc = str_replace('/mnt/user/', "/mnt/$sourcerealdisk/", $source); } - $cmdstr = "cp --reflink=always '$repsrc' '$reptgt'"; - if ($reflink == true) { $refcmd = $cmdstr; } else {$refcmd = false; } + + $refresult = getFilesystemAndReflinkMode($repsrc); + $refcmdaction = $refresult['reflink_mode']; + $refcmdfs = $refresult['filesystem']; + $cmdstr = "cp --reflink=$refcmdaction '$repsrc' '$reptgt'"; + write("addLog\0".htmlspecialchars(_("Reflink Action")." ".$refcmdaction." "._("for filesystem")." ".$refcmdfs)); + if ($reflink == true) { $refcmd = $cmdstr; } else {$refcmd = false; } + if ($refresult['filesystem'] == "zfs" && $refresult['reflink_mode'] == "skip") { + write("addLog\0".htmlspecialchars(_("Reflink copy not supported on ZFS skipping"))); + $refcmd = false; + } + $cmdstr = "rsync -ahPIXS --out-format=%f --info=flist0,misc0,stats0,name1,progress2 '$repsrc' '$reptgt'"; $error = execCommand_nchan_clone($cmdstr,$target,$refcmd); if (!$error) { write("addLog\0".htmlspecialchars("Image copied failed.")); return( false); } @@ -2936,4 +2949,71 @@ function get_storage_fstype($storage="default") { $fstype = trim(shell_exec(" stat -f -c '%T' $storage")); return $fstype; } + +function getFilesystemAndReflinkMode(string $filePath): array { + // Default return values + $result = [ + 'filesystem' => 'unknown', + 'reflink_mode' => 'always' // safest fallback if unsupported + ]; + + $realPath = realpath($filePath); + if ($realPath === false || !file_exists($realPath)) { + return $result; + } + + $dirname = dirname($realPath); + + // Handle Unraid-style virtual paths + if (strpos($dirname, '/mnt/user/') === 0) { + error_log("Getting real disks\n"); + $realdisk = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION " . escapeshellarg($dirname) . " 2>/dev/null")); + if (!empty($realdisk)) { + $dirname = str_replace('/mnt/user/', "/mnt/$realdisk/", $dirname); + } + } + + // Get filesystem type + $fstype = trim(shell_exec("stat -f -c '%T' " . escapeshellarg($dirname))); + $result['filesystem'] = $fstype; + + // If not ZFS, return early + if ($fstype !== 'zfs') { + return $result; + } + + $result['reflink_mode'] = 'skip'; + + // Get dataset from the file + $dataset = trim(shell_exec("zfs list -H -o name " . escapeshellarg($dirname) . " 2>/dev/null")); + if (empty($dataset)) { + return $result; + } + + // Get pool from dataset + $parts = explode('/', $dataset); + $pool = $parts[0] ?? ''; + if (empty($pool)) { + return $result; + } + + // Check ZFS block_cloning feature + $output = []; + $resultCode = 0; + exec("zpool get -H -o value feature@block_cloning " . escapeshellarg($pool) . " 2>/dev/null", $output, $resultCode); + if ($resultCode !== 0 || empty($output) || (trim($output[0]) !== 'enabled' && trim($output[0]) !== 'active')) { + return $result; + } + + // Check if bclone is enabled at runtime + $bcloneValue = @file_get_contents("/sys/module/zfs/parameters/zfs_bclone_enabled"); + if ($bcloneValue === false || trim($bcloneValue) !== '1') { + return $result; + } + + // If both checks passed, block cloning is supported + $result['reflink_mode'] = 'auto'; + return $result; +} + ?> diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php b/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php index 8862807ba..f198b6c34 100755 --- a/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php +++ b/emhttp/plugins/dynamix.vm.manager/scripts/VMClone.php @@ -99,7 +99,7 @@ write("addLog\0".htmlspecialchars("Cloning $name to $clone")); switch ($action) { case "clone": - $rtn = vm_clone($name,$clone,$overwrite,$start,$edit,$free,$waitID) ; + $rtn = vm_clone($name,$clone,$overwrite,$start,$edit,$free,$waitID,$regenmac) ; break ; } write("stop_Wait\0$waitID") ; diff --git a/emhttp/plugins/dynamix/DashStats.page b/emhttp/plugins/dynamix/DashStats.page index 032a7d72b..a07371802 100644 --- a/emhttp/plugins/dynamix/DashStats.page +++ b/emhttp/plugins/dynamix/DashStats.page @@ -698,6 +698,7 @@ function hideShow() { _(Overwrite)_: _(Start Cloned VM)_: _(Edit VM after clone)_: +_(Regenerate MAC addresses)_: _(Check free space)_: @@ -1464,7 +1465,8 @@ function VMClone(uuid, name){ var edit = box.find('#Edit').prop('checked') ? 'yes' : 'no'; var overwrite = box.find('#Overwrite').prop('checked') ? 'yes' : 'no'; var free = box.find('#Free').prop('checked') ? 'yes' : 'no'; - var scripturl = "VMClone.php " + encodeURIComponent("/usr/local/emhttp/plugins/dynamix.vm.manager/include/VMClone.php&" + $.param({action:"clone", name:name, clone:clone, overwrite:overwrite, edit:edit, start:start, free:free})); + var regenmac = box.find('#Regenmac').prop('checked') ? 'yes' : 'no'; + var scripturl = "VMClone.php " + encodeURIComponent("/usr/local/emhttp/plugins/dynamix.vm.manager/include/VMClone.php&" + $.param({action:"clone", name:name, clone:clone, overwrite:overwrite, edit:edit, start:start, free:free, regenmac:regenmac})); openVMAction((scripturl),"VM Clone", "dynamix.vm.manager", "loadlist"); box.dialog('close'); }, From cb20ef286dac2f0371da43c47c8edced72c73a72 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Tue, 3 Jun 2025 19:01:26 +0100 Subject: [PATCH 02/10] fix ID --- emhttp/plugins/dynamix/DashStats.page | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix/DashStats.page b/emhttp/plugins/dynamix/DashStats.page index a07371802..267460f1a 100644 --- a/emhttp/plugins/dynamix/DashStats.page +++ b/emhttp/plugins/dynamix/DashStats.page @@ -698,7 +698,7 @@ function hideShow() { _(Overwrite)_: _(Start Cloned VM)_: _(Edit VM after clone)_: -_(Regenerate MAC addresses)_: +_(Regenerate MAC addresses)_: _(Check free space)_: From 44a33882680054f9a716a3920269fd7543c5c3cf Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Thu, 5 Jun 2025 13:38:58 +0100 Subject: [PATCH 03/10] Update libvirt_helpers.php --- .../plugins/dynamix.vm.manager/include/libvirt_helpers.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php index fad119ed2..92f136c0a 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1243,7 +1243,7 @@ class Array2XML { } function getValidNetworks() { - global $lv; + global $lv,$libvirt_running; $arrValidNetworks = []; exec("ls --indicator-style=none /sys/class/net | grep -Po '^(br|bond|eth|wlan)[0-9]+(\.[0-9]+)?'",$arrBridges); // add 'virbr0' as default first choice @@ -1260,7 +1260,7 @@ class Array2XML { $arrValidNetworks['bridges'] = array_diff($arrBridges, $remove); // This breaks VMSettings.page if libvirt is not running - /* if ($libvirt_running == "yes") { + if ($libvirt_running == "yes") { $arrVirtual = $lv->libvirt_get_net_list($lv->get_connection()); if (($key = array_search('default', $arrVirtual)) !== false) { @@ -1270,7 +1270,7 @@ class Array2XML { array_unshift($arrVirtual, 'default'); $arrValidNetworks['libvirt'] = array_values($arrVirtual); - }*/ + } return $arrValidNetworks; } From 252d599ae182e2ab70eec0e7201006c4126beefd Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Fri, 6 Jun 2025 20:26:47 +0100 Subject: [PATCH 04/10] Regen MAC is change from WLAN. --- .../dynamix.vm.manager/templates/Custom.form.php | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php index 5382cb95b..cee55786e 100644 --- a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php +++ b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php @@ -350,6 +350,7 @@ if ($snapshots!=null && count($snapshots) && !$boolNew) { + @@ -1493,6 +1494,10 @@ foreach ($arrConfig['nic'] as $i => $arrNic) { $strLabel = ($i > 0) ? appendOrdinalSuffix($i + 1) : ''; $disabled = $arrNic['network']=='wlan0' ? 'disabled' : ''; ?> + _(Network MAC)_: @@ -2024,12 +2029,17 @@ var storageType = "=get_storage_fstype($arrConfig['template']['storage']);?>"; var storageLoc = "=$arrConfig['template']['storage']?>"; function updateMAC(index,port) { + const prevPort = previousPorts[index]; + previousPorts[index] = port; $('input[name="nic['+index+'][mac]"').prop('disabled',port=='wlan0'); $('i.mac_generate.'+index).prop('disabled',port=='wlan0'); $('span.wlan0').removeClass('hidden'); - if (port != 'wlan0') { - $('span.wlan0').addClass('hidden'); - $('i.mac_generate.'+index).click(); + if (port !== 'wlan0') { + $('span.wlan0').addClass('hidden'); + if (prevPort === 'wlan0') { + // Only click if previous port was wlan0 + $('i.mac_generate.' + index).click(); + } } } From dd0349f3633e366eb7423a1f1141e1eae6446c61 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Fri, 6 Jun 2025 20:34:35 +0100 Subject: [PATCH 05/10] Fix jquery. --- emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php index cee55786e..4262748e5 100644 --- a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php +++ b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php @@ -2031,7 +2031,7 @@ var storageLoc = "=$arrConfig['template']['storage']?>"; function updateMAC(index,port) { const prevPort = previousPorts[index]; previousPorts[index] = port; - $('input[name="nic['+index+'][mac]"').prop('disabled',port=='wlan0'); + $('input[name="nic['+index+'][mac]"]').prop('disabled',port=='wlan0'); $('i.mac_generate.'+index).prop('disabled',port=='wlan0'); $('span.wlan0').removeClass('hidden'); if (port !== 'wlan0') { From 5ac98519b5c8cf79ecaa35f5acaab9aa8fcd0d43 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sat, 7 Jun 2025 08:25:56 +0100 Subject: [PATCH 06/10] Add virbr to list --- emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 92f136c0a..a09b9fc73 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1245,7 +1245,7 @@ class Array2XML { function getValidNetworks() { global $lv,$libvirt_running; $arrValidNetworks = []; - exec("ls --indicator-style=none /sys/class/net | grep -Po '^(br|bond|eth|wlan)[0-9]+(\.[0-9]+)?'",$arrBridges); + exec("ls --indicator-style=none /sys/class/net | grep -Po '^((vir)?br|bond|eth|wlan)[0-9]+(\.[0-9]+)?'",$arrBridges); // add 'virbr0' as default first choice array_unshift($arrBridges, 'virbr0'); // remove redundant references of bridge and bond interfaces From 7b57860252bf1cb22d21df9b78d5dcd2ff6baa66 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sat, 7 Jun 2025 09:01:43 +0100 Subject: [PATCH 07/10] remove duplicate virbr0 --- emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php | 1 + 1 file changed, 1 insertion(+) diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php index a09b9fc73..a5aa36afa 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1246,6 +1246,7 @@ class Array2XML { global $lv,$libvirt_running; $arrValidNetworks = []; exec("ls --indicator-style=none /sys/class/net | grep -Po '^((vir)?br|bond|eth|wlan)[0-9]+(\.[0-9]+)?'",$arrBridges); + unset($arrBridges[array_search('virbr0', $arrBridges)]); // add 'virbr0' as default first choice array_unshift($arrBridges, 'virbr0'); // remove redundant references of bridge and bond interfaces From 8978ab2d8298c468503023a39c39f752a8c77c87 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 8 Jun 2025 09:42:21 +0100 Subject: [PATCH 08/10] Update Custom.form.php --- .../templates/Custom.form.php | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php index 4262748e5..5382cb95b 100644 --- a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php +++ b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php @@ -350,7 +350,6 @@ if ($snapshots!=null && count($snapshots) && !$boolNew) { - @@ -1494,10 +1493,6 @@ foreach ($arrConfig['nic'] as $i => $arrNic) { $strLabel = ($i > 0) ? appendOrdinalSuffix($i + 1) : ''; $disabled = $arrNic['network']=='wlan0' ? 'disabled' : ''; ?> - _(Network MAC)_: @@ -2029,17 +2024,12 @@ var storageType = "=get_storage_fstype($arrConfig['template']['storage']);?>"; var storageLoc = "=$arrConfig['template']['storage']?>"; function updateMAC(index,port) { - const prevPort = previousPorts[index]; - previousPorts[index] = port; - $('input[name="nic['+index+'][mac]"]').prop('disabled',port=='wlan0'); + $('input[name="nic['+index+'][mac]"').prop('disabled',port=='wlan0'); $('i.mac_generate.'+index).prop('disabled',port=='wlan0'); $('span.wlan0').removeClass('hidden'); - if (port !== 'wlan0') { - $('span.wlan0').addClass('hidden'); - if (prevPort === 'wlan0') { - // Only click if previous port was wlan0 - $('i.mac_generate.' + index).click(); - } + if (port != 'wlan0') { + $('span.wlan0').addClass('hidden'); + $('i.mac_generate.'+index).click(); } } From b8144bb85e1844584a9287959993036b5a6fb90c Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 8 Jun 2025 09:42:35 +0100 Subject: [PATCH 09/10] Revert "remove duplicate virbr0" This reverts commit 7b57860252bf1cb22d21df9b78d5dcd2ff6baa66. --- emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php | 1 - 1 file changed, 1 deletion(-) diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php index a5aa36afa..a09b9fc73 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1246,7 +1246,6 @@ class Array2XML { global $lv,$libvirt_running; $arrValidNetworks = []; exec("ls --indicator-style=none /sys/class/net | grep -Po '^((vir)?br|bond|eth|wlan)[0-9]+(\.[0-9]+)?'",$arrBridges); - unset($arrBridges[array_search('virbr0', $arrBridges)]); // add 'virbr0' as default first choice array_unshift($arrBridges, 'virbr0'); // remove redundant references of bridge and bond interfaces From ff0b8846e70e55f99d64a109130e40cd6e25e02e Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 8 Jun 2025 09:42:48 +0100 Subject: [PATCH 10/10] Revert "Add virbr to list" This reverts commit 5ac98519b5c8cf79ecaa35f5acaab9aa8fcd0d43. --- emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php index a09b9fc73..92f136c0a 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1245,7 +1245,7 @@ class Array2XML { function getValidNetworks() { global $lv,$libvirt_running; $arrValidNetworks = []; - exec("ls --indicator-style=none /sys/class/net | grep -Po '^((vir)?br|bond|eth|wlan)[0-9]+(\.[0-9]+)?'",$arrBridges); + exec("ls --indicator-style=none /sys/class/net | grep -Po '^(br|bond|eth|wlan)[0-9]+(\.[0-9]+)?'",$arrBridges); // add 'virbr0' as default first choice array_unshift($arrBridges, 'virbr0'); // remove redundant references of bridge and bond interfaces