Author SHA1 Message Date
copilot-swe-agent[bot]andMeliox 5fc971e0a6 fix: update existing issue body with latest patch error, not just a comment
Co-authored-by: Meliox <5264368+Meliox@users.noreply.github.com>
2026-08-22 18:39:35 +00:00
copilot-swe-agent[bot]andMeliox 2ae9708c4e fix: scope revert to tested mod and add pvemanager version to issue reports
Co-authored-by: Meliox <5264368+Meliox@users.noreply.github.com>
2026-08-22 18:30:13 +00:00
Evan 8a9b7568a7 fix(node_info): hide GPU Details and GPU Fans when no GPU is present (#311)
Empty graphics payloads currently leave the labelled rows visible with
no content. Hide those widgets unless they can render at least one GPU.
2026-08-22 17:49:56 +02:00
MelioxandMeliox f754331c3a fix(node_info): fix stale worker lock detection for zombie processes (#306)
Co-authored-by: Meliox <na>
2026-08-19 21:58:49 +02:00
MelioxandMeliox e46734cd37 fix(node_info): return average and core temps for intel (#302)
Co-authored-by: Meliox <na>
2026-08-17 20:51:43 +02:00
MelioxandMeliox bc87579d77 fix(ci): stop force-pushing empty diff on version-bump branch to prevent PR churn (#303)
Co-authored-by: Meliox <na>
2026-08-17 20:47:42 +02:00
MelioxandMeliox e59d09a788 fix(ci): run weekly patch tests in debian:trixie container to fix missing dependencies(#304)
Co-authored-by: Meliox <na>
2026-08-17 20:47:01 +02:00
MelioxandMeliox cee8d04e36 feat/fix(node_info): expose temp_unit in graphics API response & fix temperature conversion (#295)
Co-authored-by: Meliox <na>
2026-08-14 20:32:10 +02:00
MelioxandMeliox 8ebc2b4753 feat(node_info): add GPU utilization display with color-coded thresholds for NVIDIA GPUs (#296)
Co-authored-by: Meliox <na>
2026-08-14 20:15:43 +02:00
MelioxandMeliox 795b0ebf39 build(ci): fix weekly patch test build steps and align with test-release workflow (#293)
Co-authored-by: Meliox <na>
2026-08-10 18:47:22 +02:00
MelioxandMeliox 43514fc949 fix readme dl link (#290)
Co-authored-by: Meliox <na>
2026-08-09 14:28:48 +02:00
MelioxandMeliox c1c26aff56 Readme update for version 1.0 (#225)
* Readme update for next version

* update readme

* link fix

* language

* more changes

* typo

* more cleanup

---------

Co-authored-by: Meliox <na>
2026-08-09 14:25:14 +02:00
9 changed files with 193 additions and 45 deletions

No files matched your search

+2 -3
View File
@@ -58,7 +58,6 @@ jobs:
if [[ "${{ github.event_name }}" == "push" ]]; then
# Always recreate from main — avoids rebase conflicts after PR merge
git checkout -b chore/version-bump origin/main
git push --force origin chore/version-bump
else
# Label change: check out the existing branch
git fetch origin chore/version-bump
@@ -133,8 +132,8 @@ jobs:
echo "$MAIN_CHANGELOG"
} > debian/changelog
git add debian/changelog
git diff --cached --quiet || git commit -m "chore: update changelog to v${NEW_VERSION}"
git push --force-with-lease origin chore/version-bump
git commit -m "chore: update changelog to v${NEW_VERSION}"
git push --force origin chore/version-bump
- name: Build PR body
env:
+50 -13
View File
@@ -36,11 +36,20 @@ jobs:
test:
needs: discover
runs-on: ubuntu-latest
container:
image: debian:trixie
strategy:
fail-fast: false
matrix:
mod: ${{ fromJSON(needs.discover.outputs.mods) }}
steps:
# ── Base tools (mirrors validate-mods.yml) ──────────────────────────────
- name: Install base tools
run: |
apt-get update
apt-get install -y --no-install-recommends \
ca-certificates git sudo wget xz-utils dpkg nodejs perl
- uses: actions/checkout@v7
with:
fetch-depth: 0
@@ -48,22 +57,32 @@ jobs:
# ── Build (mirrors test-release.yml) ───────────────────────────────────
- name: Install build deps
run: |
sudo apt-get update -qq
sudo apt-get install -y devscripts debhelper build-essential
apt-get update -qq
apt-get install -y devscripts debhelper build-essential
- name: Compute build version
id: version
run: |
BASE_VERSION=$(grep -m1 '(' debian/changelog | sed 's/.*(\(.*\)).*/\1/')
SHORT_SHA=$(git rev-parse --short HEAD)
BUILD_VERSION="${BASE_VERSION}+weekly.${SHORT_SHA}"
echo "build_version=$BUILD_VERSION" >> "$GITHUB_OUTPUT"
echo "Building version: $BUILD_VERSION"
- name: Inject version into Config.pm
run: |
VERSION=$(grep -m1 '(' debian/changelog | sed 's/.*(\(.*\)).*/\1/')
SHORT_SHA=$(git rev-parse --short HEAD)
export BUILD_VERSION="${VERSION}-test-${SHORT_SHA}"
perl -i -pe 'BEGIN { $v = $ENV{BUILD_VERSION} }
s/(our \$VERSION\s*=\s*'"'"')[^'"'"']*'"'"';/$1'"'"'$v'"'"';/
' src/modules/node_info/files/Config.pm
BUILD_VERSION="${{ steps.version.outputs.build_version }}"
sed -i -E "s/^our \\\$VERSION\s*=.*/our \\\$VERSION = '${BUILD_VERSION}';/" \
src/modules/node_info/files/Config.pm
- name: Set package version in changelog
run: |
BUILD_VERSION="${{ steps.version.outputs.build_version }}"
sed -i -E "0,/\(([^)]*)\)/s//(${BUILD_VERSION})/" debian/changelog
- name: Generate dynamic debian rules
run: |
bash build/gen-rules.sh >> debian/rules
bash build/gen-rules.sh conffiles >> debian/pve-mod.conffiles
- name: Build package
run: dpkg-buildpackage -us -uc -b
@@ -72,13 +91,26 @@ jobs:
- name: Fetch upstream Proxmox files
run: bash build/test/fetch-proxmox-files.sh
- name: Capture upstream package versions
id: pve_versions
run: |
PVE_MANAGER_VERSION=$(dpkg-query -W -f='${Version}' pve-manager 2>/dev/null || echo "unknown")
PWT_VERSION=$(dpkg-query -W -f='${Version}' proxmox-widget-toolkit 2>/dev/null || echo "unknown")
echo "pve_manager=${PVE_MANAGER_VERSION}" >> "$GITHUB_OUTPUT"
echo "proxmox_widget_toolkit=${PWT_VERSION}" >> "$GITHUB_OUTPUT"
echo "pve-manager: ${PVE_MANAGER_VERSION}"
echo "proxmox-widget-toolkit: ${PWT_VERSION}"
- name: Install built package
run: sudo apt-get install -y ../pve-mod_*.deb
run: apt-get install -y ../pve-mod_*.deb
- name: Test mod
run: |
set -o pipefail
sudo bash build/test/test-patches.sh "${{ matrix.mod }}" 2>&1 | tee test-output.log
bash build/test/test-patches.sh "${{ matrix.mod }}" 2>&1 | tee test-output.log
- name: Validate source syntax
run: bash build/test/test-syntax.sh "${{ matrix.mod }}"
- name: Report failure as issue
if: failure()
@@ -90,6 +122,10 @@ jobs:
{
echo "The weekly patch test failed for mod \`${MOD}\` against the latest Proxmox VE files."
echo ""
echo "**Proxmox versions tested:**"
echo "- pve-manager: \`${{ steps.pve_versions.outputs.pve_manager }}\`"
echo "- proxmox-widget-toolkit: \`${{ steps.pve_versions.outputs.proxmox_widget_toolkit }}\`"
echo ""
echo "Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo ""
echo "<details><summary>Test log</summary>"
@@ -109,9 +145,10 @@ jobs:
--jq ".[] | select(.title==\"$TITLE\") | .number" | head -n1)
if [[ -n "$EXISTING" ]]; then
gh issue edit "$EXISTING" --body-file issue-body.md
gh issue comment "$EXISTING" --body-file issue-body.md
echo "Commented on existing issue #$EXISTING"
echo "Updated and commented on existing issue #$EXISTING"
else
gh issue create --title "$TITLE" --label patch-break --body-file issue-body.md
echo "Opened new issue for mod $MOD"
fi
fi
+1 -1
View File
@@ -121,7 +121,7 @@ test_one_mod() {
if [[ $ok -eq 0 ]]; then
info "Reverting patches for '$mod'..."
local revert_log
revert_log="$("$REVERT" 2>&1)"
revert_log="$("$REVERT" "$mod" 2>&1)"
echo "$revert_log"
if echo "$revert_log" | grep -qE "could not be reverted cleanly|reported an error"; then
warn "revert-patches.sh reported an unclean revert for mod '$mod'"
+77 -8
View File
@@ -3,11 +3,80 @@ A small collection of scripts and mods for Proxmox Virtual Environment (PVE)
If you find this helpful, a small donation is appreciated, [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=K8XPMSEBERH3W).
## Node sensor readings view
| Version | Description | Status |
|---------|-------------|--------|
| **v2** (current) | Debian package (`pve-mod`) with interactive `pve-mod-configure` wizard | Recommended |
| v1 (legacy) | Standalone bash scripts, manual install | Archived — see [Legacy section](#legacy-v1-shell-scripts) |
---
## Version 2 (beta) — Debian package
Compatibility: PVE 9.0+
TBD PICTURE
`pve-mod` is a Debian package that installs UI patches and an interactive configuration wizard.
### Mods
| Mod | Description | Dependencies |
|--------|-------------|--------------|
| [`node_info`](src/modules/node_info/readme.md) | Displays sensor readings in the node summary panel: CPU, NVMe/HDD/SSD temperatures (°C/°F), fan speeds, RAM temperatures, GPU stats (Intel/NVIDIA), UPS status, and system/motherboard info. <br> Can optionally run as background sensor daemon | - General sensors: `lm-sensors`<br>- HDD/SSD: Kernal module `drivetemp`<br>- UPS: `upsc`<br>- GPU: INTEL `intel-gpu-tools` and/or NVIDIA `nvidia-driver-* `
| [`nag_screen`](src/modules/nag_screen/readme.md) | Removes the subscription nag screen from the PVE web UI. | - |
### How it works
1. The `pve-mod` package installs the mods installation, patch files under `/usr/lib/pve-mod/`.
2. The mods main configuration file and mods can be found under `/etc/pve-mod/`.
2. Running `pve-mod-configure` prompts for which modules to enable and configuration of mods.
3. The wizard applies the selected patches to the PVE system files and restarts `pveproxy`.
### Install
Commands and mod configuration must be run as `root`.
```bash
curl -sL https://raw.githubusercontent.com/Meliox/PVE-mods/refs/heads/main/install.sh | bash
```
Install respective mod dependencies - see Mod table.
Run `pve-mod-configure` to install/uninstall and configure mods.
#### Manual configuration changes
Configuration files are located in `/etc/pve-mod/`. After any manual edits, restart `pveproxy` to apply the changes:
```bash
systemctl restart pveproxy
```
### Uninstall
Commands and mod configuration must be run as `root`.
```bash
apt-get remove pve-mod
```
And remove dependencies needed by respective mods.
### Notes
Multiple node support requires application to be installed on all nodes with identical configuration. (untested)
---
## Legacy / v1 (shell scripts)
> **These scripts are archived.** For new installations, use the [v2 Debian package](#version-2--debian-package) above.
### Node sensor readings view
![Proxmox temp mod](https://github.com/Meliox/PVE-mods/blob/main/pve-mod-sensors.png?raw=true)
Compatibility:
- 9.0-9.2. Newer versions may often work
- Older version (7.x-8.x), use git version from Apr 6th 2025
![Promxox temp mod](https://github.com/Meliox/PVE-mods/blob/main/pve-mod-sensors.png?raw=true)
This bash script installs a modification to the Proxmox Virtual Environment (PVE) web user interface (UI) to display sensor readings in a flexible and readable manner.
The following readings are possible:
@@ -15,7 +84,7 @@ The following readings are possible:
- UPS information via Network Monitoring Tool
- Motherboard information or system information via dmidecode
### How it works
#### How it works
The modification involves the following steps:
1. Backup original files in the home directory/backup
- `/usr/share/pve-manager/js/pvemanagerlib.js`
@@ -38,7 +107,7 @@ Notes:
- UPS support in multi-node setups require identical login credentials across nodes. This has not been fully tested.
- Proxmox upgrades may overwrite modified files; reinstallation of this mod could be required.
### Install
#### Install
Instructions be performed as 'root', as normal users do not have access to the files.
```
@@ -51,7 +120,7 @@ bash pve-mod-gui-sensors.sh install
```
Additionally, adjustments are available in the first part of the script, where paths can be edited, cpucore offset and display information.
## Nag screen deactivation
### Nag screen deactivation
(Tested compatibility: 7.x - 8.3.5)
This bash script installs a modification to the Proxmox Virtual Environment (PVE) web user interface (UI) which deactivates the subscription nag screen.
@@ -65,21 +134,21 @@ The script provides three options:
| `install` | Installs the modification by applying the necessary changes. |
| `uninstall` | Removes the modification by restoring the original files from backups. |
### Install
#### Install
Instructions be performed as 'root', as normal users do not have access to the files.
```
wget https://raw.githubusercontent.com/Meliox/PVE-mods/refs/heads/main/legacy-scripts/pve-mod-nag-screen.sh
bash pve-mod-nag-screen.sh install
```
## Script to update all containers
### Script to update all containers
(Tested compatibility: 7.x - 8.3.5)
This script updates all running Proxmox containers, skipping specified excluded containers, and generates a separate log file for each container.
The script first updates the Proxmox host system, then iterates through each container, updates the container, and reboots it if necessary.
Each container's log file is stored in $log_path and the main script log file is named container-upgrade-main.log.
### Install
#### Install
```
wget https://raw.githubusercontent.com/Meliox/PVE-mods/refs/heads/main/legacy-scripts/updateallcontainers.sh
```
@@ -98,6 +98,7 @@ sub _get_temperature_sensors {
hdd => $config{lm_sensors}{enable_hdd_temp} ? \1 : \0,
other => $config{lm_sensors}{enable_other_temp} ? \1 : \0,
temp_unit => $config{lm_sensors}{temp_unit},
cpu_temp_target => $config{lm_sensors}{cpu_temp_target},
data => { 'PVE MOD lm-sensors Enhanced' => $sensors_json },
});
@@ -112,7 +112,14 @@ sub notify_pve_mod_worker {
# ============================================================================
sub _worker_lock_file_exists {
return -f $pve_mod_worker_lock;
return 0 unless -f $pve_mod_worker_lock;
my $pid = read_lock_pid($pve_mod_worker_lock);
if (defined $pid && $pid =~ /^(\d+)$/ && is_process_alive($1)) {
return 1;
}
debug(__LINE__, "Stale or zombie worker lock found, removing it");
unlink($pve_mod_worker_lock);
return 0;
}
# Forks the worker process and records its PID in the lock file.
@@ -68,9 +68,10 @@ sub _merge_graphics_files {
my $merged = {
Graphics => {
Intel => {},
NVIDIA => {},
AMD => {},
Intel => {},
NVIDIA => {},
AMD => {},
temp_unit => $config{lm_sensors}{temp_unit},
}
};
@@ -134,7 +135,7 @@ sub _load_graphics_data {
\@filepaths,
$graphics_cache,
\&_merge_graphics_files,
{ Graphics => { Intel => {}, NVIDIA => {}, AMD => {} } }
{ Graphics => { Intel => {}, NVIDIA => {}, AMD => {}, temp_unit => $config{lm_sensors}{temp_unit} } }
);
my $intel_count = scalar(keys %{$data->{Graphics}{Intel} // {}});
@@ -224,14 +224,13 @@ Ext.define('PVE.node.StatusView', {
objValue = {};
}
// sensors configuration
const cpuTempHelper = Ext.create('PVE.mod.TempHelper', {srcUnit: PVE.mod.TempHelper.CELSIUS, dstUnit: objValue.temp_unit === 'F' ? PVE.mod.TempHelper.FAHRENHEIT : PVE.mod.TempHelper.CELSIUS});
const cpuTempHelper = Ext.create('PVE.mod.TempHelper', {srcUnit: PVE.mod.TempHelper.CELSIUS, dstUnit: value.temp_unit === 'F' ? PVE.mod.TempHelper.FAHRENHEIT : PVE.mod.TempHelper.CELSIUS});
const cpuKeysI = Object.keys(objValue).filter(item => String(item).startsWith('coretemp-isa-')).sort();
const cpuKeysA = Object.keys(objValue).filter(item => String(item).startsWith('k10temp-pci-')).sort();
const cpuKeysRpi = Object.keys(objValue).filter(item => String(item).startsWith('cpu_thermal-virtual-')).sort();
const bINTEL = cpuKeysI.length > 0 ? true : false;
const INTELPackagePrefix = 'Core' == 'Core' ? 'Core ' : 'Package id';
const INTELPackageCaption = 'Core' == 'Core' ? 'Core' : 'Package';
const INTELPackagePrefix = value.cpu_temp_target == 'Core' ? 'Core ' : 'Package id';
const INTELPackageCaption = value.cpu_temp_target == 'Core' ? 'Core' : 'Package';
let AMDPackagePrefix = 'Tccd';
let AMDPackageCaption = 'CCD';
@@ -392,9 +391,16 @@ Ext.define('PVE.node.StatusView', {
this.hide();
return '';
} else if (!gpuStats || !gpuStats.Graphics) {
this.hide();
return '';
}
// Create temperature helper for GPU temperature conversion
const gpuTempHelper = Ext.create('PVE.mod.TempHelper', {
srcUnit: PVE.mod.TempHelper.CELSIUS,
dstUnit: gpuStats.temp_unit === 'F' ? PVE.mod.TempHelper.FAHRENHEIT : PVE.mod.TempHelper.CELSIUS
});
let html = '<table style="width: 100%; border-collapse: collapse; table-layout: fixed;">';
// Intel GPUs - Secondary details
@@ -444,7 +450,16 @@ Ext.define('PVE.node.StatusView', {
const stats = gpuData.stats;
let details = [];
// GPU Utilization
if (stats.utilization && stats.utilization.gpu) {
const gpuUsage = parseInt(stats.utilization.gpu);
let gpuStyle = '';
if (gpuUsage >= 90) gpuStyle = 'color: #d9534f; font-weight: bold;';
else if (gpuUsage >= 70) gpuStyle = 'color: #f0ad4e; font-weight: bold;';
details.push(`<span style="${gpuStyle}">GPU: ${stats.utilization.gpu}%</span>`);
}
// Memory Utilization
if (stats.utilization && stats.utilization.memory) {
const memUsage = parseInt(stats.utilization.memory);
@@ -467,13 +482,18 @@ Ext.define('PVE.node.StatusView', {
// Temperature
if (stats.temperature) {
const gpuTemp = gpuTempHelper.getTemp(parseFloat(stats.temperature.gpu));
const tempUnit = gpuTempHelper.getUnit();
// Convert thresholds to target unit for comparison
const tempHigh = gpuTempHelper.getTemp(80);
const tempWarn = gpuTempHelper.getTemp(70);
let tempStyle = '';
if (stats.temperature.gpu >= 80) {
if (gpuTemp >= tempHigh) {
tempStyle = 'color: red; font-weight: bold;';
} else if (stats.temperature.gpu >= 70) {
} else if (gpuTemp >= tempWarn) {
tempStyle = 'color: #FFC300; font-weight: bold;';
}
details.push(`Temp: <span style="${tempStyle}">${stats.temperature.gpu}${stats.temperature.unit}</span>`);
details.push(`Temp: <span style="${tempStyle}">${Ext.util.Format.number(gpuTemp, '0')}${tempUnit}</span>`);
}
// Power
@@ -489,9 +509,12 @@ Ext.define('PVE.node.StatusView', {
}
html += '</table>';
return html.indexOf('<tr>') > 0
? '<div style="padding-left: 20px; box-sizing: border-box;">' + html + '</div>'
: '';
if (html.indexOf('<tr>') <= 0) {
this.hide();
return '';
}
this.show();
return '<div style="padding-left: 20px; box-sizing: border-box;">' + html + '</div>';
},
},
{
@@ -520,7 +543,7 @@ Ext.define('PVE.node.StatusView', {
} catch(e) {
objValue = {};
}
const tempHelper = Ext.create('PVE.mod.TempHelper', {srcUnit: PVE.mod.TempHelper.CELSIUS, dstUnit: objValue.temp_unit === 'F' ? PVE.mod.TempHelper.FAHRENHEIT : PVE.mod.TempHelper.CELSIUS});
const tempHelper = Ext.create('PVE.mod.TempHelper', {srcUnit: PVE.mod.TempHelper.CELSIUS, dstUnit: value.temp_unit === 'F' ? PVE.mod.TempHelper.FAHRENHEIT : PVE.mod.TempHelper.CELSIUS});
const drvKeys = Object.keys(objValue).filter(item => String(item).startsWith(addressPrefix)).sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }));
let drvData = [];
drvKeys.forEach((drvKey) => {
@@ -600,7 +623,7 @@ Ext.define('PVE.node.StatusView', {
} catch(e) {
objValue = {};
}
const tempHelper = Ext.create('PVE.mod.TempHelper', {srcUnit: PVE.mod.TempHelper.CELSIUS, dstUnit: objValue.temp_unit === 'F' ? PVE.mod.TempHelper.FAHRENHEIT : PVE.mod.TempHelper.CELSIUS});
const tempHelper = Ext.create('PVE.mod.TempHelper', {srcUnit: PVE.mod.TempHelper.CELSIUS, dstUnit: value.temp_unit === 'F' ? PVE.mod.TempHelper.FAHRENHEIT : PVE.mod.TempHelper.CELSIUS});
const nvmeKeys = Object.keys(objValue).filter(item => String(item).startsWith(addressPrefix)).sort();
let nvmeData = [];
nvmeKeys.forEach((nvmeKey, index) => {
@@ -688,7 +711,7 @@ Ext.define('PVE.node.StatusView', {
} catch(e) {
objValue = {};
}
const tempHelper = Ext.create('PVE.mod.TempHelper', {srcUnit: PVE.mod.TempHelper.CELSIUS, dstUnit: objValue.temp_unit === 'F' ? PVE.mod.TempHelper.FAHRENHEIT : PVE.mod.TempHelper.CELSIUS});
const tempHelper = Ext.create('PVE.mod.TempHelper', {srcUnit: PVE.mod.TempHelper.CELSIUS, dstUnit: value.temp_unit === 'F' ? PVE.mod.TempHelper.FAHRENHEIT : PVE.mod.TempHelper.CELSIUS});
// Keep only keys that do not belong to known categories
const otherKeys = Object.keys(objValue).filter(key =>
@@ -846,6 +869,7 @@ Ext.define('PVE.node.StatusView', {
this.hide();
return '';
} else if (!gpuStats || !gpuStats.Graphics || !gpuStats.Graphics.NVIDIA) {
this.hide();
return '';
}
@@ -873,8 +897,10 @@ Ext.define('PVE.node.StatusView', {
});
if (rows.length === 0) {
this.hide();
return '';
}
this.show();
return '<div style="padding-left: 20px; box-sizing: border-box;"><table style="width: 100%; border-collapse: collapse; table-layout: fixed;">' + rows.join('') + '</table></div>';
},
+9 -1
View File
@@ -91,7 +91,15 @@ sub read_sysfs {
sub is_process_alive {
my ($pid) = @_;
return -d "/proc/$pid";
return 0 unless -d "/proc/$pid";
# A zombie still has /proc/<pid> but is dead - otherwise a stale
# worker lock is never cleaned up.
if (open my $fh, '<', "/proc/$pid/stat") {
my $line = <$fh>;
close $fh;
return 0 if defined $line && $line =~ /\)\s+Z\s/;
}
return 1;
}
sub read_lock_pid {