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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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); + } } ?>