From f5d204e5b273562a6eb4759d80492e816af71719 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 28 Sep 2025 09:54:41 +0100 Subject: [PATCH 01/63] Add power placeholder and Split physical CPUS --- emhttp/plugins/dynamix/DashStats.page | 34 +++++++++++++--------- emhttp/plugins/dynamix/include/Helpers.php | 17 +++++++++++ 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/emhttp/plugins/dynamix/DashStats.page b/emhttp/plugins/dynamix/DashStats.page index b959f60bf..68ca33c79 100755 --- a/emhttp/plugins/dynamix/DashStats.page +++ b/emhttp/plugins/dynamix/DashStats.page @@ -94,7 +94,7 @@ foreach ($devs as $disk) { } $array_percent = number_format(100*$array_used/($array_size ?: 1),1,$dot,''); -exec('cat /sys/devices/system/cpu/*/topology/thread_siblings_list|sort -nu', $cpus); +$cpus=get_cpu_packages(); $wg_up = $wireguard ? exec("wg show interfaces") : ''; $wg_up = $wg_up ? explode(' ',$wg_up) : []; $up = count($wg_up); @@ -345,6 +345,9 @@ switch ($themeHelper->getThemeName()) { // $themeHelper set in DefaultPageLayout + + _(Total Power)_: N/A + @@ -395,20 +398,23 @@ switch ($themeHelper->getThemeName()) { // $themeHelper set in DefaultPageLayout "; - if ($is_intel_cpu && count($core_types) > 0) - $core_type = "({$core_types[$cpu1]})"; - else - $core_type = ""; + foreach ($cpus as $cpu_index=>$package) { + echo ""._("Physical")." CPU $cpu_index "._("Power").": N/A"; + foreach ($package as $pair) { + [$cpu1, $cpu2] = my_preg_split('/[,-]/',$pair); + echo ""; + if ($is_intel_cpu && count($core_types) > 0) + $core_type = "({$core_types[$cpu1]})"; + else + $core_type = ""; - if ($cpu2) - echo "CPU $cpu1 $core_type - HT $cpu2 0%
0%
"; - else - echo "CPU $cpu1 $core_type0%
"; - echo ""; - } + if ($cpu2) + echo "CPU $cpu1 $core_type - HT $cpu2 0%
0%
"; + else + echo "CPU $cpu1 $core_type0%
"; + echo ""; + } + } ?> diff --git a/emhttp/plugins/dynamix/include/Helpers.php b/emhttp/plugins/dynamix/include/Helpers.php index 5c3dfacc5..75b152448 100644 --- a/emhttp/plugins/dynamix/include/Helpers.php +++ b/emhttp/plugins/dynamix/include/Helpers.php @@ -857,4 +857,21 @@ HTML; return $html; } + +function get_cpu_packages(string $separator = ','): array { + $packages = []; + foreach (glob("/sys/devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list") as $path) { + $pkg_id = (int)file_get_contents(dirname($path) . "/physical_package_id"); + $siblings = str_replace(",", $separator, trim(file_get_contents($path))); + if (!in_array($siblings, $packages[$pkg_id] ?? [])) { + $packages[$pkg_id][] = $siblings; + } + } + foreach ($packages as &$list) { + $keys = array_map(fn($s) => (int)explode($separator, $s)[0], $list); + array_multisort($keys, SORT_ASC, SORT_NUMERIC, $list); + } + unset($list); + return $packages; +} ?> From e64211b8e5d083f99c0dbfae6ec8319b0c752e91 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 28 Sep 2025 10:11:26 +0100 Subject: [PATCH 02/63] Add icon to total power. --- emhttp/plugins/dynamix/DashStats.page | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix/DashStats.page b/emhttp/plugins/dynamix/DashStats.page index 68ca33c79..f03d557a9 100755 --- a/emhttp/plugins/dynamix/DashStats.page +++ b/emhttp/plugins/dynamix/DashStats.page @@ -346,7 +346,7 @@ switch ($themeHelper->getThemeName()) { // $themeHelper set in DefaultPageLayout - _(Total Power)_: N/A + _(Total Power)_: N/A From e4ed5896f6e5fd3c7022f47c3ca0f91b1a6c0f27 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 28 Sep 2025 10:17:16 +0100 Subject: [PATCH 03/63] Fix placeholder index --- emhttp/plugins/dynamix/DashStats.page | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix/DashStats.page b/emhttp/plugins/dynamix/DashStats.page index f03d557a9..42ccf33a8 100755 --- a/emhttp/plugins/dynamix/DashStats.page +++ b/emhttp/plugins/dynamix/DashStats.page @@ -399,7 +399,7 @@ switch ($themeHelper->getThemeName()) { // $themeHelper set in DefaultPageLayout $package) { - echo ""._("Physical")." CPU $cpu_index "._("Power").": N/A"; + echo ""._("Physical")." CPU $cpu_index "._("Power").": N/A"; foreach ($package as $pair) { [$cpu1, $cpu2] = my_preg_split('/[,-]/',$pair); echo ""; From b54bb1a16472b415ff49655d0cb6c9e151fe058f Mon Sep 17 00:00:00 2001 From: Squidly271 Date: Thu, 16 Oct 2025 10:23:31 -0400 Subject: [PATCH 04/63] Refactor fixed listing height processing --- .../dynamix.vm.manager/VMMachines.page | 59 +++++++++++-------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/emhttp/plugins/dynamix.vm.manager/VMMachines.page b/emhttp/plugins/dynamix.vm.manager/VMMachines.page index b6da578d3..f39f20da4 100755 --- a/emhttp/plugins/dynamix.vm.manager/VMMachines.page +++ b/emhttp/plugins/dynamix.vm.manager/VMMachines.page @@ -3,6 +3,7 @@ Title="Virtual Machines" Tag="columns" Cond="is_file('/var/run/libvirt/libvirtd.pid')" Markdown="false" +Focus="tableHeaderResize" --- - fillAvailableHeight({ - targetElementSelector: '.js-fill-available-height', - elementSelectorsForHeight: [ - '.js-actions', - '#kvm_table thead', - ], - elementSelectorsForSpacing: [ - '#kvm_table', - ], - manualSpacingOffset: 50, // without this, the main content will still be scrollable by like 20px - }); - // Handle table header fixed positioning after resize - function tableHeaderResize() { - $('#kvm_table thead,#kvm_table tbody').removeClass('fixed'); - $('#kvm_table thead tr th').each(function(){$(this).width($(this).width());}); - $('#kvm_table tbody tr td').each(function(){$(this).width($(this).width());}); - $('#kvm_table thead,#kvm_table tbody').not('.child').addClass('fixed'); - } tableHeaderResize(); + + // Handle table header resizing when window is resized with debouncing + var resizeTimeout; $(window).bind('resize',function(){ - tableHeaderResize(); - }); - // Handle table header resizing when tab is clicked - 2):?> - $("#tab1").on('click',function(){ + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(function(){ tableHeaderResize(); - }); - - + }, 150); + }); + }); } + +// Handle table header fixed positioning after resize +// Only applicable to listing height: fixed +// Function needs to exist in the global scope due to "Focus" attribute in the page +function tableHeaderResize() { + + fillAvailableHeight({ + targetElementSelector: '.js-fill-available-height', + elementSelectorsForHeight: [ + '.js-actions', + '#kvm_table thead', + ], + elementSelectorsForSpacing: [ + '#kvm_table', + ], + manualSpacingOffset: 50, // without this, the main content will still be scrollable by like 20px + }); + $('#kvm_table thead,#kvm_table tbody').removeClass('fixed'); + $('#kvm_table thead tr th').each(function(){$(this).width($(this).width());}); + $('#kvm_table tbody tr td').each(function(){$(this).width($(this).width());}); + $('#kvm_table thead,#kvm_table tbody').not('.child').addClass('fixed'); + +} + $(function() { From 7e88002238d97e2d92d2f57686e8af117a62c7d7 Mon Sep 17 00:00:00 2001 From: Squidly271 Date: Thu, 16 Oct 2025 11:07:43 -0400 Subject: [PATCH 05/63] Fix respawning of events --- emhttp/plugins/dynamix.vm.manager/VMMachines.page | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix.vm.manager/VMMachines.page b/emhttp/plugins/dynamix.vm.manager/VMMachines.page index f39f20da4..69e450aa7 100755 --- a/emhttp/plugins/dynamix.vm.manager/VMMachines.page +++ b/emhttp/plugins/dynamix.vm.manager/VMMachines.page @@ -467,7 +467,7 @@ function loadlist() { // Handle table header resizing when window is resized with debouncing var resizeTimeout; - $(window).bind('resize',function(){ + $(window).off('resize.vmTableResize').on('resize.vmTableResize',function(){ clearTimeout(resizeTimeout); resizeTimeout = setTimeout(function(){ tableHeaderResize(); From 2f66cd3d021f225767ed0bc62520b01a7f1b6ee8 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Tue, 21 Oct 2025 20:30:49 +0100 Subject: [PATCH 06/63] Changes for power and temp. --- emhttp/plugins/dynamix/DashStats.page | 96 ++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 3 deletions(-) diff --git a/emhttp/plugins/dynamix/DashStats.page b/emhttp/plugins/dynamix/DashStats.page index 42ccf33a8..09b63a7d2 100755 --- a/emhttp/plugins/dynamix/DashStats.page +++ b/emhttp/plugins/dynamix/DashStats.page @@ -346,11 +346,13 @@ switch ($themeHelper->getThemeName()) { // $themeHelper set in DefaultPageLayout - _(Total Power)_: N/A + _(Total)_ _(Power)_: N/A + - + _(Temp)_: + _(Load)_: 0% @@ -399,7 +401,11 @@ switch ($themeHelper->getThemeName()) { // $themeHelper set in DefaultPageLayout $package) { - echo ""._("Physical")." CPU $cpu_index "._("Power").": N/A"; + if (count($cpus) > 1) { + echo ""._("Physical")." CPU $cpu_index "._("Power").": N/A "; + if (count($cpus)>1) echo _("Temp").": N/A"; + echo ""; + } foreach ($package as $pair) { [$cpu1, $cpu2] = my_preg_split('/[,-]/',$pair); echo ""; @@ -1696,6 +1702,36 @@ function addChartNet(rx, tx) { txTimeSeries.append(now, Math.floor(tx / 1000)); } +function updateCPUPower() { + if (!cpupower) return; + + // Update total power + const totalEl = document.getElementById('cpu-total-power'); + const totalPower = cpupower.totalPower ?? 0; + if (totalEl) { + totalEl.innerHTML = ` _(Total)_ _(Power)_: ${totalPower.toFixed(2)} W`; + } + + // Update each core's span + const cpuspower = cpupower.power ?? []; + cpuspower.forEach((power, index) => { + const coreEl = document.getElementById(`cpu-power${index}`); + const coreTempEl = document.getElementById(`cpu-temp${index}`); + if (coreEl) { + coreEl.innerHTML = `${power.toFixed(2)} W`; + } + }); + + const cpustemps = cpupower.temp ?? []; + cpustemps.forEach((temp, index) => { + const coreTempEl = document.getElementById(`cpu-temp${index}`); + if (coreTempEl) { + coreTempEl.innerHTML = `${temp.toFixed(0)} C`;; + } + }); + +} + // Cache for last values to avoid unnecessary DOM updates var lastCpuValues = { load: -1, @@ -2771,6 +2807,60 @@ $(function() { setTimeout(function() { // Charts initialized },500); + + + + // Start GraphQL CPU power subscription with retry logic + let cpuInitPWRAttempts = 0, cpuPWRRetryMs = 100; + function initPwrCpuSubscription() { + + + if (window.gql && window.apolloClient) { + // Define the subscription query when GraphQL is available + // corepower has the temps currently. + CPU_POWER_SUBSCRIPTION = window.gql(` + subscription SystemMetricsCpuTelemetry { + systemMetricsCpuTelemetry { + totalPower, + power, + temp, + } + } + `); + cpuPowerSubscription = window.apolloClient.subscribe({ + query: CPU_POWER_SUBSCRIPTION + }).subscribe({ + next: (result) => { + + + if (result.data?.systemMetricsCpuTelemetry){ + cpupower = result.data.systemMetricsCpuTelemetry; + + updateCPUPower(); + } + }, + error: (err) => { + console.error('CPU power subscription error:', err); + // Try to resubscribe with capped backoff + if (cpuPowerSubscription) { try { cpuPowerSubscription.unsubscribe(); } catch(e){} } + setTimeout(initPwrCpuSubscription, Math.min(cpuPWRRetryMs *= 2, 5000)); + } + }); + } else { + // Retry with capped backoff if GraphQL client not ready + cpuPWRInitAttempts++; + setTimeout(initPwrCpuSubscription, Math.min(cpuPWRRetryMs *= 2, 2000)); + } + } + initPwrCpuSubscription(); + // Cleanup GraphQL subscription on page unload + $(window).on('beforeunload', function() { + if (cpuPowerSubscription) { + cpuPowerSubscription.unsubscribe(); + } + }); + + // Cleanup GraphQL subscription on page unload $(window).on('beforeunload', function() { From e799c9144d925a3a0c139fec038f12f93a7f3185 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Wed, 22 Oct 2025 21:53:07 +0100 Subject: [PATCH 07/63] Layout change and fixes --- emhttp/plugins/dynamix/DashStats.page | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/emhttp/plugins/dynamix/DashStats.page b/emhttp/plugins/dynamix/DashStats.page index c4b08fbde..c2863c20e 100755 --- a/emhttp/plugins/dynamix/DashStats.page +++ b/emhttp/plugins/dynamix/DashStats.page @@ -95,6 +95,7 @@ foreach ($devs as $disk) { $array_percent = number_format(100*$array_used/($array_size ?: 1),1,$dot,''); $cpus=get_cpu_packages(); +$cpus[] = ["16,17"]; $wg_up = $wireguard ? exec("wg show interfaces") : ''; $wg_up = $wg_up ? explode(' ',$wg_up) : []; $up = count($wg_up); @@ -351,11 +352,11 @@ switch ($themeHelper->getThemeName()) { // $themeHelper set in DefaultPageLayout - _(Total)_ _(Power)_: N/A + _(Total)_ _(Power)_: N/A - _(Temp)_: + _(Temperature)_: N/A @@ -403,12 +404,12 @@ switch ($themeHelper->getThemeName()) { // $themeHelper set in DefaultPageLayout - + $package) { if (count($cpus) > 1) { - echo ""._("Physical")." CPU $cpu_index "._("Power").": N/A "; - if (count($cpus)>1) echo _("Temp").": N/A"; + echo " "._("Physical")." CPU $cpu_index "._("Power").": N/A "; + if (count($cpus)>1) echo " "._("Temperature").": N/A"; echo ""; } foreach ($package as $pair) { @@ -1453,6 +1454,8 @@ var startup = true; var stopgap = ''; var recall = null; var recover = null; +var tempunit=""; + // Helper function to calculate millisPerPixel based on container width function getMillisPerPixel(timeInSeconds, containerId) { @@ -1721,7 +1724,6 @@ function updateCPUPower() { const cpuspower = cpupower.power ?? []; cpuspower.forEach((power, index) => { const coreEl = document.getElementById(`cpu-power${index}`); - const coreTempEl = document.getElementById(`cpu-temp${index}`); if (coreEl) { coreEl.innerHTML = `${power.toFixed(2)} W`; } @@ -1731,7 +1733,11 @@ function updateCPUPower() { cpustemps.forEach((temp, index) => { const coreTempEl = document.getElementById(`cpu-temp${index}`); if (coreTempEl) { - coreTempEl.innerHTML = `${temp.toFixed(0)} C`;; + tempdisplay = temp.toFixed(0); + if (tempunit === "F") { + tempdisplay = ((temp.toFixed(0))* 9 / 5) + 32; + } + coreTempEl.innerHTML = Math.round(tempdisplay)+` °`+tempunit;; } }); @@ -2853,7 +2859,7 @@ $(function() { }); } else { // Retry with capped backoff if GraphQL client not ready - cpuPWRInitAttempts++; + cpuPInitPWRAttempts++; setTimeout(initPwrCpuSubscription, Math.min(cpuPWRRetryMs *= 2, 2000)); } } From 5083f85d8a298ba71f1cf4ca9a5a6aef70f9102c Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Wed, 22 Oct 2025 22:00:41 +0100 Subject: [PATCH 08/63] Remove test code --- emhttp/plugins/dynamix/DashStats.page | 2 -- emhttp/plugins/dynamix/include/cpulist.php | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 emhttp/plugins/dynamix/include/cpulist.php diff --git a/emhttp/plugins/dynamix/DashStats.page b/emhttp/plugins/dynamix/DashStats.page index c2863c20e..1259ff11b 100755 --- a/emhttp/plugins/dynamix/DashStats.page +++ b/emhttp/plugins/dynamix/DashStats.page @@ -95,7 +95,6 @@ foreach ($devs as $disk) { $array_percent = number_format(100*$array_used/($array_size ?: 1),1,$dot,''); $cpus=get_cpu_packages(); -$cpus[] = ["16,17"]; $wg_up = $wireguard ? exec("wg show interfaces") : ''; $wg_up = $wg_up ? explode(' ',$wg_up) : []; $up = count($wg_up); @@ -1456,7 +1455,6 @@ var recall = null; var recover = null; var tempunit=""; - // Helper function to calculate millisPerPixel based on container width function getMillisPerPixel(timeInSeconds, containerId) { var container = document.getElementById(containerId); diff --git a/emhttp/plugins/dynamix/include/cpulist.php b/emhttp/plugins/dynamix/include/cpulist.php new file mode 100644 index 000000000..8d1db3573 --- /dev/null +++ b/emhttp/plugins/dynamix/include/cpulist.php @@ -0,0 +1,22 @@ + (int)explode($separator, $s)[0], $list); + array_multisort($keys, SORT_ASC, SORT_NUMERIC, $list); + } + unset($list); + + return $packages; +} From 117a49491a2595f3f18267461baf18d9357df8b7 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:09:13 +0100 Subject: [PATCH 09/63] Update DashStats.page --- emhttp/plugins/dynamix/DashStats.page | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix/DashStats.page b/emhttp/plugins/dynamix/DashStats.page index 1259ff11b..60fbc8f96 100755 --- a/emhttp/plugins/dynamix/DashStats.page +++ b/emhttp/plugins/dynamix/DashStats.page @@ -2857,7 +2857,7 @@ $(function() { }); } else { // Retry with capped backoff if GraphQL client not ready - cpuPInitPWRAttempts++; + cpuInitPWRAttempts++; setTimeout(initPwrCpuSubscription, Math.min(cpuPWRRetryMs *= 2, 2000)); } } From 1ad896a5bed4433ccf238bb929d1b50a60ee330a Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:29:45 +0100 Subject: [PATCH 10/63] Fix: Disk creation error for VM change --- emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php index bde11c7c7..c9f721932 100644 --- a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php +++ b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php @@ -255,7 +255,7 @@ if (isset($_POST['updatevm'])) { $xml = str_replace($olduuid,$newuuid,$xml); } else { // form view - if ($error = create_vdisk($_POST) === false) { + if (($error = create_vdisk($_POST)) === false) { $arrExistingConfig = custom::createArray('domain',$strXML); $arrUpdatedConfig = custom::createArray('domain',$lv->config_to_xml($_POST)); if ($debug) { From c3d88a7dd80d746c66e97e347c493ce4221da863 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 26 Oct 2025 17:10:27 +0000 Subject: [PATCH 11/63] Bug: Cdrom bus IDE for Q35 is invalid set to SATA --- .../plugins/dynamix.vm.manager/templates/Custom.form.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php index bde11c7c7..6e08339a4 100644 --- a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php +++ b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php @@ -2484,6 +2484,13 @@ $(function() { }); + $("#vmform #domain_machine").change(function changeMachineEvent(){ + // Cdrom Bus: select IDE for i440 and SATA for q35 + if ($(this).val().indexOf('q35') != -1) { + $('#vmform .cdrom_bus').val('sata'); + } + }); + $("#vmform .domain_vcpu").change(function changeVCPUEvent(){ var $cores = $("#vmform .domain_vcpu:checked"); if ($cores.length < 1) { From 5fb682c2253147c7c17b4e3b6d5190b938e92cec Mon Sep 17 00:00:00 2001 From: Tom Mortensen Date: Tue, 28 Oct 2025 00:16:51 -0700 Subject: [PATCH 12/63] Fix: ZFS allocation profile always shows 1 vdev only --- emhttp/plugins/dynamix/DeviceInfo.page | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix/DeviceInfo.page b/emhttp/plugins/dynamix/DeviceInfo.page index 88df03ea2..9cb8bc83a 100755 --- a/emhttp/plugins/dynamix/DeviceInfo.page +++ b/emhttp/plugins/dynamix/DeviceInfo.page @@ -346,7 +346,7 @@ function selectDiskFsWidthZFS(slots,init) { })); } } - selected_width = slots; + if (selected_width == 0) selected_width = width; } $('#diskFsWidth').val(selected_width); } From a593173c7d67245df65083c0536740b0123080ef Mon Sep 17 00:00:00 2001 From: Tom Mortensen Date: Tue, 28 Oct 2025 00:18:59 -0700 Subject: [PATCH 13/63] fix: Docker NAT failure due to missing br_netfilter --- etc/rc.d/rc.docker | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etc/rc.d/rc.docker b/etc/rc.d/rc.docker index d5ba3bf29..90cec4c68 100755 --- a/etc/rc.d/rc.docker +++ b/etc/rc.d/rc.docker @@ -243,6 +243,8 @@ container_add_route(){ docker_network_start(){ log "Starting network..." + # ensure br_netfilter modules is loaded, re: https://github.com/moby/moby/issues/48948 + modprobe br_netfilter # create list of possible custom networks EXCLUDE=; INCLUDE=$(ls --indicator-style=none $SYSTEM | awk '/^br[0-9]+/' ORS=' ') while IFS=$'\n' read -r NETWORK; do From a7885eca09135d3e59d490dcdfe58d5c8543c21b Mon Sep 17 00:00:00 2001 From: Tom Mortensen Date: Wed, 29 Oct 2025 01:35:23 -0700 Subject: [PATCH 14/63] Fix: ZFS allocation profile always shows 1 vdev only --- emhttp/plugins/dynamix/DeviceInfo.page | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix/DeviceInfo.page b/emhttp/plugins/dynamix/DeviceInfo.page index 9cb8bc83a..35c2def45 100755 --- a/emhttp/plugins/dynamix/DeviceInfo.page +++ b/emhttp/plugins/dynamix/DeviceInfo.page @@ -344,9 +344,9 @@ function selectDiskFsWidthZFS(slots,init) { value: width, text: _(sprintf('%s '+label+' of %s devices',groups,width)), })); + if (selected_width == 0) selected_width = width; } } - if (selected_width == 0) selected_width = width; } $('#diskFsWidth').val(selected_width); } From 1018244c1f0d525b3a9229b7ea597d315cc77120 Mon Sep 17 00:00:00 2001 From: Tom Mortensen Date: Wed, 29 Oct 2025 01:35:59 -0700 Subject: [PATCH 15/63] fix: mover run not logged when schedulled --- sbin/mover | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sbin/mover b/sbin/mover index 62a2afb2c..7ddffb89e 100755 --- a/sbin/mover +++ b/sbin/mover @@ -36,13 +36,13 @@ move() { start() { if [ -f $PIDFILE ]; then if ps h $(cat $PIDFILE) | grep mover ; then - echo "mover: already running" + echo "mover: already running" >&2 exit 1 fi fi echo $$ >/var/run/mover.pid - echo "mover: started" + echo "mover: started" >&2 shopt -s nullglob @@ -108,7 +108,7 @@ start() { done rm -f $PIDFILE - echo "mover: finished" + echo "mover: finished" >&2 } empty() { @@ -116,13 +116,13 @@ empty() { if [ -f $PIDFILE ]; then if ps h $(cat $PIDFILE) | grep mover ; then - echo "mover: already running" + echo "mover: already running" >&2 exit 1 fi fi echo $$ >/var/run/mover.pid - echo "mover: started" + echo "mover: started" >&2 shopt -s nullglob @@ -145,7 +145,7 @@ empty() { fi rm -f $PIDFILE - echo "mover: finished" + echo "mover: finished" >&2 } killtree() { From 160575b8a8815c8614e46e6f242ea050d02d0c6d Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Fri, 31 Oct 2025 08:32:51 +0000 Subject: [PATCH 16/63] Fix for systems not reporting correctly in DMI. --- emhttp/plugins/dynamix/include/Helpers.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/emhttp/plugins/dynamix/include/Helpers.php b/emhttp/plugins/dynamix/include/Helpers.php index 61a84cab9..e07536cbc 100644 --- a/emhttp/plugins/dynamix/include/Helpers.php +++ b/emhttp/plugins/dynamix/include/Helpers.php @@ -565,8 +565,7 @@ function dmidecode($key, $n, $all=true) { } function is_intel_cpu() { - $cpu = dmidecode('Processor Information','4',0); - $cpu_vendor = $cpu['Manufacturer'] ?? ""; + $cpu_vendor = exec("grep -Pom1 '^model name\s+:\s*\K.+' /proc/cpuinfo") ?? ""; $is_intel_cpu = stripos($cpu_vendor, "intel") !== false ? true : false; return $is_intel_cpu; } From 80173a4f87dcd99208b8ca389e888fb2b0058b20 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Fri, 31 Oct 2025 12:01:22 +0000 Subject: [PATCH 17/63] Code rabbit suggestions --- emhttp/plugins/dynamix/include/Helpers.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/emhttp/plugins/dynamix/include/Helpers.php b/emhttp/plugins/dynamix/include/Helpers.php index e07536cbc..e0113c730 100644 --- a/emhttp/plugins/dynamix/include/Helpers.php +++ b/emhttp/plugins/dynamix/include/Helpers.php @@ -565,9 +565,8 @@ function dmidecode($key, $n, $all=true) { } function is_intel_cpu() { - $cpu_vendor = exec("grep -Pom1 '^model name\s+:\s*\K.+' /proc/cpuinfo") ?? ""; - $is_intel_cpu = stripos($cpu_vendor, "intel") !== false ? true : false; - return $is_intel_cpu; + $cpu_vendor_check = exec("grep -Pom1 '^model name\s+:\s*\K.+' /proc/cpuinfo") ?? ""; + return stripos($cpu_vendor_check, "intel") !== false; } // Load saved PCI data From 6a90e9d10c8a507bb9e1f7db313dae5b51ad08b1 Mon Sep 17 00:00:00 2001 From: Tom Mortensen Date: Tue, 4 Nov 2025 15:58:24 -0800 Subject: [PATCH 18/63] fix: DeviceInfo: should not show 'Pool Device Status' section if pool not Mounted --- emhttp/plugins/dynamix/DeviceInfo.page | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix/DeviceInfo.page b/emhttp/plugins/dynamix/DeviceInfo.page index 35c2def45..e07dd554b 100755 --- a/emhttp/plugins/dynamix/DeviceInfo.page +++ b/emhttp/plugins/dynamix/DeviceInfo.page @@ -990,7 +990,7 @@ _(Critical disk utilization threshold)_ (%): - +
_(Pool Device Status)_ From 8e9b892145fed857cab70c272a07a66530d88b7b Mon Sep 17 00:00:00 2001 From: Tom Mortensen Date: Tue, 4 Nov 2025 15:58:53 -0800 Subject: [PATCH 19/63] fix: rc.S.cont: restore /etc/machine-id from /boot/config/machine-id file --- etc/rc.d/rc.S.cont | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/etc/rc.d/rc.S.cont b/etc/rc.d/rc.S.cont index f5a4f954f..2e00799cb 100755 --- a/etc/rc.d/rc.S.cont +++ b/etc/rc.d/rc.S.cont @@ -12,6 +12,14 @@ # run & log functions . /etc/rc.d/rc.runlog +# LimeTech - restore machine-id +SRC="/boot/config/machine-id" +DEST="/etc/machine-id" +if [[ ! -f "$SRC" ]] || ! grep -Eq '^[0-9a-f]{32}$' "$SRC" ; then + /usr/bin/dbus-uuidgen --ensure="$SRC" +fi +/usr/bin/install -m 0644 "$SRC" "$DEST" + # LimeTech - bind selected devices to vfio-pci /usr/local/sbin/vfio-pci 1>/var/log/vfio-pci 2>/var/log/vfio-pci-errors From 5e91b061229fdff739fe58302345a5e719efa02e Mon Sep 17 00:00:00 2001 From: Squidly271 Date: Tue, 4 Nov 2025 22:20:36 -0500 Subject: [PATCH 20/63] Feat: Better identify safemode --- emhttp/plugins/dynamix/ArrayOperation.page | 29 +++++++++++++++++-- .../plugins/dynamix/styles/default-base.css | 4 +++ 2 files changed, 31 insertions(+), 2 deletions(-) mode change 100644 => 100755 emhttp/plugins/dynamix/ArrayOperation.page diff --git a/emhttp/plugins/dynamix/ArrayOperation.page b/emhttp/plugins/dynamix/ArrayOperation.page old mode 100644 new mode 100755 index 74381e295..9434adc99 --- a/emhttp/plugins/dynamix/ArrayOperation.page +++ b/emhttp/plugins/dynamix/ArrayOperation.page @@ -348,8 +348,33 @@ function shutdown_now(form, cmd) { $(form).append(''); switch (cmd) { - case 'reboot': var text = "_(This will reboot the system)_"; break; - case 'shutdown': var text = "_(This will shutdown the system)_"; break; + case 'reboot': + if ($('input[name="safemode"]').prop('checked')) { + $value) { + if (stripos($key, 'tailscale') !== false && $value == $_SERVER['HTTP_HOST']) { + $tailscaleUI = true; + break; + } + } + ?> + var text = "_(This will reboot the system in safe mode)_\n\n"; + var text = text.concat("_(No plugins will be loaded in safe mode.)_"); + + var text = text.concat(" _(If you use Tailscale to access the GUI or services of the server, this access will be lost.)_"); + + var text = text.concat("\n\n_(You are currently accessing the GUI of the server via Tailscale.)_"); + + + } else { + var text = "_(This will reboot the system)_"; + } + break; + case 'shutdown': + var text = "_(This will shutdown the system)_"; + break; } swal({ title:"_(Proceed)_?", diff --git a/emhttp/plugins/dynamix/styles/default-base.css b/emhttp/plugins/dynamix/styles/default-base.css index 164202fdc..19d6a6a7e 100755 --- a/emhttp/plugins/dynamix/styles/default-base.css +++ b/emhttp/plugins/dynamix/styles/default-base.css @@ -1902,6 +1902,10 @@ span#wlan0 { position: relative; } +.font-bold { + font-weight: bold; +} + .font-mono { font-family: bitstream, monospace; } From 8f64bd76837f4bd7b29bf0c5e538995857e4fb12 Mon Sep 17 00:00:00 2001 From: Squidly271 Date: Wed, 5 Nov 2025 00:55:55 -0500 Subject: [PATCH 21/63] Check for access via connect remote --- emhttp/plugins/dynamix/ArrayOperation.page | 23 ++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/emhttp/plugins/dynamix/ArrayOperation.page b/emhttp/plugins/dynamix/ArrayOperation.page index 9434adc99..7abc578ce 100755 --- a/emhttp/plugins/dynamix/ArrayOperation.page +++ b/emhttp/plugins/dynamix/ArrayOperation.page @@ -353,27 +353,34 @@ function shutdown_now(form, cmd) { $value) { - if (stripos($key, 'tailscale') !== false && $value == $_SERVER['HTTP_HOST']) { + if (strpos($key, 'NGINX_TAILSCALE') !== false && $value == $_SERVER['HTTP_HOST']) { $tailscaleUI = true; break; } + if (strpos($key, 'NGINX_WANFQDN') !== false && $value == $_SERVER['HTTP_HOST']) { + $connectRemote = true; + break; + } } ?> - var text = "_(This will reboot the system in safe mode)_\n\n"; - var text = text.concat("_(No plugins will be loaded in safe mode.)_"); - - var text = text.concat(" _(If you use Tailscale to access the GUI or services of the server, this access will be lost.)_"); + var text = "_(This will reboot the server in safe mode.)_\n\n" + "_(No plugins will be loaded in safe mode.)_"; + + text += " _(If you use Tailscale or Unraid Connect Remote Access to access the webGUI or services of the server, this access will be lost.)_"; - var text = text.concat("\n\n_(You are currently accessing the GUI of the server via Tailscale.)_"); + text += "\n\n_(You are currently accessing the webGUI of the server via Tailscale.)_"; + + + text += "\n\n_(You are currently accessing the webGUI of the server via Unraid Connect Remote Access.)_"; } else { - var text = "_(This will reboot the system)_"; + var text = "_(This will reboot the server.)_"; } break; case 'shutdown': - var text = "_(This will shutdown the system)_"; + var text = "_(This will shutdown the server.)_"; break; } swal({ From c66eb75876a1a8584ec890275abd327e17397159 Mon Sep 17 00:00:00 2001 From: Squidly271 Date: Wed, 5 Nov 2025 01:09:11 -0500 Subject: [PATCH 22/63] Remove duplicated class --- emhttp/plugins/dynamix/ArrayOperation.page | 4 ++-- emhttp/plugins/dynamix/styles/default-base.css | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/emhttp/plugins/dynamix/ArrayOperation.page b/emhttp/plugins/dynamix/ArrayOperation.page index 7abc578ce..672f6edf2 100755 --- a/emhttp/plugins/dynamix/ArrayOperation.page +++ b/emhttp/plugins/dynamix/ArrayOperation.page @@ -369,10 +369,10 @@ function shutdown_now(form, cmd) { text += " _(If you use Tailscale or Unraid Connect Remote Access to access the webGUI or services of the server, this access will be lost.)_"; - text += "\n\n_(You are currently accessing the webGUI of the server via Tailscale.)_"; + text += "\n\n_(You are currently accessing the webGUI of the server via Tailscale.)_"; - text += "\n\n_(You are currently accessing the webGUI of the server via Unraid Connect Remote Access.)_"; + text += "\n\n_(You are currently accessing the webGUI of the server via Unraid Connect Remote Access.)_"; } else { diff --git a/emhttp/plugins/dynamix/styles/default-base.css b/emhttp/plugins/dynamix/styles/default-base.css index 19d6a6a7e..164202fdc 100755 --- a/emhttp/plugins/dynamix/styles/default-base.css +++ b/emhttp/plugins/dynamix/styles/default-base.css @@ -1902,10 +1902,6 @@ span#wlan0 { position: relative; } -.font-bold { - font-weight: bold; -} - .font-mono { font-family: bitstream, monospace; } From f988d26b419d1354f6e05e4b47597cc086713fb9 Mon Sep 17 00:00:00 2001 From: Squidly271 Date: Wed, 5 Nov 2025 01:12:54 -0500 Subject: [PATCH 23/63] Update emhttp/plugins/dynamix/ArrayOperation.page Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- emhttp/plugins/dynamix/ArrayOperation.page | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix/ArrayOperation.page b/emhttp/plugins/dynamix/ArrayOperation.page index 672f6edf2..2dd90660a 100755 --- a/emhttp/plugins/dynamix/ArrayOperation.page +++ b/emhttp/plugins/dynamix/ArrayOperation.page @@ -366,7 +366,7 @@ function shutdown_now(form, cmd) { } ?> var text = "_(This will reboot the server in safe mode.)_\n\n" + "_(No plugins will be loaded in safe mode.)_"; - + text += " _(If you use Tailscale or Unraid Connect Remote Access to access the webGUI or services of the server, this access will be lost.)_"; text += "\n\n_(You are currently accessing the webGUI of the server via Tailscale.)_"; From 6e3c618cfe67ed23b56914ffc2a21e8f0e21380d Mon Sep 17 00:00:00 2001 From: Squidly271 Date: Wed, 5 Nov 2025 01:14:23 -0500 Subject: [PATCH 24/63] Update emhttp/plugins/dynamix/ArrayOperation.page Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- emhttp/plugins/dynamix/ArrayOperation.page | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix/ArrayOperation.page b/emhttp/plugins/dynamix/ArrayOperation.page index 2dd90660a..4f452b11f 100755 --- a/emhttp/plugins/dynamix/ArrayOperation.page +++ b/emhttp/plugins/dynamix/ArrayOperation.page @@ -351,7 +351,7 @@ function shutdown_now(form, cmd) { case 'reboot': if ($('input[name="safemode"]').prop('checked')) { $value) { From a3679c55734b66fdf037aa5c3778204763580153 Mon Sep 17 00:00:00 2001 From: Squidly271 Date: Thu, 6 Nov 2025 13:55:47 -0500 Subject: [PATCH 25/63] Fix/Feat: Re-add Join as notification agent --- emhttp/plugins/dynamix/agents/Join.xml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 emhttp/plugins/dynamix/agents/Join.xml diff --git a/emhttp/plugins/dynamix/agents/Join.xml b/emhttp/plugins/dynamix/agents/Join.xml new file mode 100644 index 000000000..19872f120 --- /dev/null +++ b/emhttp/plugins/dynamix/agents/Join.xml @@ -0,0 +1,25 @@ + + Join + + API_KEY + TITLE + MESSAGE + + + From 0b7c2caf75ee6970884a2977b0a588997cf487ea Mon Sep 17 00:00:00 2001 From: ljm42 Date: Thu, 6 Nov 2025 12:43:55 -0700 Subject: [PATCH 26/63] Fix: remove special chars from notify subject --- emhttp/plugins/dynamix/scripts/notify | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/emhttp/plugins/dynamix/scripts/notify b/emhttp/plugins/dynamix/scripts/notify index 8a5adc246..3725fde2f 100755 --- a/emhttp/plugins/dynamix/scripts/notify +++ b/emhttp/plugins/dynamix/scripts/notify @@ -222,8 +222,8 @@ case 'add': $archive = "{$archive}/".safe_filename("{$event}-{$ticket}.notify"); if (file_exists($archive)) break; $entity = $overrule===false ? $notify[$importance] : $overrule; - if (!$mailtest) file_put_contents($archive,"timestamp=$timestamp\nevent=$event\nsubject=$subject\ndescription=$description\nimportance=$importance\n".($message ? "message=".str_replace('\n','
',$message)."\n" : "")); - if (($entity & 1)==1 && !$mailtest && !$noBrowser) file_put_contents($unread,"timestamp=$timestamp\nevent=$event\nsubject=$subject\ndescription=$description\nimportance=$importance\nlink=$link\n"); + if (!$mailtest) file_put_contents($archive,"timestamp=$timestamp\nevent=$event\nsubject=".clean_subject($subject)."\ndescription=$description\nimportance=$importance\n".($message ? "message=".str_replace('\n','
',$message)."\n" : "")); + if (($entity & 1)==1 && !$mailtest && !$noBrowser) file_put_contents($unread,"timestamp=$timestamp\nevent=$event\nsubject=".clean_subject($subject)."\ndescription=$description\nimportance=$importance\nlink=$link\n"); if (($entity & 2)==2 || $mailtest) generate_email($event, clean_subject($subject), str_replace('
','. ',$description), $importance, $message, $recipients, $fqdnlink); if (($entity & 4)==4 && !$mailtest) { if (is_array($agents)) {foreach ($agents as $agent) {exec("TIMESTAMP='$timestamp' EVENT=".escapeshellarg($event)." SUBJECT=".escapeshellarg(clean_subject($subject))." DESCRIPTION=".escapeshellarg($description)." IMPORTANCE=".escapeshellarg($importance)." CONTENT=".escapeshellarg($message)." LINK=".escapeshellarg($fqdnlink)." bash ".$agent);};}}; break; From 8aa50db48dd99a5e923e493c9433389468752afc Mon Sep 17 00:00:00 2001 From: Squidly271 Date: Thu, 6 Nov 2025 17:08:33 -0500 Subject: [PATCH 27/63] Fix: Agents dropdowns not working in tabbed mode --- emhttp/plugins/dynamix/NotificationAgents.page | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/emhttp/plugins/dynamix/NotificationAgents.page b/emhttp/plugins/dynamix/NotificationAgents.page index af6d65bf3..ffaf3da02 100644 --- a/emhttp/plugins/dynamix/NotificationAgents.page +++ b/emhttp/plugins/dynamix/NotificationAgents.page @@ -1,6 +1,7 @@ Menu="Notifications:3" Title="Notification Agents" Tag="rss-square" +Focus="initDropdown" --- - -$('#tab3').bind({click:function(){initDropdown();}}); -$(function(){if ($('#tab3').is(':checked')) initDropdown();}); - + $(function(){initDropdown();}); - if (!String.prototype.format) { String.prototype.format = function() { From d8acaad47fe51c6cd0ef5e9651948c20a63d1e60 Mon Sep 17 00:00:00 2001 From: Squidly271 Date: Fri, 7 Nov 2025 01:29:43 -0500 Subject: [PATCH 28/63] Tweaks --- emhttp/plugins/dynamix/NotificationAgents.page | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix/NotificationAgents.page b/emhttp/plugins/dynamix/NotificationAgents.page index ffaf3da02..28e178f66 100644 --- a/emhttp/plugins/dynamix/NotificationAgents.page +++ b/emhttp/plugins/dynamix/NotificationAgents.page @@ -4,7 +4,7 @@ Tag="rss-square" Focus="initDropdown" --- + $(function(){initDropdown();}); + if (!String.prototype.format) { String.prototype.format = function() { From bff2cc691c2afc91ea9b7ff1664326470d1ab01f Mon Sep 17 00:00:00 2001 From: Squidly271 Date: Fri, 7 Nov 2025 01:45:34 -0500 Subject: [PATCH 29/63] Fix when reloading page with focus on agent --- emhttp/plugins/dynamix/NotificationAgents.page | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/emhttp/plugins/dynamix/NotificationAgents.page b/emhttp/plugins/dynamix/NotificationAgents.page index 28e178f66..b6ecebc35 100644 --- a/emhttp/plugins/dynamix/NotificationAgents.page +++ b/emhttp/plugins/dynamix/NotificationAgents.page @@ -25,6 +25,10 @@ var openPage = true; $(function(){initDropdown();}); + +$(function(){ + if ( $('#tab3').attr('checked') ) initDropdown(); +}); if (!String.prototype.format) { From 1312098d7b840cd788b8464adefb136d69608519 Mon Sep 17 00:00:00 2001 From: Tom Mortensen Date: Fri, 7 Nov 2025 00:53:40 -0800 Subject: [PATCH 30/63] fix: remove '-e' test option from 'mover' script --- sbin/mover | 74 +++++------------------------------------------------- 1 file changed, 6 insertions(+), 68 deletions(-) diff --git a/sbin/mover b/sbin/mover index 7ddffb89e..25107d999 100755 --- a/sbin/mover +++ b/sbin/mover @@ -28,9 +28,9 @@ CFGPATH="/boot/config/shares" DEBUGGING="" move() { - find "$1" -depth 2>/dev/null | /usr/libexec/unraid/move $2 $DEBUGGING + find "$1" -depth 2>/dev/null | /usr/libexec/unraid/move $DEBUGGING # second pass to clean up leftover empty directories - find "$1" -depth -type d 2>/dev/null | /usr/libexec/unraid/move $2 $DEBUGGING + find "$1" -depth -type d 2>/dev/null | /usr/libexec/unraid/move $DEBUGGING } start() { @@ -92,7 +92,7 @@ start() { for SHAREPATH in /mnt/$DISK/* ; do SHARE=$(basename "$SHAREPATH") if [[ -d "$SHAREPATH" && -f "$CFGPATH/$SHARE.cfg" ]]; then - move "$SHAREPATH" "-e" + move "$SHAREPATH" fi done @@ -111,43 +111,6 @@ start() { echo "mover: finished" >&2 } -empty() { - DISK="$1" - - if [ -f $PIDFILE ]; then - if ps h $(cat $PIDFILE) | grep mover ; then - echo "mover: already running" >&2 - exit 1 - fi - fi - - echo $$ >/var/run/mover.pid - echo "mover: started" >&2 - - shopt -s nullglob - - # we can only empty share directories - for SHAREPATH in /mnt/$DISK/* ; do - SHARE=$(basename "$SHAREPATH") - if [[ -d "$SHAREPATH" && -f "$CFGPATH/$SHARE.cfg" ]]; then - move "$SHAREPATH" "-e" - fi - done - - # output list of files which could not be moved - # use 'find' in case huge number of files left in /mnt/$DISK - count=$(find /mnt/$DISK -mindepth 1 | wc -l) - if [ "$count" -gt 0 ]; then - find /mnt/$DISK -mindepth 1 -depth -printf 'move: %p Not moved\n' | head -n 100 - if [ "$count" -gt 100 ]; then - echo "[output truncated to first 100 entries]" - fi - fi - - rm -f $PIDFILE - echo "mover: finished" >&2 -} - killtree() { local pid=$1 child @@ -170,43 +133,18 @@ stop() { # display usage and then exit usage() { - echo "Usage: $0 start [-e ]" - echo " $0 stop|status" - echo " must match pattern 'disk[0-9]*' and /mnt/ must be a mountpoint" + echo "Usage: $0 start|stop|status" exit 1 } -# validate disk name -validate_disk() { - if [[ ! "$1" =~ ^disk[0-9]+$ ]]; then - echo "Error: must match pattern 'disk[0-9]+$'" - usage - fi - if ! mountpoint --nofollow /mnt/$1 > /dev/null 2>&1; then - echo "Error: nothing mounted at /mnt/$1" - usage - fi -} - -if [ "$#" -lt 1 ]; then +if [ "$#" -ne 1 ]; then usage exit 1 fi case $1 in start) - if [ -z "$2" ]; then - start - elif [ "$2" == "-e" ]; then - if [ -z "$3" ]; then - usage - else - validate_disk "$3" - empty "$3" - fi - else - usage - fi + start ;; stop) stop From e4979bae63630e86a95aac02f95d535920d55ffc Mon Sep 17 00:00:00 2001 From: Squidly271 Date: Sat, 8 Nov 2025 01:59:24 -0500 Subject: [PATCH 31/63] Fix Search & language sizing on sidebar theme --- emhttp/plugins/dynamix.gui.search/sheets/gui_search.css | 1 + emhttp/plugins/dynamix/styles/default-base.css | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) mode change 100644 => 100755 emhttp/plugins/dynamix.gui.search/sheets/gui_search.css diff --git a/emhttp/plugins/dynamix.gui.search/sheets/gui_search.css b/emhttp/plugins/dynamix.gui.search/sheets/gui_search.css old mode 100644 new mode 100755 index aaef0d82e..8788ebed8 --- a/emhttp/plugins/dynamix.gui.search/sheets/gui_search.css +++ b/emhttp/plugins/dynamix.gui.search/sheets/gui_search.css @@ -58,6 +58,7 @@ #guiSearchBoxSpan { margin: 0; padding: 0; + height: 18px !important; } #guiSearchBox { position: relative; diff --git a/emhttp/plugins/dynamix/styles/default-base.css b/emhttp/plugins/dynamix/styles/default-base.css index 164202fdc..9bfce5361 100755 --- a/emhttp/plugins/dynamix/styles/default-base.css +++ b/emhttp/plugins/dynamix/styles/default-base.css @@ -2563,7 +2563,7 @@ div#title.ud { padding: 0; } - .LanguageButton { + .Theme--nav-top .LanguageButton { font-size: 12px !important; } /* Fix Switch Language Being Cut-Off */ From c450daee2e69affb55cd96737a81732d01a91090 Mon Sep 17 00:00:00 2001 From: Squidly271 Date: Sat, 8 Nov 2025 03:12:48 -0500 Subject: [PATCH 32/63] Feat: Add port checking for VNC and sane defaults --- .../templates/Custom.form.php | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) mode change 100644 => 100755 emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php diff --git a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php old mode 100644 new mode 100755 index 5f668838c..ddb47ab81 --- a/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php +++ b/emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php @@ -103,8 +103,8 @@ $arrConfigDefaults = [ 'autoport' => 'yes', 'model' => 'qxl', 'keymap' => 'none', - 'port' => -1 , - 'wsport' => -1, + 'port' => 5900, + 'wsport' => 5901, 'copypaste' => 'no', 'render' => 'auto', 'DisplayOptions' => "" @@ -1325,9 +1325,9 @@ foreach ($arrConfig['shares'] as $i => $arrShare) { ?>
_(VM Console Port)_: - + _(VM Console WS Port)_: - + @@ -2088,6 +2088,18 @@ foreach ($arrConfig['evdev'] as $i => $arrEvdev) { var storageType = ""; var storageLoc = ""; +function checkVNCPorts() { + const port = $("#port").val(); + const wsport = $("#wsport").val(); + if (port < 5900 || port > 65535 || wsport < 5900 || wsport > 65535 || port == wsport) { + swal({ + title: "_(Invalid Port)_", + text: "_(VNC/SPICE ports must be between 5900 and 65535, and cannot be equal to each other)_", + type: "error", + confirmButtonText: "_(Ok)_" + }); + } +} function updateMAC(index, port) { var wlan0 = ''; // mac address of wlan0 var mac = $('input[name="nic['+index+'][mac]"'); From 89afd5a8b20764470b1e1eeb890c36f195435f5f Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Sat, 8 Nov 2025 12:20:51 -0500 Subject: [PATCH 33/63] fix: quote ini string values produced by `notify` script this improves compatibility with other ini parsers, making it easier for the api to parse notification data correctly. --- emhttp/plugins/dynamix/scripts/notify | 61 +++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/emhttp/plugins/dynamix/scripts/notify b/emhttp/plugins/dynamix/scripts/notify index 3725fde2f..04372c5c1 100755 --- a/emhttp/plugins/dynamix/scripts/notify +++ b/emhttp/plugins/dynamix/scripts/notify @@ -104,6 +104,37 @@ function clean_subject($subject) { return $subject; } +/** + * Wrap string values in double quotes for INI compatibility and escape quotes/backslashes. + * Numeric types remain unquoted so they can be parsed as-is. + */ +function ini_encode_value($value) { + if (is_int($value) || is_float($value)) return $value; + if (is_bool($value)) return $value ? 'true' : 'false'; + $value = (string)$value; + return '"'.strtr($value, ["\\"=>"\\\\", '"' => '\\"']).'"'; +} + +function build_ini_string(array $data) { + $lines = []; + foreach ($data as $key => $value) { + $lines[] = "{$key}=".ini_encode_value($value); + } + return implode("\n", $lines)."\n"; +} + +/** + * Trims and unescapes strings (eg quotes, backslashes) if necessary. + */ +function ini_decode_value($value) { + $value = trim($value); + $length = strlen($value); + if ($length >= 2 && $value[0] === '"' && $value[$length-1] === '"') { + return stripcslashes(substr($value, 1, -1)); + } + return $value; +} + // start if ($argc == 1) exit(usage()); @@ -222,8 +253,27 @@ case 'add': $archive = "{$archive}/".safe_filename("{$event}-{$ticket}.notify"); if (file_exists($archive)) break; $entity = $overrule===false ? $notify[$importance] : $overrule; - if (!$mailtest) file_put_contents($archive,"timestamp=$timestamp\nevent=$event\nsubject=".clean_subject($subject)."\ndescription=$description\nimportance=$importance\n".($message ? "message=".str_replace('\n','
',$message)."\n" : "")); - if (($entity & 1)==1 && !$mailtest && !$noBrowser) file_put_contents($unread,"timestamp=$timestamp\nevent=$event\nsubject=".clean_subject($subject)."\ndescription=$description\nimportance=$importance\nlink=$link\n"); + $cleanSubject = clean_subject($subject); + $archiveData = [ + 'timestamp' => $timestamp, + 'event' => $event, + 'subject' => $cleanSubject, + 'description' => $description, + 'importance' => $importance, + ]; + if ($message) $archiveData['message'] = str_replace('\n','
',$message); + if (!$mailtest) file_put_contents($archive, build_ini_string($archiveData)); + if (($entity & 1)==1 && !$mailtest && !$noBrowser) { + $unreadData = [ + 'timestamp' => $timestamp, + 'event' => $event, + 'subject' => $cleanSubject, + 'description' => $description, + 'importance' => $importance, + 'link' => $link, + ]; + file_put_contents($unread, build_ini_string($unreadData)); + } if (($entity & 2)==2 || $mailtest) generate_email($event, clean_subject($subject), str_replace('
','. ',$description), $importance, $message, $recipients, $fqdnlink); if (($entity & 4)==4 && !$mailtest) { if (is_array($agents)) {foreach ($agents as $agent) {exec("TIMESTAMP='$timestamp' EVENT=".escapeshellarg($event)." SUBJECT=".escapeshellarg(clean_subject($subject))." DESCRIPTION=".escapeshellarg($description)." IMPORTANCE=".escapeshellarg($importance)." CONTENT=".escapeshellarg($message)." LINK=".escapeshellarg($fqdnlink)." bash ".$agent);};}}; break; @@ -241,9 +291,12 @@ case 'get': $output[$i]['show'] = (fileperms($file) & 0x0FFF)==0400 ? 0 : 1; foreach ($fields as $field) { if (!$field) continue; - [$key,$val] = array_pad(explode('=', $field),2,''); + # limit the explode('=', …) used during reads to two pieces so values containing = remain intact + [$key,$val] = array_pad(explode('=', $field, 2),2,''); if ($time) {$val = date($notify['date'].' '.$notify['time'], $val); $time = false;} - $output[$i][trim($key)] = trim($val); + # unescape the value before emitting JSON, so the browser UI + # and any scripts calling `notify get` still see plain strings + $output[$i][trim($key)] = ini_decode_value($val); } $i++; } From 49ff54cb0a9447180a94868445d1ce896c74b4e6 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Sat, 8 Nov 2025 12:50:39 -0500 Subject: [PATCH 34/63] fix: use `stripslashes` instead of `stripcslashes` in `notify` script --- emhttp/plugins/dynamix/scripts/notify | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix/scripts/notify b/emhttp/plugins/dynamix/scripts/notify index 04372c5c1..a912aefde 100755 --- a/emhttp/plugins/dynamix/scripts/notify +++ b/emhttp/plugins/dynamix/scripts/notify @@ -130,7 +130,7 @@ function ini_decode_value($value) { $value = trim($value); $length = strlen($value); if ($length >= 2 && $value[0] === '"' && $value[$length-1] === '"') { - return stripcslashes(substr($value, 1, -1)); + return stripslashes(substr($value, 1, -1)); } return $value; } From 4d404b7cf1babd3b0642e6454cbec179853ad1f1 Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Sat, 8 Nov 2025 12:52:36 -0500 Subject: [PATCH 35/63] fix: redundant `clean_subject` calls --- emhttp/plugins/dynamix/scripts/notify | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/emhttp/plugins/dynamix/scripts/notify b/emhttp/plugins/dynamix/scripts/notify index a912aefde..1c4db802b 100755 --- a/emhttp/plugins/dynamix/scripts/notify +++ b/emhttp/plugins/dynamix/scripts/notify @@ -274,8 +274,8 @@ case 'add': ]; file_put_contents($unread, build_ini_string($unreadData)); } - if (($entity & 2)==2 || $mailtest) generate_email($event, clean_subject($subject), str_replace('
','. ',$description), $importance, $message, $recipients, $fqdnlink); - if (($entity & 4)==4 && !$mailtest) { if (is_array($agents)) {foreach ($agents as $agent) {exec("TIMESTAMP='$timestamp' EVENT=".escapeshellarg($event)." SUBJECT=".escapeshellarg(clean_subject($subject))." DESCRIPTION=".escapeshellarg($description)." IMPORTANCE=".escapeshellarg($importance)." CONTENT=".escapeshellarg($message)." LINK=".escapeshellarg($fqdnlink)." bash ".$agent);};}}; + if (($entity & 2)==2 || $mailtest) generate_email($event, $cleanSubject, str_replace('
','. ',$description), $importance, $message, $recipients, $fqdnlink); + if (($entity & 4)==4 && !$mailtest) { if (is_array($agents)) {foreach ($agents as $agent) {exec("TIMESTAMP='$timestamp' EVENT=".escapeshellarg($event)." SUBJECT=".escapeshellarg($cleanSubject)." DESCRIPTION=".escapeshellarg($description)." IMPORTANCE=".escapeshellarg($importance)." CONTENT=".escapeshellarg($message)." LINK=".escapeshellarg($fqdnlink)." bash ".$agent);};}}; break; case 'get': From 1859921036240e5835cc746a164aa8797434174a Mon Sep 17 00:00:00 2001 From: Squidly271 Date: Sun, 9 Nov 2025 13:52:27 -0500 Subject: [PATCH 36/63] Fix: GUI Search wouldn't update when plugin uninstalled --- .../dynamix.plugin.manager/post-hooks/post_plugin_checks | 2 ++ 1 file changed, 2 insertions(+) diff --git a/emhttp/plugins/dynamix.plugin.manager/post-hooks/post_plugin_checks b/emhttp/plugins/dynamix.plugin.manager/post-hooks/post_plugin_checks index f84d646d6..8f10eaf56 100755 --- a/emhttp/plugins/dynamix.plugin.manager/post-hooks/post_plugin_checks +++ b/emhttp/plugins/dynamix.plugin.manager/post-hooks/post_plugin_checks @@ -67,6 +67,8 @@ case 'language': // nothing defined break; } +// unset GUI search cache +@unlink("/tmp/gui.search/searchResults.json"); // unset pending status if ($method != 'check') @unlink("$pending/$name"); From 40b62796df59080ba29192b6482c5df81297523a Mon Sep 17 00:00:00 2001 From: Squidly271 Date: Wed, 12 Nov 2025 17:42:18 -0500 Subject: [PATCH 37/63] Fix: Array usage bar sidenav theme --- emhttp/plugins/dynamix/include/DefaultPageLayout/Header.php | 4 +++- .../dynamix/include/DefaultPageLayout/Navigation/Main.php | 6 ++++-- emhttp/plugins/dynamix/styles/default-base.css | 6 ++++-- 3 files changed, 11 insertions(+), 5 deletions(-) mode change 100644 => 100755 emhttp/plugins/dynamix/include/DefaultPageLayout/Header.php mode change 100644 => 100755 emhttp/plugins/dynamix/include/DefaultPageLayout/Navigation/Main.php diff --git a/emhttp/plugins/dynamix/include/DefaultPageLayout/Header.php b/emhttp/plugins/dynamix/include/DefaultPageLayout/Header.php old mode 100644 new mode 100755 index ea6e88031..579dd637d --- a/emhttp/plugins/dynamix/include/DefaultPageLayout/Header.php +++ b/emhttp/plugins/dynamix/include/DefaultPageLayout/Header.php @@ -1,5 +1,7 @@ diff --git a/emhttp/plugins/dynamix/include/DefaultPageLayout/Navigation/Main.php b/emhttp/plugins/dynamix/include/DefaultPageLayout/Navigation/Main.php old mode 100644 new mode 100755 index 3d4b5a0e0..40bd5e99d --- a/emhttp/plugins/dynamix/include/DefaultPageLayout/Navigation/Main.php +++ b/emhttp/plugins/dynamix/include/DefaultPageLayout/Navigation/Main.php @@ -37,10 +37,12 @@
- + isSidebarTheme()): ?> - + isSidebarTheme()): ?> + + diff --git a/emhttp/plugins/dynamix/styles/default-base.css b/emhttp/plugins/dynamix/styles/default-base.css index 9bfce5361..b0d13e033 100755 --- a/emhttp/plugins/dynamix/styles/default-base.css +++ b/emhttp/plugins/dynamix/styles/default-base.css @@ -2594,8 +2594,10 @@ div#title.ud { } .usage-bar { - position: relative; - width: 6.4rem; + POSITION: absolute; + bottom: .5rem; + left: 50%; + width: 14rem; margin: 0; padding: 0; } From 911d9da630a1b59b8ea67c28f8cb5c95c57887af Mon Sep 17 00:00:00 2001 From: Squidly271 Date: Wed, 12 Nov 2025 17:44:28 -0500 Subject: [PATCH 38/63] Update default-base.css --- emhttp/plugins/dynamix/styles/default-base.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix/styles/default-base.css b/emhttp/plugins/dynamix/styles/default-base.css index b0d13e033..cde054164 100755 --- a/emhttp/plugins/dynamix/styles/default-base.css +++ b/emhttp/plugins/dynamix/styles/default-base.css @@ -2594,7 +2594,7 @@ div#title.ud { } .usage-bar { - POSITION: absolute; + position: absolute; bottom: .5rem; left: 50%; width: 14rem; From 515cded921977034ee98e64a101e1465b6917d03 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Fri, 14 Nov 2025 12:17:25 -0500 Subject: [PATCH 39/63] Update Crowdin configuration file --- crowdin.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 crowdin.yml diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 000000000..d7faad685 --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,2 @@ +bundles: + - 1 From 993a5f42a50e74f09fdef807bbb8ea3e5dc17a4b Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Fri, 14 Nov 2025 12:18:17 -0500 Subject: [PATCH 40/63] Delete crowdin.yml --- crowdin.yml | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 crowdin.yml diff --git a/crowdin.yml b/crowdin.yml deleted file mode 100644 index d7faad685..000000000 --- a/crowdin.yml +++ /dev/null @@ -1,2 +0,0 @@ -bundles: - - 1 From 972e1be6fc827beda94397c8f065b5592b91d179 Mon Sep 17 00:00:00 2001 From: Tom Mortensen Date: Fri, 14 Nov 2025 11:42:13 -0800 Subject: [PATCH 41/63] Fix: NFS security, "write settings to" does not work for security modes Public and Secure --- emhttp/plugins/dynamix/SecurityNFS.page | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix/SecurityNFS.page b/emhttp/plugins/dynamix/SecurityNFS.page index da4a9cf20..ce7d4f478 100755 --- a/emhttp/plugins/dynamix/SecurityNFS.page +++ b/emhttp/plugins/dynamix/SecurityNFS.page @@ -214,7 +214,7 @@ function writeNFS(data, n, i) { var data = []; /* Get the setting from the share config. */ - var hostList = $('textarea[name="shareHostListNFS"]').val().trim(); + var hostList = $('textarea[name="shareHostListNFS"]').val()?.trim() || ""; /* Replace all new lines in data.hostList with spaces. */ var formattedHostList = .replace(/\n/g, ' '); From 77ea548bebfc393c78b45de4d61badc445b01e77 Mon Sep 17 00:00:00 2001 From: Tom Mortensen Date: Fri, 14 Nov 2025 13:22:46 -0800 Subject: [PATCH 42/63] Fix: add Disable option to mover schedule --- emhttp/plugins/dynamix/MoverSettings.page | 48 ++++++++++++++--------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/emhttp/plugins/dynamix/MoverSettings.page b/emhttp/plugins/dynamix/MoverSettings.page index 4ca5a060e..e6576111a 100755 --- a/emhttp/plugins/dynamix/MoverSettings.page +++ b/emhttp/plugins/dynamix/MoverSettings.page @@ -15,6 +15,8 @@ Tag="calendar-check-o" */ ?> "._('No Cache device present')."!

"; @@ -23,11 +25,13 @@ if (!$pool_devices) { echo "

"._('User shares not enabled')."!

"; $setup = false; } -$cron = explode(' ',$var['shareMoverSchedule']); -$move = $cron[2]!='*' ? 3 : ($cron[4]!='*' ? 2 : (substr($cron[1],0,1)!='*' ? 1 : 0)); -$mode = ['Hourly','Daily','Weekly','Monthly']; -$days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']; - +if (empty($var['shareMoverSchedule'])) { + $cron = explode(' ', "* * * * *"); + $move = 0; +} else { + $cron = explode(' ', $var['shareMoverSchedule']); + $move = $cron[2]!='*' ? 4 : ($cron[4]!='*' ? 3 : (substr($cron[1],0,1)!='*' ? 2 : 1)); +} $showMoverButton = $setup && $pool_devices; $moverRunning = file_exists('/var/run/mover.pid'); ?> @@ -37,27 +41,33 @@ $(function() { presetMover(document.mover_schedule); }); +function presetMover(form) { + var mode = form.shareMoverSchedule.value; + form.min.disabled = mode==0; + form.day.disabled = mode==0 || mode!=3; + form.dotm.disabled = mode==0 || mode!=4; + form.hour1.disabled = mode==0; + form.hour2.disabled = mode==0; + form.day.value = form.day.disabled ? '*' : (form.day.value=='*' ? 0 : form.day.value); + form.dotm.value = form.dotm.disabled ? '*' : (form.dotm.value=='*' ? 1 : form.dotm.value); + if (mode==1) {$('#H1').hide(); $('#H2').show();} else {$('#H2').hide(); $('#H1').show();} +} // Fool Unraid by simulating the original input field function prepareMover(form) { var mode = form.shareMoverSchedule.value; - var min = mode!=0 ? form.min.value : 0; - var hour = mode!=0 ? form.hour1.value : form.hour2.value; - form.shareMoverSchedule.options[mode].value = min+' '+hour+' '+form.dotm.value+' * '+form.day.value; + if (mode == 0) + form.shareMoverSchedule.options[mode].value = ''; + else { + var hour = mode!=1 ? form.hour1.value : form.hour2.value; + var min = mode!=1 ? form.min.value : 0; + form.shareMoverSchedule.options[mode].value = min+' '+hour+' '+form.dotm.value+' * '+form.day.value; + } form.min.disabled = true; form.hour1.disabled = true; form.hour2.disabled = true; form.dotm.disabled = true; form.day.disabled = true; } -function presetMover(form) { - var mode = form.shareMoverSchedule.value; - form.min.disabled = false; - form.day.disabled = mode!=2; - form.dotm.disabled = mode!=3; - form.day.value = form.day.disabled ? '*' : (form.day.value=='*' ? 0 : form.day.value); - form.dotm.value = form.dotm.disabled ? '*' : (form.dotm.value=='*' ? 1 : form.dotm.value); - if (mode==0) {$('#H1').hide(); $('#H2').show();} else {$('#H2').hide(); $('#H1').show();} -}
@@ -92,7 +102,7 @@ _(Day of the month)_: _(Time of the day)_: : - style="display:none"> @@ -102,7 +112,7 @@ _(Time of the day)_:   _(HH:MM)_ - style="display:none"> From 068af34ac2192703e005ec4dbeb8590b67c9034c Mon Sep 17 00:00:00 2001 From: Tom Mortensen Date: Fri, 14 Nov 2025 13:34:58 -0800 Subject: [PATCH 43/63] Fix: starting docker causes all IPv6 packets on br0 to be dropped --- etc/rc.d/rc.docker | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/etc/rc.d/rc.docker b/etc/rc.d/rc.docker index 90cec4c68..e9ac334d9 100755 --- a/etc/rc.d/rc.docker +++ b/etc/rc.d/rc.docker @@ -484,11 +484,11 @@ docker_network_start(){ fi fi done - # create IPv6 forward accept rule - if [[ $IPV6_FORWARD == accept ]]; then - ip6tables -P FORWARD ACCEPT - log "created forward accept rule for IPv6 network" - fi +# # create IPv6 forward accept rule +# if [[ $IPV6_FORWARD == accept ]]; then +# ip6tables -P FORWARD ACCEPT +# log "created forward accept rule for IPv6 network" +# fi log "Network started." } @@ -575,6 +575,9 @@ docker_service_start(){ fi nohup $UNSHARE --propagation slave -- $DOCKER -p $DOCKER_PIDFILE $DOCKER_OPTS >>$DOCKER_LOG 2>&1 & wait_daemon + # after docket started, continue to accept non-docker IPv6 traffic on br0 + ip6tables -P FORWARD ACCEPT +# log "created forward accept rule for IPv6 network" if docker_running; then REPLY="Started"; else REPLY="Failed"; fi fi log "$DAEMON... $REPLY." From 2e21bc3f74ba62413bf6826f08cf5a296e00cf4c Mon Sep 17 00:00:00 2001 From: Tom Mortensen Date: Fri, 14 Nov 2025 13:42:39 -0800 Subject: [PATCH 44/63] Fix: when stopping Samba, terminate master smbd process first --- etc/rc.d/rc.samba | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/etc/rc.d/rc.samba b/etc/rc.d/rc.samba index 8d30144f8..06e494a5d 100755 --- a/etc/rc.d/rc.samba +++ b/etc/rc.d/rc.samba @@ -155,12 +155,17 @@ samba_stop(){ REPLY="Already stopped" else REPLY="Stopped" + # stop gracefully with SIGTERM the "master" smbd process first + master=$(ps -o pid=,ppid= -C smbd | awk '$2==1 {print $1}') + [[ -n "master" ]] && run kill "$master" + sleep 1 # stop gracefully with SIGTERM run killall --ns $$ smbd nmbd wsdd2 winbindd samba_waitfor_shutdown if samba_running; then REPLY="Killed" # stop forcibly with SIGKILL + [[ -n "master" ]] && run kill -SIGKILL "master" run killall --ns $$ -SIGKILL smbd nmbd wsdd2 winbindd samba_waitfor_shutdown fi From 129fd5415bd2fe9d4f758aad02f06c749b98d865 Mon Sep 17 00:00:00 2001 From: Tom Mortensen Date: Sat, 15 Nov 2025 00:48:34 -0800 Subject: [PATCH 45/63] Fix: when stopping Samba, terminate master smbd process first (again) --- etc/rc.d/rc.samba | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/etc/rc.d/rc.samba b/etc/rc.d/rc.samba index 06e494a5d..a098b68c6 100755 --- a/etc/rc.d/rc.samba +++ b/etc/rc.d/rc.samba @@ -156,8 +156,8 @@ samba_stop(){ else REPLY="Stopped" # stop gracefully with SIGTERM the "master" smbd process first - master=$(ps -o pid=,ppid= -C smbd | awk '$2==1 {print $1}') - [[ -n "master" ]] && run kill "$master" + master=$(pgrep --ns $$ -o smbd) + [[ -n "$master" ]] && run kill "$master" sleep 1 # stop gracefully with SIGTERM run killall --ns $$ smbd nmbd wsdd2 winbindd @@ -165,7 +165,7 @@ samba_stop(){ if samba_running; then REPLY="Killed" # stop forcibly with SIGKILL - [[ -n "master" ]] && run kill -SIGKILL "master" + [[ -n "$master" ]] && run kill -SIGKILL "$master" run killall --ns $$ -SIGKILL smbd nmbd wsdd2 winbindd samba_waitfor_shutdown fi From 109563cc0b643a481fc1d6ba9c5e36046a427812 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Mon, 24 Nov 2025 09:49:20 -0500 Subject: [PATCH 46/63] feat: Enhance plugin installation command to support 'download-only' option, allowing users to download plugin files without executing installation commands. Update usage instructions and adjust processing logic accordingly. --- .../dynamix.plugin.manager/scripts/plugin | 105 +++++++++++++----- 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin index 8d601aced..96c0b4b8a 100755 --- a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin +++ b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin @@ -14,7 +14,7 @@ $logger = 'plugin-manager'; $usage = <<attributes()->Run) { $command = $file->attributes()->Run; + if ($download_only) { + $target = $name ?: ($file->LOCAL ?: 'inline script'); + my_logger("skipping run: $command $target - download-only mode", $logger); + continue; + } if ($name) { my_logger("running: $command $name", $logger); $retval = run("$command $name"); @@ -484,13 +492,16 @@ function move($src_file, $tar_dir) { return rename($src_file, $tar_dir."/".basename($src_file)); } -$notify = '/usr/local/emhttp/webGui/scripts/notify'; -$boot = '/boot/config/plugins'; -$plugins = '/var/log/plugins'; -$tmp = '/tmp/plugins'; -$method = $argv[1]; -$builtin = ['unRAIDServer','unRAIDServer-']; -$nchan = $argv[$argc-1] == 'nchan'; // console or nchan output +$notify = '/usr/local/emhttp/webGui/scripts/notify'; +$boot = '/boot/config/plugins'; +$plugins = '/var/log/plugins'; +$tmp = '/tmp/plugins'; +$download_only = false; +$script = $argv[0]; +$method = $argv[1]; +$optional_args = array_slice($argv, 2); +$builtin = ['unRAIDServer','unRAIDServer-']; +$nchan = in_array('nchan', $optional_args, true); // console or nchan output // In following code, // $plugin - is a basename of a plugin, eg, "myplugin.plg" @@ -506,8 +517,8 @@ if ($argc < 2) { // check all installed plugins, except built-in // if ($method == 'checkall') { - if (!$cmd = realpath($argv[0])) { - write("Unknown command: {$argv[0]}\n"); + if (!$cmd = realpath($script)) { + write("Unknown command: {$script}\n"); done(1); } foreach (glob("$plugins/*.plg", GLOB_NOSORT) as $link) { @@ -529,8 +540,8 @@ if ($method == 'checkall') { // update all installed plugins, which have a update available // if ($method == 'updateall') { - if (!$cmd = realpath($argv[0])) { - write("Unknown command: {$argv[0]}\n"); + if (!$cmd = realpath($script)) { + write("Unknown command: {$script}\n"); done(1); } foreach (glob("$plugins/*.plg", GLOB_NOSORT) as $link) { @@ -557,8 +568,8 @@ if ($method == 'updateall') { // check built-in only // if ($method == 'checkos') { - if (!$cmd = realpath($argv[0])) { - write("Unknown command: {$argv[0]}\n"); + if (!$cmd = realpath($script)) { + write("Unknown command: {$script}\n"); done(1); } foreach ($builtin as $link) { @@ -590,23 +601,34 @@ $unraid = parse_ini_file('/etc/unraid-version'); if ($method == 'install') { $argv[2] = preg_replace('#[\x00-\x1F\x80-\xFF]#', '', $argv[2]); $plugin = basename($argv[2]); + $options = array_slice($argv, 3); + $download_only = in_array('download-only', $options, true) || in_array('download', $options, true); + $non_download_options = array_diff($options, ['download-only', 'download']); + $forced = in_array('force', $options, true) || in_array('forced', $options, true) || !empty($non_download_options); if (pathinfo($plugin, PATHINFO_EXTENSION) != "plg") { write("plugin: $plugin is not a plg file\n"); done(1); } + if ($download_only) { + write("plugin: download-only mode enabled, skipping install commands\n"); + } write("plugin: installing: $plugin\n"); // check for URL if (preg_match('#^https?://#',$argv[2])) { $pluginURL = $argv[2]; // run hook scripts for pre processing - pre_hooks(); + if (!$download_only) { + pre_hooks(); + } $plugin_file = "$tmp/$plugin"; write("plugin: downloading: $plugin\n"); if ( (download($pluginURL, $plugin_file, $error) === false) && (download(filter_url($pluginURL), $plugin_file, $error) === false) ) { write("plugin: $error\n"); @unlink($plugin_file); // run hook scripts for post processing - post_hooks($error); + if (!$download_only) { + post_hooks($error); + } done(1); } } else { @@ -620,7 +642,9 @@ if ($method == 'install') { move($plugin_file, "$boot-error"); } // run hook scripts for post processing - post_hooks($error); + if (!$download_only) { + post_hooks($error); + } done(1); } $max = plugin('max', $plugin_file, $error) ?: plugin('Unraid', $plugin_file, $error); @@ -630,7 +654,9 @@ if ($method == 'install') { move($plugin_file, "$boot-error"); } // run hook scripts for post processing - post_hooks($error); + if (!$download_only) { + post_hooks($error); + } done(1); } $symlink = "$plugins/$plugin"; @@ -640,7 +666,9 @@ if ($method == 'install') { if ($plugin_file == $installed_plugin_file) { write("plugin: not re-installing same plugin\n"); // run hook scripts for post processing - post_hooks($error); + if (!$download_only) { + post_hooks($error); + } done(1); } // must have version attributes for re-install @@ -648,35 +676,44 @@ if ($method == 'install') { if ($version === false) { write("plugin: $error\n"); // run hook scripts for post processing - post_hooks($error); + if (!$download_only) { + post_hooks($error); + } done(1); } $installed_version = plugin('version', $installed_plugin_file, $error); if ($installed_version === false) { write("plugin: $error\n"); // run hook scripts for post processing - post_hooks($error); + if (!$download_only) { + post_hooks($error); + } done(1); } // check version installation? - $forced = $nchan ? ($argc==5 ? $argv[4] : false) : ($argc==4 ? $argv[3] : false); if (!$forced) { // do not re-install if same plugin already installed or has higher version if (strcmp($version, $installed_version) < 0) { write("plugin: not installing older version\n"); // run hook scripts for post processing - post_hooks($error); + if (!$download_only) { + post_hooks($error); + } done(1); } if (strcmp($version, $installed_version) === 0) { write("plugin: not reinstalling same version\n"); // run hook scripts for post processing - post_hooks($error); + if (!$download_only) { + post_hooks($error); + } done(1); } } // run hook scripts for pre processing - pre_hooks(); + if (!$download_only) { + pre_hooks(); + } if (plugin('install', $plugin_file, $error) === false) { write("plugin: $error\n"); if (dirname($plugin_file) == "$boot") { @@ -687,14 +724,18 @@ if ($method == 'install') { $description = "Plugin failed to install"; exec("$notify -e ".escapeshellarg($event)." -s ".escapeshellarg($subject)." -d ".escapeshellarg($description)." -i 'warning'"); // run hook scripts for post processing - post_hooks($error); + if (!$download_only) { + post_hooks($error); + } done(1); } // remove symlink for re-install unlink($symlink); } else { // run hook scripts for pre processing - pre_hooks(); + if (!$download_only) { + pre_hooks(); + } // fresh install if (plugin('install', $plugin_file, $error) === false) { write("plugin: $error\n"); @@ -702,7 +743,9 @@ if ($method == 'install') { move($plugin_file, "$boot-error"); } // run hook scripts for post processing - post_hooks($error); + if (!$download_only) { + post_hooks($error); + } done(1); } } @@ -718,7 +761,9 @@ if ($method == 'install') { my_logger("script: $plugin executed", $logger); } // run hook scripts for post processing - post_hooks(); + if (!$download_only) { + post_hooks(); + } done(0); } From 67a5e0d483bbce0991520be000edb42d62df3cf5 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Mon, 24 Nov 2025 10:09:25 -0500 Subject: [PATCH 47/63] refactor: Update plugin command usage to replace 'download-only' with a dedicated 'download' command. Adjust processing logic and improve logging for clarity during plugin installation and downloading. --- .../dynamix.plugin.manager/scripts/plugin | 46 +++++++++++++------ 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin index 96c0b4b8a..83366f364 100755 --- a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin +++ b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin @@ -14,7 +14,7 @@ $logger = 'plugin-manager'; $usage = << Date: Mon, 24 Nov 2025 10:33:08 -0500 Subject: [PATCH 48/63] refactor: Simplify plugin processing logic by replacing 'download_only' variable with 'download_mode'. Update related conditions for clarity and consistency in plugin installation and downloading behavior. --- .../dynamix.plugin.manager/scripts/plugin | 53 ++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin index 83366f364..e8ac7fe68 100755 --- a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin +++ b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin @@ -294,7 +294,11 @@ function filter_url($url) { // is processed for any of those methods. // function plugin($method, $plugin_file, &$error) { - global $unraid, $logger, $download_only; + global $unraid, $logger; + $download_mode = ($method === 'download'); + if ($download_mode) { + $method = 'install'; + } $methods = ['install', 'remove']; // parse plugin definition XML file @@ -464,7 +468,7 @@ function plugin($method, $plugin_file, &$error) { // if ($file->attributes()->Run) { $command = $file->attributes()->Run; - if ($download_only) { + if ($download_mode) { $target = $name ?: ($file->LOCAL ?: 'inline script'); my_logger("skipping run: $command $target - download-only mode", $logger); continue; @@ -502,7 +506,6 @@ $notify = '/usr/local/emhttp/webGui/scripts/notify'; $boot = '/boot/config/plugins'; $plugins = '/var/log/plugins'; $tmp = '/tmp/plugins'; -$download_only = false; $script = $argv[0]; $method = $argv[1]; $optional_args = array_slice($argv, 2); @@ -608,8 +611,8 @@ if ($method == 'install' || $method == 'download') { $argv[2] = preg_replace('#[\x00-\x1F\x80-\xFF]#', '', $argv[2]); $plugin = basename($argv[2]); $options = array_slice($argv, 3); - $download_only = ($method === 'download'); - if (!$download_only) { + $is_download = ($method === 'download'); + if (!$is_download) { $legacy_download_flags = array_intersect($options, ['download-only', 'download']); if (!empty($legacy_download_flags)) { write("plugin: use 'plugin download' instead of 'plugin install ... download-only'\n"); @@ -622,16 +625,16 @@ if ($method == 'install' || $method == 'download') { write("plugin: $plugin is not a plg file\n"); done(1); } - if ($download_only) { + if ($is_download) { write("plugin: download-only mode enabled, skipping install commands\n"); } - $action = $download_only ? 'downloading' : 'installing'; + $action = $is_download ? 'downloading' : 'installing'; write("plugin: $action: $plugin\n"); // check for URL if (preg_match('#^https?://#',$argv[2])) { $pluginURL = $argv[2]; // run hook scripts for pre processing - if (!$download_only) { + if (!$is_download) { pre_hooks(); } $plugin_file = "$tmp/$plugin"; @@ -640,7 +643,7 @@ if ($method == 'install' || $method == 'download') { write("plugin: $error\n"); @unlink($plugin_file); // run hook scripts for post processing - if (!$download_only) { + if (!$is_download) { post_hooks($error); } done(1); @@ -656,7 +659,7 @@ if ($method == 'install' || $method == 'download') { move($plugin_file, "$boot-error"); } // run hook scripts for post processing - if (!$download_only) { + if (!$is_download) { post_hooks($error); } done(1); @@ -668,7 +671,7 @@ if ($method == 'install' || $method == 'download') { move($plugin_file, "$boot-error"); } // run hook scripts for post processing - if (!$download_only) { + if (!$is_download) { post_hooks($error); } done(1); @@ -680,7 +683,7 @@ if ($method == 'install' || $method == 'download') { if ($plugin_file == $installed_plugin_file) { write("plugin: not re-installing same plugin\n"); // run hook scripts for post processing - if (!$download_only) { + if (!$is_download) { post_hooks($error); } done(1); @@ -690,7 +693,7 @@ if ($method == 'install' || $method == 'download') { if ($version === false) { write("plugin: $error\n"); // run hook scripts for post processing - if (!$download_only) { + if (!$is_download) { post_hooks($error); } done(1); @@ -699,7 +702,7 @@ if ($method == 'install' || $method == 'download') { if ($installed_version === false) { write("plugin: $error\n"); // run hook scripts for post processing - if (!$download_only) { + if (!$is_download) { post_hooks($error); } done(1); @@ -710,7 +713,7 @@ if ($method == 'install' || $method == 'download') { if (strcmp($version, $installed_version) < 0) { write("plugin: not installing older version\n"); // run hook scripts for post processing - if (!$download_only) { + if (!$is_download) { post_hooks($error); } done(1); @@ -718,17 +721,17 @@ if ($method == 'install' || $method == 'download') { if (strcmp($version, $installed_version) === 0) { write("plugin: not reinstalling same version\n"); // run hook scripts for post processing - if (!$download_only) { + if (!$is_download) { post_hooks($error); } done(1); } } // run hook scripts for pre processing - if (!$download_only) { + if (!$is_download) { pre_hooks(); } - if (plugin('install', $plugin_file, $error) === false) { + if (plugin($method, $plugin_file, $error) === false) { write("plugin: $error\n"); if (dirname($plugin_file) == "$boot") { move($plugin_file, "$boot-error"); @@ -738,7 +741,7 @@ if ($method == 'install' || $method == 'download') { $description = "Plugin failed to install"; exec("$notify -e ".escapeshellarg($event)." -s ".escapeshellarg($subject)." -d ".escapeshellarg($description)." -i 'warning'"); // run hook scripts for post processing - if (!$download_only) { + if (!$is_download) { post_hooks($error); } done(1); @@ -747,17 +750,17 @@ if ($method == 'install' || $method == 'download') { unlink($symlink); } else { // run hook scripts for pre processing - if (!$download_only) { + if (!$is_download) { pre_hooks(); } // fresh install - if (plugin('install', $plugin_file, $error) === false) { + if (plugin($method, $plugin_file, $error) === false) { write("plugin: $error\n"); if (dirname($plugin_file) == "$boot") { move($plugin_file, "$boot-error"); } // run hook scripts for post processing - if (!$download_only) { + if (!$is_download) { post_hooks($error); } done(1); @@ -768,16 +771,16 @@ if ($method == 'install' || $method == 'download') { if (!plugin('noInstall', $plugin_file, $error)) { if ($target != $plugin_file) copy($plugin_file, $target); symlink($target, $symlink); - $status = $download_only ? 'downloaded' : 'installed'; + $status = $is_download ? 'downloaded' : 'installed'; write("plugin: $plugin $status\n"); my_logger("$plugin $status", $logger); } else { - $script_action = $download_only ? 'staged' : 'executed'; + $script_action = $is_download ? 'staged' : 'executed'; write("script: $plugin $script_action\n"); my_logger("script: $plugin $script_action", $logger); } // run hook scripts for post processing - if (!$download_only) { + if (!$is_download) { post_hooks(); } done(0); From d350257753f8cc8d354f0e764e882450d8d453ab Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Mon, 24 Nov 2025 10:40:09 -0500 Subject: [PATCH 49/63] Revert "refactor: Simplify plugin processing logic by replacing 'download_only' variable with 'download_mode'. Update related conditions for clarity and consistency in plugin installation and downloading behavior." This reverts commit 3ac24e47af66ebfbc980bc70e1d4cb2caa05aaff. --- .../dynamix.plugin.manager/scripts/plugin | 53 +++++++++---------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin index e8ac7fe68..83366f364 100755 --- a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin +++ b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin @@ -294,11 +294,7 @@ function filter_url($url) { // is processed for any of those methods. // function plugin($method, $plugin_file, &$error) { - global $unraid, $logger; - $download_mode = ($method === 'download'); - if ($download_mode) { - $method = 'install'; - } + global $unraid, $logger, $download_only; $methods = ['install', 'remove']; // parse plugin definition XML file @@ -468,7 +464,7 @@ function plugin($method, $plugin_file, &$error) { // if ($file->attributes()->Run) { $command = $file->attributes()->Run; - if ($download_mode) { + if ($download_only) { $target = $name ?: ($file->LOCAL ?: 'inline script'); my_logger("skipping run: $command $target - download-only mode", $logger); continue; @@ -506,6 +502,7 @@ $notify = '/usr/local/emhttp/webGui/scripts/notify'; $boot = '/boot/config/plugins'; $plugins = '/var/log/plugins'; $tmp = '/tmp/plugins'; +$download_only = false; $script = $argv[0]; $method = $argv[1]; $optional_args = array_slice($argv, 2); @@ -611,8 +608,8 @@ if ($method == 'install' || $method == 'download') { $argv[2] = preg_replace('#[\x00-\x1F\x80-\xFF]#', '', $argv[2]); $plugin = basename($argv[2]); $options = array_slice($argv, 3); - $is_download = ($method === 'download'); - if (!$is_download) { + $download_only = ($method === 'download'); + if (!$download_only) { $legacy_download_flags = array_intersect($options, ['download-only', 'download']); if (!empty($legacy_download_flags)) { write("plugin: use 'plugin download' instead of 'plugin install ... download-only'\n"); @@ -625,16 +622,16 @@ if ($method == 'install' || $method == 'download') { write("plugin: $plugin is not a plg file\n"); done(1); } - if ($is_download) { + if ($download_only) { write("plugin: download-only mode enabled, skipping install commands\n"); } - $action = $is_download ? 'downloading' : 'installing'; + $action = $download_only ? 'downloading' : 'installing'; write("plugin: $action: $plugin\n"); // check for URL if (preg_match('#^https?://#',$argv[2])) { $pluginURL = $argv[2]; // run hook scripts for pre processing - if (!$is_download) { + if (!$download_only) { pre_hooks(); } $plugin_file = "$tmp/$plugin"; @@ -643,7 +640,7 @@ if ($method == 'install' || $method == 'download') { write("plugin: $error\n"); @unlink($plugin_file); // run hook scripts for post processing - if (!$is_download) { + if (!$download_only) { post_hooks($error); } done(1); @@ -659,7 +656,7 @@ if ($method == 'install' || $method == 'download') { move($plugin_file, "$boot-error"); } // run hook scripts for post processing - if (!$is_download) { + if (!$download_only) { post_hooks($error); } done(1); @@ -671,7 +668,7 @@ if ($method == 'install' || $method == 'download') { move($plugin_file, "$boot-error"); } // run hook scripts for post processing - if (!$is_download) { + if (!$download_only) { post_hooks($error); } done(1); @@ -683,7 +680,7 @@ if ($method == 'install' || $method == 'download') { if ($plugin_file == $installed_plugin_file) { write("plugin: not re-installing same plugin\n"); // run hook scripts for post processing - if (!$is_download) { + if (!$download_only) { post_hooks($error); } done(1); @@ -693,7 +690,7 @@ if ($method == 'install' || $method == 'download') { if ($version === false) { write("plugin: $error\n"); // run hook scripts for post processing - if (!$is_download) { + if (!$download_only) { post_hooks($error); } done(1); @@ -702,7 +699,7 @@ if ($method == 'install' || $method == 'download') { if ($installed_version === false) { write("plugin: $error\n"); // run hook scripts for post processing - if (!$is_download) { + if (!$download_only) { post_hooks($error); } done(1); @@ -713,7 +710,7 @@ if ($method == 'install' || $method == 'download') { if (strcmp($version, $installed_version) < 0) { write("plugin: not installing older version\n"); // run hook scripts for post processing - if (!$is_download) { + if (!$download_only) { post_hooks($error); } done(1); @@ -721,17 +718,17 @@ if ($method == 'install' || $method == 'download') { if (strcmp($version, $installed_version) === 0) { write("plugin: not reinstalling same version\n"); // run hook scripts for post processing - if (!$is_download) { + if (!$download_only) { post_hooks($error); } done(1); } } // run hook scripts for pre processing - if (!$is_download) { + if (!$download_only) { pre_hooks(); } - if (plugin($method, $plugin_file, $error) === false) { + if (plugin('install', $plugin_file, $error) === false) { write("plugin: $error\n"); if (dirname($plugin_file) == "$boot") { move($plugin_file, "$boot-error"); @@ -741,7 +738,7 @@ if ($method == 'install' || $method == 'download') { $description = "Plugin failed to install"; exec("$notify -e ".escapeshellarg($event)." -s ".escapeshellarg($subject)." -d ".escapeshellarg($description)." -i 'warning'"); // run hook scripts for post processing - if (!$is_download) { + if (!$download_only) { post_hooks($error); } done(1); @@ -750,17 +747,17 @@ if ($method == 'install' || $method == 'download') { unlink($symlink); } else { // run hook scripts for pre processing - if (!$is_download) { + if (!$download_only) { pre_hooks(); } // fresh install - if (plugin($method, $plugin_file, $error) === false) { + if (plugin('install', $plugin_file, $error) === false) { write("plugin: $error\n"); if (dirname($plugin_file) == "$boot") { move($plugin_file, "$boot-error"); } // run hook scripts for post processing - if (!$is_download) { + if (!$download_only) { post_hooks($error); } done(1); @@ -771,16 +768,16 @@ if ($method == 'install' || $method == 'download') { if (!plugin('noInstall', $plugin_file, $error)) { if ($target != $plugin_file) copy($plugin_file, $target); symlink($target, $symlink); - $status = $is_download ? 'downloaded' : 'installed'; + $status = $download_only ? 'downloaded' : 'installed'; write("plugin: $plugin $status\n"); my_logger("$plugin $status", $logger); } else { - $script_action = $is_download ? 'staged' : 'executed'; + $script_action = $download_only ? 'staged' : 'executed'; write("script: $plugin $script_action\n"); my_logger("script: $plugin $script_action", $logger); } // run hook scripts for post processing - if (!$is_download) { + if (!$download_only) { post_hooks(); } done(0); From 22ff21f7125f855180d2041ffaca830121eb9920 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Mon, 24 Nov 2025 10:52:01 -0500 Subject: [PATCH 50/63] refactor: Update plugin processing logic to consistently handle 'download_only' variable. Ensure pre and post hook execution respects the download-only mode, improving clarity and functionality during plugin installation and downloading. --- .../dynamix.plugin.manager/scripts/plugin | 62 ++++++------------- 1 file changed, 18 insertions(+), 44 deletions(-) diff --git a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin index 83366f364..eb468ddac 100755 --- a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin +++ b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin @@ -211,7 +211,8 @@ function run($command) { // hook programs receives three parameters: type=plugin and method and plugin-name // function pre_hooks() { - global $method, $plugin; + global $method, $plugin, $download_only; + if ($download_only) return; $hooks = "/usr/local/emhttp/plugins/dynamix.plugin.manager/pre-hooks"; foreach (glob("$hooks/*") as $hook) if (is_executable($hook)) { write("Executing hook script: ".basename($hook)."\n"); @@ -224,7 +225,8 @@ function pre_hooks() { // hook programs receives four parameters: type=plugin and method and plugin-name and error (empty if none) // function post_hooks($error='') { - global $method, $plugin; + global $method, $plugin, $download_only; + if ($download_only) return; $hooks = "/usr/local/emhttp/plugins/dynamix.plugin.manager/post-hooks"; foreach (glob("$hooks/*") as $hook) if (is_executable($hook)) { write("Executing hook script: ".basename($hook)."\n"); @@ -631,18 +633,14 @@ if ($method == 'install' || $method == 'download') { if (preg_match('#^https?://#',$argv[2])) { $pluginURL = $argv[2]; // run hook scripts for pre processing - if (!$download_only) { - pre_hooks(); - } + pre_hooks(); $plugin_file = "$tmp/$plugin"; write("plugin: downloading: $plugin\n"); if ( (download($pluginURL, $plugin_file, $error) === false) && (download(filter_url($pluginURL), $plugin_file, $error) === false) ) { write("plugin: $error\n"); @unlink($plugin_file); // run hook scripts for post processing - if (!$download_only) { - post_hooks($error); - } + post_hooks($error); done(1); } } else { @@ -656,9 +654,7 @@ if ($method == 'install' || $method == 'download') { move($plugin_file, "$boot-error"); } // run hook scripts for post processing - if (!$download_only) { - post_hooks($error); - } + post_hooks($error); done(1); } $max = plugin('max', $plugin_file, $error) ?: plugin('Unraid', $plugin_file, $error); @@ -668,9 +664,7 @@ if ($method == 'install' || $method == 'download') { move($plugin_file, "$boot-error"); } // run hook scripts for post processing - if (!$download_only) { - post_hooks($error); - } + post_hooks($error); done(1); } $symlink = "$plugins/$plugin"; @@ -680,9 +674,7 @@ if ($method == 'install' || $method == 'download') { if ($plugin_file == $installed_plugin_file) { write("plugin: not re-installing same plugin\n"); // run hook scripts for post processing - if (!$download_only) { - post_hooks($error); - } + post_hooks($error); done(1); } // must have version attributes for re-install @@ -690,18 +682,14 @@ if ($method == 'install' || $method == 'download') { if ($version === false) { write("plugin: $error\n"); // run hook scripts for post processing - if (!$download_only) { - post_hooks($error); - } + post_hooks($error); done(1); } $installed_version = plugin('version', $installed_plugin_file, $error); if ($installed_version === false) { write("plugin: $error\n"); // run hook scripts for post processing - if (!$download_only) { - post_hooks($error); - } + post_hooks($error); done(1); } // check version installation? @@ -710,24 +698,18 @@ if ($method == 'install' || $method == 'download') { if (strcmp($version, $installed_version) < 0) { write("plugin: not installing older version\n"); // run hook scripts for post processing - if (!$download_only) { - post_hooks($error); - } + post_hooks($error); done(1); } if (strcmp($version, $installed_version) === 0) { write("plugin: not reinstalling same version\n"); // run hook scripts for post processing - if (!$download_only) { - post_hooks($error); - } + post_hooks($error); done(1); } } // run hook scripts for pre processing - if (!$download_only) { - pre_hooks(); - } + pre_hooks(); if (plugin('install', $plugin_file, $error) === false) { write("plugin: $error\n"); if (dirname($plugin_file) == "$boot") { @@ -738,18 +720,14 @@ if ($method == 'install' || $method == 'download') { $description = "Plugin failed to install"; exec("$notify -e ".escapeshellarg($event)." -s ".escapeshellarg($subject)." -d ".escapeshellarg($description)." -i 'warning'"); // run hook scripts for post processing - if (!$download_only) { - post_hooks($error); - } + post_hooks($error); done(1); } // remove symlink for re-install unlink($symlink); } else { // run hook scripts for pre processing - if (!$download_only) { - pre_hooks(); - } + pre_hooks(); // fresh install if (plugin('install', $plugin_file, $error) === false) { write("plugin: $error\n"); @@ -757,9 +735,7 @@ if ($method == 'install' || $method == 'download') { move($plugin_file, "$boot-error"); } // run hook scripts for post processing - if (!$download_only) { - post_hooks($error); - } + post_hooks($error); done(1); } } @@ -777,9 +753,7 @@ if ($method == 'install' || $method == 'download') { my_logger("script: $plugin $script_action", $logger); } // run hook scripts for post processing - if (!$download_only) { - post_hooks(); - } + post_hooks(); done(0); } From f6370ef331b8cb1fab9afbc542c916c5b73adecd Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Mon, 24 Nov 2025 10:58:08 -0500 Subject: [PATCH 51/63] refactor: Simplify plugin option handling by removing unnecessary flags. Update logic to determine if a plugin is forced based solely on the presence of options, enhancing clarity in plugin processing. --- emhttp/plugins/dynamix.plugin.manager/scripts/plugin | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin index eb468ddac..375f22d0d 100755 --- a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin +++ b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin @@ -618,8 +618,7 @@ if ($method == 'install' || $method == 'download') { done(1); } } - $non_flag_options = array_diff($options, ['force', 'forced']); - $forced = in_array('force', $options, true) || in_array('forced', $options, true) || !empty($non_flag_options); + $forced = !empty($options); if (pathinfo($plugin, PATHINFO_EXTENSION) != "plg") { write("plugin: $plugin is not a plg file\n"); done(1); From 28452597a87fabb0385025ebf7af0343722d6189 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Mon, 24 Nov 2025 10:59:06 -0500 Subject: [PATCH 52/63] refactor: Remove legacy download flags from plugin installation logic. Streamline the handling of options to enhance clarity and maintainability in plugin processing. --- emhttp/plugins/dynamix.plugin.manager/scripts/plugin | 7 ------- 1 file changed, 7 deletions(-) diff --git a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin index 375f22d0d..6b33f55e5 100755 --- a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin +++ b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin @@ -611,13 +611,6 @@ if ($method == 'install' || $method == 'download') { $plugin = basename($argv[2]); $options = array_slice($argv, 3); $download_only = ($method === 'download'); - if (!$download_only) { - $legacy_download_flags = array_intersect($options, ['download-only', 'download']); - if (!empty($legacy_download_flags)) { - write("plugin: use 'plugin download' instead of 'plugin install ... download-only'\n"); - done(1); - } - } $forced = !empty($options); if (pathinfo($plugin, PATHINFO_EXTENSION) != "plg") { write("plugin: $plugin is not a plg file\n"); From e9c9f03cc7ed1320e4bda3dc47a800bd640de668 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Mon, 24 Nov 2025 11:11:02 -0500 Subject: [PATCH 53/63] refactor: Normalize download handling in plugin installation logic. Adjusted method flow to reuse installation steps for download requests, enhancing clarity and maintainability in plugin processing. --- emhttp/plugins/dynamix.plugin.manager/scripts/plugin | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin index 6b33f55e5..dae362634 100755 --- a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin +++ b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin @@ -511,6 +511,12 @@ $optional_args = array_slice($argv, 2); $builtin = ['unRAIDServer','unRAIDServer-']; $nchan = in_array('nchan', $optional_args, true); // console or nchan output +// Normalize download to reuse the install flow while skipping run steps. +if ($method === 'download') { + $download_only = true; + $method = 'install'; +} + // In following code, // $plugin - is a basename of a plugin, eg, "myplugin.plg" // $plugin_file - is an absolute path, eg, "/boot/config/plugins/myplugin.plg" @@ -606,11 +612,10 @@ if ($argc < 3) { // c) dirname of [plugin_file] is not /boot/config/plugins // $unraid = parse_ini_file('/etc/unraid-version'); -if ($method == 'install' || $method == 'download') { +if ($method == 'install') { $argv[2] = preg_replace('#[\x00-\x1F\x80-\xFF]#', '', $argv[2]); $plugin = basename($argv[2]); $options = array_slice($argv, 3); - $download_only = ($method === 'download'); $forced = !empty($options); if (pathinfo($plugin, PATHINFO_EXTENSION) != "plg") { write("plugin: $plugin is not a plg file\n"); From d2d326f4e1cd536925140a2f904e4c740f3e2816 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Mon, 24 Nov 2025 11:45:28 -0500 Subject: [PATCH 54/63] refactor: Enhance plugin argument handling by introducing optional argument parsing. Streamline the logic for determining forced plugin installations and improve clarity in command processing. --- .../plugins/dynamix.plugin.manager/scripts/plugin | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin index dae362634..e5d26f108 100755 --- a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin +++ b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin @@ -505,11 +505,16 @@ $boot = '/boot/config/plugins'; $plugins = '/var/log/plugins'; $tmp = '/tmp/plugins'; $download_only = false; -$script = $argv[0]; -$method = $argv[1]; -$optional_args = array_slice($argv, 2); +$script = $argv[0] ?? ''; +$method = $argv[1] ?? ''; +$args = array_slice($argv, 2); +$extra_args = array_slice($args, 1); // anything after the plugin path $builtin = ['unRAIDServer','unRAIDServer-']; -$nchan = in_array('nchan', $optional_args, true); // console or nchan output + +// Optional flags +$nchan = ($argc > 0) && ($argv[$argc-1] === 'nchan'); // console or nchan output +if ($nchan) array_pop($extra_args); +$forced = !empty($extra_args); // any extra arg (besides nchan) signals forced // Normalize download to reuse the install flow while skipping run steps. if ($method === 'download') { @@ -615,8 +620,6 @@ $unraid = parse_ini_file('/etc/unraid-version'); if ($method == 'install') { $argv[2] = preg_replace('#[\x00-\x1F\x80-\xFF]#', '', $argv[2]); $plugin = basename($argv[2]); - $options = array_slice($argv, 3); - $forced = !empty($options); if (pathinfo($plugin, PATHINFO_EXTENSION) != "plg") { write("plugin: $plugin is not a plg file\n"); done(1); From 1dd843df976c84f4e54fb00cb1556da63bfa158f Mon Sep 17 00:00:00 2001 From: ljm42 Date: Mon, 24 Nov 2025 16:18:15 -0700 Subject: [PATCH 55/63] Add plugin download method with target version support for OS upgrades --- .../dynamix.plugin.manager/scripts/plugin | 79 ++++++++++++------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin index e5d26f108..95ba79c4d 100755 --- a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin +++ b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin @@ -23,18 +23,6 @@ Usage: plugin install PLUGIN-FILE [forced] forced is optional and can be used to install a lower version than currently running. -Usage: plugin download PLUGIN-FILE [forced] - download plugin files without executing any Run commands defined for the install method - - PLUGIN-FILE is a plugin definition XML file with ".plg" extension. - - PLUGIN-FILE can be a local file, or a URL. If a URL, the plugin file is first downloaded to /tmp/plugins. - - forced is optional and can be used to download a lower version than currently running. - - Both install and download commands will process all FILE elements in PLUGIN-FILE which are tagged with the - \"install\" method (or that have no method tag). - This command has two major use cases: 1) Invoked at system startup by /etc/rc.d/rc.local on each .plg file found int /boot/config/plugins. @@ -88,6 +76,15 @@ Usage: plugin update PLUGIN Note: to support `plugin check` and `plugin update` the plugin file must contain both "pluginURL" and "version" attributes. +Usage: plugin download PLUGIN-FILE [TARGET-VERSION] [forced] + Downloads plugin files without executing any Run commands defined for the install method. + This method first updates the plugin definition file (.plg) to the latest version, then + downloads all required files but skips script execution. This makes it suitable for + preparing plugin files before an Unraid OS upgrade. + + TARGET-VERSION is optional and specifies the Unraid version to use for version compatibility + checks (Min/Max attributes). If omitted, the current Unraid version is used. + Usage: plugin [attribute name] PLUGIN-FILE Any method which is not one of the actions listed above is assumed to be the name of an attribute of @@ -296,7 +293,7 @@ function filter_url($url) { // is processed for any of those methods. // function plugin($method, $plugin_file, &$error) { - global $unraid, $logger, $download_only; + global $logger, $download_only, $check_version; $methods = ['install', 'remove']; // parse plugin definition XML file @@ -363,13 +360,17 @@ function plugin($method, $plugin_file, &$error) { $name = $file->attributes()->Name ?: ''; // bergware - check Unraid version dependency (if present) $min = $file->attributes()->Min; - if ($min && version_compare($unraid['version'],$min,'<')) { - write("plugin: skipping: ".basename($name)." - Unraid version too low, requires at least version $min\n"); + if ($min && version_compare($check_version,$min,'<')) { + if (!$download_only) { + write("plugin: skipping: ".basename($name)." - Unraid version too low, requires at least version $min\n"); + } continue; } $max = $file->attributes()->Max; - if ($max && version_compare($unraid['version'],$max,'>')) { - write("plugin: skipping: ".basename($name)." - Unraid version too high, requires at most version $max\n"); + if ($max && version_compare($check_version,$max,'>')) { + if (!$download_only) { + write("plugin: skipping: ".basename($name)." - Unraid version too high, requires at most version $max\n"); + } continue; } // Name can be missing but only makes sense if Run attribute is present @@ -478,7 +479,6 @@ function plugin($method, $plugin_file, &$error) { my_logger("running: $command $file->LOCAL", $logger); $retval = run("$command $file->LOCAL"); } elseif ($file->INLINE) { - $name = '/tmp/inline'.$current_file.'-'.pathinfo($plugin_file, PATHINFO_FILENAME).'.sh'; file_put_contents($name, $file->INLINE); $exec = $command." ".escapeshellarg($name); @@ -511,15 +511,31 @@ $args = array_slice($argv, 2); $extra_args = array_slice($args, 1); // anything after the plugin path $builtin = ['unRAIDServer','unRAIDServer-']; +// Load Unraid version and initialize check_version +$unraid = parse_ini_file('/etc/unraid-version'); +$check_version = $unraid['version']; + // Optional flags +// nchan must be the final argument $nchan = ($argc > 0) && ($argv[$argc-1] === 'nchan'); // console or nchan output if ($nchan) array_pop($extra_args); -$forced = !empty($extra_args); // any extra arg (besides nchan) signals forced -// Normalize download to reuse the install flow while skipping run steps. +// Extract target version if present (for download/update methods) +// Version pattern: starts with digit, contains dots, optionally has dash suffix (e.g., "7.2.0", "7.2.0-rc.1") +if (!empty($extra_args) && ($method === 'download' || $method === 'update')) { + $first_arg = $extra_args[0]; + if (preg_match('/^\d+\.\d+\.\d+(-.*)?$/', $first_arg)) { + $check_version = $first_arg; + array_shift($extra_args); + } +} + +$forced = !empty($extra_args); // any extra arg (besides nchan and TARGET-VERSION) signals forced + +// Normalize download to reuse the update flow while skipping run steps. if ($method === 'download') { $download_only = true; - $method = 'install'; + $method = 'update'; } // In following code, @@ -616,7 +632,6 @@ if ($argc < 3) { // b) [plugin_file] is a URL // c) dirname of [plugin_file] is not /boot/config/plugins // -$unraid = parse_ini_file('/etc/unraid-version'); if ($method == 'install') { $argv[2] = preg_replace('#[\x00-\x1F\x80-\xFF]#', '', $argv[2]); $plugin = basename($argv[2]); @@ -647,8 +662,9 @@ if ($method == 'install') { $plugin_file = realpath($argv[2]); } // bergware - check Unraid version dependency (if present) + global $check_version; $min = plugin('min', $plugin_file, $error); - if ($min && version_compare($unraid['version'], $min, '<')) { + if ($min && version_compare($check_version, $min, '<')) { write("plugin: installed Unraid version is too low, require at least version $min\n"); if (dirname($plugin_file) == "$boot") { move($plugin_file, "$boot-error"); @@ -658,7 +674,7 @@ if ($method == 'install') { done(1); } $max = plugin('max', $plugin_file, $error) ?: plugin('Unraid', $plugin_file, $error); - if ($max && version_compare($unraid['version'], $max, '>')) { + if ($max && version_compare($check_version, $max, '>')) { write("plugin: installed Unraid version is too high, require at most version $max\n"); if (dirname($plugin_file) == "$boot") { move($plugin_file, "$boot-error"); @@ -813,7 +829,11 @@ if ($method == 'check') { if ($method == 'update') { $plugin = $argv[2]; $symlink = "$plugins/$plugin"; - write("plugin: updating: $plugin\n"); + if ($download_only) { + write("plugin: download-only mode enabled, skipping install commands\n"); + } + $action = $download_only ? 'downloading' : 'updating'; + write("plugin: $action: $plugin\n"); $installed_plugin_file = @readlink($symlink); if ($installed_plugin_file === false) { write("plugin: $plugin not installed\n"); @@ -829,14 +849,14 @@ if ($method == 'update') { } // bergware - check Unraid version dependency (if present) $min = plugin('min', $plugin_file, $error); - if ($min && version_compare($unraid['version'], $min, '<')) { + if ($min && version_compare($check_version, $min, '<')) { write("plugin: installed Unraid version is too low, require at least version $min\n"); // run hook scripts for post processing post_hooks($error); done(1); } $max = plugin('max', $plugin_file, $error) ?: plugin('Unraid', $plugin_file, $error); - if ($max && version_compare($unraid['version'], $max, '>')) { + if ($max && version_compare($check_version, $max, '>')) { write("plugin: installed Unraid version is too high, require at most version $max\n"); // run hook scripts for post processing post_hooks($error); @@ -863,8 +883,9 @@ if ($method == 'update') { $target = "$boot/$plugin"; copy($plugin_file, $target); symlink($target, $symlink); - write("plugin: $plugin updated\n"); - my_logger("$plugin updated", $logger); + $status = $download_only ? 'downloaded' : 'updated'; + write("plugin: $plugin $status\n"); + my_logger("$plugin $status", $logger); // run hook scripts for post processing post_hooks(); done(0); From 77b8cc7638e1c33cac662e40294c600768a047db Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:00:13 +0000 Subject: [PATCH 56/63] Fix missing novnc files --- emhttp/plugins/dynamix.vm.manager/novnc/defaults.json | 1 + emhttp/plugins/dynamix.vm.manager/novnc/mandatory.json | 1 + 2 files changed, 2 insertions(+) create mode 100644 emhttp/plugins/dynamix.vm.manager/novnc/defaults.json create mode 100644 emhttp/plugins/dynamix.vm.manager/novnc/mandatory.json diff --git a/emhttp/plugins/dynamix.vm.manager/novnc/defaults.json b/emhttp/plugins/dynamix.vm.manager/novnc/defaults.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/emhttp/plugins/dynamix.vm.manager/novnc/defaults.json @@ -0,0 +1 @@ +{} diff --git a/emhttp/plugins/dynamix.vm.manager/novnc/mandatory.json b/emhttp/plugins/dynamix.vm.manager/novnc/mandatory.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/emhttp/plugins/dynamix.vm.manager/novnc/mandatory.json @@ -0,0 +1 @@ +{} From 31957f1c5546f9c7fec1fbc0c0c08de94f5a285e Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Mon, 1 Dec 2025 14:28:05 -0500 Subject: [PATCH 57/63] fix: exclude unapi from raw element rules --- .../plugins/dynamix/styles/default-base.css | 112 +++++++++--------- 1 file changed, 57 insertions(+), 55 deletions(-) diff --git a/emhttp/plugins/dynamix/styles/default-base.css b/emhttp/plugins/dynamix/styles/default-base.css index cde054164..1f358713c 100755 --- a/emhttp/plugins/dynamix/styles/default-base.css +++ b/emhttp/plugins/dynamix/styles/default-base.css @@ -139,15 +139,15 @@ hr { resize: none; } -input[type="text"], -input[type="password"], -input[type="number"], -input[type="url"], -input[type="email"], -input[type="date"], -input[type="file"], -textarea, -.textarea { +input[type="text"]:where(:not(.unapi *)), +input[type="password"]:where(:not(.unapi *)), +input[type="number"]:where(:not(.unapi *)), +input[type="url"]:where(:not(.unapi *)), +input[type="email"]:where(:not(.unapi *)), +input[type="date"]:where(:not(.unapi *)), +input[type="file"]:where(:not(.unapi *)), +textarea:where(:not(.unapi *)), +.textarea:where(:not(.unapi *)) { color: var(--text-color); font-family: clear-sans; font-size: 1.3rem; @@ -181,12 +181,12 @@ select:focus { } } -input[type="button"], -input[type="reset"], -input[type="submit"], -button, -button[type="button"], -a.button { +input[type="button"]:where(:not(.unapi *)), +input[type="reset"]:where(:not(.unapi *)), +input[type="submit"]:where(:not(.unapi *)), +button:where(:not(.unapi *)), +button[type="button"]:where(:not(.unapi *)), +a.button:where(:not(.unapi *)) { font-family: clear-sans; font-size: 1.1rem; font-weight: bold; @@ -250,48 +250,50 @@ input[type="number"]::-webkit-inner-spin-button { input[type="number"] { -moz-appearance: textfield; } -input:focus[type="text"], -input:focus[type="password"], -input:focus[type="number"], -input:focus[type="url"], -input:focus[type="email"], -input:focus[type="file"], -textarea:focus { +input:focus[type="text"]:where(:not(.unapi *)), +input:focus[type="password"]:where(:not(.unapi *)), +input:focus[type="number"]:where(:not(.unapi *)), +input:focus[type="url"]:where(:not(.unapi *)), +input:focus[type="email"]:where(:not(.unapi *)), +input:focus[type="file"]:where(:not(.unapi *)), +textarea:focus:where(:not(.unapi *)) { background-color: var(--focus-input-bg-color); outline: 0; } -input:hover[type="button"], -input:hover[type="reset"], -input:hover[type="submit"], -button:hover, -button:hover[type="button"], -a.button:hover { +input:hover[type="button"]:where(:not(.unapi *)), +input:hover[type="reset"]:where(:not(.unapi *)), +input:hover[type="submit"]:where(:not(.unapi *)), +button:hover:where(:not(.unapi *)), +button:hover[type="button"]:where(:not(.unapi *)), +a.button:hover:where(:not(.unapi *)) { color: var(--hover-button-text-color); background: var(--hover-button-background); } -input[disabled], -textarea[disabled] { +input[disabled]:where(:not(.unapi *)), +textarea[disabled]:where(:not(.unapi *)) { color: var(--text-color); border-bottom-color: var(--disabled-input-border-color); opacity: 0.5; cursor: default; } -input[type="button"][disabled], -input[type="reset"][disabled], -input[type="submit"][disabled], -button[disabled], -button[type="button"][disabled], -a.button[disabled] input:hover[type="button"][disabled], -input:hover[type="reset"][disabled], -input:hover[type="submit"][disabled], -button:hover[disabled], -button:hover[type="button"][disabled], -a.button:hover[disabled] input:active[type="button"][disabled], -input:active[type="reset"][disabled], -input:active[type="submit"][disabled], -button:active[disabled], -button:active[type="button"][disabled], -a.button:active[disabled] { +input[type="button"][disabled]:where(:not(.unapi *)), +input[type="reset"][disabled]:where(:not(.unapi *)), +input[type="submit"][disabled]:where(:not(.unapi *)), +button[disabled]:where(:not(.unapi *)), +button[type="button"][disabled]:where(:not(.unapi *)), +a.button[disabled]:where(:not(.unapi *)), +input:hover[type="button"][disabled]:where(:not(.unapi *)), +input:hover[type="reset"][disabled]:where(:not(.unapi *)), +input:hover[type="submit"][disabled]:where(:not(.unapi *)), +button:hover[disabled]:where(:not(.unapi *)), +button:hover[type="button"][disabled]:where(:not(.unapi *)), +a.button:hover[disabled]:where(:not(.unapi *)), +input:active[type="button"][disabled]:where(:not(.unapi *)), +input:active[type="reset"][disabled]:where(:not(.unapi *)), +input:active[type="submit"][disabled]:where(:not(.unapi *)), +button:active[disabled]:where(:not(.unapi *)), +button:active[type="button"][disabled]:where(:not(.unapi *)), +a.button:active[disabled]:where(:not(.unapi *)) { opacity: 0.5; cursor: default; color: var(--disabled-text-color); @@ -333,7 +335,7 @@ a.button:active[disabled] { input::-webkit-input-placeholder { color: var(--link-text-color); } -select { +select:where(:not(.unapi *)) { -webkit-appearance: none; font-family: clear-sans; font-size: 1.3rem; @@ -356,18 +358,18 @@ select { display: inline-block; cursor: pointer; } -select option { +select:where(:not(.unapi *)) option { color: var(--text-color); background-color: var(--mild-background-color); } -select option:disabled { +select:where(:not(.unapi *)) option:disabled { color: var(--disabled-text-color); } -select:focus { +select:focus:where(:not(.unapi *)) { background-color: var(--focus-input-bg-color); outline: 0; } -select[disabled] { +select[disabled]:where(:not(.unapi *)) { color: var(--text-color); border-bottom-color: var(--disabled-border-color); opacity: 0.5; @@ -414,7 +416,7 @@ input.trim { width: 76px; min-width: 76px; } -textarea { +textarea:where(:not(.unapi *)) { resize: none; padding: 6px; border: 1px solid var(--textarea-border-color); @@ -951,7 +953,7 @@ div.title span img { -webkit-overflow-scrolling: touch; } -table { +table:where(:not(.unapi *)) { border-collapse: collapse; border-spacing: 0; border-style: hidden; @@ -959,12 +961,12 @@ table { width: 100%; background-color: var(--background-color); } -table thead td { +table:where(:not(.unapi *)) thead td { line-height: 2.8rem; height: 2.8rem; white-space: nowrap; } -table tbody td { +table:where(:not(.unapi *)) tbody td { line-height: 2.6rem; height: 2.6rem; white-space: nowrap; From 9f8ee5587836e5d8ef326c0dd90adf3071a9c1fb Mon Sep 17 00:00:00 2001 From: ljm42 Date: Mon, 1 Dec 2025 22:36:25 -0700 Subject: [PATCH 58/63] Feat: Plugin download method now stages plugin updates for next boot --- .../dynamix.plugin.manager/scripts/plugin | 50 +++++++++++++------ etc/rc.d/rc.local | 20 ++++++++ 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin index 95ba79c4d..48a3853c5 100755 --- a/emhttp/plugins/dynamix.plugin.manager/scripts/plugin +++ b/emhttp/plugins/dynamix.plugin.manager/scripts/plugin @@ -77,10 +77,12 @@ Usage: plugin update PLUGIN "version" attributes. Usage: plugin download PLUGIN-FILE [TARGET-VERSION] [forced] - Downloads plugin files without executing any Run commands defined for the install method. - This method first updates the plugin definition file (.plg) to the latest version, then - downloads all required files but skips script execution. This makes it suitable for - preparing plugin files before an Unraid OS upgrade. + Downloads newer versions of installed plugin files without executing any Run commands. + This method downloads the updated plugin definition file (.plg) to /boot/config/plugins-nextboot/ + then downloads all required files to the same directory but skips script execution. + On the next boot, rc.local will move the files from /boot/config/plugins-nextboot/ to /boot/config/plugins/ + and install them normally. + This method is suitable for updating plugin files before an Unraid OS upgrade. TARGET-VERSION is optional and specifies the Unraid version to use for version compatibility checks (Min/Max attributes). If omitted, the current Unraid version is used. @@ -133,6 +135,12 @@ Here is the set of directories and files used by the plugin system: successful `plugin install`, the plugin file is copied here (if not here already). Upon successful `plugin remove`, the plugin file is deleted from here. +/boot/config/plugins-nextboot/ + This directory contains the plugin files for plugins to be updated at next boot-time. + Upon successful `plugin download`, plugin files will be located here. + On the next boot, rc.local will move the files from /boot/config/plugins-nextboot/ to /boot/config/plugins/ + and install them normally. + /boot/config/plugins-error/ This directory contains plugin files that failed to install. @@ -293,7 +301,7 @@ function filter_url($url) { // is processed for any of those methods. // function plugin($method, $plugin_file, &$error) { - global $logger, $download_only, $check_version; + global $logger, $download_only, $check_version, $boot, $nextboot; $methods = ['install', 'remove']; // parse plugin definition XML file @@ -375,6 +383,16 @@ function plugin($method, $plugin_file, &$error) { } // Name can be missing but only makes sense if Run attribute is present if ($name) { + // If download_only mode, only process files that go to $boot/ (persistent location) + if ($download_only) { + if (strpos($name, "$boot/") === 0) { + // Redirect $boot/ paths to $nextboot/ to be installed at next boot + $name = "$nextboot/" . substr($name, strlen("$boot/")); + } else { + // Skip other files - they won't persist after boot + continue; + } + } // Ensure parent directory exists // if (!file_exists(dirname($name))) { @@ -502,6 +520,7 @@ function move($src_file, $tar_dir) { $notify = '/usr/local/emhttp/webGui/scripts/notify'; $boot = '/boot/config/plugins'; +$nextboot = '/boot/config/plugins-nextboot'; $plugins = '/var/log/plugins'; $tmp = '/tmp/plugins'; $download_only = false; @@ -639,10 +658,7 @@ if ($method == 'install') { write("plugin: $plugin is not a plg file\n"); done(1); } - if ($download_only) { - write("plugin: download-only mode enabled, skipping install commands\n"); - } - $action = $download_only ? 'downloading' : 'installing'; + $action = 'installing'; write("plugin: $action: $plugin\n"); // check for URL if (preg_match('#^https?://#',$argv[2])) { @@ -760,11 +776,11 @@ if ($method == 'install') { if (!plugin('noInstall', $plugin_file, $error)) { if ($target != $plugin_file) copy($plugin_file, $target); symlink($target, $symlink); - $status = $download_only ? 'downloaded' : 'installed'; + $status = 'installed'; write("plugin: $plugin $status\n"); my_logger("$plugin $status", $logger); } else { - $script_action = $download_only ? 'staged' : 'executed'; + $script_action = 'executed'; write("script: $plugin $script_action\n"); my_logger("script: $plugin $script_action", $logger); } @@ -879,11 +895,15 @@ if ($method == 'update') { done(1); } // install was successful, save the updated plugin so it installs again next boot - unlink($symlink); - $target = "$boot/$plugin"; - copy($plugin_file, $target); - symlink($target, $symlink); + $target = $download_only ? "$nextboot/$plugin" : "$boot/$plugin"; $status = $download_only ? 'downloaded' : 'updated'; + // For normal update, unlink existing symlink before copying + if (!$download_only) unlink($symlink); + // Ensure target directory exists and copy plugin file + @mkdir(dirname($target), 0770, true); + copy($plugin_file, $target); + // For normal update, create symlink to mark as installed + if (!$download_only) symlink($target, $symlink); write("plugin: $plugin $status\n"); my_logger("$plugin $status", $logger); // run hook scripts for post processing diff --git a/etc/rc.d/rc.local b/etc/rc.d/rc.local index d59ea8fca..5fbe4263d 100755 --- a/etc/rc.d/rc.local +++ b/etc/rc.d/rc.local @@ -190,6 +190,26 @@ else log "Installing /boot/extra packages" ( export -f log; find /boot/extra -maxdepth 1 -type f -exec sh -c 'upgradepkg --terse --install-new "$1" | log' -- "{}" \; ) fi + # Move any downloaded plugins in $CONFIG/plugins-nextboot to $CONFIG/plugins so they can be installed + if [[ -d "$CONFIG/plugins-nextboot" ]]; then + shopt -s nullglob + for ITEM in "$CONFIG/plugins-nextboot"/*; do + if [[ -f "$ITEM" && "$ITEM" == *.plg ]]; then + # Move .plg files + mv "$ITEM" "$CONFIG/plugins/" && log "Moved $(basename "$ITEM") from plugins-nextboot to plugins" + elif [[ -d "$ITEM" ]]; then + # Merge plugin directories + PLUGIN_NAME=$(basename "$ITEM") + DEST_DIR="$CONFIG/plugins/$PLUGIN_NAME" + mkdir -p "$DEST_DIR" + cp -r "$ITEM"/* "$DEST_DIR/" 2>/dev/null + rm -rf "$ITEM" + log "Moved plugin directory $PLUGIN_NAME and contents from plugins-nextboot to plugins" + fi + done + shopt -u nullglob + rmdir "$CONFIG/plugins-nextboot" 2>/dev/null; + fi PRIORITY_PLUGINS=("dynamix.unraid.net.plg") # Install priority plugins first for PRIORITY_PLUGIN in "${PRIORITY_PLUGINS[@]}"; do From 8f2e6edc3a8793e6b78c9f957b523a1bc754dcb2 Mon Sep 17 00:00:00 2001 From: ljm42 Date: Wed, 3 Dec 2025 11:18:32 -0700 Subject: [PATCH 59/63] fix: disable rc.avahidnsconfd by default rc.avahidaemon remains active --- etc/rc.d/rc.M | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/etc/rc.d/rc.M b/etc/rc.d/rc.M index 714bdf993..97d5f6f72 100755 --- a/etc/rc.d/rc.M +++ b/etc/rc.d/rc.M @@ -246,7 +246,8 @@ fi # Start avahi: if [[ -x /etc/rc.d/rc.avahidaemon ]]; then /etc/rc.d/rc.avahidaemon start - /etc/rc.d/rc.avahidnsconfd start + # disable by default, users can start manualy if needed + # /etc/rc.d/rc.avahidnsconfd start fi # Start Samba (a file/print server for Windows machines). From 2f6904e1700d08fb7b4a48de4fe1b29b82befd61 Mon Sep 17 00:00:00 2001 From: ljm42 Date: Wed, 3 Dec 2025 11:23:52 -0700 Subject: [PATCH 60/63] feat: add lsusb details to diagnostics --- emhttp/plugins/dynamix/scripts/diagnostics | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emhttp/plugins/dynamix/scripts/diagnostics b/emhttp/plugins/dynamix/scripts/diagnostics index e6d3be1f8..859c37b89 100755 --- a/emhttp/plugins/dynamix/scripts/diagnostics +++ b/emhttp/plugins/dynamix/scripts/diagnostics @@ -497,7 +497,7 @@ run("lscpu 2>/dev/null|todos >".escapeshellarg("/$diag/system/lscpu.txt")); run("lsscsi -vgl 2>/dev/null|todos >".escapeshellarg("/$diag/system/lsscsi.txt")); run("lspci -knn 2>/dev/null|todos >".escapeshellarg("/$diag/system/lspci.txt")); run("lspci -vv 2>/dev/null| awk -b '/ASPM/{print $0}' RS=|grep -P '(^[a-z0-9:.]+|ASPM |Disabled;|Enabled;)'|todos >".escapeshellarg("/$diag/system/aspm-status.txt")); -run("lsusb 2>/dev/null|todos >".escapeshellarg("/$diag/system/lsusb.txt")); +run("lsusb -vt 2>/dev/null|todos >".escapeshellarg("/$diag/system/lsusb.txt")); run("free -mth 2>/dev/null|todos >".escapeshellarg("/$diag/system/memory.txt")); run("lsof -Pni 2>/dev/null|todos >".escapeshellarg("/$diag/system/lsof.txt")); run("lsmod|sort 2>/dev/null|todos >".escapeshellarg("/$diag/system/lsmod.txt")); From 408fc9721bb3cede4c31a449a1ae0409e2bc60c2 Mon Sep 17 00:00:00 2001 From: ljm42 Date: Wed, 3 Dec 2025 11:41:21 -0700 Subject: [PATCH 61/63] fix typo Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- etc/rc.d/rc.M | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/rc.d/rc.M b/etc/rc.d/rc.M index 97d5f6f72..04bbd0740 100755 --- a/etc/rc.d/rc.M +++ b/etc/rc.d/rc.M @@ -246,7 +246,7 @@ fi # Start avahi: if [[ -x /etc/rc.d/rc.avahidaemon ]]; then /etc/rc.d/rc.avahidaemon start - # disable by default, users can start manualy if needed + # disable by default, users can start manually if needed # /etc/rc.d/rc.avahidnsconfd start fi From 0bdb7849396519508927056a037c7a736e3dbd8b Mon Sep 17 00:00:00 2001 From: ljm42 Date: Thu, 4 Dec 2025 12:42:36 -0700 Subject: [PATCH 62/63] fix: standardize on /etc/hostname /etc/HOSTNAME is no longer set --- etc/rc.d/rc.M | 3 --- etc/rc.d/rc.S.cont | 3 ++- etc/rc.d/rc.avahidaemon | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/etc/rc.d/rc.M b/etc/rc.d/rc.M index 04bbd0740..c875bf3ad 100755 --- a/etc/rc.d/rc.M +++ b/etc/rc.d/rc.M @@ -31,9 +31,6 @@ if [[ -x /etc/rc.d/rc.setterm ]]; then /etc/rc.d/rc.setterm fi -# Set the hostname: -hostname $(cat /etc/HOSTNAME) - # Set the permissions on /var/log/dmesg according to whether the kernel # permits non-root users to access kernel dmesg information: if [[ -r /proc/sys/kernel/dmesg_restrict ]]; then diff --git a/etc/rc.d/rc.S.cont b/etc/rc.d/rc.S.cont index 2e00799cb..90bd58244 100755 --- a/etc/rc.d/rc.S.cont +++ b/etc/rc.d/rc.S.cont @@ -225,7 +225,8 @@ if [[ -r /boot/config/ident.cfg ]]; then . <(/usr/bin/fromdos /etc/HOSTNAME +hostname "$NAME" +hostname -s >/etc/hostname /bin/echo "# Generated" >/etc/hosts /bin/echo "127.0.0.1 $NAME localhost" >>/etc/hosts diff --git a/etc/rc.d/rc.avahidaemon b/etc/rc.d/rc.avahidaemon index acdd434f1..e6b30af48 100755 --- a/etc/rc.d/rc.avahidaemon +++ b/etc/rc.d/rc.avahidaemon @@ -31,7 +31,7 @@ CALLER="avahi" AVAHI="/usr/sbin/avahi-daemon" CONF="/etc/avahi/avahi-daemon.conf" HOSTS="/etc/hosts" -NAME=$( Date: Fri, 5 Dec 2025 12:17:26 -0800 Subject: [PATCH 63/63] Minor changes to /etc/hosts file handling. --- etc/rc.d/rc.S.cont | 6 +++--- etc/rc.d/rc.avahidaemon | 17 ++++++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/etc/rc.d/rc.S.cont b/etc/rc.d/rc.S.cont index 90bd58244..487d658bf 100755 --- a/etc/rc.d/rc.S.cont +++ b/etc/rc.d/rc.S.cont @@ -225,10 +225,10 @@ if [[ -r /boot/config/ident.cfg ]]; then . <(/usr/bin/fromdos /etc/hostname +/bin/hostname "$NAME" +/bin/hostname -s >/etc/hostname /bin/echo "# Generated" >/etc/hosts -/bin/echo "127.0.0.1 $NAME localhost" >>/etc/hosts +/bin/echo "127.0.0.1 $NAME localhost" >>/etc/hosts # LimeTech - restore the configured timezone if [[ $timeZone == custom ]]; then diff --git a/etc/rc.d/rc.avahidaemon b/etc/rc.d/rc.avahidaemon index e6b30af48..33195164f 100755 --- a/etc/rc.d/rc.avahidaemon +++ b/etc/rc.d/rc.avahidaemon @@ -51,19 +51,19 @@ disable(){ sed -ri "s/^#?(use-$1)=.*/\1=no/" $CONF } -# when starting avahidaemon, add name.local to the hosts file +# add name.local to the hosts file add_local_to_hosts(){ local OLD="^127\.0\.0\.1.*" - local NEW="127.0.0.1 $NAME $NAME.local localhost" - sed -i "s/$OLD/$NEW/gm;t" $HOSTS + local NEW="127.0.0.1 $NAME $NAME.local localhost" + sed -i "s/$OLD/$NEW/" $HOSTS return 0 } -# when stopping avahidaemon, remove name.local from the hosts file +# remove name.local from the hosts file remove_local_from_hosts(){ local OLD="^127\.0\.0\.1.*" - local NEW="127.0.0.1 $NAME localhost" - sed -i "s/$OLD/$NEW/gm;t" $HOSTS + local NEW="127.0.0.1 $NAME localhost" + sed -i "s/$OLD/$NEW/" $HOSTS return 0 } @@ -113,7 +113,10 @@ avahid_restart(){ } avahid_reload(){ - $AVAHI --reload 2>/dev/null + if avahid_running; then + add_local_to_hosts + $AVAHI --reload 2>/dev/null + fi } avahid_update(){