From 8b495f87ebf7c70b9c9f9d4c22ecde75334b3ccb Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Wed, 8 May 2024 20:41:24 +0100 Subject: [PATCH 01/64] Updates for image removal --- .../plugins/dynamix.vm.manager/VMSettings.page | 3 +-- etc/rc.d/rc.libvirt | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/emhttp/plugins/dynamix.vm.manager/VMSettings.page b/emhttp/plugins/dynamix.vm.manager/VMSettings.page index 99d2501a3c..81e36a9292 100644 --- a/emhttp/plugins/dynamix.vm.manager/VMSettings.page +++ b/emhttp/plugins/dynamix.vm.manager/VMSettings.page @@ -67,8 +67,7 @@ $libvirt_log = file_exists("/var/log/libvirt/libvirtd.log");
- - + _(Enable VMs)_: : - + + _(Enable VMs)_: : +: _(Modify with caution: unable to validate path until Array is Started)_ _(Path does not exist)_ From 12da995ddfa9649fac1150f9d2914f2b0a789c17 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Wed, 8 May 2024 21:39:44 +0100 Subject: [PATCH 03/64] Update rc.libvirt --- 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 4995fb06df..4bcbaed82e 100755 --- a/etc/rc.d/rc.libvirt +++ b/etc/rc.d/rc.libvirt @@ -316,9 +316,14 @@ libvirtd_cleanup(){ check_cfg_status{ if [ "$SERVICE" == "enable" ]; then - libvirtd_start + libvirtd_start + virtlogd_start + libvirtd_start else - libvirtd_stop + libvirtd_stop + virtlogd_stop + virtlockd_stop + libvirtd_cleanup fi } From 81dd45a490b7d955044ffedb78537a0f3722933d Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Thu, 9 May 2024 21:53:16 +0100 Subject: [PATCH 04/64] Add libvirt_update script --- emhttp/plugins/dynamix.vm.manager/VMSettings.page | 3 +-- .../plugins/dynamix.vm.manager/scripts/libvirt_update | 11 +++++++++++ etc/rc.d/rc.libvirt | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 emhttp/plugins/dynamix.vm.manager/scripts/libvirt_update diff --git a/emhttp/plugins/dynamix.vm.manager/VMSettings.page b/emhttp/plugins/dynamix.vm.manager/VMSettings.page index 1380c823c3..b7d70c7ed6 100644 --- a/emhttp/plugins/dynamix.vm.manager/VMSettings.page +++ b/emhttp/plugins/dynamix.vm.manager/VMSettings.page @@ -67,8 +67,7 @@ $libvirt_log = file_exists("/var/log/libvirt/libvirtd.log"); - - + _(Enable VMs)_: : - + + + _(Enable VMs)_: : +: _(Modify with caution: unable to validate path until Array is Started)_ _(Path does not exist)_ @@ -230,7 +233,7 @@ _(VFIO allow unsafe interrupts)_: - +
_(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)_: +: + +: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(""); + $("#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)_: -: - -: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(""); - $("#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 + +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 + + $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 @@ ?> $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 + + 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 @@ ?> $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 @@ */ ?> /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 @@ */ ?> 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 @@ + + 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 @@ */ ?> 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 - -/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