_(Libvirt volume info)_
_(btrfs filesystem show)_:
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_update b/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_update
deleted file mode 100755
index 0c1d2f9130..0000000000
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_update
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-
-# Process settings update.
-if [ -f /boot/config/domain.cfg ]; then
- . /boot/config/domain.cfg
- if [ "$SERVICE" == "enable" ]; then
- /etc/rc.d/rc.libvirt start
- else
- /etc/rc.d/rc.libvirt stop
- fi
-fi
From 28ec84805a0e3c1e5a1453ef1272e10a81262e5e Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Sat, 19 Apr 2025 18:40:16 +0100
Subject: [PATCH 08/64] revert rc.libvirt changes
---
etc/rc.d/rc.libvirt | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/etc/rc.d/rc.libvirt b/etc/rc.d/rc.libvirt
index 60c1bdf8ba..013220079e 100755
--- a/etc/rc.d/rc.libvirt
+++ b/etc/rc.d/rc.libvirt
@@ -173,7 +173,10 @@ version(){
libvirtd_start(){
log "Starting $DAEMON..."
- if [[ -f $LIBVIRTD_PIDFILE ]]; then
+ if ! mountpoint /etc/libvirt &>/dev/null; then
+ log "$DAEMON... No image mounted at /etc/libvirt."
+ exit 1
+ elif [[ -f $LIBVIRTD_PIDFILE ]]; then
log "$DAEMON... Already started."
return 1
fi
From de53cf49e28892bc8a6457d31978884ebedece8a Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Sun, 20 Apr 2025 10:19:42 +0100
Subject: [PATCH 09/64] Initial moving libvirt image to folder and vice versa.
Default set to folder.
---
.../dynamix.vm.manager/VMSettings.page | 18 ++++++
.../dynamix.vm.manager/scripts/libvirt_init | 58 +++++++++++++++++++
.../dynamix.vm.manager/scripts/libvirtconfig | 3 +-
etc/rc.d/rc.libvirt | 2 +-
4 files changed, 79 insertions(+), 2 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/VMSettings.page b/emhttp/plugins/dynamix.vm.manager/VMSettings.page
index 9d331c9a6f..1a5851576d 100644
--- a/emhttp/plugins/dynamix.vm.manager/VMSettings.page
+++ b/emhttp/plugins/dynamix.vm.manager/VMSettings.page
@@ -110,6 +110,12 @@ _(Libvirt storage location)_:
:vms_libvirt_volume_help:
+_(Libvirt secondary storage location)_:
+: =htmlspecialchars($domain_cfg['IMAGE_FILE_SECONDARY'])."Test"?>
+
+:vms_libvirt_secondary_volume_help:
+
+
_(Libvirt vdisk size)_:
:
_(GB)_
@@ -125,6 +131,14 @@ _(Libvirt storage location)_:
:vms_libvirt_location_help:
+_(Libvirt secondary storage location)_:
+:
+
_(Modify with caution: unable to validate path until Array is Started)_
+
_(Path does not exist)_
+
+
+:vms_libvirt_secondary_location_help:
+
_(Default VM storage path)_:
:
@@ -428,6 +442,9 @@ $(function(){
$("#IMAGE_FILE").fileTreeAttach(null, null, function(folder) {
$("#IMAGE_FILE").val(folder + 'libvirt.img').change();
});
+ $("#IMAGE_FILE_SECONDARY").fileTreeAttach(null, null, function(folder) {
+ $("#IMAGE_FILE_SECONDARY").val(folder + 'libvirt.img').change();
+ });
$('#domaindir').fileTreeAttach();
$('#mediadir').fileTreeAttach();
$('#winvirtio').fileTreeAttach();
@@ -451,6 +468,7 @@ $(function(){
$("#SERVICE").prop("disabled", checked).val('disable');
$("#IMAGE_SIZE").prop("disabled", checked);
$("#IMAGE_FILE").prop("disabled", checked).val("=$domain_cfg['IMAGE_FILE']?>");
+ $("#IMAGE_FILE_SECONDARY").prop("disabled");
$("#domaindir").prop("disabled", checked);
$("#mediadir").prop("disabled", checked);
$("#winvirtio_select").prop("disabled", checked);
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init b/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
index 4b98d16f0c..1189383f11 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
@@ -5,6 +5,64 @@
# run & log functions
. /etc/rc.d/rc.runlog
+
+# Sync domain data if IMAGE_FILE and OLD_IMAGE_FILE differ
+DOMAIN_CFG=/boot/config/domain.cfg
+
+# Read values from domain.cfg
+eval $(grep -E '^(IMAGE_FILE|OLD_IMAGE_FILE)=' "$DOMAIN_CFG")
+
+# Remove quotes
+IMAGE_FILE="${IMAGE_FILE%\"}"
+IMAGE_FILE="${IMAGE_FILE#\"}"
+OLD_IMAGE_FILE="${OLD_IMAGE_FILE%\"}"
+OLD_IMAGE_FILE="${OLD_IMAGE_FILE#\"}"
+
+# Proceed only if both variables are set and OLD_IMAGE_FILE exists
+if [ -n "$IMAGE_FILE" ] && [ -n "$OLD_IMAGE_FILE" ] && [ "$IMAGE_FILE" != "$OLD_IMAGE_FILE" ]; then
+ if [ ! -e "$OLD_IMAGE_FILE" ]; then
+ log "OLD_IMAGE_FILE not found: $OLD_IMAGE_FILE — skipping sync"
+ else
+ log "IMAGE_FILE and OLD_IMAGE_FILE differ, syncing..."
+
+ TMP_MNT=/etc/libvirt-sync
+ IMG_FILE_NAME=$(basename "$IMAGE_FILE")
+ OLD_IMG_FILE_NAME=$(basename "$OLD_IMAGE_FILE")
+ TIMESTAMP=$(date +%Y%m%d-%H%M%S)
+
+ if [[ "$OLD_IMAGE_FILE" == *.img ]]; then
+ # Backup image before mounting
+ BACKUP_PATH="${OLD_IMAGE_FILE%.img}.bak-${TIMESTAMP}.img"
+ log "Creating backup of OLD_IMAGE_FILE: $BACKUP_PATH"
+ cp -p "$OLD_IMAGE_FILE" "$BACKUP_PATH"
+
+ log "Mounting $OLD_IMAGE_FILE to $TMP_MNT"
+ mkdir -p "$TMP_MNT"
+ mount "$OLD_IMAGE_FILE" "$TMP_MNT"
+ log "Copying full contents from image to directory $IMAGE_FILE"
+ rsync -a --exclude="$OLD_IMG_FILE_NAME" "$TMP_MNT/" "$IMAGE_FILE/"
+ umount "$TMP_MNT"
+ elif [[ "$IMAGE_FILE" == *.img ]]; then
+ log "Mounting $IMAGE_FILE to $TMP_MNT"
+ mkdir -p "$TMP_MNT"
+ mount "$IMAGE_FILE" "$TMP_MNT"
+ log "Copying full contents from directory $OLD_IMAGE_FILE to image"
+ rsync -a --exclude="$IMG_FILE_NAME" --exclude='*.bak-*.img' "$OLD_IMAGE_FILE/" "$TMP_MNT/"
+ umount "$TMP_MNT"
+ else
+ log "Both IMAGE_FILE and OLD_IMAGE_FILE are directories, copying full contents"
+ rsync -a --exclude="$IMG_FILE_NAME" "$OLD_IMAGE_FILE/" "$IMAGE_FILE/"
+ fi
+
+ # Update OLD_IMAGE_FILE in domain.cfg
+ log "Updating OLD_IMAGE_FILE in $DOMAIN_CFG"
+ sed -i "s|^OLD_IMAGE_FILE=.*|OLD_IMAGE_FILE=\"$IMAGE_FILE\"|" "$DOMAIN_CFG"
+ fi
+else
+ log "IMAGE_FILE and OLD_IMAGE_FILE match, or one is unset — skipping sync"
+fi
+
+
# missing qemu directory would indicate new libvirt image file created
if [ ! -d /etc/libvirt/qemu ]; then
log "initializing /etc/libvirt"
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtconfig b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtconfig
index d09296f9ae..b588cf89b2 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtconfig
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtconfig
@@ -15,7 +15,8 @@
$cfgfile = "/boot/config/domain.cfg";
$cfg_defaults = [
"SERVICE" => "disable",
- "IMAGE_FILE" => "/mnt/user/system/libvirt/libvirt.img",
+ "IMAGE_FILE" => "/mnt/user/system/libvirt/",
+ "OLD_IMAGE_FILE" => "/mnt/user/system/libvirt/",
"IMAGE_SIZE" => "1",
"DEBUG" => "no",
"DOMAINDIR" => "/mnt/user/domains/",
diff --git a/etc/rc.d/rc.libvirt b/etc/rc.d/rc.libvirt
index 013220079e..e41bc61b61 100755
--- a/etc/rc.d/rc.libvirt
+++ b/etc/rc.d/rc.libvirt
@@ -173,7 +173,7 @@ version(){
libvirtd_start(){
log "Starting $DAEMON..."
- if ! mountpoint /etc/libvirt &>/dev/null; then
+ if ! mountpoint /etc/libvirt &>/dev/null; then
log "$DAEMON... No image mounted at /etc/libvirt."
exit 1
elif [[ -f $LIBVIRTD_PIDFILE ]]; then
From 3ffebdfb6e5e40d5fda67330af76c76de05142b9 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Sun, 20 Apr 2025 11:26:10 +0100
Subject: [PATCH 10/64] Update help text
---
emhttp/languages/en_US/helptext.txt | 12 ++++++++++--
emhttp/plugins/dynamix.vm.manager/VMSettings.page | 5 ++---
2 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/emhttp/languages/en_US/helptext.txt b/emhttp/languages/en_US/helptext.txt
index b878ec9afa..b11c1c65b0 100644
--- a/emhttp/languages/en_US/helptext.txt
+++ b/emhttp/languages/en_US/helptext.txt
@@ -1618,7 +1618,11 @@ Stop VMs from Autostarting\Starting when VM Manager starts or open is run from t
:end
:vms_libvirt_volume_help:
-This is the libvirt volume.
+This is the libvirt volume/directory.
+:end
+
+:vms_libvirt_secondary_volume_help:
+This is a location for storing previous versions of xml and nvram at change.
:end
:vms_libvirt_vdisk_size_help:
@@ -1627,7 +1631,11 @@ To resize an existing image file, specify the new size here. Next time the Libvi
:end
:vms_libvirt_location_help:
-You must specify an image file for Libvirt. The system will automatically create this file when the Libvirt service is first started.
+You must specify an image file/directory for Libvirt. The system will automatically create this file/directory when the Libvirt service is first started.
+:end
+
+:vms_libvirt_secondary_location_help:
+This is a directory for storing previous versions of xml and nvram at change. Does not need to be specified.
:end
:vms_libvirt_storage_help:
diff --git a/emhttp/plugins/dynamix.vm.manager/VMSettings.page b/emhttp/plugins/dynamix.vm.manager/VMSettings.page
index 1a5851576d..0abf50b3c9 100644
--- a/emhttp/plugins/dynamix.vm.manager/VMSettings.page
+++ b/emhttp/plugins/dynamix.vm.manager/VMSettings.page
@@ -123,7 +123,7 @@ _(Libvirt vdisk size)_:
:vms_libvirt_vdisk_size_help:
_(Libvirt storage location)_:
-:
+:
_(Modify with caution: unable to validate path until Array is Started)_
_(Path does not exist)_
@@ -132,9 +132,8 @@ _(Libvirt storage location)_:
:vms_libvirt_location_help:
_(Libvirt secondary storage location)_:
-:
+:
_(Modify with caution: unable to validate path until Array is Started)_
-
_(Path does not exist)_
:vms_libvirt_secondary_location_help:
From 7b033823528e74667aa009056f29c5e7a41d4bb2 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Tue, 6 May 2025 20:18:40 +0100
Subject: [PATCH 11/64] Remove secondary path.
---
emhttp/plugins/dynamix.vm.manager/VMSettings.page | 7 -------
1 file changed, 7 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/VMSettings.page b/emhttp/plugins/dynamix.vm.manager/VMSettings.page
index f00d792524..11bb44245f 100644
--- a/emhttp/plugins/dynamix.vm.manager/VMSettings.page
+++ b/emhttp/plugins/dynamix.vm.manager/VMSettings.page
@@ -169,13 +169,6 @@ _(Libvirt storage location)_:
:vms_libvirt_location_help:
-_(Libvirt secondary storage location)_:
-:
-
_(Modify with caution: unable to validate path until Array is Started)_
-
-
-:vms_libvirt_secondary_location_help:
-
_(Default VM storage path)_:
:
From fca221a93e3ad6013756708d9e131bfd4b47b90f Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Tue, 6 May 2025 20:21:12 +0100
Subject: [PATCH 12/64] Remove secondary code.
---
emhttp/plugins/dynamix.vm.manager/VMSettings.page | 10 ----------
1 file changed, 10 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/VMSettings.page b/emhttp/plugins/dynamix.vm.manager/VMSettings.page
index 11bb44245f..03c4235d43 100644
--- a/emhttp/plugins/dynamix.vm.manager/VMSettings.page
+++ b/emhttp/plugins/dynamix.vm.manager/VMSettings.page
@@ -146,12 +146,6 @@ _(Libvirt storage location)_:
:vms_libvirt_volume_help:
-_(Libvirt secondary storage location)_:
-: =htmlspecialchars($domain_cfg['IMAGE_FILE_SECONDARY'])."Test"?>
-
-:vms_libvirt_secondary_volume_help:
-
-
_(Libvirt vdisk size)_:
:
_(GB)_
@@ -482,9 +476,6 @@ $(function(){
$("#IMAGE_FILE").fileTreeAttach(null, null, function(folder) {
$("#IMAGE_FILE").val(folder + 'libvirt.img').change();
});
- $("#IMAGE_FILE_SECONDARY").fileTreeAttach(null, null, function(folder) {
- $("#IMAGE_FILE_SECONDARY").val(folder + 'libvirt.img').change();
- });
$('#domaindir').fileTreeAttach();
$('#mediadir').fileTreeAttach();
$('#winvirtio').fileTreeAttach();
@@ -508,7 +499,6 @@ $(function(){
$("#SERVICE").prop("disabled", checked).val('disable');
$("#IMAGE_SIZE").prop("disabled", checked);
$("#IMAGE_FILE").prop("disabled", checked).val("=$domain_cfg['IMAGE_FILE']?>");
- $("#IMAGE_FILE_SECONDARY").prop("disabled");
$("#domaindir").prop("disabled", checked);
$("#mediadir").prop("disabled", checked);
$("#winvirtio_select").prop("disabled", checked);
From 72562977caea2b9cd28bf080018e5e0faaea98c7 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Wed, 7 Jan 2026 10:59:08 +0000
Subject: [PATCH 13/64] Create location file.
---
.../dynamix.vm.manager/VMSettings.page | 8 +-
.../scripts/libvirtlocation | 137 ++++++++++++++++++
etc/rc.d/rc.libvirt | 3 +
3 files changed, 145 insertions(+), 3 deletions(-)
create mode 100644 emhttp/plugins/dynamix.vm.manager/scripts/libvirtlocation
diff --git a/emhttp/plugins/dynamix.vm.manager/VMSettings.page b/emhttp/plugins/dynamix.vm.manager/VMSettings.page
index ce191b0436..8ce9d5dee8 100644
--- a/emhttp/plugins/dynamix.vm.manager/VMSettings.page
+++ b/emhttp/plugins/dynamix.vm.manager/VMSettings.page
@@ -162,14 +162,15 @@ _(Libvirt storage location)_:
:vms_libvirt_volume_help:
-
-_(Libvirt vdisk size)_ (_(GB)_):
+
+
+ _(Libvirt vdisk size)_ (_(GB)_):
:
:vms_libvirt_vdisk_size_help:
_(Libvirt storage location)_:
-:
+:
@@ -180,6 +181,7 @@ _(Libvirt storage location)_:
:vms_libvirt_location_help:
+
_(Default VM storage path)_:
:
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtlocation b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtlocation
new file mode 100644
index 0000000000..e96897be22
--- /dev/null
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtlocation
@@ -0,0 +1,137 @@
+#!/usr/bin/php
+
+
+
+/* ---------------------------------------------------------
+ * Read default VM directory from Unraid domain.cfg
+ * --------------------------------------------------------- */
+function get_default_domain_dir() {
+ $cfg = '/boot/config/domain.cfg';
+
+ if (!file_exists($cfg)) {
+ return null;
+ }
+
+ $lines = file($cfg, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+ if ($lines === false) {
+ return null;
+ }
+
+ foreach ($lines as $line) {
+ $line = trim($line);
+
+ if ($line === '' || $line[0] === '#') {
+ continue;
+ }
+
+ if (preg_match('/^DOMAINDIR="([^"]+)"/', $line, $m)) {
+ return rtrim($m[1], '/');
+ }
+ }
+
+ return null;
+}
+
+/* ---------------------------------------------------------
+ * Connect to libvirt
+ * --------------------------------------------------------- */
+$lv = libvirt_connect('qemu:///system', false);
+if (!$lv) {
+ die("Failed to connect to libvirt\n");
+}
+
+/* Running VMs (or all, if you prefer libvirt_list_all_domains) */
+$domains = libvirt_list_domains($lv);
+if ($domains === false) {
+ die("Failed to list domains\n");
+}
+
+$default_domain_dir = get_default_domain_dir();
+
+$vms = [];
+
+/* ---------------------------------------------------------
+ * Enumerate VMs
+ * --------------------------------------------------------- */
+foreach ($domains as $dom) {
+
+ $domget = libvirt_domain_lookup_by_name($lv, $dom);
+ if ($domget === false) {
+ continue;
+ }
+
+ $xml = libvirt_domain_get_xml_desc($domget, 0);
+ if ($xml === false) {
+ continue;
+ }
+
+ $sx = new SimpleXMLElement($xml);
+
+ $vm_name = (string)$sx->name;
+ $uuid = (string)$sx->uuid;
+
+ /* -----------------------------------------------------
+ * Read storage metadata (Unraid vmtemplate)
+ * ----------------------------------------------------- */
+ $metadata_storage = null;
+
+ if (isset($sx->metadata)) {
+ foreach ($sx->metadata->children() as $child) {
+ if ($child->getName() === 'vmtemplate') {
+ $metadata_storage = trim((string)$child['storage']);
+ break;
+ }
+ }
+ }
+
+ /* -----------------------------------------------------
+ * Resolve filesystem path
+ * Treat empty, null, or "default" as DOMAINDIR
+ * ----------------------------------------------------- */
+ if ($metadata_storage === null || $metadata_storage === '' || strtolower($metadata_storage) === 'default') {
+ /* TRUE default storage */
+ $path_root = $default_domain_dir; // e.g. /mnt/user/domains2
+ $storage_name = 'default';
+ } else {
+ /* Explicit Unraid pool */
+ $path_root = '/mnt/user/' . $metadata_storage;
+ $storage_name = $metadata_storage;
+ }
+
+ /* Shell-safe path (VM name quoted) */
+ $path = $path_root
+ ? $path_root . '/"' . $vm_name . '"'
+ : null;
+
+ /* Filesystem existence check (remove quotes for is_dir) */
+ $exists = ($path_root && is_dir($path_root . '/' . $vm_name));
+
+ /* -----------------------------------------------------
+ * Store result
+ * ----------------------------------------------------- */
+ $vms[] = [
+ 'name' => $vm_name,
+ 'uuid' => $uuid,
+ 'storage' => $storage_name,
+ 'path' => $path,
+ 'exists' => $exists,
+ ];
+}
+
+/* ---------------------------------------------------------
+ * Output
+ * --------------------------------------------------------- */
+#print_r($vms);
+
+file_put_contents("/boot/config/plugins/dynamix.vm.manager/vms.json",json_encode($vms,JSON_PRETTY_PRINT));
+?>
diff --git a/etc/rc.d/rc.libvirt b/etc/rc.d/rc.libvirt
index ae1c88e7b7..06117661ef 100755
--- a/etc/rc.d/rc.libvirt
+++ b/etc/rc.d/rc.libvirt
@@ -247,6 +247,9 @@ libvirtd_start(){
}
libvirtd_stop(){
+ # Save VM locations
+ /usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtlocation
+ #
log "Stopping $DAEMON..."
if [[ ! -f $LIBVIRTD_PIDFILE ]]; then
log "$DAEMON... Already stopped."
From 00ed5d2f2ebec8c864346ff97137d9c60c4a9235 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Wed, 7 Jan 2026 10:59:45 +0000
Subject: [PATCH 14/64] Make script executable
---
emhttp/plugins/dynamix.vm.manager/scripts/libvirtlocation | 0
1 file changed, 0 insertions(+), 0 deletions(-)
mode change 100644 => 100755 emhttp/plugins/dynamix.vm.manager/scripts/libvirtlocation
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtlocation b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtlocation
old mode 100644
new mode 100755
From d13e6b202a0cf02a651582f79d839ed618f3fcb2 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Wed, 7 Jan 2026 14:11:27 +0000
Subject: [PATCH 15/64] XML movements
---
.../scripts/{libvirtlocation => libvirtcopy} | 21 +++++++++++---
.../dynamix.vm.manager/scripts/libvirtrestore | 28 +++++++++++++++++++
.../dynamix.vm.manager/scripts/savehook.php | 12 ++++++++
etc/rc.d/rc.libvirt | 2 +-
4 files changed, 58 insertions(+), 5 deletions(-)
rename emhttp/plugins/dynamix.vm.manager/scripts/{libvirtlocation => libvirtcopy} (84%)
mode change 100755 => 100644
create mode 100644 emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
create mode 100644 emhttp/plugins/dynamix.vm.manager/scripts/savehook.php
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtlocation b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
old mode 100755
new mode 100644
similarity index 84%
rename from emhttp/plugins/dynamix.vm.manager/scripts/libvirtlocation
rename to emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
index e96897be22..222ce20051
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtlocation
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
@@ -108,22 +108,23 @@ foreach ($domains as $dom) {
$storage_name = $metadata_storage;
}
- /* Shell-safe path (VM name quoted) */
+
$path = $path_root
- ? $path_root . '/"' . $vm_name . '"'
+ ? $path_root . '/' . $vm_name
: null;
+
/* Filesystem existence check (remove quotes for is_dir) */
$exists = ($path_root && is_dir($path_root . '/' . $vm_name));
/* -----------------------------------------------------
* Store result
* ----------------------------------------------------- */
- $vms[] = [
- 'name' => $vm_name,
+ $vms[$vm_name] = [
'uuid' => $uuid,
'storage' => $storage_name,
'path' => $path,
+ 'path_shell' => $path ? escapeshellarg($path) : null,
'exists' => $exists,
];
}
@@ -134,4 +135,16 @@ foreach ($domains as $dom) {
#print_r($vms);
file_put_contents("/boot/config/plugins/dynamix.vm.manager/vms.json",json_encode($vms,JSON_PRETTY_PRINT));
+
+
+foreach ($vms as $vm => $vmdetail) {
+ file_put_contents("/tmp/Stopcopy","");
+ $from_file = "/etc/libvirt/qemu/$vm.xml";
+ $to_file = $vmdetail['path']."/$vm.xml";
+ #echo " from:$from_file to:$to_file";
+ if ($vmdetail['exists']) {
+ file_put_contents("/tmp/Stopcopy","$vm from:$from_file to:$to_file\n",FILE_APPEND); #echo " from:$from_file to:$to_file";
+ #copy($from_file,$to_file);
+ } else file_put_contents("/tmp/Stopcopy","Nocpy $vm from:$from_file to:$to_file\n",FILE_APPEND); #echo " from:$from_file to:$to_file";
+}
?>
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
new file mode 100644
index 0000000000..e153fc6bd5
--- /dev/null
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
@@ -0,0 +1,28 @@
+#!/usr/bin/php
+
+
+
+$vmsjson = file_get_contents("/boot/config/plugins/dynamix.vm.manager/vms.json");
+$vms = json_decode($vmsjson,true);
+
+foreach ($vms as $vm => $vmdetail) {
+ file_put_contents("/tmp/Stopcopy","");
+ $to_file = "/etc/libvirt/qemu/$vm.xml";
+ $from_file = $vmdetail['path']."/$vm.xml";
+ #echo " from:$from_file to:$to_file";
+ if (file_exists($from_file)) {
+ file_put_contents("/tmp/libvirtrestore","$vm from:$from_file to:$to_file\n",FILE_APPEND); #echo " from:$from_file to:$to_file";
+ #copy($from_file,$to_file);
+ } else file_put_contents("/tmp/libvirtrestore","Nocpy $vm from:$from_file to:$to_file\n",FILE_APPEND); #echo " from:$from_file to:$to_file";
+}
+?>
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/savehook.php b/emhttp/plugins/dynamix.vm.manager/scripts/savehook.php
new file mode 100644
index 0000000000..f06e25279d
--- /dev/null
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/savehook.php
@@ -0,0 +1,12 @@
+#!/usr/bin/env php
+
diff --git a/etc/rc.d/rc.libvirt b/etc/rc.d/rc.libvirt
index 06117661ef..a53c4d237e 100755
--- a/etc/rc.d/rc.libvirt
+++ b/etc/rc.d/rc.libvirt
@@ -248,7 +248,7 @@ libvirtd_start(){
libvirtd_stop(){
# Save VM locations
- /usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtlocation
+ /usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
#
log "Stopping $DAEMON..."
if [[ ! -f $LIBVIRTD_PIDFILE ]]; then
From a41622bd7c288a65d05e02c78102671549c62af2 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Wed, 7 Jan 2026 14:13:16 +0000
Subject: [PATCH 16/64] Make script executable
---
emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy | 0
emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore | 0
2 files changed, 0 insertions(+), 0 deletions(-)
mode change 100644 => 100755 emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
mode change 100644 => 100755 emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
old mode 100644
new mode 100755
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
old mode 100644
new mode 100755
From 092af901af850f392e1d595c9b0fd668b71d4213 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Thu, 8 Jan 2026 15:56:19 +0000
Subject: [PATCH 17/64] Udates to XML movement.
---
emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init | 4 ++++
emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy | 8 ++++----
emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore | 2 +-
3 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init b/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
index 1189383f11..a9643d538b 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
@@ -94,3 +94,7 @@ if [ -s /var/log/vfio-pci-errors ]; then
echo "vfio-pci bind error" > /run/libvirt/qemu/autostarted
/usr/local/emhttp/webGui/scripts/notify -e "VM Autostart disabled" -s "vfio-pci-errors " -d "VM Autostart disabled due to vfio-bind error" -m "Please review /var/log/vfio-pci-errors" -i "alert" -l "/VMs"
fi
+
+# Copy XML from VM Directories to QEMU directory/
+/usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
+#
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
index 222ce20051..1acf75c066 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
@@ -104,7 +104,7 @@ foreach ($domains as $dom) {
$storage_name = 'default';
} else {
/* Explicit Unraid pool */
- $path_root = '/mnt/user/' . $metadata_storage;
+ $path_root = str_replace("/mnt/user/","/mnt/$metadata_storage/",$default_domain_dir);
$storage_name = $metadata_storage;
}
@@ -133,12 +133,12 @@ foreach ($domains as $dom) {
* Output
* --------------------------------------------------------- */
#print_r($vms);
-
+ksort($vms,SORT_NATURAL);
file_put_contents("/boot/config/plugins/dynamix.vm.manager/vms.json",json_encode($vms,JSON_PRETTY_PRINT));
-
+file_put_contents("/tmp/Stopcopy","");
foreach ($vms as $vm => $vmdetail) {
- file_put_contents("/tmp/Stopcopy","");
+
$from_file = "/etc/libvirt/qemu/$vm.xml";
$to_file = $vmdetail['path']."/$vm.xml";
#echo " from:$from_file to:$to_file";
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
index e153fc6bd5..89282531e6 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
@@ -15,8 +15,8 @@
$vmsjson = file_get_contents("/boot/config/plugins/dynamix.vm.manager/vms.json");
$vms = json_decode($vmsjson,true);
+file_put_contents("/tmp/libvirtrestore","");
foreach ($vms as $vm => $vmdetail) {
- file_put_contents("/tmp/Stopcopy","");
$to_file = "/etc/libvirt/qemu/$vm.xml";
$from_file = $vmdetail['path']."/$vm.xml";
#echo " from:$from_file to:$to_file";
From 6d7c50cabda34eb680a907019bc76872eb3ef9f0 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Fri, 9 Jan 2026 16:17:42 +0000
Subject: [PATCH 18/64] Updates
---
.../dynamix.vm.manager/include/fs_helpers.php | 99 ++++
.../dynamix.vm.manager/scripts/libvirtcopy | 23 +-
.../dynamix.vm.manager/scripts/libvirtmigrate | 498 ++++++++++++++++++
.../dynamix.vm.manager/scripts/libvirtrestore | 22 +-
4 files changed, 636 insertions(+), 6 deletions(-)
create mode 100644 emhttp/plugins/dynamix.vm.manager/include/fs_helpers.php
create mode 100644 emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
diff --git a/emhttp/plugins/dynamix.vm.manager/include/fs_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/fs_helpers.php
new file mode 100644
index 0000000000..3825cedd4d
--- /dev/null
+++ b/emhttp/plugins/dynamix.vm.manager/include/fs_helpers.php
@@ -0,0 +1,99 @@
+ $src,
+ 'dst' => $dst,
+ 'would_copy' => false,
+ 'copied' => false,
+ 'error' => null
+ ];
+
+ if (!file_exists($src)) {
+ $result['error'] = 'source not found';
+ return $result;
+ }
+
+ $dst_dir = dirname($dst);
+ if (!is_dir($dst_dir)) {
+ if ($dry_run) {
+ $result['would_copy'] = true;
+ return $result;
+ }
+ if (!@mkdir($dst_dir, 0755, true)) {
+ $result['error'] = 'failed to create dest dir';
+ return $result;
+ }
+ }
+
+ if (file_exists($dst)) {
+ if (files_identical($src, $dst)) {
+ return $result; // identical, nothing to do
+ }
+ $result['would_copy'] = true;
+ } else {
+ $result['would_copy'] = true;
+ }
+
+ if ($dry_run) return $result;
+
+ if (@copy($src, $dst)) {
+ $result['copied'] = true;
+ } else {
+ $result['error'] = 'copy_failed';
+ }
+
+ return $result;
+}
+
+function dir_copy($src, $dst) {
+ if (!is_dir($src)) return false;
+ if (!is_dir($dst)) {
+ if (!@mkdir($dst, 0755, true)) return false;
+ }
+ $items = scandir($src);
+ foreach ($items as $item) {
+ if ($item === '.' || $item === '..') continue;
+ $s = $src . DIRECTORY_SEPARATOR . $item;
+ $d = $dst . DIRECTORY_SEPARATOR . $item;
+ if (is_dir($s)) {
+ if (!dir_copy($s, $d)) return false;
+ } else {
+ if (file_exists($d)) {
+ if (files_identical($s, $d)) continue;
+ }
+ if (!@copy($s, $d)) return false;
+ }
+ }
+ return true;
+}
+
+function dir_remove($dir) {
+ if (!is_dir($dir)) return false;
+ $items = scandir($dir);
+ foreach ($items as $item) {
+ if ($item === '.' || $item === '..') continue;
+ $path = $dir . DIRECTORY_SEPARATOR . $item;
+ if (is_dir($path)) {
+ dir_remove($path);
+ } else {
+ @unlink($path);
+ }
+ }
+ return @rmdir($dir);
+}
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
index 1acf75c066..19936df874 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
@@ -12,6 +12,13 @@
?>
+/* ---------------------------------------------------------
+ * Standard includes
+ * --------------------------------------------------------- */
+$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
+require_once "$docroot/webGui/include/Helpers.php";
+require_once "$docroot/plugins/dynamix.vm.manager/include/fs_helpers.php";
+
/* ---------------------------------------------------------
* Read default VM directory from Unraid domain.cfg
* --------------------------------------------------------- */
@@ -141,10 +148,20 @@ foreach ($vms as $vm => $vmdetail) {
$from_file = "/etc/libvirt/qemu/$vm.xml";
$to_file = $vmdetail['path']."/$vm.xml";
- #echo " from:$from_file to:$to_file";
+ #echo " from:$from_file to:$to_file\n";
if ($vmdetail['exists']) {
- file_put_contents("/tmp/Stopcopy","$vm from:$from_file to:$to_file\n",FILE_APPEND); #echo " from:$from_file to:$to_file";
- #copy($from_file,$to_file);
+ $res = copy_if_different($from_file, $to_file, false);
+ $msg = "$vm from:$from_file to:$to_file";
+ if (!empty($res['error'])) {
+ $msg .= " ERROR:" . $res['error'];
+ } elseif (!empty($res['copied'])) {
+ $msg .= " COPIED";
+ } elseif (!empty($res['would_copy'])) {
+ $msg .= " WOULD_COPY";
+ } else {
+ $msg .= " SKIPPED_IDENTICAL";
+ }
+ file_put_contents("/tmp/Stopcopy", $msg . "\n", FILE_APPEND);
} else file_put_contents("/tmp/Stopcopy","Nocpy $vm from:$from_file to:$to_file\n",FILE_APPEND); #echo " from:$from_file to:$to_file";
}
?>
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
new file mode 100644
index 0000000000..c10d93a618
--- /dev/null
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
@@ -0,0 +1,498 @@
+#!/usr/bin/php
+
+
+
+$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
+require_once "$docroot/webGui/include/Helpers.php";
+require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
+require_once "$docroot/plugins/dynamix.vm.manager/include/fs_helpers.php";
+$libvirt_location ="/etc/libvirtold";
+
+/* ---------------------------------------------------------
+ * Load vms.json created by libvirtcopy
+ * --------------------------------------------------------- */
+function load_vms_json() {
+ $vms_json_path = '/boot/config/plugins/dynamix.vm.manager/vms.json';
+ if (!file_exists($vms_json_path)) {
+ return [];
+ }
+
+ $json = @json_decode(file_get_contents($vms_json_path), true);
+ return is_array($json) ? $json : [];
+}
+
+/* filesystem helpers moved to include/fs_helpers.php */
+
+/* ---------------------------------------------------------
+ * Migrate NVRAM file to VM folder and update XML
+ * --------------------------------------------------------- */
+function migrate_nvram_file($valid_nvram, $vm_path, $vm_uuid, $vm_name, $dry_run = false) {
+ global $libvirt_location;
+ // Create nvram subdirectory
+ $nvram_dest_dir = $vm_path . '/nvram';
+ if (!is_dir($nvram_dest_dir)) {
+ if (!$dry_run && !@mkdir($nvram_dest_dir, 0755, true)) {
+ return [
+ 'success' => false,
+ 'error' => 'Failed to create nvram directory: ' . $nvram_dest_dir
+ ];
+ }
+ }
+
+ // Determine destination filename (preserve original)
+ $src_file = $valid_nvram['file'];
+ $dest_file = $nvram_dest_dir . '/' . basename($src_file);
+
+ // Copy NVRAM file (compare first)
+ $would_copy = false;
+ $copied = false;
+ if (file_exists($dest_file)) {
+ $same = false;
+ if (filesize($src_file) === filesize($dest_file)) {
+ $hs = @md5_file($src_file);
+ $hd = @md5_file($dest_file);
+ if ($hs !== false && $hd !== false && $hs === $hd) {
+ $same = true;
+ }
+ }
+ if (!$same) $would_copy = true;
+ } else {
+ $would_copy = true;
+ }
+
+ if ($would_copy) {
+ if ($dry_run) {
+ // indicate would-copy in result, don't actually copy
+ } else {
+ if (!@copy($src_file, $dest_file)) {
+ return [
+ 'success' => false,
+ 'error' => "Failed to copy NVRAM file from $src_file to $dest_file"
+ ];
+ }
+ $copied = true;
+ }
+ }
+
+ // Update XML file
+ $xml_old_path = $libvirt_location . "/qemu/$vm_name.xml";
+ $xml_new_path = "$vm_path/$vm_name.xml";
+
+ // Read old XML
+ if (!file_exists($xml_old_path)) {
+ @unlink($dest_file); // Rollback
+ return [
+ 'success' => false,
+ 'error' => "XML file not found: $xml_old_path"
+ ];
+ }
+
+ $xml_content = file_get_contents($xml_old_path);
+ $xml = @simplexml_load_string($xml_content);
+
+ if ($xml === false) {
+ if (!$dry_run) @unlink($dest_file); // Rollback
+ return [
+ 'success' => false,
+ 'error' => "Failed to parse XML: $xml_old_path"
+ ];
+ }
+
+ // Update nvram path in XML
+ if (isset($xml->os->nvram)) {
+ $xml->os->nvram = $dest_file;
+ }
+
+ // Write updated XML to new location
+ $xml_formatted = $xml->asXML();
+ if (!$dry_run && !@file_put_contents($xml_new_path, $xml_formatted)) {
+ @unlink($dest_file); // Rollback
+ return [
+ 'success' => false,
+ 'error' => "Failed to write updated XML to: $xml_new_path"
+ ];
+ }
+
+ return [
+ 'success' => true,
+ 'nvram_src' => $src_file,
+ 'nvram_dest' => $dest_file,
+ 'xml_old_path' => $xml_old_path,
+ 'xml_new_path' => $xml_new_path,
+ 'dry_run' => $dry_run,
+ 'would_copy' => $would_copy,
+ 'copied' => $copied
+ ];
+}
+
+/* ---------------------------------------------------------
+ * Perform NVRAM migration for valid files
+ * --------------------------------------------------------- */
+function perform_migration($valid_nvrams, $dry_run = false) {
+ if (empty($valid_nvrams)) {
+ return ['migrated' => 0, 'failed' => 0, 'errors' => []];
+ }
+
+ $vms_json = load_vms_json();
+ if (empty($vms_json)) {
+ return [
+ 'migrated' => 0,
+ 'failed' => count($valid_nvrams),
+ 'errors' => [['error' => 'vms.json not found or empty']]
+ ];
+ }
+
+ $migrated = 0;
+ $failed = 0;
+ $results = [];
+ global $libvirt_location;
+ $snapshot_moves = [];
+ $moved_snapshotdb = [];
+
+ foreach ($valid_nvrams as $nvram_item) {
+ $vm_name = $nvram_item['vm_name'];
+ $vm_uuid = $nvram_item['uuid'];
+
+ // Find VM in vms.json
+ if (!isset($vms_json[$vm_name])) {
+ $failed++;
+ $results[] = [
+ 'vm_name' => $vm_name,
+ 'success' => false,
+ 'error' => "VM not found in vms.json"
+ ];
+ continue;
+ }
+
+ $vm_path = $vms_json[$vm_name]['path'];
+ if (empty($vm_path)) {
+ $failed++;
+ $results[] = [
+ 'vm_name' => $vm_name,
+ 'success' => false,
+ 'error' => "VM path not found in vms.json"
+ ];
+ continue;
+ }
+
+ // Ensure snapshotdb for this VM is moved once
+ if (!isset($moved_snapshotdb[$vm_name])) {
+ $moved_snapshotdb[$vm_name] = true;
+ $old_snap_dir = $libvirt_location . "/qemu/snapshotdb/" . $vm_name;
+ $new_snap_dir = rtrim($vm_path, '/') . "/snapshotdb";
+
+ // Only move snapshotdb if snapshots.db exists and is non-empty
+ $snap_db_file = $old_snap_dir . '/snapshots.db';
+ $snap_contents = [];
+ if (file_exists($snap_db_file) && filesize($snap_db_file) > 0) {
+ $snap_contents = load_snapshot_db($vm_name);
+ }
+
+ if (!empty($snap_contents)) {
+ if ($dry_run) {
+ $snapshot_moves[] = [
+ 'vm_name' => $vm_name,
+ 'src' => $old_snap_dir,
+ 'dest' => $new_snap_dir,
+ 'would_move' => true,
+ 'dry_run' => true
+ ];
+ } else {
+ // If destination exists, merge; otherwise attempt rename then fallback to copy
+ if (is_dir($new_snap_dir)) {
+ $ok = dir_copy($old_snap_dir, $new_snap_dir);
+ if ($ok) {
+ $removed = dir_remove($old_snap_dir);
+ $snapshot_moves[] = [
+ 'vm_name' => $vm_name,
+ 'src' => $old_snap_dir,
+ 'dest' => $new_snap_dir,
+ 'success' => $ok && $removed,
+ 'action' => 'merge'
+ ];
+ } else {
+ $snapshot_moves[] = [
+ 'vm_name' => $vm_name,
+ 'src' => $old_snap_dir,
+ 'dest' => $new_snap_dir,
+ 'success' => false,
+ 'error' => 'Failed to merge snapshotdb into existing destination'
+ ];
+ }
+ } else {
+ if (@rename($old_snap_dir, $new_snap_dir)) {
+ $snapshot_moves[] = [
+ 'vm_name' => $vm_name,
+ 'src' => $old_snap_dir,
+ 'dest' => $new_snap_dir,
+ 'success' => true,
+ 'action' => 'rename'
+ ];
+ } else {
+ // Fallback to copy
+ $ok = dir_copy($old_snap_dir, $new_snap_dir);
+ if ($ok) {
+ $removed = dir_remove($old_snap_dir);
+ $snapshot_moves[] = [
+ 'vm_name' => $vm_name,
+ 'src' => $old_snap_dir,
+ 'dest' => $new_snap_dir,
+ 'success' => $ok && $removed,
+ 'action' => 'copy'
+ ];
+ } else {
+ $snapshot_moves[] = [
+ 'vm_name' => $vm_name,
+ 'src' => $old_snap_dir,
+ 'dest' => $new_snap_dir,
+ 'success' => false,
+ 'error' => 'Failed to move or copy snapshotdb'
+ ];
+ }
+ }
+ }
+ }
+ } else {
+ // No snapshots present; skip moving/creating snapshotdb
+ $snapshot_moves[] = [
+ 'vm_name' => $vm_name,
+ 'found' => false,
+ 'reason' => 'snapshots.db missing or empty'
+ ];
+ }
+ }
+
+ // Perform migration
+ $migration_result = migrate_nvram_file($nvram_item, $vm_path, $vm_uuid, $vm_name, $dry_run);
+ $migration_result['vm_name'] = $vm_name;
+
+ if ($migration_result['success']) {
+ $migrated++;
+ } else {
+ $failed++;
+ }
+
+ $results[] = $migration_result;
+ }
+
+ return [
+ 'migrated' => $migrated,
+ 'failed' => $failed,
+ 'results' => $results,
+ 'snapshotdb_moves' => $snapshot_moves
+ ];
+}
+
+/* ---------------------------------------------------------
+ * Load snapshot database for a VM
+ * --------------------------------------------------------- */
+function load_snapshot_db($vm_name) {
+ global $libvirt_location;
+ $snap_db = $libvirt_location . "/qemu/snapshotdb/" . $vm_name . "/snapshots.db";
+ if (!file_exists($snap_db)) {
+ return [];
+ }
+
+ $json = @json_decode(file_get_contents($snap_db), true);
+ return is_array($json) ? $json : [];
+}
+
+/* ---------------------------------------------------------
+ * Validate NVRAM files against libvirt VM UUIDs and snapshots
+ * Returns array with 'valid' and 'orphaned' keys
+ * --------------------------------------------------------- */
+function validate_nvram_uuids() {
+ global $libvirt_location;
+ // Connect to libvirt
+ $lv = libvirt_connect('qemu:///system', false);
+ if (!$lv) {
+ die("ERROR: Failed to connect to libvirt\n");
+ }
+
+ // Get all valid VM UUIDs
+ $domains = libvirt_list_domains($lv);
+ if ($domains === false) {
+ die("ERROR: Failed to list domains\n");
+ }
+
+ $valid_uuids = [];
+ $snapshot_dbs = [];
+
+ foreach ($domains as $dom) {
+ $domget = libvirt_domain_lookup_by_name($lv, $dom);
+ if ($domget === false) continue;
+
+ // Use the libvirt function to get UUID string directly
+ $uuid = @libvirt_domain_get_uuid_string($domget);
+ if ($uuid) {
+ $valid_uuids[$uuid] = $dom;
+ // Preload snapshot database for this VM
+ $snapshot_dbs[$dom] = load_snapshot_db($dom);
+ }
+ }
+
+ // Scan NVRAM directory
+ $nvram_dir = $libvirt_location . "/qemu/nvram";
+ if (!is_dir($nvram_dir)) {
+ return ['valid' => [], 'orphaned' => []];
+ }
+
+ $nvram_files = glob("$nvram_dir/*");
+ if ($nvram_files === false || count($nvram_files) === 0) {
+ return ['valid' => [], 'orphaned' => []];
+ }
+
+ $valid = [];
+ $orphaned = [];
+
+ foreach ($nvram_files as $file) {
+ $basename = basename($file);
+
+ // Extract UUID and optional snapshot name from filename
+ // Regular: {UUID}_VARS-pure-efi.fd
+ // Snapshot: {UUID}S{snapshot_name}_VARS-pure-efi.fd
+ if (preg_match('/^([a-f0-9\-]+)(?:S([^_]+))?_VARS/', $basename, $matches)) {
+ $uuid = $matches[1];
+ $snapshot_name = isset($matches[2]) ? $matches[2] : null;
+
+ if (isset($valid_uuids[$uuid])) {
+ $vm_name = $valid_uuids[$uuid];
+ $is_snapshot = $snapshot_name !== null;
+ $snapshot_valid = true;
+
+ // If it's a snapshot, validate against snapshots.db
+ if ($is_snapshot) {
+ $snapshots = $snapshot_dbs[$vm_name] ?? [];
+ $snapshot_valid = isset($snapshots[$snapshot_name]);
+ }
+
+ if ($snapshot_valid) {
+ $valid[] = [
+ 'file' => $file,
+ 'basename' => $basename,
+ 'uuid' => $uuid,
+ 'vm_name' => $vm_name,
+ 'snapshot_name' => $snapshot_name,
+ 'is_snapshot' => $is_snapshot,
+ 'size' => filesize($file)
+ ];
+ } else {
+ $orphaned[] = [
+ 'file' => $file,
+ 'basename' => $basename,
+ 'uuid' => $uuid,
+ 'vm_name' => $vm_name,
+ 'snapshot_name' => $snapshot_name,
+ 'is_snapshot' => true,
+ 'size' => filesize($file),
+ 'reason' => 'snapshot not found in snapshots.db'
+ ];
+ }
+ } else {
+ $orphaned[] = [
+ 'file' => $file,
+ 'basename' => $basename,
+ 'uuid' => $uuid,
+ 'snapshot_name' => $snapshot_name,
+ 'is_snapshot' => $snapshot_name !== null,
+ 'size' => filesize($file),
+ 'reason' => 'VM not found'
+ ];
+ }
+ }
+ }
+
+ return ['valid' => $valid, 'orphaned' => $orphaned];
+}
+
+/* ---------------------------------------------------------
+ * Delete orphaned NVRAM files
+ * --------------------------------------------------------- */
+function delete_orphaned_files($orphaned_files, $dry_run = false) {
+ if (empty($orphaned_files)) {
+ return ['deleted' => 0, 'failed' => 0, 'errors' => []];
+ }
+
+ $deleted = 0;
+ $failed = 0;
+ $errors = [];
+
+ foreach ($orphaned_files as $item) {
+ if (file_exists($item['file'])) {
+ if ($dry_run) {
+ // In dry-run mode, just count as would-be deleted
+ $deleted++;
+ } elseif (@unlink($item['file'])) {
+ $deleted++;
+ } else {
+ $failed++;
+ $errors[] = [
+ 'file' => $item['file'],
+ 'error' => 'Failed to delete'
+ ];
+ }
+ } else {
+ $failed++;
+ $errors[] = [
+ 'file' => $item['file'],
+ 'error' => 'File not found'
+ ];
+ }
+ }
+
+ return ['deleted' => $deleted, 'failed' => $failed, 'errors' => $errors, 'dry_run' => $dry_run];
+}
+
+// Parse command line arguments
+$delete_flag = in_array('--delete', $argv) || in_array('-d', $argv);
+$migrate_flag = in_array('--migrate', $argv) || in_array('-m', $argv);
+$valid_only = in_array('--valid-only', $argv) || in_array('-v', $argv);
+$orphaned_only = in_array('--orphaned-only', $argv) || in_array('-o', $argv);
+$confirm = in_array('--confirm', $argv) || in_array('-y', $argv);
+$dry_run = !$confirm; // Default to dry-run unless --confirm is set
+
+// Run validation and output results
+$result = validate_nvram_uuids();
+
+// Build output based on filters
+if ($valid_only) {
+ $output = ['valid' => $result['valid']];
+} elseif ($orphaned_only) {
+ $output = ['orphaned' => $result['orphaned']];
+} else {
+ $output = [
+ 'valid' => $result['valid'],
+ 'orphaned' => $result['orphaned']
+ ];
+}
+
+// Delete orphaned files if flag is set
+if ($delete_flag && !empty($result['orphaned'])) {
+ $output['deletion_result'] = delete_orphaned_files($result['orphaned'], $dry_run);
+}
+
+// Migrate valid NVRAM files if flag is set
+if ($migrate_flag && !empty($result['valid'])) {
+ $output['migration_result'] = perform_migration($result['valid'], $dry_run);
+}
+
+// Add dry-run flag to output if set
+if ($dry_run) {
+ $output['dry_run'] = true;
+}
+
+echo json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
+
+exit(count($result['orphaned']) === 0 ? 0 : 1);
+
+?>
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
index 89282531e6..e6e11227e6 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
@@ -12,6 +12,10 @@
?>
+$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
+require_once "$docroot/webGui/include/Helpers.php";
+require_once "$docroot/plugins/dynamix.vm.manager/include/fs_helpers.php";
+
$vmsjson = file_get_contents("/boot/config/plugins/dynamix.vm.manager/vms.json");
$vms = json_decode($vmsjson,true);
@@ -21,8 +25,20 @@ foreach ($vms as $vm => $vmdetail) {
$from_file = $vmdetail['path']."/$vm.xml";
#echo " from:$from_file to:$to_file";
if (file_exists($from_file)) {
- file_put_contents("/tmp/libvirtrestore","$vm from:$from_file to:$to_file\n",FILE_APPEND); #echo " from:$from_file to:$to_file";
- #copy($from_file,$to_file);
- } else file_put_contents("/tmp/libvirtrestore","Nocpy $vm from:$from_file to:$to_file\n",FILE_APPEND); #echo " from:$from_file to:$to_file";
+ $res = copy_if_different($from_file, $to_file, false);
+ $msg = "$vm from:$from_file to:$to_file";
+ if (!empty($res['error'])) {
+ $msg .= " ERROR:" . $res['error'];
+ } elseif (!empty($res['copied'])) {
+ $msg .= " COPIED";
+ } elseif (!empty($res['would_copy'])) {
+ $msg .= " WOULD_COPY";
+ } else {
+ $msg .= " SKIPPED_IDENTICAL";
+ }
+ file_put_contents("/tmp/libvirtrestore", $msg . "\n", FILE_APPEND);
+ } else {
+ file_put_contents("/tmp/libvirtrestore","Nocpy $vm from:$from_file to:$to_file\n",FILE_APPEND);
+ }
}
?>
From 5287435d1557e37975956eda1ad49229fb8c29d3 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Thu, 22 Jan 2026 12:06:22 +0000
Subject: [PATCH 19/64] Add XML processing to save configs in vm dir.
---
.../dynamix.vm.manager/include/libvirt.php | 82 +++++++++++++++++--
1 file changed, 75 insertions(+), 7 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index aaf7a951dd..3035fbe2ca 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -1043,7 +1043,8 @@ function domain_new($config) {
}
// Define the VM to persist
if ($config['domain']['persistent']) {
- $tmp = libvirt_domain_define_xml($this->conn, $strXML);
+ #$tmp = libvirt_domain_define_xml($this->conn, $strXML); ## Update to use new function
+ $tmp = $this->domain_define($strXML);
if (!$tmp) return $this->_set_last_error();
$this->domain_set_autostart($tmp, $config['domain']['autostart'] == 1);
return $tmp;
@@ -1472,9 +1473,9 @@ function domain_change_xml($domain, $xml) {
if (!libvirt_domain_undefine($dom)) {
return $this->_set_last_error();
}
- if (!libvirt_domain_define_xml($this->conn, $xml)) {
+ if (!manage_domain_xml(null, $xml,true)) { ## Update to use new function
$this->last_error = libvirt_get_last_error();
- libvirt_domain_define_xml($this->conn, $old_xml);
+ manage_domain_xml(null, $old_xml,true);
return false;
}
return true;
@@ -1632,11 +1633,66 @@ function domain_start($dom) {
$this->last_error = libvirt_get_last_error();
return $ret;
}
- $ret = libvirt_domain_create_xml($this->conn, $dom);
+ $ret = libvirt_domain_create_xml($this->conn, $dom); ## Update to use new function
$this->last_error = libvirt_get_last_error();
return $ret;
}
+ function manage_domain_xml($domain, $xml = null, $save = true) {
+ // Save or delete XML in VM directory based on $save flag
+ // $domain is the domain name (already validated by caller)
+ $xml_dir = null;
+ $storage = "default";
+
+ // Extract storage location from VM metadata if available
+ if ($xml && preg_match('/
]*storage="([^"]*)"/', $xml, $matches)) {
+ $storage = $matches[1];
+ }
+
+ // Determine storage path
+ if ($storage === "default") {
+ // Read default storage location from domains.cfg
+ $domain_cfg = parse_ini_file('/boot/config/domain.cfg', true);
+ if (isset($domain_cfg['DOMAINDIR'])) {
+ $storage_path = rtrim($domain_cfg['DOMAINDIR'], '/');
+ } else {
+ // Fallback to standard location
+ $storage_path = "/mnt/user/domains";
+ }
+ } else {
+ // Storage is a pool name - construct pool path
+ $storage_path = "/mnt/$storage";
+ }
+
+ // Build full VM directory path
+ $xml_dir = "$storage_path/$domain";
+
+ // Verify directory exists
+ if (!is_dir($xml_dir)) {
+ return false;
+ }
+
+ $xml_file = $xml_dir . '/' . $domain . '.xml';
+
+ if ($save === false) {
+ if (is_file($xml_file)) {
+ $backup_file = $xml_file . '.prev';
+ @copy($xml_file, $backup_file);
+ return unlink($xml_file);
+ }
+ return true;
+ }
+
+ // Backup existing XML before writing new content
+ if (is_file($xml_file)) {
+ $backup_file = $xml_file . '.prev';
+ @copy($xml_file, $backup_file);
+ }
+
+ // Write XML to file
+ return file_put_contents($xml_file, $xml) !== false;
+ }
+
function domain_define($xml, $autostart=false) {
if (strpos($xml,'') || strpos($xml,'')) {
$tmp = explode("\n", $xml);
@@ -1645,11 +1701,18 @@ function domain_define($xml, $autostart=false) {
$tmp[$i] = "";
$xml = join("\n", $tmp);
}
+ # PR1722 Save XML to VM directory
if ($autostart) {
- $tmp = libvirt_domain_create_xml($this->conn, $xml);
+ $tmp = libvirt_domain_create_xml($this->conn, $xml);
if (!$tmp) return $this->_set_last_error();
}
- $tmp = libvirt_domain_define_xml($this->conn, $xml);
+ $tmp = libvirt_domain_define_xml($this->conn, $xml);
+ if ($tmp) {
+ // Extract domain name from XML to save it
+ if (preg_match('/(.*?)<\/name>/s', $xml, $matches)) {
+ $this->manage_domain_xml($matches[1], $xml, true);
+ }
+ }
return $tmp ?: $this->_set_last_error();
}
@@ -1759,6 +1822,11 @@ function domain_undefine($domain) {
if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
unlink('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd');
}
+ # PR1722 Remove XML from VM directory using storage metadata
+ $xml = libvirt_domain_get_xml_desc($dom, 0);
+ if ($xml) {
+ $this->manage_domain_xml($domain, $xml, false);
+ }
$tmp = libvirt_domain_undefine($dom);
return $tmp ?: $this->_set_last_error();
}
@@ -2584,7 +2652,7 @@ function domain_change_cdrom($domain, $iso, $dev, $bus) {
$tmp = libvirt_domain_update_device($domain, "", VIR_DOMAIN_DEVICE_MODIFY_CONFIG);
if ($this->domain_is_active($domain)) {
libvirt_domain_update_device($domain, "", VIR_DOMAIN_DEVICE_MODIFY_LIVE);
- }
+ } ## Use new function?
return $tmp ?: $this->_set_last_error();
}
From 111f3d0aa09cc1ff57d91c9af1b8c889539c82f4 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Thu, 22 Jan 2026 12:37:26 +0000
Subject: [PATCH 20/64] Update libvirt.php
---
emhttp/plugins/dynamix.vm.manager/include/libvirt.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index 3035fbe2ca..66b1b09834 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -1473,9 +1473,9 @@ function domain_change_xml($domain, $xml) {
if (!libvirt_domain_undefine($dom)) {
return $this->_set_last_error();
}
- if (!manage_domain_xml(null, $xml,true)) { ## Update to use new function
+ if (!$this->domain_define($this->conn, $xml)) { ## Update to use new function
$this->last_error = libvirt_get_last_error();
- manage_domain_xml(null, $old_xml,true);
+ $this->domain_define($this->conn, $old_xml);
return false;
}
return true;
From 75ffb61c22a90f6e5364d5de142e6cde65fa6359 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Thu, 22 Jan 2026 13:58:16 +0000
Subject: [PATCH 21/64] Update libvirt.php
---
emhttp/plugins/dynamix.vm.manager/include/libvirt.php | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index 66b1b09834..65da1c0801 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -1689,8 +1689,10 @@ function manage_domain_xml($domain, $xml = null, $save = true) {
@copy($xml_file, $backup_file);
}
- // Write XML to file
- return file_put_contents($xml_file, $xml) !== false;
+ // Copy XML saved by libvirt
+ $libvirt_xml_file = '/etc/libvirt/qemu/' . $domain . '.xml';
+ if (!is_file($libvirt_xml_file)) return false;
+ return @copy($libvirt_xml_file, $xml_file);
}
function domain_define($xml, $autostart=false) {
From eab65bf24cb73f8efb1f43c18bd169729b9afce0 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Thu, 22 Jan 2026 14:19:29 +0000
Subject: [PATCH 22/64] Update emhttp/languages/en_US/helptext.txt
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---
emhttp/languages/en_US/helptext.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/emhttp/languages/en_US/helptext.txt b/emhttp/languages/en_US/helptext.txt
index 13fc39f033..94b6b91677 100644
--- a/emhttp/languages/en_US/helptext.txt
+++ b/emhttp/languages/en_US/helptext.txt
@@ -1715,7 +1715,7 @@ This is the libvirt volume/directory.
:end
:vms_libvirt_secondary_volume_help:
-This is a location for storing previous versions of xml and nvram at change.
+This is a location for storing previous versions of XML and NVRAM when changes are made.
:end
:vms_libvirt_vdisk_size_help:
From f82fc7bcec261331b90c3acfce645efc75696686 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Thu, 22 Jan 2026 14:20:31 +0000
Subject: [PATCH 23/64] Update
emhttp/plugins/dynamix.vm.manager/VMSettings.page
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---
emhttp/plugins/dynamix.vm.manager/VMSettings.page | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/VMSettings.page b/emhttp/plugins/dynamix.vm.manager/VMSettings.page
index 8ce9d5dee8..e75450a11e 100644
--- a/emhttp/plugins/dynamix.vm.manager/VMSettings.page
+++ b/emhttp/plugins/dynamix.vm.manager/VMSettings.page
@@ -302,7 +302,7 @@ _(VFIO allow unsafe interrupts)_:
-
+
_(Libvirt volume info)_
_(btrfs filesystem show)_:
From 3b4139b5502948a72a733f0dc6be5cadf7eb5694 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Thu, 22 Jan 2026 14:21:48 +0000
Subject: [PATCH 24/64] Update
emhttp/plugins/dynamix.vm.manager/include/fs_helpers.php
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---
emhttp/plugins/dynamix.vm.manager/include/fs_helpers.php | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/fs_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/fs_helpers.php
index 3825cedd4d..1975e51999 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/fs_helpers.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/fs_helpers.php
@@ -86,13 +86,14 @@ function dir_copy($src, $dst) {
function dir_remove($dir) {
if (!is_dir($dir)) return false;
$items = scandir($dir);
+ if ($items === false) return false;
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$path = $dir . DIRECTORY_SEPARATOR . $item;
if (is_dir($path)) {
- dir_remove($path);
+ if (!dir_remove($path)) return false;
} else {
- @unlink($path);
+ if (!@unlink($path)) return false;
}
}
return @rmdir($dir);
From ab766fb694bdf977f9e6fd087b9d04c831178e7f Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Thu, 22 Jan 2026 14:25:31 +0000
Subject: [PATCH 25/64] Update
emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---
emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init b/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
index a9643d538b..90b23ffadf 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
@@ -9,8 +9,9 @@
# Sync domain data if IMAGE_FILE and OLD_IMAGE_FILE differ
DOMAIN_CFG=/boot/config/domain.cfg
-# Read values from domain.cfg
-eval $(grep -E '^(IMAGE_FILE|OLD_IMAGE_FILE)=' "$DOMAIN_CFG")
+# Read values from domain.cfg safely (no eval)
+IMAGE_FILE=$(grep -E '^IMAGE_FILE=' "$DOMAIN_CFG" | head -1 | cut -d= -f2-)
+OLD_IMAGE_FILE=$(grep -E '^OLD_IMAGE_FILE=' "$DOMAIN_CFG" | head -1 | cut -d= -f2-)
# Remove quotes
IMAGE_FILE="${IMAGE_FILE%\"}"
From 2e45d298a0d625874c94ef2081496e90a9b1f499 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Thu, 22 Jan 2026 14:26:14 +0000
Subject: [PATCH 26/64] Update
emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---
.../plugins/dynamix.vm.manager/scripts/libvirt_init | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init b/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
index 90b23ffadf..4fa5858cee 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
@@ -39,10 +39,17 @@ if [ -n "$IMAGE_FILE" ] && [ -n "$OLD_IMAGE_FILE" ] && [ "$IMAGE_FILE" != "$OLD_
log "Mounting $OLD_IMAGE_FILE to $TMP_MNT"
mkdir -p "$TMP_MNT"
- mount "$OLD_IMAGE_FILE" "$TMP_MNT"
+ if ! mount "$OLD_IMAGE_FILE" "$TMP_MNT"; then
+ log "ERROR: Failed to mount $OLD_IMAGE_FILE"
+ rm -rf "$TMP_MNT"
+ exit 1
+ fi
log "Copying full contents from image to directory $IMAGE_FILE"
- rsync -a --exclude="$OLD_IMG_FILE_NAME" "$TMP_MNT/" "$IMAGE_FILE/"
- umount "$TMP_MNT"
+ if ! rsync -a --exclude="$OLD_IMG_FILE_NAME" "$TMP_MNT/" "$IMAGE_FILE/"; then
+ log "WARNING: rsync encountered errors"
+ fi
+ umount "$TMP_MNT" || log "WARNING: Failed to unmount $TMP_MNT"
+ rmdir "$TMP_MNT" 2>/dev/null
elif [[ "$IMAGE_FILE" == *.img ]]; then
log "Mounting $IMAGE_FILE to $TMP_MNT"
mkdir -p "$TMP_MNT"
From 4e7b667be632564e20a7b87f5e75cfbe00b23c25 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Thu, 22 Jan 2026 14:27:52 +0000
Subject: [PATCH 27/64] Update
emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---
emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
index 19936df874..0e96e5869e 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
@@ -141,7 +141,16 @@ foreach ($domains as $dom) {
* --------------------------------------------------------- */
#print_r($vms);
ksort($vms,SORT_NATURAL);
-file_put_contents("/boot/config/plugins/dynamix.vm.manager/vms.json",json_encode($vms,JSON_PRETTY_PRINT));
+$json_path = "/boot/config/plugins/dynamix.vm.manager/vms.json";
+$json_dir = dirname($json_path);
+if (!is_dir($json_dir)) {
+ if (!@mkdir($json_dir, 0755, true)) {
+ die("Failed to create directory: $json_dir\n");
+ }
+}
+if (file_put_contents($json_path, json_encode($vms, JSON_PRETTY_PRINT)) === false) {
+ die("Failed to write vms.json\n");
+}
file_put_contents("/tmp/Stopcopy","");
foreach ($vms as $vm => $vmdetail) {
From 80b04cb8a740ad3917ec10cfeddeb8c2b6168a5b Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Thu, 22 Jan 2026 14:29:44 +0000
Subject: [PATCH 28/64] Update etc/rc.d/rc.libvirt
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---
etc/rc.d/rc.libvirt | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/etc/rc.d/rc.libvirt b/etc/rc.d/rc.libvirt
index a53c4d237e..936ba5900f 100755
--- a/etc/rc.d/rc.libvirt
+++ b/etc/rc.d/rc.libvirt
@@ -248,8 +248,13 @@ libvirtd_start(){
libvirtd_stop(){
# Save VM locations
- /usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
- #
+ LIBVIRTCOPY="/usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy"
+ if [[ -x "$LIBVIRTCOPY" ]]; then
+ "$LIBVIRTCOPY" || log "Warning: Failed to save VM locations"
+ else
+ log "Warning: libvirtcopy script not found or not executable"
+ fi
+
log "Stopping $DAEMON..."
if [[ ! -f $LIBVIRTD_PIDFILE ]]; then
log "$DAEMON... Already stopped."
From 835b348877d7c57082998cf668808cbee4b1e105 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Thu, 22 Jan 2026 14:32:54 +0000
Subject: [PATCH 29/64] Update
emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---
emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
index c10d93a618..21e3fc1169 100644
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
@@ -1,4 +1,4 @@
-#!/usr/bin/php
+#!/usr/bin/php
Date: Thu, 22 Jan 2026 14:34:06 +0000
Subject: [PATCH 30/64] Update
emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---
.../dynamix.vm.manager/scripts/libvirtrestore | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
index e6e11227e6..d1727114df 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
@@ -16,8 +16,21 @@ $docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Helpers.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/fs_helpers.php";
-$vmsjson = file_get_contents("/boot/config/plugins/dynamix.vm.manager/vms.json");
-$vms = json_decode($vmsjson,true);
+$json_path = "/boot/config/plugins/dynamix.vm.manager/vms.json";
+
+if (!file_exists($json_path)) {
+ die("Configuration file not found: $json_path\n");
+}
+
+$vmsjson = file_get_contents($json_path);
+if ($vmsjson === false) {
+ die("Failed to read configuration file: $json_path\n");
+}
+
+$vms = json_decode($vmsjson, true);
+if ($vms === null && json_last_error() !== JSON_ERROR_NONE) {
+ die("Invalid JSON in configuration file: " . json_last_error_msg() . "\n");
+}
file_put_contents("/tmp/libvirtrestore","");
foreach ($vms as $vm => $vmdetail) {
From 62629a171b3e85a6217936f4794c27a2e2eb812e Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Thu, 22 Jan 2026 14:34:30 +0000
Subject: [PATCH 31/64] Update
emhttp/plugins/dynamix.vm.manager/scripts/savehook.php
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---
.../dynamix.vm.manager/scripts/savehook.php | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/savehook.php b/emhttp/plugins/dynamix.vm.manager/scripts/savehook.php
index f06e25279d..8daa3d207b 100644
--- a/emhttp/plugins/dynamix.vm.manager/scripts/savehook.php
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/savehook.php
@@ -1,7 +1,23 @@
#!/usr/bin/env php
Date: Thu, 22 Jan 2026 14:38:23 +0000
Subject: [PATCH 32/64] Update libvirt.php
---
emhttp/plugins/dynamix.vm.manager/include/libvirt.php | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index 65da1c0801..12eddee9f4 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -1473,9 +1473,9 @@ function domain_change_xml($domain, $xml) {
if (!libvirt_domain_undefine($dom)) {
return $this->_set_last_error();
}
- if (!$this->domain_define($this->conn, $xml)) { ## Update to use new function
+ if (!$this->domain_define($xml)) { ## Update to use new function
$this->last_error = libvirt_get_last_error();
- $this->domain_define($this->conn, $old_xml);
+ $this->domain_define($old_xml);
return false;
}
return true;
@@ -1633,7 +1633,7 @@ function domain_start($dom) {
$this->last_error = libvirt_get_last_error();
return $ret;
}
- $ret = libvirt_domain_create_xml($this->conn, $dom); ## Update to use new function
+ $ret = libvirt_domain_create_xml($this->conn, $dom);
$this->last_error = libvirt_get_last_error();
return $ret;
}
@@ -1824,7 +1824,6 @@ function domain_undefine($domain) {
if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
unlink('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd');
}
- # PR1722 Remove XML from VM directory using storage metadata
$xml = libvirt_domain_get_xml_desc($dom, 0);
if ($xml) {
$this->manage_domain_xml($domain, $xml, false);
From a6405874330ba7984a55e4c89b290a89fb25f899 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Thu, 22 Jan 2026 16:27:08 +0000
Subject: [PATCH 33/64] Update libvirtmigrate
---
emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
index 21e3fc1169..efce356f79 100644
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
@@ -90,7 +90,7 @@ function migrate_nvram_file($valid_nvram, $vm_path, $vm_uuid, $vm_name, $dry_run
// Read old XML
if (!file_exists($xml_old_path)) {
- @unlink($dest_file); // Rollback
+ if ($copied) @unlink($dest_file); // Rollback only if we copied
return [
'success' => false,
'error' => "XML file not found: $xml_old_path"
From 0f4cda729d0ffc873c10c774945524c2fcc45cb2 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Fri, 23 Jan 2026 10:11:26 +0000
Subject: [PATCH 34/64] Coderabbit updates
---
emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy | 5 ++++-
emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate | 2 +-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
index 0e96e5869e..effa47bf0b 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
@@ -111,7 +111,10 @@ foreach ($domains as $dom) {
$storage_name = 'default';
} else {
/* Explicit Unraid pool */
- $path_root = str_replace("/mnt/user/","/mnt/$metadata_storage/",$default_domain_dir);
+ $path_root = preg_replace('#^/mnt/[^/]+/#', "/mnt/$metadata_storage/", $default_domain_dir, 1, $replaced);
+ if ($replaced === 0) {
+ $path_root = $default_domain_dir;
+ }
$storage_name = $metadata_storage;
}
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
index efce356f79..72fefc7be2 100644
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
@@ -101,7 +101,7 @@ function migrate_nvram_file($valid_nvram, $vm_path, $vm_uuid, $vm_name, $dry_run
$xml = @simplexml_load_string($xml_content);
if ($xml === false) {
- if (!$dry_run) @unlink($dest_file); // Rollback
+ if ($copied) `@unlink`($dest_file); // Rollback only if we copied
return [
'success' => false,
'error' => "Failed to parse XML: $xml_old_path"
From c2533c6d7c961d1691c97434ccfad5add45f0ab7 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Mon, 26 Jan 2026 14:38:06 +0000
Subject: [PATCH 35/64] check for new model flag and change paths
---
.../dynamix.vm.manager/include/VMajax.php | 3 +-
.../dynamix.vm.manager/include/libvirt.php | 85 ++++++++++---------
.../include/libvirt_helpers.php | 17 ++--
.../include/libvirt_paths.php | 65 ++++++++++++++
.../codemirror/addon/hint/libvirt-schema.js | 6 +-
.../dynamix.vm.manager/scripts/libvirtmigrate | 35 +++++++-
etc/libvirt/paths.conf | 4 +
7 files changed, 159 insertions(+), 56 deletions(-)
create mode 100644 emhttp/plugins/dynamix.vm.manager/include/libvirt_paths.php
create mode 100644 etc/libvirt/paths.conf
diff --git a/emhttp/plugins/dynamix.vm.manager/include/VMajax.php b/emhttp/plugins/dynamix.vm.manager/include/VMajax.php
index 34acadd4d7..a511b7758e 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/VMajax.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/VMajax.php
@@ -484,7 +484,8 @@ function embed(&$bootcfg, $env, $key, $value) {
$list = glob($pathinfo['dirname']."/*");
$uuid = $lv->domain_get_uuid($domName);
- $list2 = glob("/etc/libvirt/qemu/nvram/*$uuid*");
+ $vm_path = libvirt_get_vm_path($domName);
+ $list2 = glob(libvirt_get_nvram_dir($vm_path, $domName)."/*$uuid*");
$listnew = array();
$list=array_merge($list,$list2);
foreach($list as $key => $listent)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index 12eddee9f4..449ab1a369 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -12,6 +12,7 @@
*/
?>
+require_once __DIR__ . '/libvirt_paths.php';
class Libvirt {
private $conn;
private $last_error;
@@ -274,31 +275,31 @@ function config_to_xml($config, $vmclone=false) {
$osbootdev = '';
if (!empty($domain['ovmf'])) {
if ($domain['ovmf'] == 1) {
- if (!is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd')) {
+ if (!is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd')) {
// Create a new copy of OVMF VARS for this VM
- mkdir('/etc/libvirt/qemu/nvram/', 0777, true);
- copy('/usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi.fd', '/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd');
+ mkdir(libvirt_get_nvram_dir().'/', 0777, true);
+ copy('/usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi.fd', libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd');
}
- if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
+ if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
// Delete OVMF-TPM VARS for this VM if found
- unlink('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd');
+ unlink(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd');
}
$loader = "
/usr/share/qemu/ovmf-x64/OVMF_CODE-pure-efi.fd
-
/etc/libvirt/qemu/nvram/".$uuid."_VARS-pure-efi.fd";
+
".libvirt_get_nvram_dir()."/".$uuid."_VARS-pure-efi.fd";
if ($domain['usbboot'] == 'Yes') $osbootdev = "
";
}
if ($domain['ovmf'] == 2) {
- if (!is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
+ if (!is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
// Create a new copy of OVMF VARS for this VM
- mkdir('/etc/libvirt/qemu/nvram/', 0777, true);
- copy('/usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi-tpm.fd', '/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd');
+ mkdir(libvirt_get_nvram_dir().'/', 0777, true);
+ copy('/usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi-tpm.fd', libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd');
}
- if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd')) {
+ if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd')) {
// Delete OVMF VARS for this VM if found
- unlink('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd');
+ unlink(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd');
}
$loader = "
/usr/share/qemu/ovmf-x64/OVMF_CODE-pure-efi-tpm.fd
-
/etc/libvirt/qemu/nvram/".$uuid."_VARS-pure-efi-tpm.fd";
+
".libvirt_get_nvram_dir()."/".$uuid."_VARS-pure-efi-tpm.fd";
$swtpm = "
";
@@ -1818,11 +1819,11 @@ function domain_undefine($domain) {
if (!$dom) return false;
$uuid = $this->domain_get_uuid($dom);
// remove OVMF VARS if this domain had them
- if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd')) {
- unlink('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd');
+ if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd')) {
+ unlink(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd');
}
- if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
- unlink('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd');
+ if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
+ unlink(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd');
}
$xml = libvirt_domain_get_xml_desc($dom, 0);
if ($xml) {
@@ -1871,12 +1872,12 @@ function domain_delete($domain) {
function nvram_backup($uuid) {
// move OVMF VARS to a backup file if this domain has them
- if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd')) {
- rename('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd', '/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd_backup');
+ if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd')) {
+ rename(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd', libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd_backup');
return true;
}
- if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
- rename('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd', '/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd_backup');
+ if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
+ rename(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd', libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd_backup');
return true;
}
return false;
@@ -1884,12 +1885,12 @@ function nvram_backup($uuid) {
function nvram_restore($uuid) {
// restore backup OVMF VARS if this domain had them
- if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd_backup')) {
- rename('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd_backup', '/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd');
+ if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd_backup')) {
+ rename(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd_backup', libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd');
return true;
}
- if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd_backup')) {
- rename('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd_backup', '/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd');
+ if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd_backup')) {
+ rename(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd_backup', libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd');
return true;
}
return false;
@@ -1897,12 +1898,12 @@ function nvram_restore($uuid) {
function nvram_rename($uuid, $newuuid) {
// rename backup OVMF VARS if this domain had them
- if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd')) {
- rename('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd_backup', '/etc/libvirt/qemu/nvram/'.$newuuid.'_VARS-pure-efi.fd');
+ if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd')) {
+ rename(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd_backup', libvirt_get_nvram_dir().'/'.$newuuid.'_VARS-pure-efi.fd');
return true;
}
- if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
- rename('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd_backup', '/etc/libvirt/qemu/nvram/'.$newuuid.'_VARS-pure-efi-tpm.fd');
+ if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
+ rename(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd_backup', libvirt_get_nvram_dir().'/'.$newuuid.'_VARS-pure-efi-tpm.fd');
return true;
}
return false;
@@ -1910,12 +1911,12 @@ function nvram_rename($uuid, $newuuid) {
function nvram_create_snapshot($uuid, $snapshotname) {
// snapshot backup OVMF VARS if this domain had them
- if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd')) {
- copy('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd', '/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
+ if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd')) {
+ copy(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd', libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
return true;
}
- if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
- copy('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd', '/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
+ if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
+ copy(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd', libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
return true;
}
return false;
@@ -1923,14 +1924,14 @@ function nvram_create_snapshot($uuid, $snapshotname) {
function nvram_revert_snapshot($uuid, $snapshotname) {
// snapshot backup OVMF VARS if this domain had them
- if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd')) {
- copy('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd', '/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd');
- unlink('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
+ if (is_file(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd')) {
+ copy(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd', libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd');
+ unlink(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
return true;
}
- if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd')) {
- copy('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd', '/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd');
- unlink('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
+ if (is_file(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd')) {
+ copy(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd', libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd');
+ unlink(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
return true;
}
return false;
@@ -1938,12 +1939,12 @@ function nvram_revert_snapshot($uuid, $snapshotname) {
function nvram_delete_snapshot($uuid, $snapshotname) {
// snapshot backup OVMF VARS if this domain had them
- if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd')) {
- unlink('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
+ if (is_file(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd')) {
+ unlink(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
return true;
}
- if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd')) {
- unlink('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
+ if (is_file(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd')) {
+ unlink(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
return true;
}
return false;
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
index e102fe0d17..e7d22a3def 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
@@ -12,6 +12,7 @@
*/
?>
+require_once __DIR__ . '/libvirt_paths.php';
/**
* Array2XML: A class to convert array in PHP to XML
* It also takes into account attributes names unlike SimpleXML in PHP
@@ -1857,7 +1858,7 @@ function compare_creationtimelt($a, $b) {
function getvmsnapshots($vm) {
$snaps=array();
- $dbpath = "/etc/libvirt/qemu/snapshotdb/$vm";
+ $dbpath = libvirt_get_snapshotdb_dir(null, $vm) . "/$vm";
$snaps_json = file_get_contents($dbpath."/snapshots.db");
$snaps = json_decode($snaps_json,true);
if (is_array($snaps)) uasort($snaps,'compare_creationtime');
@@ -1866,7 +1867,7 @@ function getvmsnapshots($vm) {
function write_snapshots_database($vm,$name,$state,$desc,$method="QEMU") {
global $lv;
- $dbpath = "/etc/libvirt/qemu/snapshotdb/$vm";
+ $dbpath = libvirt_get_snapshotdb_dir(null, $vm) . "/$vm";
if (!is_dir($dbpath)) mkdir($dbpath);
$noxml = "";
$snaps_json = file_get_contents($dbpath."/snapshots.db");
@@ -1934,7 +1935,7 @@ function write_snapshots_database($vm,$name,$state,$desc,$method="QEMU") {
function refresh_snapshots_database($vm) {
global $lv;
- $dbpath = "/etc/libvirt/qemu/snapshotdb/$vm";
+ $dbpath = libvirt_get_snapshotdb_dir(null, $vm) . "/$vm";
if (!is_dir($dbpath)) mkdir($dbpath);
$snaps_json = file_get_contents($dbpath."/snapshots.db");
$snaps = json_decode($snaps_json,true);
@@ -1969,13 +1970,13 @@ function refresh_snapshots_database($vm) {
# Get uuid
$vmuuid = $lv->domain_get_uuid($vm);
#Get list of files
- $filepath = "/etc/libvirt/qemu/nvram/$vmuuid*"; #$snapshotname"
+ $filepath = libvirt_get_nvram_dir(null, $vm) . "/$vmuuid*"; #$snapshotname"
$nvram_files=glob($filepath);
foreach($nvram_files as $key => $nvram_file) {
- if ($nvram_file == "/etc/libvirt/qemu/nvram/$vmuuid"."_VARS-pure-efi.fd" || $nvram_file == "/etc/libvirt/qemu/nvram/$vmuuid"."_VARS-pure-efi-tpm.fd" ) unset($nvram_files[$key]);
+ if ($nvram_file == libvirt_get_nvram_dir(null, $vm) . "/$vmuuid"."_VARS-pure-efi.fd" || $nvram_file == libvirt_get_nvram_dir(null, $vm) . "/$vmuuid"."_VARS-pure-efi-tpm.fd" ) unset($nvram_files[$key]);
foreach ($snaps as $snapshotname => $snap) {
- $tpmfilename = "/etc/libvirt/qemu/nvram/".$vmuuid.$snapshotname."_VARS-pure-efi-tpm.fd";
- $nontpmfilename = "/etc/libvirt/qemu/nvram/".$vmuuid.$snapshotname."_VARS-pure-efi.fd";
+ $tpmfilename = libvirt_get_nvram_dir(null, $vm) . "/".$vmuuid.$snapshotname."_VARS-pure-efi-tpm.fd";
+ $nontpmfilename = libvirt_get_nvram_dir(null, $vm) . "/".$vmuuid.$snapshotname."_VARS-pure-efi.fd";
if ($nvram_file == $tpmfilename || $nvram_file == $nontpmfilename ) {
unset($nvram_files[$key]);}
}
@@ -1987,7 +1988,7 @@ function refresh_snapshots_database($vm) {
function delete_snapshots_database($vm,$name) {
global $lv;
- $dbpath = "/etc/libvirt/qemu/snapshotdb/$vm";
+ $dbpath = libvirt_get_snapshotdb_dir(null, $vm) . "/$vm";
$snaps_json = file_get_contents($dbpath."/snapshots.db");
$snaps = json_decode($snaps_json,true);
unset($snaps[$name]);
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_paths.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_paths.php
new file mode 100644
index 0000000000..7eeb2fceee
--- /dev/null
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_paths.php
@@ -0,0 +1,65 @@
+
+
+$libvirt_paths = @parse_ini_file('/etc/libvirt/paths.conf', false, INI_SCANNER_RAW);
+if (!is_array($libvirt_paths)) {
+ $libvirt_paths = [];
+}
+
+if (!defined('LIBVIRT_QEMU_DIR')) {
+ define('LIBVIRT_QEMU_DIR', $libvirt_paths['LIBVIRT_QEMU_DIR'] ?? '/etc/libvirt/qemu');
+}
+if (!defined('LIBVIRT_NVRAM_DIR')) {
+ define('LIBVIRT_NVRAM_DIR', $libvirt_paths['LIBVIRT_NVRAM_DIR'] ?? (LIBVIRT_QEMU_DIR . '/nvram'));
+}
+if (!defined('LIBVIRT_SNAPSHOTDB_DIR')) {
+ define('LIBVIRT_SNAPSHOTDB_DIR', $libvirt_paths['LIBVIRT_SNAPSHOTDB_DIR'] ?? (LIBVIRT_QEMU_DIR . '/snapshotdb'));
+}
+
+function libvirt_get_vms_json() {
+ static $vms_json_cache = null;
+ if ($vms_json_cache !== null) {
+ return $vms_json_cache;
+ }
+ $vms_json_path = '/boot/config/plugins/dynamix.vm.manager/vms.json';
+ if (!file_exists($vms_json_path)) {
+ $vms_json_cache = [];
+ return $vms_json_cache;
+ }
+ $json = @json_decode(file_get_contents($vms_json_path), true);
+ $vms_json_cache = is_array($json) ? $json : [];
+ return $vms_json_cache;
+}
+
+function libvirt_get_vm_path($vm_name) {
+ if (empty($vm_name)) {
+ return null;
+ }
+ $vms_json = libvirt_get_vms_json();
+ return $vms_json[$vm_name]['path'] ?? null;
+}
+
+function libvirt_get_nvram_dir($vm_path = null, $vm_name = null) {
+ if (empty($vm_path) && !empty($vm_name) && file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
+ $vm_path = libvirt_get_vm_path($vm_name);
+ }
+ if (!empty($vm_path) && file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
+ return rtrim($vm_path, '/') . '/nvram';
+ }
+ return LIBVIRT_NVRAM_DIR;
+}
+
+function libvirt_get_snapshotdb_dir($vm_path = null, $vm_name = null) {
+ if (empty($vm_path) && !empty($vm_name) && file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
+ $vm_path = libvirt_get_vm_path($vm_name);
+ }
+ if (!empty($vm_path) && file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
+ return rtrim($vm_path, '/') . '/snapshotdb';
+ }
+ return LIBVIRT_SNAPSHOTDB_DIR;
+}
+?>
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/codemirror/addon/hint/libvirt-schema.js b/emhttp/plugins/dynamix.vm.manager/scripts/codemirror/addon/hint/libvirt-schema.js
index f0ae88b7a2..06221f9fc7 100644
--- a/emhttp/plugins/dynamix.vm.manager/scripts/codemirror/addon/hint/libvirt-schema.js
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/codemirror/addon/hint/libvirt-schema.js
@@ -2,6 +2,10 @@ function getLibvirtSchema() {
var root = {};
+ var LIBVIRT_NVRAM_DIR = (typeof window !== "undefined" && window.LIBVIRT_NVRAM_DIR)
+ ? window.LIBVIRT_NVRAM_DIR
+ : "/etc/libvirt/qemu/nvram";
+
root.domain = {
"!attrs": {
type: ["kvm"],
@@ -167,7 +171,7 @@ function getLibvirtSchema() {
"!value": "/usr/share/qemu/ovmf-x64/OVMF_CODE-pure-efi.fd"
};
root.domain.os.nvram = {
- "!value": "/etc/libvirt/qemu/nvram/{{UUID}}_VARS-pure-efi.fd"
+ "!value": LIBVIRT_NVRAM_DIR + "/{{UUID}}_VARS-pure-efi.fd"
};
root.domain.features = {};
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
index 72fefc7be2..457154dc25 100644
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
@@ -16,7 +16,10 @@ $docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Helpers.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/fs_helpers.php";
-$libvirt_location ="/etc/libvirtold";
+$libvirt_location ="/etc/libvirt";
+
+$log_file = "/tmp/libvirtmigrate";
+file_put_contents($log_file, "");
/* ---------------------------------------------------------
* Load vms.json created by libvirtcopy
@@ -38,6 +41,7 @@ function load_vms_json() {
* --------------------------------------------------------- */
function migrate_nvram_file($valid_nvram, $vm_path, $vm_uuid, $vm_name, $dry_run = false) {
global $libvirt_location;
+ $deleted = false;
// Create nvram subdirectory
$nvram_dest_dir = $vm_path . '/nvram';
if (!is_dir($nvram_dest_dir)) {
@@ -101,7 +105,7 @@ function migrate_nvram_file($valid_nvram, $vm_path, $vm_uuid, $vm_name, $dry_run
$xml = @simplexml_load_string($xml_content);
if ($xml === false) {
- if ($copied) `@unlink`($dest_file); // Rollback only if we copied
+ if ($copied) @unlink($dest_file); // Rollback only if we copied
return [
'success' => false,
'error' => "Failed to parse XML: $xml_old_path"
@@ -122,6 +126,11 @@ function migrate_nvram_file($valid_nvram, $vm_path, $vm_uuid, $vm_name, $dry_run
'error' => "Failed to write updated XML to: $xml_new_path"
];
}
+
+ // Remove source NVRAM after successful copy and XML update
+ if (!$dry_run && $copied && file_exists($src_file)) {
+ $deleted = @unlink($src_file);
+ }
return [
'success' => true,
@@ -131,7 +140,8 @@ function migrate_nvram_file($valid_nvram, $vm_path, $vm_uuid, $vm_name, $dry_run
'xml_new_path' => $xml_new_path,
'dry_run' => $dry_run,
'would_copy' => $would_copy,
- 'copied' => $copied
+ 'copied' => $copied,
+ 'deleted' => $deleted
];
}
@@ -184,6 +194,10 @@ function perform_migration($valid_nvrams, $dry_run = false) {
];
continue;
}
+
+ if ($dry_run) {
+ file_put_contents("/tmp/libvirtmigrate", "DRY RUN: migrate NVRAM for $vm_name at $vm_path\n", FILE_APPEND);
+ }
// Ensure snapshotdb for this VM is moved once
if (!isset($moved_snapshotdb[$vm_name])) {
@@ -207,6 +221,7 @@ function perform_migration($valid_nvrams, $dry_run = false) {
'would_move' => true,
'dry_run' => true
];
+ file_put_contents("/tmp/libvirtmigrate", "DRY RUN: would move snapshotdb for $vm_name from $old_snap_dir to $new_snap_dir\n", FILE_APPEND);
} else {
// If destination exists, merge; otherwise attempt rename then fallback to copy
if (is_dir($new_snap_dir)) {
@@ -313,6 +328,8 @@ function load_snapshot_db($vm_name) {
* --------------------------------------------------------- */
function validate_nvram_uuids() {
global $libvirt_location;
+ $log_file = "/tmp/libvirtmigrate";
+ file_put_contents($log_file, "validate_nvram_uuids: start\n", FILE_APPEND);
// Connect to libvirt
$lv = libvirt_connect('qemu:///system', false);
if (!$lv) {
@@ -327,6 +344,7 @@ function validate_nvram_uuids() {
$valid_uuids = [];
$snapshot_dbs = [];
+ file_put_contents($log_file, "validate_nvram_uuids: domains=" . count($domains) . "\n", FILE_APPEND);
foreach ($domains as $dom) {
$domget = libvirt_domain_lookup_by_name($lv, $dom);
@@ -344,16 +362,20 @@ function validate_nvram_uuids() {
// Scan NVRAM directory
$nvram_dir = $libvirt_location . "/qemu/nvram";
if (!is_dir($nvram_dir)) {
+ file_put_contents($log_file, "validate_nvram_uuids: nvram dir missing: $nvram_dir\n", FILE_APPEND);
return ['valid' => [], 'orphaned' => []];
}
$nvram_files = glob("$nvram_dir/*");
if ($nvram_files === false || count($nvram_files) === 0) {
+ file_put_contents($log_file, "validate_nvram_uuids: no nvram files found\n", FILE_APPEND);
return ['valid' => [], 'orphaned' => []];
}
$valid = [];
$orphaned = [];
+ $valid_count = 0;
+ $orphaned_count = 0;
foreach ($nvram_files as $file) {
$basename = basename($file);
@@ -386,6 +408,7 @@ function validate_nvram_uuids() {
'is_snapshot' => $is_snapshot,
'size' => filesize($file)
];
+ $valid_count++;
} else {
$orphaned[] = [
'file' => $file,
@@ -397,6 +420,7 @@ function validate_nvram_uuids() {
'size' => filesize($file),
'reason' => 'snapshot not found in snapshots.db'
];
+ $orphaned_count++;
}
} else {
$orphaned[] = [
@@ -408,10 +432,13 @@ function validate_nvram_uuids() {
'size' => filesize($file),
'reason' => 'VM not found'
];
+ $orphaned_count++;
}
}
}
+ file_put_contents($log_file, "validate_nvram_uuids: valid=$valid_count orphaned=$orphaned_count\n", FILE_APPEND);
+
return ['valid' => $valid, 'orphaned' => $orphaned];
}
@@ -432,6 +459,7 @@ function delete_orphaned_files($orphaned_files, $dry_run = false) {
if ($dry_run) {
// In dry-run mode, just count as would-be deleted
$deleted++;
+ file_put_contents("/tmp/libvirtmigrate", "DRY RUN: would delete orphaned NVRAM {$item['file']}\n", FILE_APPEND);
} elseif (@unlink($item['file'])) {
$deleted++;
} else {
@@ -475,7 +503,6 @@ if ($valid_only) {
'orphaned' => $result['orphaned']
];
}
-
// Delete orphaned files if flag is set
if ($delete_flag && !empty($result['orphaned'])) {
$output['deletion_result'] = delete_orphaned_files($result['orphaned'], $dry_run);
diff --git a/etc/libvirt/paths.conf b/etc/libvirt/paths.conf
new file mode 100644
index 0000000000..2f30576c38
--- /dev/null
+++ b/etc/libvirt/paths.conf
@@ -0,0 +1,4 @@
+# Libvirt path overrides
+LIBVIRT_QEMU_DIR="/etc/libvirt/qemu"
+LIBVIRT_NVRAM_DIR="/etc/libvirt/qemu/nvram"
+LIBVIRT_SNAPSHOTDB_DIR="/etc/libvirt/qemu/snapshotdb"
From b1ae311f124691833d78d783b13f8d74bd826442 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Mon, 26 Jan 2026 16:19:12 +0000
Subject: [PATCH 36/64] Update libvirt_paths.php
---
emhttp/plugins/dynamix.vm.manager/include/libvirt_paths.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_paths.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_paths.php
index 7eeb2fceee..b72159494f 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_paths.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_paths.php
@@ -5,7 +5,7 @@
*/
?>
-$libvirt_paths = @parse_ini_file('/etc/libvirt/paths.conf', false, INI_SCANNER_RAW);
+$libvirt_paths = @parse_ini_file('/etc/rc.d/rc.libvirt.conf', false, INI_SCANNER_RAW);
if (!is_array($libvirt_paths)) {
$libvirt_paths = [];
}
From 43d6d876f621a2842fe20e0b18f9298b7fe47d4a Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Mon, 26 Jan 2026 16:19:31 +0000
Subject: [PATCH 37/64] Create rc.libvirt.conf
---
etc/rc.d/rc.libvirt.conf | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 etc/rc.d/rc.libvirt.conf
diff --git a/etc/rc.d/rc.libvirt.conf b/etc/rc.d/rc.libvirt.conf
new file mode 100644
index 0000000000..2f30576c38
--- /dev/null
+++ b/etc/rc.d/rc.libvirt.conf
@@ -0,0 +1,4 @@
+# Libvirt path overrides
+LIBVIRT_QEMU_DIR="/etc/libvirt/qemu"
+LIBVIRT_NVRAM_DIR="/etc/libvirt/qemu/nvram"
+LIBVIRT_SNAPSHOTDB_DIR="/etc/libvirt/qemu/snapshotdb"
From 560ed51b1c66e1243186febab3c52da159ddade2 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Tue, 27 Jan 2026 14:32:43 +0000
Subject: [PATCH 38/64] Make script executable
---
emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate | 0
1 file changed, 0 insertions(+), 0 deletions(-)
mode change 100644 => 100755 emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
old mode 100644
new mode 100755
From 0f8de492718f6f28b02eb9f30ab0c0f7ef07c33e Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Wed, 28 Jan 2026 11:32:06 +0000
Subject: [PATCH 39/64] Path changes.
---
.../dynamix.vm.manager/include/libvirt.php | 146 ++++++++++++------
.../include/libvirt_helpers.php | 8 +-
.../include/libvirt_paths.php | 87 +++++++++++
.../dynamix.vm.manager/scripts/libvirtcopy | 69 +--------
etc/rc.d/rc.libvirt | 12 +-
5 files changed, 205 insertions(+), 117 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index 449ab1a369..c03e828984 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -273,33 +273,35 @@ function config_to_xml($config, $vmclone=false) {
$loader = '';
$swtpm = '';
$osbootdev = '';
+ $vm_path = $domain['path'] ?? null;
+ $nvram_dir = libvirt_get_nvram_dir($vm_path, $name);
if (!empty($domain['ovmf'])) {
if ($domain['ovmf'] == 1) {
- if (!is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd')) {
+ if (!is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd')) {
// Create a new copy of OVMF VARS for this VM
- mkdir(libvirt_get_nvram_dir().'/', 0777, true);
- copy('/usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi.fd', libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd');
+ mkdir($nvram_dir.'/', 0777, true);
+ copy('/usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi.fd', $nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd');
}
- if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
+ if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
// Delete OVMF-TPM VARS for this VM if found
- unlink(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd');
+ unlink($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd');
}
$loader = "
/usr/share/qemu/ovmf-x64/OVMF_CODE-pure-efi.fd
-
".libvirt_get_nvram_dir()."/".$uuid."_VARS-pure-efi.fd";
+
".$nvram_dir."/".$uuid."_VARS-pure-efi.fd";
if ($domain['usbboot'] == 'Yes') $osbootdev = "
";
}
if ($domain['ovmf'] == 2) {
- if (!is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
+ if (!is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
// Create a new copy of OVMF VARS for this VM
- mkdir(libvirt_get_nvram_dir().'/', 0777, true);
- copy('/usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi-tpm.fd', libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd');
+ mkdir($nvram_dir.'/', 0777, true);
+ copy('/usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi-tpm.fd', $nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd');
}
- if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd')) {
+ if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd')) {
// Delete OVMF VARS for this VM if found
- unlink(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd');
+ unlink($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd');
}
$loader = "
/usr/share/qemu/ovmf-x64/OVMF_CODE-pure-efi-tpm.fd
-
".libvirt_get_nvram_dir()."/".$uuid."_VARS-pure-efi-tpm.fd";
+
".$nvram_dir."/".$uuid."_VARS-pure-efi-tpm.fd";
$swtpm = "
";
@@ -1015,7 +1017,24 @@ function appendqemucmdline($xml, $cmdline) {
return $newxml;
}
+ function build_vm_paths($config) {
+ if (!file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
+ return $config;
+ }
+ $vm_name = $config['domain']['name'] ?? null;
+ $storage = $config['template']['storage'] ?? 'default';
+ $uuid = $config['domain']['uuid'] ?? null;
+ $entry = libvirt_build_vm_entry($vm_name, $storage, null, $uuid);
+ if ($entry !== null) {
+ libvirt_update_vms_json_entry($vm_name, $entry);
+ $config['domain']['path'] = $entry['path'] ?? null;
+ }
+ return $config;
+ }
+
function domain_new($config) {
+ # Build paths for VM and store in the vms.json
+ $config = $this->build_vm_paths($config);
# Set storage for disks.
foreach ($config['disk'] as $i => $disk) { $config['disk'][$i]['storage'] = $config['template']['storage'];}
// attempt to create all disk images if needed
@@ -1818,14 +1837,24 @@ function domain_undefine($domain) {
$dom = $this->get_domain_object($domain);
if (!$dom) return false;
$uuid = $this->domain_get_uuid($dom);
+ $xml = libvirt_domain_get_xml_desc($dom, 0);
+ $vm_path = libvirt_get_vm_path($domain);
+ if (empty($vm_path) && $xml) {
+ $storage = 'default';
+ if (preg_match('/
]*storage="([^"]*)"/', $xml, $matches)) {
+ $storage = $matches[1] ?: 'default';
+ }
+ $entry = libvirt_build_vm_entry($domain, $storage, null, $uuid);
+ $vm_path = $entry['path'] ?? null;
+ }
+ $nvram_dir = libvirt_get_nvram_dir($vm_path, $domain);
// remove OVMF VARS if this domain had them
- if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd')) {
- unlink(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd');
+ if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd')) {
+ unlink($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd');
}
- if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
- unlink(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd');
+ if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
+ unlink($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd');
}
- $xml = libvirt_domain_get_xml_desc($dom, 0);
if ($xml) {
$this->manage_domain_xml($domain, $xml, false);
}
@@ -1839,6 +1868,7 @@ function domain_delete($domain) {
$disks = $this->get_disk_stats($dom);
$tmp = $this->domain_undefine($dom);
if (!$tmp) return $this->_set_last_error();
+ libvirt_remove_vms_json_entry($domain);
// remove the first disk only
if (array_key_exists('file', $disks[0])) {
$disk = $disks[0]['file'];
@@ -1870,81 +1900,99 @@ function domain_delete($domain) {
return true;
}
- function nvram_backup($uuid) {
+ function nvram_backup($uuid, $vm_name = null) {
+ if (empty($vm_name) && !empty($uuid)) {
+ $vm_name = $this->domain_get_name_by_uuid($uuid) ?: null;
+ }
+ $vm_path = $vm_name ? libvirt_get_vm_path($vm_name) : null;
+ $nvram_dir = libvirt_get_nvram_dir($vm_path, $vm_name);
// move OVMF VARS to a backup file if this domain has them
- if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd')) {
- rename(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd', libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd_backup');
+ if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd')) {
+ rename($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd', $nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd_backup');
return true;
}
- if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
- rename(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd', libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd_backup');
+ if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
+ rename($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd', $nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd_backup');
return true;
}
return false;
}
- function nvram_restore($uuid) {
+ function nvram_restore($uuid, $vm_name = null) {
+ if (empty($vm_name) && !empty($uuid)) {
+ $vm_name = $this->domain_get_name_by_uuid($uuid) ?: null;
+ }
+ $vm_path = $vm_name ? libvirt_get_vm_path($vm_name) : null;
+ $nvram_dir = libvirt_get_nvram_dir($vm_path, $vm_name);
// restore backup OVMF VARS if this domain had them
- if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd_backup')) {
- rename(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd_backup', libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd');
+ if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd_backup')) {
+ rename($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd_backup', $nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd');
return true;
}
- if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd_backup')) {
- rename(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd_backup', libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd');
+ if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd_backup')) {
+ rename($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd_backup', $nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd');
return true;
}
return false;
}
- function nvram_rename($uuid, $newuuid) {
+ function nvram_rename($uuid, $newuuid, $vm_name = null) {
+ $vm_path = $vm_name ? libvirt_get_vm_path($vm_name) : null;
+ $nvram_dir = libvirt_get_nvram_dir($vm_path, $vm_name);
// rename backup OVMF VARS if this domain had them
- if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd')) {
- rename(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd_backup', libvirt_get_nvram_dir().'/'.$newuuid.'_VARS-pure-efi.fd');
+ if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd')) {
+ rename($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd_backup', $nvram_dir.'/'.$newuuid.'_VARS-pure-efi.fd');
return true;
}
- if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
- rename(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd_backup', libvirt_get_nvram_dir().'/'.$newuuid.'_VARS-pure-efi-tpm.fd');
+ if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
+ rename($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd_backup', $nvram_dir.'/'.$newuuid.'_VARS-pure-efi-tpm.fd');
return true;
}
return false;
}
- function nvram_create_snapshot($uuid, $snapshotname) {
+ function nvram_create_snapshot($uuid, $snapshotname, $vm_name = null) {
+ $vm_path = $vm_name ? libvirt_get_vm_path($vm_name) : null;
+ $nvram_dir = libvirt_get_nvram_dir($vm_path, $vm_name);
// snapshot backup OVMF VARS if this domain had them
- if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd')) {
- copy(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd', libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
+ if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd')) {
+ copy($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd', $nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
return true;
}
- if (is_file(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
- copy(libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd', libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
+ if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
+ copy($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd', $nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
return true;
}
return false;
}
- function nvram_revert_snapshot($uuid, $snapshotname) {
+ function nvram_revert_snapshot($uuid, $snapshotname, $vm_name = null) {
+ $vm_path = $vm_name ? libvirt_get_vm_path($vm_name) : null;
+ $nvram_dir = libvirt_get_nvram_dir($vm_path, $vm_name);
// snapshot backup OVMF VARS if this domain had them
- if (is_file(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd')) {
- copy(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd', libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi.fd');
- unlink(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
+ if (is_file($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd')) {
+ copy($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd', $nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd');
+ unlink($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
return true;
}
- if (is_file(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd')) {
- copy(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd', libvirt_get_nvram_dir().'/'.$uuid.'_VARS-pure-efi-tpm.fd');
- unlink(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
+ if (is_file($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd')) {
+ copy($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd', $nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd');
+ unlink($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
return true;
}
return false;
}
- function nvram_delete_snapshot($uuid, $snapshotname) {
+ function nvram_delete_snapshot($uuid, $snapshotname, $vm_name = null) {
+ $vm_path = $vm_name ? libvirt_get_vm_path($vm_name) : null;
+ $nvram_dir = libvirt_get_nvram_dir($vm_path, $vm_name);
// snapshot backup OVMF VARS if this domain had them
- if (is_file(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd')) {
- unlink(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
+ if (is_file($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd')) {
+ unlink($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
return true;
}
- if (is_file(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd')) {
- unlink(libvirt_get_nvram_dir().'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
+ if (is_file($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd')) {
+ unlink($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
return true;
}
return false;
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
index d5b4acd706..2ae484e78b 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
@@ -2098,7 +2098,7 @@ function vm_snapshot($vm,$snapshotname, $snapshotdescinput, $free = "yes", $meth
#Copy nvram
if ($logging) qemu_log($vm,"Copy NVRAM");
- if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_create_snapshot($lv->domain_get_uuid($vm),$name);
+ if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_create_snapshot($lv->domain_get_uuid($vm), $name);
$xmlfile = $dirpath."/".$name.".running";
if ($logging) qemu_log($vm,"Save XML if state is running current $state");
@@ -2251,7 +2251,7 @@ function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes',$dryr
if (is_file($xmlfile) && $action == "yes") if (!$dryrun) unlink($xmlfile); else echo ("$xmlfile \n");
if ($logging) qemu_log($vm,"mem $memoryfile xml $xmlfile");
# Delete NVRAM
- if (!empty($lv->domain_get_ovmf($res)) && $action == "yes") if (!$dryrun) if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm),$name); else echo "Remove old NV\n";
+ if (!empty($lv->domain_get_ovmf($res)) && $action == "yes") if (!$dryrun) if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm), $name); else echo "Remove old NV\n";
if ($actionmeta == "yes") {
if (!$dryrun) $ret = delete_snapshots_database("$vm","$name"); else echo "Old Delete snapshot meta\n";
if ($logging) qemu_log($vm,"Old Delete snapshot meta");
@@ -2306,7 +2306,7 @@ function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes',$dryr
if ($logging) qemu_log($vm,"Delete Snapshot DB entry");
}
- if (!$dryrun) if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm),$snap); else echo "Delete NV $vm,$snap\n";
+ if (!$dryrun) if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm), $snap); else echo "Delete NV $vm,$snap\n";
$arrResponse = ['success' => true];
if ($dryrun) var_dump($arrResponse);
@@ -2424,7 +2424,7 @@ function vm_snapremove($vm, $snap) {
}
# Delete NVRAM
- if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_delete_snapshot($lv->domain_get_uuid($vm),$snap);
+ if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_delete_snapshot($lv->domain_get_uuid($vm), $snap);
$ret = delete_snapshots_database("$vm","$snap") ;
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_paths.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_paths.php
index b72159494f..c1213c0d7f 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_paths.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_paths.php
@@ -62,4 +62,91 @@ function libvirt_get_snapshotdb_dir($vm_path = null, $vm_name = null) {
}
return LIBVIRT_SNAPSHOTDB_DIR;
}
+
+function libvirt_get_default_domain_dir() {
+ $cfg = '/boot/config/domain.cfg';
+ if (!file_exists($cfg)) {
+ return null;
+ }
+ $lines = file($cfg, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+ if ($lines === false) {
+ return null;
+ }
+ foreach ($lines as $line) {
+ $line = trim($line);
+ if ($line === '' || $line[0] === '#') {
+ continue;
+ }
+ if (preg_match('/^DOMAINDIR="([^"]+)"/', $line, $m)) {
+ return rtrim($m[1], '/');
+ }
+ }
+ return null;
+}
+
+function libvirt_build_vm_entry($vm_name, $storage_name = null, $default_domain_dir = null, $uuid = null) {
+ if (empty($vm_name)) {
+ return null;
+ }
+ $default_domain_dir = $default_domain_dir ?? libvirt_get_default_domain_dir();
+ $storage_name = ($storage_name === null || $storage_name === '' || strtolower($storage_name) === 'default')
+ ? 'default'
+ : $storage_name;
+
+ if ($storage_name === 'default') {
+ $path_root = $default_domain_dir;
+ } else {
+ $path_root = preg_replace('#^/mnt/[^/]+/#', "/mnt/$storage_name/", $default_domain_dir, 1, $replaced);
+ if ($replaced === 0) {
+ $path_root = $default_domain_dir;
+ }
+ }
+
+ $path = $path_root ? $path_root . '/' . $vm_name : null;
+ $exists = ($path_root && is_dir($path_root . '/' . $vm_name));
+
+ return [
+ 'uuid' => $uuid,
+ 'storage' => $storage_name,
+ 'path' => $path,
+ 'path_shell' => $path ? escapeshellarg($path) : null,
+ 'exists' => $exists,
+ ];
+}
+
+function libvirt_update_vms_json_entry($vm_name, array $entry) {
+ $cfg = '/boot/config/plugins/dynamix.vm.manager/vms.json';
+ $dir = dirname($cfg);
+ if (!is_dir($dir)) {
+ @mkdir($dir, 0755, true);
+ }
+ $vms = [];
+ if (file_exists($cfg)) {
+ $json = @json_decode(@file_get_contents($cfg), true);
+ if (is_array($json)) {
+ $vms = $json;
+ }
+ }
+ $vms[$vm_name] = array_filter($entry, fn($v) => $v !== null);
+ ksort($vms, SORT_NATURAL);
+ @file_put_contents($cfg, json_encode($vms, JSON_PRETTY_PRINT));
+}
+
+function libvirt_remove_vms_json_entry($vm_name) {
+ if (empty($vm_name)) {
+ return false;
+ }
+ $cfg = '/boot/config/plugins/dynamix.vm.manager/vms.json';
+ if (!file_exists($cfg)) {
+ return false;
+ }
+ $json = @json_decode(@file_get_contents($cfg), true);
+ if (!is_array($json) || !array_key_exists($vm_name, $json)) {
+ return false;
+ }
+ unset($json[$vm_name]);
+ ksort($json, SORT_NATURAL);
+ @file_put_contents($cfg, json_encode($json, JSON_PRETTY_PRINT));
+ return true;
+}
?>
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
index effa47bf0b..dcc9e28441 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
@@ -18,36 +18,8 @@
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Helpers.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/fs_helpers.php";
+require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_paths.php";
-/* ---------------------------------------------------------
- * Read default VM directory from Unraid domain.cfg
- * --------------------------------------------------------- */
-function get_default_domain_dir() {
- $cfg = '/boot/config/domain.cfg';
-
- if (!file_exists($cfg)) {
- return null;
- }
-
- $lines = file($cfg, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
- if ($lines === false) {
- return null;
- }
-
- foreach ($lines as $line) {
- $line = trim($line);
-
- if ($line === '' || $line[0] === '#') {
- continue;
- }
-
- if (preg_match('/^DOMAINDIR="([^"]+)"/', $line, $m)) {
- return rtrim($m[1], '/');
- }
- }
-
- return null;
-}
/* ---------------------------------------------------------
* Connect to libvirt
@@ -63,7 +35,7 @@ if ($domains === false) {
die("Failed to list domains\n");
}
-$default_domain_dir = get_default_domain_dir();
+$default_domain_dir = libvirt_get_default_domain_dir();
$vms = [];
@@ -101,42 +73,13 @@ foreach ($domains as $dom) {
}
}
- /* -----------------------------------------------------
- * Resolve filesystem path
- * Treat empty, null, or "default" as DOMAINDIR
- * ----------------------------------------------------- */
- if ($metadata_storage === null || $metadata_storage === '' || strtolower($metadata_storage) === 'default') {
- /* TRUE default storage */
- $path_root = $default_domain_dir; // e.g. /mnt/user/domains2
- $storage_name = 'default';
- } else {
- /* Explicit Unraid pool */
- $path_root = preg_replace('#^/mnt/[^/]+/#', "/mnt/$metadata_storage/", $default_domain_dir, 1, $replaced);
- if ($replaced === 0) {
- $path_root = $default_domain_dir;
- }
- $storage_name = $metadata_storage;
- }
-
-
- $path = $path_root
- ? $path_root . '/' . $vm_name
- : null;
-
-
- /* Filesystem existence check (remove quotes for is_dir) */
- $exists = ($path_root && is_dir($path_root . '/' . $vm_name));
-
/* -----------------------------------------------------
* Store result
* ----------------------------------------------------- */
- $vms[$vm_name] = [
- 'uuid' => $uuid,
- 'storage' => $storage_name,
- 'path' => $path,
- 'path_shell' => $path ? escapeshellarg($path) : null,
- 'exists' => $exists,
- ];
+ $entry = libvirt_build_vm_entry($vm_name, $metadata_storage, $default_domain_dir, $uuid);
+ if ($entry !== null) {
+ $vms[$vm_name] = $entry;
+ }
}
/* ---------------------------------------------------------
diff --git a/etc/rc.d/rc.libvirt b/etc/rc.d/rc.libvirt
index 936ba5900f..7227260f82 100755
--- a/etc/rc.d/rc.libvirt
+++ b/etc/rc.d/rc.libvirt
@@ -34,6 +34,14 @@ VIRTLOGD_OPTS=${VIRTLOGD_OPTS:-" -f /etc/libvirt/virtlogd.conf -p $VIRTLOGD_PIDF
VIRTLOCKD_PIDFILE="/var/run/libvirt/virtlockd.pid"
VIRTLOCKD_OPTS=${VIRTLOCKD_OPTS:-" -f /etc/libvirt/virtlockd.conf -p $VIRTLOCKD_PIDFILE "}
+# libvirt path configuration
+LIBVIRT_QEMU_DIR="/etc/libvirt/qemu"
+LIBVIRT_SNAPSHOTDB_DIR="$LIBVIRT_QEMU_DIR/snapshotdb"
+if [[ -r /etc/rc.d/rc.libvirt.conf ]]; then
+ # shellcheck disable=SC1091
+ . /etc/rc.d/rc.libvirt.conf
+fi
+
BOOT_DOMAIN="/boot/config/domain.cfg"
SYSTEM="/sys/class/net"
VIRTLOG="virtlog daemon"
@@ -233,7 +241,9 @@ libvirtd_start(){
mkdir -p /etc/libvirt/qemu/swtpm/tpm-states
# setup snapshot persistance.
mkdir -p /etc/libvirt/qemu/snapshot
- mkdir -p /etc/libvirt/qemu/snapshotdb
+ if [[ ! -f /boot/config/plugins/dynamix.vm.manager/vm_newmodel ]]; then
+ mkdir -p "$LIBVIRT_SNAPSHOTDB_DIR"
+ fi
rm -rf /var/lib/libvirt/qemu/snapshot
ln -sf /etc/libvirt/qemu/snapshot /var/lib/libvirt/qemu/snapshot
# create directory for pid file
From 3283bfea060aa30d824fde29e8f58c4929f23595 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Wed, 28 Jan 2026 14:58:38 +0000
Subject: [PATCH 40/64] Path processing.
---
emhttp/plugins/dynamix.vm.manager/include/libvirt.php | 3 +++
.../plugins/dynamix.vm.manager/include/libvirt_helpers.php | 1 +
emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php | 5 +++--
3 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index c03e828984..43f4b4eda5 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -1021,6 +1021,9 @@ function build_vm_paths($config) {
if (!file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
return $config;
}
+ if (empty($config['domain']['uuid'])) {
+ $config['domain']['uuid'] = $this->domain_generate_uuid();
+ }
$vm_name = $config['domain']['name'] ?? null;
$storage = $config['template']['storage'] ?? 'default';
$uuid = $config['domain']['uuid'] ?? null;
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
index 2ae484e78b..e608ed1848 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
@@ -1846,6 +1846,7 @@ function vm_clone($vm, $clone ,$overwrite,$start,$edit, $free, $waitID, $regenma
}
}
+ $config = $lv->build_vm_paths($config);
$xml = $lv->config_to_xml($config, true);
$rtn = $lv->domain_define($xml);
diff --git a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php
index 2b4db8e265..876e531b06 100755
--- a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php
+++ b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php
@@ -258,10 +258,11 @@
// form view
if (($error = create_vdisk($_POST)) === false) {
$arrExistingConfig = custom::createArray('domain',$strXML);
- $arrUpdatedConfig = custom::createArray('domain',$lv->config_to_xml($_POST));
+ $postConfig = $lv->build_vm_paths($_POST);
+ $arrUpdatedConfig = custom::createArray('domain',$lv->config_to_xml($postConfig));
if ($debug) {
file_put_contents("/tmp/vmdebug_exist",$strXML);
- file_put_contents("/tmp/vmdebug_new",$lv->config_to_xml($_POST));
+ file_put_contents("/tmp/vmdebug_new",$lv->config_to_xml($postConfig));
file_put_contents("/tmp/vmdebug_arrayN",json_encode($arrUpdatedConfig,JSON_PRETTY_PRINT));
file_put_contents("/tmp/vmdebug_arrayE",json_encode($arrExistingConfig,JSON_PRETTY_PRINT));
}
From e0524d900abbad6f71f0c26e75392b789d9e352d Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Thu, 29 Jan 2026 15:05:40 +0000
Subject: [PATCH 41/64] Fix delete issue.
---
emhttp/plugins/dynamix.vm.manager/include/libvirt.php | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index 43f4b4eda5..881b099002 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -280,6 +280,7 @@ function config_to_xml($config, $vmclone=false) {
if (!is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd')) {
// Create a new copy of OVMF VARS for this VM
mkdir($nvram_dir.'/', 0777, true);
+ @file_put_contents('/tmp/nvram_create.log', date('c')." DEBUG ONLY - remove before production - create nvram: {$nvram_dir}/{$uuid}_VARS-pure-efi.fd\n", FILE_APPEND);
copy('/usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi.fd', $nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd');
}
if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
@@ -294,6 +295,7 @@ function config_to_xml($config, $vmclone=false) {
if (!is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
// Create a new copy of OVMF VARS for this VM
mkdir($nvram_dir.'/', 0777, true);
+ @file_put_contents('/tmp/nvram_create.log', date('c')." DEBUG ONLY - remove before production - create nvram: {$nvram_dir}/{$uuid}_VARS-pure-efi-tpm.fd\n", FILE_APPEND);
copy('/usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi-tpm.fd', $nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd');
}
if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd')) {
@@ -1869,7 +1871,7 @@ function domain_delete($domain) {
$dom = $this->get_domain_object($domain);
if (!$dom) return false;
$disks = $this->get_disk_stats($dom);
- $tmp = $this->domain_undefine($dom);
+ $tmp = $this->domain_undefine($domain);
if (!$tmp) return $this->_set_last_error();
libvirt_remove_vms_json_entry($domain);
// remove the first disk only
From 9ff9afb94c8d9f96a6f0d08fdcaa254461e9c4f2 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Thu, 29 Jan 2026 22:23:13 +0000
Subject: [PATCH 42/64] fix snapshot GPF
---
.../dynamix.vm.manager/include/libvirt_helpers.php | 11 ++++++++---
1 file changed, 8 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 e608ed1848..c7414454a1 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
@@ -1888,7 +1888,11 @@ function write_snapshots_database($vm,$name,$state,$desc,$method="QEMU") {
$noxml = "";
$snaps_json = file_get_contents($dbpath."/snapshots.db");
$snaps = json_decode($snaps_json,true);
- $snapshot_res=$lv->domain_snapshot_lookup_by_name($vm,$name);
+ $snapshot_xml = @file_get_contents("/etc/libvirt/qemu/snapshot/{$vm}/{$name}.xml");
+ if (empty($snapshot_xml)) {
+ $snapshot_xml = trim(shell_exec("virsh snapshot-dumpxml ".escapeshellarg($vm)." ".escapeshellarg($name)." 2>/dev/null"));
+ }
+ $snapshot_res = !empty($snapshot_xml);
if (!$snapshot_res) {
# Manual Snap no XML
if ($state == "shutoff" && ($method == "ZFS" || $method == "BTRFS")) {
@@ -1905,7 +1909,6 @@ function write_snapshots_database($vm,$name,$state,$desc,$method="QEMU") {
$noxml = "noxml";
}
} else {
- $snapshot_xml=$lv->domain_snapshot_get_xml($snapshot_res);
$a = simplexml_load_string($snapshot_xml);
$a = json_encode($a);
$b = json_decode($a, TRUE);
@@ -2135,7 +2138,9 @@ function vm_snapshot($vm,$snapshotname, $snapshotdescinput, $free = "yes", $meth
if ($logging) qemu_log($vm,"Success write snap db");
$ret = write_snapshots_database("$vm","$name",$state,$snapshotdescinput,$method);
#remove meta data
- if ($ret != "noxml") $ret = $lv->domain_snapshot_delete($vm, "$name" ,2);
+ if ($ret != "noxml") {
+ exec("virsh snapshot-delete ".escapeshellarg($vm)." ".escapeshellarg($name)." --metadata 2>&1", $snapDelOut, $snapDelRtn);
+ }
}
return $arrResponse;
From 6fff22897b2130b7ebb267bafddc57e5714e27a3 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Thu, 29 Jan 2026 22:43:46 +0000
Subject: [PATCH 43/64] fix nvram creation issue and snapshotdb location.
---
.../dynamix.vm.manager/include/libvirt.php | 17 ++++++++++++----
.../include/libvirt_helpers.php | 20 +++++++++++++++----
.../templates/Custom.form.php | 5 ++++-
3 files changed, 33 insertions(+), 9 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index 881b099002..341e79a0c3 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -273,14 +273,23 @@ function config_to_xml($config, $vmclone=false) {
$loader = '';
$swtpm = '';
$osbootdev = '';
+ $defer_write = $domain['defer_write'] ?? false;
$vm_path = $domain['path'] ?? null;
+ if (empty($vm_path) && file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
+ $storage = $template['storage'] ?? 'default';
+ $entry = libvirt_build_vm_entry($name, $storage, null, $uuid);
+ $vm_path = $entry['path'] ?? null;
+ }
$nvram_dir = libvirt_get_nvram_dir($vm_path, $name);
+ $vms_json = libvirt_get_vms_json();
+ $vms_entry = $vms_json[$name] ?? null;
+ $vms_entry_json = $vms_entry !== null ? json_encode($vms_entry) : 'null';
if (!empty($domain['ovmf'])) {
if ($domain['ovmf'] == 1) {
- if (!is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd')) {
+ if (!$defer_write && !is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd')) {
// Create a new copy of OVMF VARS for this VM
mkdir($nvram_dir.'/', 0777, true);
- @file_put_contents('/tmp/nvram_create.log', date('c')." DEBUG ONLY - remove before production - create nvram: {$nvram_dir}/{$uuid}_VARS-pure-efi.fd\n", FILE_APPEND);
+ @file_put_contents('/tmp/nvram_create.log', date('c')." create nvram: {$nvram_dir}/{$uuid}_VARS-pure-efi.fd (nvram_dir={$nvram_dir}) vms.json_entry={$vms_entry_json}\n", FILE_APPEND); #Debug only - remove before production
copy('/usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi.fd', $nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd');
}
if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
@@ -292,10 +301,10 @@ function config_to_xml($config, $vmclone=false) {
if ($domain['usbboot'] == 'Yes') $osbootdev = "";
}
if ($domain['ovmf'] == 2) {
- if (!is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
+ if (!$defer_write && !is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
// Create a new copy of OVMF VARS for this VM
mkdir($nvram_dir.'/', 0777, true);
- @file_put_contents('/tmp/nvram_create.log', date('c')." DEBUG ONLY - remove before production - create nvram: {$nvram_dir}/{$uuid}_VARS-pure-efi-tpm.fd\n", FILE_APPEND);
+ @file_put_contents('/tmp/nvram_create.log', date('c')." create nvram: {$nvram_dir}/{$uuid}_VARS-pure-efi-tpm.fd (nvram_dir={$nvram_dir}) vms.json_entry={$vms_entry_json}\n", FILE_APPEND); #Debug only - remove before production
copy('/usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi-tpm.fd', $nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd');
}
if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd')) {
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
index c7414454a1..926276f95a 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
@@ -1874,7 +1874,10 @@ function compare_creationtimelt($a, $b) {
function getvmsnapshots($vm) {
$snaps=array();
- $dbpath = libvirt_get_snapshotdb_dir(null, $vm) . "/$vm";
+ $dbpath = libvirt_get_snapshotdb_dir(null, $vm);
+ if (!file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
+ $dbpath .= "/$vm";
+ }
$snaps_json = file_get_contents($dbpath."/snapshots.db");
$snaps = json_decode($snaps_json,true);
if (is_array($snaps)) uasort($snaps,'compare_creationtime');
@@ -1883,7 +1886,10 @@ function getvmsnapshots($vm) {
function write_snapshots_database($vm,$name,$state,$desc,$method="QEMU") {
global $lv;
- $dbpath = libvirt_get_snapshotdb_dir(null, $vm) . "/$vm";
+ $dbpath = libvirt_get_snapshotdb_dir(null, $vm);
+ if (!file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
+ $dbpath .= "/$vm";
+ }
if (!is_dir($dbpath)) mkdir($dbpath);
$noxml = "";
$snaps_json = file_get_contents($dbpath."/snapshots.db");
@@ -1969,7 +1975,10 @@ function purge_deleted_snapshots(array &$snaps){
function refresh_snapshots_database($vm,$delete_used=false) {
global $lv;
- $dbpath = libvirt_get_snapshotdb_dir(null, $vm) . "/$vm";
+ $dbpath = libvirt_get_snapshotdb_dir(null, $vm);
+ if (!file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
+ $dbpath .= "/$vm";
+ }
if (!is_dir($dbpath)) mkdir($dbpath);
$snaps_json = file_get_contents($dbpath."/snapshots.db");
$snaps = json_decode($snaps_json,true);
@@ -2028,7 +2037,10 @@ function refresh_snapshots_database($vm,$delete_used=false) {
function delete_snapshots_database($vm,$name) {
global $lv;
- $dbpath = libvirt_get_snapshotdb_dir(null, $vm) . "/$vm";
+ $dbpath = libvirt_get_snapshotdb_dir(null, $vm);
+ if (!file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
+ $dbpath .= "/$vm";
+ }
$snaps_json = file_get_contents($dbpath."/snapshots.db");
$snaps = json_decode($snaps_json,true);
unset($snaps[$name]);
diff --git a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php
index 876e531b06..bf74147265 100755
--- a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php
+++ b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php
@@ -154,7 +154,8 @@
} else {
// form view
#file_put_contents("/tmp/createpost",json_encode($_POST));
- if ($lv->domain_new($_POST)) {
+ $postConfig = $lv->build_vm_paths($_POST);
+ if ($lv->domain_new($postConfig)) {
// Fire off the vnc/spice popup if available
$dom = $lv->get_domain_by_name($_POST['domain']['name']);
$vmrcport = $lv->domain_get_vnc_port($dom);
@@ -310,6 +311,8 @@
$boolNew = true;
$arrConfig = $arrConfigDefaults;
$arrVMUSBs = getVMUSBs($strXML);
+ $arrConfig = $lv->build_vm_paths($arrConfig);
+ $arrConfig['domain']['defer_write'] = true;
$strXML = $lv->config_to_xml($arrConfig);
$domXML = new DOMDocument();
$domXML->preserveWhiteSpace = false;
From f0f671a5567f0c316af7f10a056ea553fee16dde Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Mon, 2 Feb 2026 08:36:43 +0000
Subject: [PATCH 44/64] fix nvram processing.
---
.../dynamix.vm.manager/include/libvirt.php | 24 ++++++++++++++-----
.../include/libvirt_helpers.php | 8 +++----
2 files changed, 22 insertions(+), 10 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index 341e79a0c3..2231e4c1c2 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -1672,12 +1672,13 @@ function domain_start($dom) {
return $ret;
}
- function manage_domain_xml($domain, $xml = null, $save = true) {
+ function manage_domain_xml($domain, $xml = null, $save = true, $vm_path = null) {
// Save or delete XML in VM directory based on $save flag
// $domain is the domain name (already validated by caller)
$xml_dir = null;
$storage = "default";
+ if ($save) {
// Extract storage location from VM metadata if available
if ($xml && preg_match('/]*storage="([^"]*)"/', $xml, $matches)) {
$storage = $matches[1];
@@ -1707,11 +1708,13 @@ function manage_domain_xml($domain, $xml = null, $save = true) {
}
$xml_file = $xml_dir . '/' . $domain . '.xml';
-
+ }
+
if ($save === false) {
+ $xml_file = $vm_path . '/' . $domain . '.xml';
if (is_file($xml_file)) {
- $backup_file = $xml_file . '.prev';
- @copy($xml_file, $backup_file);
+ #$backup_file = $xml_file . '.prev';
+ #@copy($xml_file, $backup_file);
return unlink($xml_file);
}
return true;
@@ -1746,7 +1749,7 @@ function domain_define($xml, $autostart=false) {
if ($tmp) {
// Extract domain name from XML to save it
if (preg_match('/(.*?)<\/name>/s', $xml, $matches)) {
- $this->manage_domain_xml($matches[1], $xml, true);
+ $this->manage_domain_xml($matches[1], $xml, true, null);
}
}
return $tmp ?: $this->_set_last_error();
@@ -1870,7 +1873,7 @@ function domain_undefine($domain) {
unlink($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd');
}
if ($xml) {
- $this->manage_domain_xml($domain, $xml, false);
+ $this->manage_domain_xml($domain, $xml, false, $vm_path);
}
$tmp = libvirt_domain_undefine($dom);
return $tmp ?: $this->_set_last_error();
@@ -1970,13 +1973,16 @@ function nvram_create_snapshot($uuid, $snapshotname, $vm_name = null) {
$nvram_dir = libvirt_get_nvram_dir($vm_path, $vm_name);
// snapshot backup OVMF VARS if this domain had them
if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd')) {
+ @file_put_contents('/tmp/nvram_snapshot.log', date('c')." nvram_create_snapshot: {$uuid} {$snapshotname} (nvram_dir={$nvram_dir}, vm_name={$vm_name}) source={$nvram_dir}/{$uuid}_VARS-pure-efi.fd target={$nvram_dir}/{$uuid}{$snapshotname}_VARS-pure-efi.fd\n", FILE_APPEND);
copy($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd', $nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
return true;
}
if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
+ @file_put_contents('/tmp/nvram_snapshot.log', date('c')." nvram_create_snapshot: {$uuid} {$snapshotname} (nvram_dir={$nvram_dir}, vm_name={$vm_name}) source={$nvram_dir}/{$uuid}_VARS-pure-efi-tpm.fd target={$nvram_dir}/{$uuid}{$snapshotname}_VARS-pure-efi-tpm.fd\n", FILE_APPEND);
copy($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd', $nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
return true;
}
+ @file_put_contents('/tmp/nvram_snapshot.log', date('c')." nvram_create_snapshot: {$uuid} {$snapshotname} (nvram_dir={$nvram_dir}, vm_name={$vm_name}) no nvram vars found\n", FILE_APPEND);
return false;
}
@@ -1985,15 +1991,18 @@ function nvram_revert_snapshot($uuid, $snapshotname, $vm_name = null) {
$nvram_dir = libvirt_get_nvram_dir($vm_path, $vm_name);
// snapshot backup OVMF VARS if this domain had them
if (is_file($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd')) {
+ @file_put_contents('/tmp/nvram_snapshot.log', date('c')." nvram_revert_snapshot: {$uuid} {$snapshotname} (nvram_dir={$nvram_dir}, vm_name={$vm_name}) source={$nvram_dir}/{$uuid}{$snapshotname}_VARS-pure-efi.fd target={$nvram_dir}/{$uuid}_VARS-pure-efi.fd\n", FILE_APPEND);
copy($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd', $nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd');
unlink($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
return true;
}
if (is_file($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd')) {
+ @file_put_contents('/tmp/nvram_snapshot.log', date('c')." nvram_revert_snapshot: {$uuid} {$snapshotname} (nvram_dir={$nvram_dir}, vm_name={$vm_name}) source={$nvram_dir}/{$uuid}{$snapshotname}_VARS-pure-efi-tpm.fd target={$nvram_dir}/{$uuid}_VARS-pure-efi-tpm.fd\n", FILE_APPEND);
copy($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd', $nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd');
unlink($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
return true;
}
+ @file_put_contents('/tmp/nvram_snapshot.log', date('c')." nvram_revert_snapshot: {$uuid} {$snapshotname} (nvram_dir={$nvram_dir}, vm_name={$vm_name}) no nvram snapshot vars found\n", FILE_APPEND);
return false;
}
@@ -2002,13 +2011,16 @@ function nvram_delete_snapshot($uuid, $snapshotname, $vm_name = null) {
$nvram_dir = libvirt_get_nvram_dir($vm_path, $vm_name);
// snapshot backup OVMF VARS if this domain had them
if (is_file($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd')) {
+ @file_put_contents('/tmp/nvram_snapshot.log', date('c')." nvram_delete_snapshot: {$uuid} {$snapshotname} (nvram_dir={$nvram_dir}, vm_name={$vm_name}) delete={$nvram_dir}/{$uuid}{$snapshotname}_VARS-pure-efi.fd\n", FILE_APPEND);
unlink($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
return true;
}
if (is_file($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd')) {
+ @file_put_contents('/tmp/nvram_snapshot.log', date('c')." nvram_delete_snapshot: {$uuid} {$snapshotname} (nvram_dir={$nvram_dir}, vm_name={$vm_name}) delete={$nvram_dir}/{$uuid}{$snapshotname}_VARS-pure-efi-tpm.fd\n", FILE_APPEND);
unlink($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
return true;
}
+ @file_put_contents('/tmp/nvram_snapshot.log', date('c')." nvram_delete_snapshot: {$uuid} {$snapshotname} (nvram_dir={$nvram_dir}, vm_name={$vm_name}) no nvram snapshot vars found\n", FILE_APPEND);
return false;
}
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
index 926276f95a..57c908bcc1 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
@@ -2114,7 +2114,7 @@ function vm_snapshot($vm,$snapshotname, $snapshotdescinput, $free = "yes", $meth
#Copy nvram
if ($logging) qemu_log($vm,"Copy NVRAM");
- if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_create_snapshot($lv->domain_get_uuid($vm), $name);
+ if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_create_snapshot($lv->domain_get_uuid($vm), $name, $vm);
$xmlfile = $dirpath."/".$name.".running";
if ($logging) qemu_log($vm,"Save XML if state is running current $state");
@@ -2269,7 +2269,7 @@ function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes',$dryr
if (is_file($xmlfile) && $action == "yes") if (!$dryrun) unlink($xmlfile); else echo ("$xmlfile \n");
if ($logging) qemu_log($vm,"mem $memoryfile xml $xmlfile");
# Delete NVRAM
- if (!empty($lv->domain_get_ovmf($res)) && $action == "yes") if (!$dryrun) if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm), $name); else echo "Remove old NV\n";
+ if (!empty($lv->domain_get_ovmf($res)) && $action == "yes") if (!$dryrun) if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm), $name,$vm); else echo "Remove old NV\n";
if ($actionmeta == "yes") {
if (!$dryrun) $ret = delete_snapshots_database("$vm","$name"); else echo "Old Delete snapshot meta\n";
if ($logging) qemu_log($vm,"Old Delete snapshot meta");
@@ -2324,7 +2324,7 @@ function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes',$dryr
if ($logging) qemu_log($vm,"Delete Snapshot DB entry");
}
- if (!$dryrun) if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm), $snap); else echo "Delete NV $vm,$snap\n";
+ if (!$dryrun) if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm), $snap, $vm); else echo "Delete NV $vm,$snap\n";
$arrResponse = ['success' => true];
if ($dryrun) var_dump($arrResponse);
@@ -2442,7 +2442,7 @@ function vm_snapremove($vm, $snap) {
}
# Delete NVRAM
- if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_delete_snapshot($lv->domain_get_uuid($vm), $snap);
+ if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_delete_snapshot($lv->domain_get_uuid($vm), $snap, $vm);
$ret = delete_snapshots_database("$vm","$snap") ;
From 28de0ee3deb5c4b5098652238b8ff47b3c07453e Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Mon, 2 Feb 2026 09:05:17 +0000
Subject: [PATCH 45/64] add delete all option
---
.../dynamix.vm.manager/include/VMajax.php | 3 +-
.../dynamix.vm.manager/include/libvirt.php | 88 ++++++++++++-------
.../javascript/vmmanager.js | 18 +++-
3 files changed, 72 insertions(+), 37 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/VMajax.php b/emhttp/plugins/dynamix.vm.manager/include/VMajax.php
index a511b7758e..d031e57029 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/VMajax.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/VMajax.php
@@ -282,7 +282,8 @@ function embed(&$bootcfg, $env, $key, $value) {
case 'domain-delete':
requireLibvirt();
- $arrResponse = $lv->domain_delete($domName)
+ $firstdisk = unscript(_var($_REQUEST,'firstdisk'));
+ $arrResponse = $lv->domain_delete($domName, $firstdisk);
? ['success' => true]
: ['error' => $lv->get_last_error()];
break;
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index 2231e4c1c2..244bf7d4d6 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -1879,40 +1879,69 @@ function domain_undefine($domain) {
return $tmp ?: $this->_set_last_error();
}
- function domain_delete($domain) {
+ function domain_delete($domain,$firstdiskonly=true) {
$dom = $this->get_domain_object($domain);
if (!$dom) return false;
$disks = $this->get_disk_stats($dom);
$tmp = $this->domain_undefine($domain);
if (!$tmp) return $this->_set_last_error();
libvirt_remove_vms_json_entry($domain);
- // remove the first disk only
- if (array_key_exists('file', $disks[0])) {
- $disk = $disks[0]['file'];
- $pathinfo = pathinfo($disk);
- $dir = $pathinfo['dirname'];
- // remove the vm config
- $cfg_vm = $dir.'/'.$domain.'.cfg';
- if (is_file($cfg_vm)) unlink($cfg_vm);
- $cfg = $dir.'/'.$pathinfo['filename'].'.cfg';
- $xml = $dir.'/'.$pathinfo['filename'].'.xml';
- if (is_file($disk)) unlink($disk);
- if (is_file($cfg)) unlink($cfg);
- if (is_file($xml)) unlink($xml);
- if (is_dir($dir) && $this->is_dir_empty($dir)) {
- $result= my_rmdir($dir);
- if ($result['type'] == "zfs") {
- qemu_log("$domain","delete empty zfs $dir {$result['rtncode']}");
- if (isset($result['dataset'])) qemu_log("$domain","dataset {$result['dataset']} ");
- if (isset($result['cmd'])) qemu_log("$domain","Command {$result['cmd']} ");
- if (isset($result['output'])) {
- $outputlogs = implode(" ",$result['output']);
- qemu_log("$domain","Output $outputlogs end");
+
+
+ # Directorys to consider removing
+ # NVRAM
+ # Snapshotdb
+ # /etc/libvirt/qemu/nvram//
+ # /etc/libvirt/qemu/snapshotdb//
+ #
+
+
+ if ($firstdiskonly) {
+ if (array_key_exists('file', $disks[0])) {
+ $disk = $disks[0]['file'];
+ $pathinfo = pathinfo($disk);
+ $dir = $pathinfo['dirname'];
+ // remove the vm config
+ $cfg_vm = $dir.'/'.$domain.'.cfg';
+ if (is_file($cfg_vm)) unlink($cfg_vm);
+ $cfg = $dir.'/'.$pathinfo['filename'].'.cfg';
+ $xml = $dir.'/'.$pathinfo['filename'].'.xml';
+ if (is_file($disk)) unlink($disk);
+ if (is_file($cfg)) unlink($cfg);
+ if (is_file($xml)) unlink($xml);
+ # Remove NVRAM Dir/Snapshots DB
+ if (is_dir($dir) && $this->is_dir_empty($dir)) {
+ $result= my_rmdir($dir);
+ if ($result['type'] == "zfs") {
+ qemu_log("$domain","delete empty zfs $dir {$result['rtncode']}");
+ if (isset($result['dataset'])) qemu_log("$domain","dataset {$result['dataset']} ");
+ if (isset($result['cmd'])) qemu_log("$domain","Command {$result['cmd']} ");
+ if (isset($result['output'])) {
+ $outputlogs = implode(" ",$result['output']);
+ qemu_log("$domain","Output $outputlogs end");
+ }
+ } else {
+ qemu_log("$domain","delete empty $dir {$result['rtncode']}");
}
- } else {
- qemu_log("$domain","delete empty $dir {$result['rtncode']}");
}
}
+ } else {
+ #REMOVE whole VM directory
+ $vm_path = libvirt_get_vm_path($domain);
+ if (is_dir($vm_path)) {
+ $result= my_rmdir($vm_path);
+ if ($result['type'] == "zfs") {
+ qemu_log("$domain","delete empty zfs $vm_path {$result['rtncode']}");
+ if (isset($result['dataset'])) qemu_log("$domain","dataset {$result['dataset']} ");
+ if (isset($result['cmd'])) qemu_log("$domain","Command {$result['cmd']} ");
+ if (isset($result['output'])) {
+ $outputlogs = implode(" ",$result['output']);
+ qemu_log("$domain","Output $outputlogs end");
+ }
+ } else {
+ qemu_log("$domain","delete empty $vm_path {$result['rtncode']}");
+ }
+ }
}
return true;
}
@@ -1973,16 +2002,13 @@ function nvram_create_snapshot($uuid, $snapshotname, $vm_name = null) {
$nvram_dir = libvirt_get_nvram_dir($vm_path, $vm_name);
// snapshot backup OVMF VARS if this domain had them
if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd')) {
- @file_put_contents('/tmp/nvram_snapshot.log', date('c')." nvram_create_snapshot: {$uuid} {$snapshotname} (nvram_dir={$nvram_dir}, vm_name={$vm_name}) source={$nvram_dir}/{$uuid}_VARS-pure-efi.fd target={$nvram_dir}/{$uuid}{$snapshotname}_VARS-pure-efi.fd\n", FILE_APPEND);
copy($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd', $nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
return true;
}
if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
- @file_put_contents('/tmp/nvram_snapshot.log', date('c')." nvram_create_snapshot: {$uuid} {$snapshotname} (nvram_dir={$nvram_dir}, vm_name={$vm_name}) source={$nvram_dir}/{$uuid}_VARS-pure-efi-tpm.fd target={$nvram_dir}/{$uuid}{$snapshotname}_VARS-pure-efi-tpm.fd\n", FILE_APPEND);
copy($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd', $nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
return true;
}
- @file_put_contents('/tmp/nvram_snapshot.log', date('c')." nvram_create_snapshot: {$uuid} {$snapshotname} (nvram_dir={$nvram_dir}, vm_name={$vm_name}) no nvram vars found\n", FILE_APPEND);
return false;
}
@@ -1991,18 +2017,15 @@ function nvram_revert_snapshot($uuid, $snapshotname, $vm_name = null) {
$nvram_dir = libvirt_get_nvram_dir($vm_path, $vm_name);
// snapshot backup OVMF VARS if this domain had them
if (is_file($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd')) {
- @file_put_contents('/tmp/nvram_snapshot.log', date('c')." nvram_revert_snapshot: {$uuid} {$snapshotname} (nvram_dir={$nvram_dir}, vm_name={$vm_name}) source={$nvram_dir}/{$uuid}{$snapshotname}_VARS-pure-efi.fd target={$nvram_dir}/{$uuid}_VARS-pure-efi.fd\n", FILE_APPEND);
copy($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd', $nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd');
unlink($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
return true;
}
if (is_file($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd')) {
- @file_put_contents('/tmp/nvram_snapshot.log', date('c')." nvram_revert_snapshot: {$uuid} {$snapshotname} (nvram_dir={$nvram_dir}, vm_name={$vm_name}) source={$nvram_dir}/{$uuid}{$snapshotname}_VARS-pure-efi-tpm.fd target={$nvram_dir}/{$uuid}_VARS-pure-efi-tpm.fd\n", FILE_APPEND);
copy($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd', $nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd');
unlink($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
return true;
}
- @file_put_contents('/tmp/nvram_snapshot.log', date('c')." nvram_revert_snapshot: {$uuid} {$snapshotname} (nvram_dir={$nvram_dir}, vm_name={$vm_name}) no nvram snapshot vars found\n", FILE_APPEND);
return false;
}
@@ -2011,16 +2034,13 @@ function nvram_delete_snapshot($uuid, $snapshotname, $vm_name = null) {
$nvram_dir = libvirt_get_nvram_dir($vm_path, $vm_name);
// snapshot backup OVMF VARS if this domain had them
if (is_file($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd')) {
- @file_put_contents('/tmp/nvram_snapshot.log', date('c')." nvram_delete_snapshot: {$uuid} {$snapshotname} (nvram_dir={$nvram_dir}, vm_name={$vm_name}) delete={$nvram_dir}/{$uuid}{$snapshotname}_VARS-pure-efi.fd\n", FILE_APPEND);
unlink($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi.fd');
return true;
}
if (is_file($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd')) {
- @file_put_contents('/tmp/nvram_snapshot.log', date('c')." nvram_delete_snapshot: {$uuid} {$snapshotname} (nvram_dir={$nvram_dir}, vm_name={$vm_name}) delete={$nvram_dir}/{$uuid}{$snapshotname}_VARS-pure-efi-tpm.fd\n", FILE_APPEND);
unlink($nvram_dir.'/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd');
return true;
}
- @file_put_contents('/tmp/nvram_snapshot.log', date('c')." nvram_delete_snapshot: {$uuid} {$snapshotname} (nvram_dir={$nvram_dir}, vm_name={$vm_name}) no nvram snapshot vars found\n", FILE_APPEND);
return false;
}
diff --git a/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js b/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js
index cfb750012c..6ed1d43772 100644
--- a/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js
+++ b/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js
@@ -236,7 +236,7 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, f
ajaxVMDispatch({action:"domain-undefine",uuid:uuid}, "loadlist");
});
}});
- opts.push({text:_("Remove VM")+" & "+_("Disks"), icon:"fa-trash", action:function(e) {
+ opts.push({text:_("Remove VM")+" & "+_("1st Disk only"), icon:"fa-trash", action:function(e) {
e.preventDefault();
swal({
title:_("Are you sure?"),
@@ -247,7 +247,21 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, f
cancelButtonText:_('Cancel')
},function(){
$('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
- ajaxVMDispatch({action:"domain-delete",uuid:uuid}, "loadlist");
+ ajaxVMDispatch({action:"domain-delete",uuid:uuid,firstdisk:true}, "loadlist");
+ });
+ }});
+ opts.push({text:_("Remove VM")+" & "+_("All disks"), icon:"fa-trash", action:function(e) {
+ e.preventDefault();
+ swal({
+ title:_("Are you sure?"),
+ text:_("Completely REMOVE")+" "+name+" "+_("disk image and definition"),
+ type:"warning",
+ showCancelButton:true,
+ confirmButtonText:_('Proceed'),
+ cancelButtonText:_('Cancel')
+ },function(){
+ $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
+ ajaxVMDispatch({action:"domain-delete",uuid:uuid,firstdisk:false}, "loadlist");
});
}});
}
From 62a461f6a4fe9c455730f4713f9e2ae8c7ce66bf Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Mon, 2 Feb 2026 09:48:56 +0000
Subject: [PATCH 46/64] remove debug logging
---
emhttp/plugins/dynamix.vm.manager/include/libvirt.php | 2 --
1 file changed, 2 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index 244bf7d4d6..39fdbe755a 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -289,7 +289,6 @@ function config_to_xml($config, $vmclone=false) {
if (!$defer_write && !is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd')) {
// Create a new copy of OVMF VARS for this VM
mkdir($nvram_dir.'/', 0777, true);
- @file_put_contents('/tmp/nvram_create.log', date('c')." create nvram: {$nvram_dir}/{$uuid}_VARS-pure-efi.fd (nvram_dir={$nvram_dir}) vms.json_entry={$vms_entry_json}\n", FILE_APPEND); #Debug only - remove before production
copy('/usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi.fd', $nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd');
}
if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
@@ -304,7 +303,6 @@ function config_to_xml($config, $vmclone=false) {
if (!$defer_write && !is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
// Create a new copy of OVMF VARS for this VM
mkdir($nvram_dir.'/', 0777, true);
- @file_put_contents('/tmp/nvram_create.log', date('c')." create nvram: {$nvram_dir}/{$uuid}_VARS-pure-efi-tpm.fd (nvram_dir={$nvram_dir}) vms.json_entry={$vms_entry_json}\n", FILE_APPEND); #Debug only - remove before production
copy('/usr/share/qemu/ovmf-x64/OVMF_VARS-pure-efi-tpm.fd', $nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd');
}
if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd')) {
From c9ec4ddd2c4d82581bd5be3127fda4cb675d000d Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Mon, 2 Feb 2026 10:59:44 +0000
Subject: [PATCH 47/64] Code rabbit changes
---
.../dynamix.vm.manager/include/VMajax.php | 10 ++++--
.../dynamix.vm.manager/include/libvirt.php | 14 ++++----
.../include/libvirt_helpers.php | 16 ++++++++--
.../javascript/vmmanager.js | 4 +--
.../dynamix.vm.manager/scripts/libvirtcopy | 13 +++++++-
.../dynamix.vm.manager/scripts/libvirtmigrate | 32 ++++++++++++++++---
6 files changed, 70 insertions(+), 19 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/VMajax.php b/emhttp/plugins/dynamix.vm.manager/include/VMajax.php
index d031e57029..91b74cf79f 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/VMajax.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/VMajax.php
@@ -282,8 +282,14 @@ function embed(&$bootcfg, $env, $key, $value) {
case 'domain-delete':
requireLibvirt();
- $firstdisk = unscript(_var($_REQUEST,'firstdisk'));
- $arrResponse = $lv->domain_delete($domName, $firstdisk);
+ if (array_key_exists('firstdisk', $_REQUEST)) {
+ $val = $_REQUEST['firstdisk'];
+ $falsey = [false, 0, '0', 'false', 'FALSE', 'False'];
+ $firstdisk = in_array($val, $falsey, true) ? false : true;
+ } else {
+ $firstdisk = true;
+ }
+ $arrResponse = $lv->domain_delete($domName, $firstdisk)
? ['success' => true]
: ['error' => $lv->get_last_error()];
break;
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index 39fdbe755a..68a9516b79 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -1738,7 +1738,6 @@ function domain_define($xml, $autostart=false) {
$tmp[$i] = "";
$xml = join("\n", $tmp);
}
- # PR1722 Save XML to VM directory
if ($autostart) {
$tmp = libvirt_domain_create_xml($this->conn, $xml);
if (!$tmp) return $this->_set_last_error();
@@ -1853,16 +1852,17 @@ function domain_undefine($domain) {
if (!$dom) return false;
$uuid = $this->domain_get_uuid($dom);
$xml = libvirt_domain_get_xml_desc($dom, 0);
- $vm_path = libvirt_get_vm_path($domain);
+ $domain_name = is_resource($dom) ? $this->domain_get_name($dom) : $domain;
+ $vm_path = libvirt_get_vm_path($domain_name);
if (empty($vm_path) && $xml) {
$storage = 'default';
if (preg_match('/]*storage="([^"]*)"/', $xml, $matches)) {
$storage = $matches[1] ?: 'default';
}
- $entry = libvirt_build_vm_entry($domain, $storage, null, $uuid);
+ $entry = libvirt_build_vm_entry($domain_name, $storage, null, $uuid);
$vm_path = $entry['path'] ?? null;
}
- $nvram_dir = libvirt_get_nvram_dir($vm_path, $domain);
+ $nvram_dir = libvirt_get_nvram_dir($vm_path, $domain_name);
// remove OVMF VARS if this domain had them
if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd')) {
unlink($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd');
@@ -1871,7 +1871,7 @@ function domain_undefine($domain) {
unlink($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd');
}
if ($xml) {
- $this->manage_domain_xml($domain, $xml, false, $vm_path);
+ $this->manage_domain_xml($domain_name, $xml, false, $vm_path);
}
$tmp = libvirt_domain_undefine($dom);
return $tmp ?: $this->_set_last_error();
@@ -1985,11 +1985,11 @@ function nvram_rename($uuid, $newuuid, $vm_name = null) {
$nvram_dir = libvirt_get_nvram_dir($vm_path, $vm_name);
// rename backup OVMF VARS if this domain had them
if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd')) {
- rename($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd_backup', $nvram_dir.'/'.$newuuid.'_VARS-pure-efi.fd');
+ rename($nvram_dir.'/'.$uuid.'_VARS-pure-efi.fd', $nvram_dir.'/'.$newuuid.'_VARS-pure-efi.fd');
return true;
}
if (is_file($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
- rename($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd_backup', $nvram_dir.'/'.$newuuid.'_VARS-pure-efi-tpm.fd');
+ rename($nvram_dir.'/'.$uuid.'_VARS-pure-efi-tpm.fd', $nvram_dir.'/'.$newuuid.'_VARS-pure-efi-tpm.fd');
return true;
}
return false;
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
index 57c908bcc1..d98af68491 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
@@ -1890,7 +1890,13 @@ function write_snapshots_database($vm,$name,$state,$desc,$method="QEMU") {
if (!file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
$dbpath .= "/$vm";
}
- if (!is_dir($dbpath)) mkdir($dbpath);
+ if (!is_dir($dbpath)) {
+ if (!mkdir($dbpath, 0755, true) && !is_dir($dbpath)) {
+ // Log error and abort
+ error_log("Failed to create snapshotdb directory: $dbpath");
+ return false;
+ }
+ }
$noxml = "";
$snaps_json = file_get_contents($dbpath."/snapshots.db");
$snaps = json_decode($snaps_json,true);
@@ -1979,7 +1985,13 @@ function refresh_snapshots_database($vm,$delete_used=false) {
if (!file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
$dbpath .= "/$vm";
}
- if (!is_dir($dbpath)) mkdir($dbpath);
+ if (!is_dir($dbpath)) {
+ if (!mkdir($dbpath, 0755, true) && !is_dir($dbpath)) {
+ // Log error and abort
+ error_log("Failed to create snapshotdb directory: $dbpath");
+ return false;
+ }
+ }
$snaps_json = file_get_contents($dbpath."/snapshots.db");
$snaps = json_decode($snaps_json,true);
diff --git a/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js b/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js
index 6ed1d43772..46302f9a73 100644
--- a/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js
+++ b/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js
@@ -240,7 +240,7 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, f
e.preventDefault();
swal({
title:_("Are you sure?"),
- text:_("Completely REMOVE")+" "+name+" "+_("disk image and definition"),
+ text:_("Completely REMOVE")+" "+name+" "+_("1st disk image and definition"),
type:"warning",
showCancelButton:true,
confirmButtonText:_('Proceed'),
@@ -254,7 +254,7 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, f
e.preventDefault();
swal({
title:_("Are you sure?"),
- text:_("Completely REMOVE")+" "+name+" "+_("disk image and definition"),
+ text:_("Completely REMOVE")+" "+name+" "+_("All disk images and definition"),
type:"warning",
showCancelButton:true,
confirmButtonText:_('Proceed'),
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
index dcc9e28441..12304c5c86 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
@@ -65,12 +65,23 @@ foreach ($domains as $dom) {
$metadata_storage = null;
if (isset($sx->metadata)) {
- foreach ($sx->metadata->children() as $child) {
+ // Try to get children in the 'http://unraid' namespace
+ $metaChildren = $sx->metadata->children('http://unraid');
+ foreach ($metaChildren as $child) {
if ($child->getName() === 'vmtemplate') {
$metadata_storage = trim((string)$child['storage']);
break;
}
}
+ // Fallback: also check for vmtemplate in default namespace if not found
+ if ($metadata_storage === null) {
+ foreach ($sx->metadata->children() as $child) {
+ if ($child->getName() === 'vmtemplate') {
+ $metadata_storage = trim((string)$child['storage']);
+ break;
+ }
+ }
+ }
}
/* -----------------------------------------------------
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
index 457154dc25..5d5ba64fe2 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
@@ -105,7 +105,7 @@ function migrate_nvram_file($valid_nvram, $vm_path, $vm_uuid, $vm_name, $dry_run
$xml = @simplexml_load_string($xml_content);
if ($xml === false) {
- if ($copied) @unlink($dest_file); // Rollback only if we copied
+ if ($copied) @unlink($dest_file); // Rollback only if we copied
return [
'success' => false,
'error' => "Failed to parse XML: $xml_old_path"
@@ -120,15 +120,37 @@ function migrate_nvram_file($valid_nvram, $vm_path, $vm_uuid, $vm_name, $dry_run
// Write updated XML to new location
$xml_formatted = $xml->asXML();
if (!$dry_run && !@file_put_contents($xml_new_path, $xml_formatted)) {
- @unlink($dest_file); // Rollback
+ if ($copied) @unlink($dest_file); // Rollback only if we copied
return [
'success' => false,
'error' => "Failed to write updated XML to: $xml_new_path"
];
}
- // Remove source NVRAM after successful copy and XML update
- if (!$dry_run && $copied && file_exists($src_file)) {
+ // Redefine the domain with the new XML so libvirt uses the new NVRAM path
+ $domain_defined = true;
+ if (!$dry_run) {
+ $domain_defined = false;
+ if ($xml_formatted) {
+ $output = null;
+ $retval = null;
+ exec("virsh define " . escapeshellarg($xml_new_path), $output, $retval);
+ if ($retval === 0) {
+ $domain_defined = true;
+ } else {
+ // Rollback: restore files if needed
+ if ($copied && file_exists($dest_file)) @unlink($dest_file);
+ // Optionally restore src_file if needed (not deleted yet)
+ return [
+ 'success' => false,
+ 'error' => "Failed to redefine domain with new XML using virsh: " . implode("\n", $output)
+ ];
+ }
+ }
+ }
+
+ // Remove source NVRAM after successful copy, XML update, and domain redefine
+ if (!$dry_run && $copied && $domain_defined && file_exists($src_file)) {
$deleted = @unlink($src_file);
}
@@ -383,7 +405,7 @@ function validate_nvram_uuids() {
// Extract UUID and optional snapshot name from filename
// Regular: {UUID}_VARS-pure-efi.fd
// Snapshot: {UUID}S{snapshot_name}_VARS-pure-efi.fd
- if (preg_match('/^([a-f0-9\-]+)(?:S([^_]+))?_VARS/', $basename, $matches)) {
+ if (preg_match('/^([a-f0-9\-]+)(?:S(.+?))?_VARS/', $basename, $matches)) {
$uuid = $matches[1];
$snapshot_name = isset($matches[2]) ? $matches[2] : null;
From 36b7e58f46f42f6cd927f3c2c66182798bc95b9c Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Mon, 2 Feb 2026 11:16:17 +0000
Subject: [PATCH 48/64] further updates
---
.../dynamix.vm.manager/scripts/libvirtmigrate | 19 ++++++++++++++++++-
.../templates/Custom.form.php | 1 -
2 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
index 5d5ba64fe2..8f3c136299 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
@@ -60,6 +60,7 @@ function migrate_nvram_file($valid_nvram, $vm_path, $vm_uuid, $vm_name, $dry_run
// Copy NVRAM file (compare first)
$would_copy = false;
$copied = false;
+ $is_snapshot = !empty($valid_nvram['is_snapshot']);
if (file_exists($dest_file)) {
$same = false;
if (filesize($src_file) === filesize($dest_file)) {
@@ -67,6 +68,7 @@ function migrate_nvram_file($valid_nvram, $vm_path, $vm_uuid, $vm_name, $dry_run
$hd = @md5_file($dest_file);
if ($hs !== false && $hd !== false && $hs === $hd) {
$same = true;
+ $copied = true; // treat as handled for downstream cleanup
}
}
if (!$same) $would_copy = true;
@@ -87,8 +89,23 @@ function migrate_nvram_file($valid_nvram, $vm_path, $vm_uuid, $vm_name, $dry_run
$copied = true;
}
}
+
+ // For snapshot NVRAMs, only perform the file move/copy and cleanup, skip XML/define
+ if ($is_snapshot) {
+ if (!$dry_run && $copied && file_exists($src_file)) {
+ $deleted = @unlink($src_file);
+ }
+ return [
+ 'success' => true,
+ 'nvram_src' => $src_file,
+ 'nvram_dest' => $dest_file,
+ 'is_snapshot' => true,
+ 'copied' => $copied,
+ 'deleted' => isset($deleted) ? $deleted : false
+ ];
+ }
- // Update XML file
+ // Update XML file (base NVRAM only)
$xml_old_path = $libvirt_location . "/qemu/$vm_name.xml";
$xml_new_path = "$vm_path/$vm_name.xml";
diff --git a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php
index bf74147265..3c9330226f 100755
--- a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php
+++ b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php
@@ -311,7 +311,6 @@
$boolNew = true;
$arrConfig = $arrConfigDefaults;
$arrVMUSBs = getVMUSBs($strXML);
- $arrConfig = $lv->build_vm_paths($arrConfig);
$arrConfig['domain']['defer_write'] = true;
$strXML = $lv->config_to_xml($arrConfig);
$domXML = new DOMDocument();
From cae0c9c1abe076cb77be7d9cd050a29959c3b92b Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Mon, 2 Feb 2026 14:39:16 +0000
Subject: [PATCH 49/64] Updates
---
.../dynamix.vm.manager/include/libvirt.php | 22 +++++++++++--
.../include/libvirt_helpers.php | 31 +++++++++++++++++--
.../dynamix.vm.manager/scripts/libvirtmigrate | 9 +++---
emhttp/plugins/dynamix/include/Helpers.php | 20 ++++++++++++
4 files changed, 72 insertions(+), 10 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index 68a9516b79..3ceba46827 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -1883,10 +1883,12 @@ function domain_delete($domain,$firstdiskonly=true) {
$disks = $this->get_disk_stats($dom);
$tmp = $this->domain_undefine($domain);
if (!$tmp) return $this->_set_last_error();
+ // Collect VM entry data before removal
+ $vm_path = libvirt_get_vm_path($domain); // or your data collection logic here
libvirt_remove_vms_json_entry($domain);
- # Directorys to consider removing
+ # Directories to consider removing
# NVRAM
# Snapshotdb
# /etc/libvirt/qemu/nvram//
@@ -1924,9 +1926,22 @@ function domain_delete($domain,$firstdiskonly=true) {
}
}
} else {
+ #Check for files outside of the main VM directory
+ foreach ($disks as $disk) {
+ if (array_key_exists('file', $disk)) {
+ $disk_path = $disk['file'];
+ if (strpos($disk_path, $vm_path) === false) {
+ if (is_file($disk_path)) {
+ unlink($disk_path);
+ qemu_log("$domain","deleted disk $disk_path outside of VM directory");
+ }
+ }
+ }
+ }
#REMOVE whole VM directory
- $vm_path = libvirt_get_vm_path($domain);
if (is_dir($vm_path)) {
+ $files_deleted = delete_dir_contents($vm_path);
+ if ($files_deleted) {
$result= my_rmdir($vm_path);
if ($result['type'] == "zfs") {
qemu_log("$domain","delete empty zfs $vm_path {$result['rtncode']}");
@@ -1939,7 +1954,10 @@ function domain_delete($domain,$firstdiskonly=true) {
} else {
qemu_log("$domain","delete empty $vm_path {$result['rtncode']}");
}
+ } else {
+ qemu_log("$domain","not deleting $vm_path not empty");
}
+ }
}
return true;
}
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
index d98af68491..960a38c963 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
@@ -1960,7 +1960,9 @@ function write_snapshots_database($vm,$name,$state,$desc,$method="QEMU") {
if (isset($b)) if (array_key_exists(0 , $b["disks"]["disk"])) $snaps[$vmsnap]["disks"]= $b["disks"]["disk"]; else $snaps[$vmsnap]["disks"][0]= $b["disks"]["disk"];
$value = json_encode($snaps,JSON_PRETTY_PRINT);
- file_put_contents($dbpath."/snapshots.db",$value);
+ if (!remove_empty_snapshots_db($dbpath, $snaps)) {
+ file_put_contents($dbpath . "/snapshots.db", $value);
+ }
return $noxml;
}
function purge_deleted_snapshots(array &$snaps){
@@ -2044,7 +2046,23 @@ function refresh_snapshots_database($vm,$delete_used=false) {
}
foreach ($nvram_files as $nvram_file) unlink($nvram_file);
- file_put_contents($dbpath."/snapshots.db",$value);
+ // Write or remove snapshots.db and directory if empty
+ if (!remove_empty_snapshots_db($dbpath, $snaps)) {
+ file_put_contents($dbpath . "/snapshots.db", $value);
+ }
+ }
+
+ /**
+ * Remove snapshots.db and its directory if the database is empty.
+ */
+ function remove_empty_snapshots_db($dbpath, $snaps) {
+ $dbfile = $dbpath . "/snapshots.db";
+ if (empty($snaps)) {
+ if (file_exists($dbfile)) unlink($dbfile);
+ if (is_dir($dbpath) && count(scandir($dbpath)) === 2) rmdir($dbpath);
+ return true;
+ }
+ return false;
}
function delete_snapshots_database($vm,$name) {
@@ -2057,7 +2075,9 @@ function delete_snapshots_database($vm,$name) {
$snaps = json_decode($snaps_json,true);
unset($snaps[$name]);
$value = json_encode($snaps,JSON_PRETTY_PRINT);
- file_put_contents($dbpath."/snapshots.db",$value);
+ if (!remove_empty_snapshots_db($dbpath, $snaps)) {
+ file_put_contents($dbpath . "/snapshots.db", $value);
+ }
return true;
}
@@ -2164,6 +2184,11 @@ function vm_snapshot($vm,$snapshotname, $snapshotdescinput, $free = "yes", $meth
#remove meta data
if ($ret != "noxml") {
exec("virsh snapshot-delete ".escapeshellarg($vm)." ".escapeshellarg($name)." --metadata 2>&1", $snapDelOut, $snapDelRtn);
+ // Remove snapshot dir if empty
+ $snapdir = "/etc/libvirt/qemu/snapshot/{$vm}";
+ if (is_dir($snapdir) && count(scandir($snapdir)) === 2) { // only . and ..
+ rmdir($snapdir);
+ }
}
}
return $arrResponse;
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
index 8f3c136299..50b934c94f 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
@@ -1,5 +1,5 @@
-#!/usr/bin/php
-
-
-$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
+
+$docroot ??= (($_SERVER['DOCUMENT_ROOT'] ?? '') ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Helpers.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
require_once "$docroot/plugins/dynamix.vm.manager/include/fs_helpers.php";
diff --git a/emhttp/plugins/dynamix/include/Helpers.php b/emhttp/plugins/dynamix/include/Helpers.php
index 12844a4b9c..3263806f44 100644
--- a/emhttp/plugins/dynamix/include/Helpers.php
+++ b/emhttp/plugins/dynamix/include/Helpers.php
@@ -506,6 +506,26 @@ function my_rmdir($dirname) {
return($return);
}
+/**
+ * Recursively delete all contents of a directory, but not the directory itself.
+ * Returns true on success, false on failure.
+ */
+function delete_dir_contents($dir) {
+ if (!is_dir($dir)) return false;
+ $success = true;
+ $items = scandir($dir);
+ foreach ($items as $item) {
+ if ($item === '.' || $item === '..') continue;
+ $path = "$dir/$item";
+ if (is_dir($path)) {
+ $success = delete_dir_contents($path) && rmdir($path) && $success;
+ } else {
+ $success = unlink($path) && $success;
+ }
+ }
+ return $success;
+}
+
function get_realvolume($path) {
if (strpos($path,"/mnt/user/",0) === 0)
$reallocation = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($path)." 2>/dev/null"));
From 5c105594872856291b6fae31b26f36bc4803d6c5 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Mon, 2 Feb 2026 15:22:41 +0000
Subject: [PATCH 50/64] Remove nvram for block commit.
---
.../dynamix.vm.manager/include/libvirt.php | 18 +++++++
.../include/libvirt_helpers.php | 49 +++++++++----------
2 files changed, 42 insertions(+), 25 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index 3ceba46827..be2aeddbee 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -1887,6 +1887,24 @@ function domain_delete($domain,$firstdiskonly=true) {
$vm_path = libvirt_get_vm_path($domain); // or your data collection logic here
libvirt_remove_vms_json_entry($domain);
+ // Remove NVRAM, snapshotdb, and related directories if empty
+ $nvram_dir = $vm_path . '/nvram';
+ if (is_dir($nvram_dir) && count(scandir($nvram_dir)) === 2) {
+ my_rmdir($nvram_dir);
+ }
+ $snapshotdb_dir = $vm_path . '/snapshotdb';
+ if (is_dir($snapshotdb_dir) && count(scandir($snapshotdb_dir)) === 2) {
+ my_rmdir($snapshotdb_dir);
+ }
+ $etc_nvram_dir = "/etc/libvirt/qemu/nvram/$domain";
+ if (is_dir($etc_nvram_dir) && count(scandir($etc_nvram_dir)) === 2) {
+ my_rmdir($etc_nvram_dir);
+ }
+ $etc_snapshotdb_dir = "/etc/libvirt/qemu/snapshotdb/$domain";
+ if (is_dir($etc_snapshotdb_dir) && count(scandir($etc_snapshotdb_dir)) === 2) {
+ my_rmdir($etc_snapshotdb_dir);
+ }
+
# Directories to consider removing
# NVRAM
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
index 960a38c963..e5aea4ef09 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
@@ -1935,31 +1935,31 @@ function write_snapshots_database($vm,$name,$state,$desc,$method="QEMU") {
}
$disks =$lv->get_disk_stats($vm);
- foreach($disks as $disk) {
- $file = $disk["file"];
- if ($disk['device'] == "hdc" ) $primarypath = dirname(transpose_user_path($file));
- $output = array();
- exec("qemu-img info --backing-chain -U '$file' | grep image:",$output); #PHPS
- foreach($output as $key => $line) {
- $line=str_replace("image: ","",$line);
- $output[$key] = $line;
- }
-
- $snaps[$vmsnap]['backing'][$disk["device"]] = $output;
- $rev = "r".$disk["device"];
- $reversed = array_reverse($output);
- $snaps[$vmsnap]['backing'][$rev] = $reversed;
+ foreach($disks as $disk) {
+ $file = $disk["file"];
+ if ($disk['device'] == "hdc" ) $primarypath = dirname(transpose_user_path($file));
+ $output = array();
+ exec("qemu-img info --backing-chain -U '$file' | grep image:",$output); #PHPS
+ foreach($output as $key => $line) {
+ $line=str_replace("image: ","",$line);
+ $output[$key] = $line;
}
- $snaps[$vmsnap]["primarypath"]= $primarypath;
- $parentfind = $snaps[$vmsnap]['backing'][$disk["device"]];
- $parendfileinfo = pathinfo($parentfind[1]);
- $snaps[$vmsnap]["parent"]= $parendfileinfo["extension"];
- $snaps[$vmsnap]["parent"] = str_replace("qcow2",'',$snaps[$vmsnap]["parent"]);
- if (isset($parentfind[1]) && !isset($parentfind[2])) $snaps[$vmsnap]["parent"]="Base";
- if (isset($b)) if (array_key_exists(0 , $b["disks"]["disk"])) $snaps[$vmsnap]["disks"]= $b["disks"]["disk"]; else $snaps[$vmsnap]["disks"][0]= $b["disks"]["disk"];
+ $snaps[$vmsnap]['backing'][$disk["device"]] = $output;
+ $rev = "r".$disk["device"];
+ $reversed = array_reverse($output);
+ $snaps[$vmsnap]['backing'][$rev] = $reversed;
+ }
+ $snaps[$vmsnap]["primarypath"]= $primarypath;
+ $parentfind = $snaps[$vmsnap]['backing'][$disk["device"]];
+ $parendfileinfo = pathinfo($parentfind[1]);
+ $snaps[$vmsnap]["parent"]= $parendfileinfo["extension"];
+ $snaps[$vmsnap]["parent"] = str_replace("qcow2",'',$snaps[$vmsnap]["parent"]);
+ if (isset($parentfind[1]) && !isset($parentfind[2])) $snaps[$vmsnap]["parent"]="Base";
+
+ if (isset($b)) if (array_key_exists(0 , $b["disks"]["disk"])) $snaps[$vmsnap]["disks"]= $b["disks"]["disk"]; else $snaps[$vmsnap]["disks"][0]= $b["disks"]["disk"];
- $value = json_encode($snaps,JSON_PRETTY_PRINT);
+ $value = json_encode($snaps,JSON_PRETTY_PRINT);
if (!remove_empty_snapshots_db($dbpath, $snaps)) {
file_put_contents($dbpath . "/snapshots.db", $value);
}
@@ -2574,13 +2574,13 @@ function vm_blockcommit($vm, $snap ,$path,$base,$top,$pivot,$action) {
}
# Delete NVRAM
- #if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_delete_snapshot($lv->domain_get_uuid($vm),$snap);
+ if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_delete_snapshot($lv->domain_get_uuid($vm),$snap,$vm);
if ($state == "shutoff") {
$lv->domain_destroy($res);
}
refresh_snapshots_database($vm, $action=="yes" ? true : false);
- $ret = $ret = delete_snapshots_database("$vm","$snap");;
+ $ret = delete_snapshots_database("$vm","$snap");;
if($ret)
$data = ["error" => "Unable to remove snap metadata $snap"];
else
@@ -2641,7 +2641,6 @@ function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) {
$snaps_json=json_encode($snaps,JSON_PRETTY_PRINT);
$pathinfo = pathinfo($file);
$dirpath = $pathinfo["dirname"];
- #file_put_contents("$dirpath/image.tracker",$snaps_json);
foreach($disks as $disk) {
$path = $disk['file'];
From 6569d179808b0fc22997fab48bd91a7a773c4f31 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Mon, 2 Feb 2026 16:02:42 +0000
Subject: [PATCH 51/64] purge nvram during block operations if removed by qemu
---
.../include/libvirt_helpers.php | 20 ++++++++++++++++---
1 file changed, 17 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 e5aea4ef09..e94910ee40 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
@@ -1965,7 +1965,12 @@ function write_snapshots_database($vm,$name,$state,$desc,$method="QEMU") {
}
return $noxml;
}
- function purge_deleted_snapshots(array &$snaps){
+ function purge_deleted_snapshots(array &$snaps, $vm = null) {
+ global $lv;
+ if ($vm === null) return; // Need VM name for NVRAM cleanup
+ $vmuuid = $lv->domain_get_uuid($vm);
+ $vm_path = libvirt_get_vm_path($vm);
+ $nvram_dir = libvirt_get_nvram_dir($vm_path, $vm);
foreach ($snaps as $snapname => $snap) {
$broken = false;
foreach ($snap['disks'] as $disk) {
@@ -1976,6 +1981,15 @@ function purge_deleted_snapshots(array &$snaps){
}
}
if ($broken) {
+ // Remove NVRAM snapshot files for this snapshot using correct path
+ $tpmfilename = rtrim($nvram_dir, '/') . '/' . $vmuuid . $snapname . "_VARS-pure-efi-tpm.fd";
+ $nontpmfilename = rtrim($nvram_dir, '/') . '/' . $vmuuid . $snapname . "_VARS-pure-efi.fd";
+ if (file_exists($tpmfilename)) {
+ unlink($tpmfilename);
+ }
+ if (file_exists($nontpmfilename)) {
+ unlink($nontpmfilename);
+ }
unset($snaps[$snapname]);
}
}
@@ -1999,7 +2013,7 @@ function refresh_snapshots_database($vm,$delete_used=false) {
// Only destructive operations may invalidate snapshots
if ($delete_used) {
- purge_deleted_snapshots($snaps);
+ purge_deleted_snapshots($snaps, $vm);
}
foreach($snaps as $vmsnap=>$snap) {
@@ -2027,7 +2041,7 @@ function refresh_snapshots_database($vm,$delete_used=false) {
}
$value = json_encode($snaps,JSON_PRETTY_PRINT);
$res = $lv->get_domain_by_name($vm);
- #if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_create_snapshot($lv->domain_get_uuid($vm),$name);
+ #if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_create_snapshot($lv->domain_get_uuid($vm),$snap,$vm);
#Remove any NVRAMs that are no longer valid.
# Get uuid
From 091bb18e0a84a675ac2df6c3fc5c0fc8054b7488 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Mon, 2 Feb 2026 16:53:17 +0000
Subject: [PATCH 52/64] Remove directory if empty.
This is for files outside of the normal location.
---
.../dynamix.vm.manager/include/libvirt.php | 19 ++++++-------------
1 file changed, 6 insertions(+), 13 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index be2aeddbee..9a17ff5f49 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -1905,15 +1905,6 @@ function domain_delete($domain,$firstdiskonly=true) {
my_rmdir($etc_snapshotdb_dir);
}
-
- # Directories to consider removing
- # NVRAM
- # Snapshotdb
- # /etc/libvirt/qemu/nvram//
- # /etc/libvirt/qemu/snapshotdb//
- #
-
-
if ($firstdiskonly) {
if (array_key_exists('file', $disks[0])) {
$disk = $disks[0]['file'];
@@ -1948,10 +1939,12 @@ function domain_delete($domain,$firstdiskonly=true) {
foreach ($disks as $disk) {
if (array_key_exists('file', $disk)) {
$disk_path = $disk['file'];
- if (strpos($disk_path, $vm_path) === false) {
- if (is_file($disk_path)) {
- unlink($disk_path);
- qemu_log("$domain","deleted disk $disk_path outside of VM directory");
+ if (is_file($disk_path)) {
+ unlink($disk_path);
+ qemu_log("$domain","deleted disk $disk_path outside of VM directory");
+ $disk_dir = dirname($disk_path);
+ if (is_dir($disk_dir) && count(scandir($disk_dir)) === 2) {
+ my_rmdir($disk_dir);
}
}
}
From 44d7fcf470ab08312d929601b0bd9847932ce206 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Mon, 2 Feb 2026 17:22:43 +0000
Subject: [PATCH 53/64] refactor by creating single is_vm_new_model
---
.../dynamix.vm.manager/include/libvirt.php | 4 ++--
.../include/libvirt_helpers.php | 8 ++++----
.../dynamix.vm.manager/include/libvirt_paths.php | 15 +++++++++++----
3 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index 9a17ff5f49..317464daaf 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -275,7 +275,7 @@ function config_to_xml($config, $vmclone=false) {
$osbootdev = '';
$defer_write = $domain['defer_write'] ?? false;
$vm_path = $domain['path'] ?? null;
- if (empty($vm_path) && file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
+ if (empty($vm_path) && is_vm_newmodel()) {
$storage = $template['storage'] ?? 'default';
$entry = libvirt_build_vm_entry($name, $storage, null, $uuid);
$vm_path = $entry['path'] ?? null;
@@ -1027,7 +1027,7 @@ function appendqemucmdline($xml, $cmdline) {
}
function build_vm_paths($config) {
- if (!file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
+ if (!is_vm_newmodel()) {
return $config;
}
if (empty($config['domain']['uuid'])) {
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
index e94910ee40..aea0165960 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php
@@ -1875,7 +1875,7 @@ function compare_creationtimelt($a, $b) {
function getvmsnapshots($vm) {
$snaps=array();
$dbpath = libvirt_get_snapshotdb_dir(null, $vm);
- if (!file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
+ if (!is_vm_newmodel()) {
$dbpath .= "/$vm";
}
$snaps_json = file_get_contents($dbpath."/snapshots.db");
@@ -1887,7 +1887,7 @@ function getvmsnapshots($vm) {
function write_snapshots_database($vm,$name,$state,$desc,$method="QEMU") {
global $lv;
$dbpath = libvirt_get_snapshotdb_dir(null, $vm);
- if (!file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
+ if (!is_vm_newmodel()) {
$dbpath .= "/$vm";
}
if (!is_dir($dbpath)) {
@@ -1998,7 +1998,7 @@ function purge_deleted_snapshots(array &$snaps, $vm = null) {
function refresh_snapshots_database($vm,$delete_used=false) {
global $lv;
$dbpath = libvirt_get_snapshotdb_dir(null, $vm);
- if (!file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
+ if (!is_vm_newmodel()) {
$dbpath .= "/$vm";
}
if (!is_dir($dbpath)) {
@@ -2082,7 +2082,7 @@ function remove_empty_snapshots_db($dbpath, $snaps) {
function delete_snapshots_database($vm,$name) {
global $lv;
$dbpath = libvirt_get_snapshotdb_dir(null, $vm);
- if (!file_exists('/boot/config/plugins/dynamix.vm.manager/vm_newmodel')) {
+ if (!is_vm_newmodel()) {
$dbpath .= "/$vm";
}
$snaps_json = file_get_contents($dbpath."/snapshots.db");
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_paths.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_paths.php
index c1213c0d7f..fa88324396 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_paths.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_paths.php
@@ -1,3 +1,4 @@
+
From e6bba6b2f1ee1ea06b06ed414b96326509d5061c Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Tue, 3 Feb 2026 10:28:47 +0000
Subject: [PATCH 54/64] code rabbit updates.
---
emhttp/plugins/dynamix.vm.manager/include/VMajax.php | 9 ++-------
emhttp/plugins/dynamix/include/Helpers.php | 5 ++++-
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/VMajax.php b/emhttp/plugins/dynamix.vm.manager/include/VMajax.php
index 91b74cf79f..24391c4af5 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/VMajax.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/VMajax.php
@@ -282,13 +282,8 @@ function embed(&$bootcfg, $env, $key, $value) {
case 'domain-delete':
requireLibvirt();
- if (array_key_exists('firstdisk', $_REQUEST)) {
- $val = $_REQUEST['firstdisk'];
- $falsey = [false, 0, '0', 'false', 'FALSE', 'False'];
- $firstdisk = in_array($val, $falsey, true) ? false : true;
- } else {
- $firstdisk = true;
- }
+ $firstdisk = filter_var(_var($_REQUEST,'firstdisk', 'true'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
+ if ($firstdisk === null) $firstdisk = true;
$arrResponse = $lv->domain_delete($domName, $firstdisk)
? ['success' => true]
: ['error' => $lv->get_last_error()];
diff --git a/emhttp/plugins/dynamix/include/Helpers.php b/emhttp/plugins/dynamix/include/Helpers.php
index 3263806f44..7a6240ca37 100644
--- a/emhttp/plugins/dynamix/include/Helpers.php
+++ b/emhttp/plugins/dynamix/include/Helpers.php
@@ -514,10 +514,13 @@ function delete_dir_contents($dir) {
if (!is_dir($dir)) return false;
$success = true;
$items = scandir($dir);
+ if ($items === false) return false;
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$path = "$dir/$item";
- if (is_dir($path)) {
+ if (is_link($path)) {
+ $success = unlink($path) && $success;
+ } elseif (is_dir($path)) {
$success = delete_dir_contents($path) && rmdir($path) && $success;
} else {
$success = unlink($path) && $success;
From 23dd1440b3a56a0030e7291ad052470675e870c5 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Tue, 3 Feb 2026 10:35:23 +0000
Subject: [PATCH 55/64] Migrate to new model
This will backup the image, copy xml files to the VM directory. Migrate snapshot database and NVRAM files.
---
.../plugins/dynamix.vm.manager/scripts/libvirt_init | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init b/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
index 4fa5858cee..f592cb5e52 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
@@ -103,6 +103,19 @@ if [ -s /var/log/vfio-pci-errors ]; then
/usr/local/emhttp/webGui/scripts/notify -e "VM Autostart disabled" -s "vfio-pci-errors " -d "VM Autostart disabled due to vfio-bind error" -m "Please review /var/log/vfio-pci-errors" -i "alert" -l "/VMs"
fi
+# Migrate configs to VM directories from QEMU directory
+if [ ! -f /boot/config/plugins/dynamix.vm.manager/vm_newmodel ]; then
+ # Backup Image files before migration
+ BACKUP_PATH="${IMAGE_FILE%.img}.bak-${TIMESTAMP}.img"
+ log "Creating backup of IMAGE_FILE: $BACKUP_PATH"
+ cp -p "$IMAGE_FILE" "$BACKUP_PATH"
+ log "Copying VM data to VM directories"
+ /usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
+ log "Migrating VM configs to new model"
+ /usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate --migrate --confirm > /var/log/libvirt-migrate 2>&1
+ touch /boot/config/plugins/dynamix.vm.manager/vm_newmodel
+fi
+
# Copy XML from VM Directories to QEMU directory/
/usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
#
From fbcc6db01fa8a9003d7fe94c2993b31e5833404f Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Tue, 3 Feb 2026 10:48:08 +0000
Subject: [PATCH 56/64] Update libvirt_init
---
.../plugins/dynamix.vm.manager/scripts/libvirt_init | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init b/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
index f592cb5e52..be4ffe8187 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
@@ -105,10 +105,15 @@ fi
# Migrate configs to VM directories from QEMU directory
if [ ! -f /boot/config/plugins/dynamix.vm.manager/vm_newmodel ]; then
+ TIMESTAMP="${TIMESTAMP:-$(date +%Y%m%d-%H%M%S)}"
# Backup Image files before migration
- BACKUP_PATH="${IMAGE_FILE%.img}.bak-${TIMESTAMP}.img"
- log "Creating backup of IMAGE_FILE: $BACKUP_PATH"
- cp -p "$IMAGE_FILE" "$BACKUP_PATH"
+ if [[ "$IMAGE_FILE" == *.img ]] && [ -f "$IMAGE_FILE" ]; then
+ BACKUP_PATH="${IMAGE_FILE%.img}.bak-${TIMESTAMP}.img"
+ log "Creating backup of IMAGE_FILE: $BACKUP_PATH"
+ cp -p "$IMAGE_FILE" "$BACKUP_PATH"
+ else
+ log "Skipping IMAGE_FILE backup (not an .img file): $IMAGE_FILE"
+ fi
log "Copying VM data to VM directories"
/usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
log "Migrating VM configs to new model"
From 7203a1f3055626aaa5e94cf9a827eae9cb4d988b Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Tue, 3 Feb 2026 10:52:26 +0000
Subject: [PATCH 57/64] Update libvirt.php
---
emhttp/plugins/dynamix.vm.manager/include/libvirt.php | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index 317464daaf..7dade50be8 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -1884,8 +1884,9 @@ function domain_delete($domain,$firstdiskonly=true) {
$tmp = $this->domain_undefine($domain);
if (!$tmp) return $this->_set_last_error();
// Collect VM entry data before removal
- $vm_path = libvirt_get_vm_path($domain); // or your data collection logic here
- libvirt_remove_vms_json_entry($domain);
+ $domain_name = is_resource($domain) ? $this->domain_get_name($domain) : $domain;
+ $vm_path = libvirt_get_vm_path($domain_name);
+ libvirt_remove_vms_json_entry($domain_name);
// Remove NVRAM, snapshotdb, and related directories if empty
$nvram_dir = $vm_path . '/nvram';
From 9d745ccd387f71675a39ad0f46fdbdc40980e28f Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Tue, 3 Feb 2026 11:22:12 +0000
Subject: [PATCH 58/64] Update libvirt.php
---
emhttp/plugins/dynamix.vm.manager/include/libvirt.php | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index 7dade50be8..f2d6d631db 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -1709,10 +1709,11 @@ function manage_domain_xml($domain, $xml = null, $save = true, $vm_path = null)
}
if ($save === false) {
- $xml_file = $vm_path . '/' . $domain . '.xml';
+ if (empty($vm_path)) {
+ return false;
+ }
+ $xml_file = rtrim($vm_path, '/') . '/' . $domain . '.xml';
if (is_file($xml_file)) {
- #$backup_file = $xml_file . '.prev';
- #@copy($xml_file, $backup_file);
return unlink($xml_file);
}
return true;
From 827c65a3d3965ea6ff7719d38cdbd8f656b9b4c9 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Tue, 3 Feb 2026 11:40:37 +0000
Subject: [PATCH 59/64] Update libvirt.php
---
.../plugins/dynamix.vm.manager/include/libvirt.php | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
index f2d6d631db..ef986fb892 100644
--- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
+++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php
@@ -2012,6 +2012,9 @@ function nvram_restore($uuid, $vm_name = null) {
}
function nvram_rename($uuid, $newuuid, $vm_name = null) {
+ if (empty($vm_name) && !empty($uuid)) {
+ $vm_name = $this->domain_get_name_by_uuid($uuid) ?: null;
+ }
$vm_path = $vm_name ? libvirt_get_vm_path($vm_name) : null;
$nvram_dir = libvirt_get_nvram_dir($vm_path, $vm_name);
// rename backup OVMF VARS if this domain had them
@@ -2027,6 +2030,9 @@ function nvram_rename($uuid, $newuuid, $vm_name = null) {
}
function nvram_create_snapshot($uuid, $snapshotname, $vm_name = null) {
+ if (empty($vm_name) && !empty($uuid)) {
+ $vm_name = $this->domain_get_name_by_uuid($uuid) ?: null;
+ }
$vm_path = $vm_name ? libvirt_get_vm_path($vm_name) : null;
$nvram_dir = libvirt_get_nvram_dir($vm_path, $vm_name);
// snapshot backup OVMF VARS if this domain had them
@@ -2042,6 +2048,9 @@ function nvram_create_snapshot($uuid, $snapshotname, $vm_name = null) {
}
function nvram_revert_snapshot($uuid, $snapshotname, $vm_name = null) {
+ if (empty($vm_name) && !empty($uuid)) {
+ $vm_name = $this->domain_get_name_by_uuid($uuid) ?: null;
+ }
$vm_path = $vm_name ? libvirt_get_vm_path($vm_name) : null;
$nvram_dir = libvirt_get_nvram_dir($vm_path, $vm_name);
// snapshot backup OVMF VARS if this domain had them
@@ -2059,6 +2068,9 @@ function nvram_revert_snapshot($uuid, $snapshotname, $vm_name = null) {
}
function nvram_delete_snapshot($uuid, $snapshotname, $vm_name = null) {
+ if (empty($vm_name) && !empty($uuid)) {
+ $vm_name = $this->domain_get_name_by_uuid($uuid) ?: null;
+ }
$vm_path = $vm_name ? libvirt_get_vm_path($vm_name) : null;
$nvram_dir = libvirt_get_nvram_dir($vm_path, $vm_name);
// snapshot backup OVMF VARS if this domain had them
From e0cae047bccb406ae5a066420ac83a09ad4017a9 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Tue, 3 Feb 2026 14:30:56 +0000
Subject: [PATCH 60/64] Update migration process.
---
.../dynamix.vm.manager/scripts/libvirt_init | 14 +++++++-------
etc/rc.d/rc.libvirt | 15 +++++++++++++++
2 files changed, 22 insertions(+), 7 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init b/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
index be4ffe8187..32698ceb4f 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
@@ -114,13 +114,13 @@ if [ ! -f /boot/config/plugins/dynamix.vm.manager/vm_newmodel ]; then
else
log "Skipping IMAGE_FILE backup (not an .img file): $IMAGE_FILE"
fi
- log "Copying VM data to VM directories"
- /usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
- log "Migrating VM configs to new model"
- /usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate --migrate --confirm > /var/log/libvirt-migrate 2>&1
- touch /boot/config/plugins/dynamix.vm.manager/vm_newmodel
+ log "Disable libvirt autostart during migration"
+ mkdir -p /run/libvirt/qemu
fi
-# Copy XML from VM Directories to QEMU directory/
-/usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
+# Copy XML from VM Directories to QEMU directory if new model is set.
+if [ -f /boot/config/plugins/dynamix.vm.manager/vm_newmodel ]; then
+ log "Restoring VM configs to QEMU directory"
+ /usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtrestore
+fi
#
diff --git a/etc/rc.d/rc.libvirt b/etc/rc.d/rc.libvirt
index 7227260f82..8df10850ee 100755
--- a/etc/rc.d/rc.libvirt
+++ b/etc/rc.d/rc.libvirt
@@ -254,6 +254,10 @@ libvirtd_start(){
echo 0 > /sys/module/kvm/parameters/report_ignored_msrs
libvirtd -d -l $LIBVIRTD_OPTS
log "$DAEMON... Started."
+
+ if [ -f /boot/config/plugins/dynamix.vm.manager/vm_newmodel ]; then
+ libvirt_migrate
+ fi
}
libvirtd_stop(){
@@ -339,6 +343,17 @@ libvirtd_cleanup(){
sleep 1
}
+libvirt_migrate(){
+ log "Copying VM data to VM directories"
+ /usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
+ log "Migrating VM configs to new model"
+ /usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate --migrate --confirm > /var/log/libvirt-migrate 2>&1
+ touch /boot/config/plugins/dynamix.vm.manager/vm_newmodel
+ rmdir /run/libvirt/qemu
+ libvirtd_stop
+ libvirtd_start
+}
+
case "$1" in
'test')
libvirtd_test
From 05dae8fff61db153d7d067e1dacefd95d0828a1c Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Tue, 3 Feb 2026 14:42:11 +0000
Subject: [PATCH 61/64] Update rc.libvirt
---
etc/rc.d/rc.libvirt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/etc/rc.d/rc.libvirt b/etc/rc.d/rc.libvirt
index 8df10850ee..b2f4c7e5b1 100755
--- a/etc/rc.d/rc.libvirt
+++ b/etc/rc.d/rc.libvirt
@@ -255,7 +255,7 @@ libvirtd_start(){
libvirtd -d -l $LIBVIRTD_OPTS
log "$DAEMON... Started."
- if [ -f /boot/config/plugins/dynamix.vm.manager/vm_newmodel ]; then
+ if [ ! -f /boot/config/plugins/dynamix.vm.manager/vm_newmodel ]; then
libvirt_migrate
fi
}
From b9c7a1bde9e78d198dbd7bfdca68ad201595c3ee Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Tue, 3 Feb 2026 15:58:58 +0000
Subject: [PATCH 62/64] fix snapshot lookups
---
emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
index 50b934c94f..d5d56fce76 100755
--- a/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
+++ b/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate
@@ -420,8 +420,8 @@ function validate_nvram_uuids() {
// Extract UUID and optional snapshot name from filename
// Regular: {UUID}_VARS-pure-efi.fd
- // Snapshot: {UUID}S{snapshot_name}_VARS-pure-efi.fd
- if (preg_match('/^([a-f0-9\-]+)(?:S(.+?))?_VARS/', $basename, $matches)) {
+ // Snapshot: {UUID}S{snapshot_name}_VARS-pure-efi.fd (snapshot name includes leading 'S')
+ if (preg_match('/^([a-f0-9\-]+)(S.+?)?_VARS/', $basename, $matches)) {
$uuid = $matches[1];
$snapshot_name = isset($matches[2]) ? $matches[2] : null;
@@ -434,6 +434,11 @@ function validate_nvram_uuids() {
if ($is_snapshot) {
$snapshots = $snapshot_dbs[$vm_name] ?? [];
$snapshot_valid = isset($snapshots[$snapshot_name]);
+ file_put_contents(
+ $log_file,
+ "validate_nvram_uuids: snapshot lookup vm=$vm_name name=$snapshot_name valid=" . ($snapshot_valid ? 'yes' : 'no') . "\n",
+ FILE_APPEND
+ );
}
if ($snapshot_valid) {
From 8c3026af75e32e7afcaa25e85a729ea3a5f8ced6 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Wed, 4 Feb 2026 09:57:18 +0000
Subject: [PATCH 63/64] Fix for VM autostart post migration
---
etc/rc.d/rc.libvirt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/etc/rc.d/rc.libvirt b/etc/rc.d/rc.libvirt
index b2f4c7e5b1..0420ce35f0 100755
--- a/etc/rc.d/rc.libvirt
+++ b/etc/rc.d/rc.libvirt
@@ -349,7 +349,7 @@ libvirt_migrate(){
log "Migrating VM configs to new model"
/usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate --migrate --confirm > /var/log/libvirt-migrate 2>&1
touch /boot/config/plugins/dynamix.vm.manager/vm_newmodel
- rmdir /run/libvirt/qemu
+ rm -r /run/libvirt/qemu
libvirtd_stop
libvirtd_start
}
From 3c406f77fdf51ee21730099dee80e2ef72e58a55 Mon Sep 17 00:00:00 2001
From: SimonFair <39065407+SimonFair@users.noreply.github.com>
Date: Wed, 4 Feb 2026 09:59:32 +0000
Subject: [PATCH 64/64] Update rc.libvirt
---
etc/rc.d/rc.libvirt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/etc/rc.d/rc.libvirt b/etc/rc.d/rc.libvirt
index 0420ce35f0..94f3d8284d 100755
--- a/etc/rc.d/rc.libvirt
+++ b/etc/rc.d/rc.libvirt
@@ -347,7 +347,7 @@ libvirt_migrate(){
log "Copying VM data to VM directories"
/usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtcopy
log "Migrating VM configs to new model"
- /usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate --migrate --confirm > /var/log/libvirt-migrate 2>&1
+ /usr/local/emhttp/plugins/dynamix.vm.manager/scripts/libvirtmigrate --migrate --confirm > /boot/config/plugins/dynamix.vm.manager/libvirtmigrate.log 2>&1
touch /boot/config/plugins/dynamix.vm.manager/vm_newmodel
rm -r /run/libvirt/qemu
libvirtd_stop