first draft for testing

This commit is contained in:
Meliox 2026-06-07 12:55:03 +02:00
parent 2b62a732e8
commit d9ff050676
19 changed files with 1283 additions and 50 deletions

View File

@ -1,45 +0,0 @@
name: Build DEB
on:
push:
tags:
- 'v*'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create package structure
run: |
mkdir -p package/DEBIAN
mkdir -p package/usr/local/bin
cp myscript.sh package/usr/local/bin/myapp
chmod +x package/usr/local/bin/myapp
- name: Create control file
run: |
cat > package/DEBIAN/control <<EOF
Package: myapp
Version: ${GITHUB_REF_NAME#v}
Section: utils
Priority: optional
Architecture: amd64
Maintainer: Your Name <you@example.com>
Description: Example package built in GitHub Actions
EOF
- name: Build .deb
run: |
dpkg-deb --build package
mv package.deb myapp_${GITHUB_REF_NAME#v}_amd64.deb
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: deb-package
path: "*.deb"

83
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,83 @@
name: Build and Release
# Triggered when a "Release vX.Y.Z" PR is merged into main
# (detected by a push to main that changes debian/changelog).
on:
push:
branches:
- main
paths:
- 'debian/changelog'
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from changelog
id: version
run: |
VERSION=$(grep -m1 '(' debian/changelog | sed 's/.*(\(.*\)).*/\1/')
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"
- name: Install build dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y debhelper dpkg-dev
- name: Build deb package
run: |
dpkg-buildpackage -us -uc -b
ls -lh ../pve-mod_*.deb
- name: Collect release assets
id: assets
run: |
DEB=$(ls ../pve-mod_${{ steps.version.outputs.version }}_all.deb)
echo "deb=$DEB" >> "$GITHUB_OUTPUT"
- name: Extract release notes from changelog
id: notes
run: |
# Pull the top changelog entry (lines between first and second "^pve-mod (")
NOTES=$(awk '
/^pve-mod \(/ { if (found) exit; found=1; next }
found && /^ --/ { exit }
found { print }
' debian/changelog | grep '^\s*\*' | sed 's/^\s*\* /- /')
EOF=$(dd if=/dev/urandom bs=15 count=1 2>/dev/null | base64)
echo "notes<<$EOF" >> "$GITHUB_OUTPUT"
echo "$NOTES" >> "$GITHUB_OUTPUT"
echo "$EOF" >> "$GITHUB_OUTPUT"
- name: Create Git tag
env:
TAG: ${{ steps.version.outputs.tag }}
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag "$TAG"
git push origin "$TAG"
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.version.outputs.tag }}
NOTES: ${{ steps.notes.outputs.notes }}
DEB: ${{ steps.assets.outputs.deb }}
run: |
gh release create "$TAG" \
--title "pve-mod $TAG" \
--notes "$NOTES" \
"$DEB" \
"src/Scripts/install.sh"

117
.github/workflows/version-bump.yml vendored Normal file
View File

@ -0,0 +1,117 @@
name: Version Bump
# Triggered by any push to src/ on non-main branches.
# Increments the patch version in debian/changelog, commits it,
# and opens (or updates) a "Release vX.Y.Z" PR to main.
# The changelog commit touches only debian/changelog (not src/),
# so it does not re-trigger this workflow.
on:
push:
branches-ignore:
- main
paths:
- 'src/**'
permissions:
contents: write
pull-requests: write
jobs:
bump:
runs-on: ubuntu-latest
# Skip commits made by this workflow itself
if: github.actor != 'github-actions[bot]'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Extract and bump version
id: version
run: |
CURRENT=$(grep -m1 '(' debian/changelog | sed 's/.*(\(.*\)).*/\1/')
MAJOR=$(echo "$CURRENT" | cut -d. -f1)
MINOR=$(echo "$CURRENT" | cut -d. -f2)
PATCH=$(echo "$CURRENT" | cut -d. -f3)
NEW="${MAJOR}.${MINOR}.$((PATCH + 1))"
echo "current=$CURRENT" >> "$GITHUB_OUTPUT"
echo "new=$NEW" >> "$GITHUB_OUTPUT"
- name: Build PR body from src/ commits since last release
id: commits
run: |
LAST_TAG=$(git tag -l 'v*' | sort -V | tail -n1)
if [[ -n "$LAST_TAG" ]]; then
LOG=$(git log "${LAST_TAG}..HEAD" --oneline -- src/ | head -50)
else
LOG=$(git log --oneline -- src/ | head -50)
fi
# Escape for GitHub multiline output
EOF=$(dd if=/dev/urandom bs=15 count=1 2>/dev/null | base64)
echo "log<<$EOF" >> "$GITHUB_OUTPUT"
echo "$LOG" >> "$GITHUB_OUTPUT"
echo "$EOF" >> "$GITHUB_OUTPUT"
- name: Prepend new changelog entry
env:
NEW_VERSION: ${{ steps.version.outputs.new }}
COMMITS: ${{ steps.commits.outputs.log }}
run: |
DATE=$(date -R)
{
echo "pve-mod ($NEW_VERSION) stable; urgency=low"
echo ""
while IFS= read -r line; do
[[ -n "$line" ]] && echo " * $line"
done <<< "$COMMITS"
[[ -z "$COMMITS" ]] && echo " * Automated version bump."
echo ""
echo " -- github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> $DATE"
echo ""
cat debian/changelog
} > debian/changelog.new
mv debian/changelog.new debian/changelog
- name: Commit changelog
env:
NEW_VERSION: ${{ steps.version.outputs.new }}
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add debian/changelog
git commit -m "Release v${NEW_VERSION}"
git push
- name: Create or update release PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NEW_VERSION: ${{ steps.version.outputs.new }}
COMMITS: ${{ steps.commits.outputs.log }}
BRANCH: ${{ github.ref_name }}
run: |
TITLE="Release v${NEW_VERSION}"
BODY="## Changes since last release
${COMMITS:-No src/ changes detected.}
---
*Merge this PR to build and publish the \`pve-mod_${NEW_VERSION}_all.deb\` release.*"
# Check if a PR for this version already exists
EXISTING=$(gh pr list \
--search "\"$TITLE\" in:title" \
--json number --jq '.[0].number' 2>/dev/null || true)
if [[ -n "$EXISTING" ]]; then
gh pr edit "$EXISTING" --body "$BODY"
echo "Updated existing PR #$EXISTING"
else
gh pr create \
--title "$TITLE" \
--body "$BODY" \
--base main \
--head "$BRANCH"
fi

5
debian/changelog vendored Normal file
View File

@ -0,0 +1,5 @@
pve-mod (1.0.0) stable; urgency=low
* Initial release.
-- Meliox <meliox@users.noreply.github.com> Sat, 07 Jun 2026 00:00:00 +0000

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
13

21
debian/control vendored Normal file
View File

@ -0,0 +1,21 @@
Source: pve-mod
Section: misc
Priority: optional
Maintainer: Meliox <meliox@users.noreply.github.com>
Build-Depends: debhelper-compat (= 13)
Standards-Version: 4.6.2
Homepage: https://github.com/Meliox/PVE-mods
Package: pve-mod
Architecture: all
Depends: ${misc:Depends}, perl, librrds-perl
Recommends: lm-sensors, nut-client
Suggests: igt-gpu-tools
Description: Proxmox VE UI modifications and sensor monitoring
Extends the Proxmox VE web interface with sensor readings including
CPU, GPU, NVMe/HDD/SSD temperatures, fan speeds, and RAM temperatures
via lm-sensors. Optionally adds UPS monitoring via Network UPS Tools,
system/motherboard information via dmidecode, GPU historical data graphs,
and removes the subscription nag screen.
.
After installation, run: pve-mod-configure

1
debian/pve-mod.conffiles vendored Normal file
View File

@ -0,0 +1 @@
/etc/pve-mod/pve-mod.conf

36
debian/pve-mod.postinst vendored Normal file
View File

@ -0,0 +1,36 @@
#!/bin/bash
set -e
NODES_PM="/usr/share/perl5/PVE/API2/Nodes.pm"
case "$1" in
configure)
# Check for legacy bash-script install markers and warn the user.
if grep -qF '$res->{sensorsJSONOutput}' "$NODES_PM" 2>/dev/null || \
grep -qF '$res->{systemInfo}' "$NODES_PM" 2>/dev/null; then
echo ""
echo "WARNING: A legacy pve-mod bash-script installation was detected in Nodes.pm."
echo "The old installation should be removed first to avoid conflicts:"
echo " bash /path/to/pve-mod-gui-sensors.sh uninstall"
echo ""
echo "Skipping patch application. Run 'pve-mod-configure' after removing the old install."
echo ""
else
# Apply patches for any enabled modules (idempotent, safe on upgrades).
/usr/lib/pve-mod/apply-patches.sh 2>&1 || true
fi
if [ -z "$2" ]; then
echo ""
echo "pve-mod installed successfully."
echo "Run 'pve-mod-configure' to enable and configure modules."
echo ""
fi
;;
abort-upgrade|abort-remove|abort-deconfigure)
;;
esac
#DEBHELPER#
exit 0

18
debian/pve-mod.postrm vendored Normal file
View File

@ -0,0 +1,18 @@
#!/bin/bash
set -e
case "$1" in
remove)
# Remove apt hook if it was installed via pve-mod-configure.
rm -f /etc/apt/apt.conf.d/99-pve-mod
;;
purge)
rm -f /etc/apt/apt.conf.d/99-pve-mod
rm -rf /etc/pve-mod
rm -rf /var/lib/pve-mod
;;
esac
#DEBHELPER#
exit 0

18
debian/pve-mod.prerm vendored Normal file
View File

@ -0,0 +1,18 @@
#!/bin/bash
set -e
case "$1" in
remove|deconfigure)
# Revert PVE file patches before files are removed.
if [ -x /usr/lib/pve-mod/revert-patches.sh ]; then
/usr/lib/pve-mod/revert-patches.sh 2>&1 || true
fi
;;
upgrade)
# On upgrade, leave patches in place; postinst will re-apply them.
;;
esac
#DEBHELPER#
exit 0

46
debian/rules vendored Normal file
View File

@ -0,0 +1,46 @@
#!/usr/bin/make -f
%:
dh $@
override_dh_install:
# PVE API2 facade
install -Dm644 src/PVENodeInfo/PveMod_SensorInfo.pm \
debian/pve-mod/usr/share/perl5/PVE/API2/PVEMod_SensorInfo.pm
# PVEMod core modules (strip PVEMod_ prefix)
install -Dm644 src/PVENodeInfo/PVEMod_Config.pm \
debian/pve-mod/usr/share/perl5/PVE/PVEMod/Config.pm
install -Dm644 src/PVENodeInfo/PVEMod_Utils.pm \
debian/pve-mod/usr/share/perl5/PVE/PVEMod/Utils.pm
install -Dm644 src/PVENodeInfo/PVEMod_Store.pm \
debian/pve-mod/usr/share/perl5/PVE/PVEMod/Store.pm
install -Dm644 src/PVENodeInfo/PVEMod_ProcessManager.pm \
debian/pve-mod/usr/share/perl5/PVE/PVEMod/ProcessManager.pm
# Collector plugins
install -Dm644 src/PVENodeInfo/PVEMod_Collectors/Intel.pm \
debian/pve-mod/usr/share/perl5/PVE/PVEMod/Collector/Intel.pm
install -Dm644 src/PVENodeInfo/PVEMod_Collectors/Nvidia.pm \
debian/pve-mod/usr/share/perl5/PVE/PVEMod/Collector/Nvidia.pm
install -Dm644 src/PVENodeInfo/PVEMod_Collectors/Amd.pm \
debian/pve-mod/usr/share/perl5/PVE/PVEMod/Collector/Amd.pm
install -Dm644 src/PVENodeInfo/PVEMod_Collectors/LmSensors.pm \
debian/pve-mod/usr/share/perl5/PVE/PVEMod/Collector/LmSensors.pm
install -Dm644 src/PVENodeInfo/PVEMod_Collectors/Ups.pm \
debian/pve-mod/usr/share/perl5/PVE/PVEMod/Collector/Ups.pm
install -Dm644 src/PVENodeInfo/PVEMod_Collectors/systemInformation.pm \
debian/pve-mod/usr/share/perl5/PVE/PVEMod/Collector/SystemInformation.pm
# JS module (rename to match loader reference)
install -Dm644 src/PVENodeInfo/PveMod_pvemanagerlib.js \
debian/pve-mod/usr/share/pve-manager/js/PveMod_PveNodeStatusView.js
# Patch helpers
install -Dm755 src/PVENodeInfo/apply-patches.sh \
debian/pve-mod/usr/lib/pve-mod/apply-patches.sh
install -Dm755 src/PVENodeInfo/revert-patches.sh \
debian/pve-mod/usr/lib/pve-mod/revert-patches.sh
install -Dm755 src/pve-mod-apt-hook.sh \
debian/pve-mod/usr/lib/pve-mod/apt-hook.sh
# Configure tool
install -Dm755 src/Scripts/pve-mod-configure \
debian/pve-mod/usr/sbin/pve-mod-configure
# Default config
install -Dm644 src/pve-mod.conf \
debian/pve-mod/etc/pve-mod/pve-mod.conf

1
debian/source/format vendored Normal file
View File

@ -0,0 +1 @@
3.0 (native)

61
install.sh Normal file
View File

@ -0,0 +1,61 @@
#!/usr/bin/env bash
# install.sh — Bootstrap installer for pve-mod
# Usage: curl -sL https://github.com/Meliox/PVE-mods/releases/latest/download/install.sh | bash
set -euo pipefail
REPO="Meliox/PVE-mods"
API_URL="https://api.github.com/repos/${REPO}/releases/latest"
#region helpers
info() { echo -e "\e[0;32m[pve-mod] ${1}\e[0m"; }
err() { echo -e "\e[0;31m[pve-mod] ERROR: ${1}\e[0m" >&2; exit 1; }
#endregion helpers
# ── Prerequisite checks ───────────────────────────────────────────────────────
[[ $EUID -eq 0 ]] || err "This installer must be run as root."
dpkg -l proxmox-ve &>/dev/null 2>&1 || \
err "This system does not appear to be running Proxmox VE."
for cmd in curl dpkg; do
command -v "$cmd" &>/dev/null || err "Required command not found: $cmd"
done
# ── Fetch latest release metadata ─────────────────────────────────────────────
info "Fetching latest release information..."
RELEASE_JSON=$(curl -sL "$API_URL") || err "Failed to contact GitHub API."
# Extract .deb download URL (no jq dependency)
DEB_URL=$(echo "$RELEASE_JSON" \
| grep '"browser_download_url"' \
| grep '\.deb"' \
| sed 's/.*"browser_download_url": "\([^"]*\)".*/\1/' \
| head -n1)
[[ -n "$DEB_URL" ]] || err "No .deb package found in the latest release."
VERSION=$(echo "$RELEASE_JSON" \
| grep '"tag_name"' \
| sed 's/.*"tag_name": "\([^"]*\)".*/\1/' \
| head -n1)
info "Installing pve-mod ${VERSION}..."
# ── Download and install ───────────────────────────────────────────────────────
TMP=$(mktemp /tmp/pve-mod-XXXXXX.deb)
trap 'rm -f "$TMP"' EXIT
curl -sL -o "$TMP" "$DEB_URL" || err "Failed to download package from $DEB_URL"
dpkg -i "$TMP" || {
info "Resolving missing dependencies..."
apt-get install -f -y
dpkg -i "$TMP"
}
# ── Done ──────────────────────────────────────────────────────────────────────
echo ""
info "pve-mod ${VERSION} installed successfully."
info "Run 'pve-mod-configure' to enable and configure modules."
echo ""

View File

@ -30,9 +30,10 @@ our $process_type = 'main'; # 'main', 'worker', or 'collector'
our %config = ( our %config = (
gpu => { gpu => {
intel_enabled => 1, intel_enabled => 0,
amd_enabled => 0, amd_enabled => 0,
nvidia_enabled => 0, nvidia_enabled => 0,
gpu_history => 0,
}, },
debug => { debug => {
log_enabled => 0, log_enabled => 0,
@ -54,14 +55,22 @@ our %config = (
collector_timeout => 10, # stop collectors after N seconds of inactivity collector_timeout => 10, # stop collectors after N seconds of inactivity
}, },
lm_sensors => { lm_sensors => {
enabled => 1, enabled => 0,
enable_cpu => 0,
cpu_temp_target => 'Core',
enable_ram_temp => 0,
enable_hdd_temp => 0,
enable_nvme_temp => 0,
enable_fan_speed => 0,
display_zero_speed_fans => 0,
temp_unit => 'C',
}, },
ups => { ups => {
enabled => 1, enabled => 0,
device_name => 'ups@192.168.3.2', device_name => 'ups@localhost',
}, },
system_info => { system_info => {
enabled => 1, enabled => 0,
type => 1, # 1 = System (dmidecode -t 1), 2 = Baseboard/Motherboard (dmidecode -t 2) type => 1, # 1 = System (dmidecode -t 1), 2 = Baseboard/Motherboard (dmidecode -t 2)
}, },
paths => { paths => {
@ -88,4 +97,52 @@ our $startup_lock = "$pve_mod_working_dir/startup.lock";
our $RRD_SOCKET = '/var/run/rrdcached.sock'; our $RRD_SOCKET = '/var/run/rrdcached.sock';
our $RRD_BASE = '/var/lib/rrdcached/db/pve-mod-gpu'; our $RRD_BASE = '/var/lib/rrdcached/db/pve-mod-gpu';
# ============================================================================
# Load configuration from /etc/pve-mod/pve-mod.conf (INI format).
# Merges file values into %config, overriding compiled-in defaults.
# Safe to call multiple times; silently skips missing file or unknown keys.
# ============================================================================
sub _load_ini_file {
my $path = '/etc/pve-mod/pve-mod.conf';
return unless -f $path;
open my $fh, '<', $path or return;
my $section = '';
while (my $line = <$fh>) {
chomp $line;
$line =~ s/#.*//; # strip inline comments
$line =~ s/^\s+|\s+$//g; # trim whitespace
next unless length $line;
if ($line =~ /^\[([^\]]+)\]$/) {
$section = $1;
next;
}
if ($line =~ /^([^=]+)=(.*)$/) {
my ($key, $val) = ($1, $2);
$key =~ s/^\s+|\s+$//g;
$val =~ s/^\s+|\s+$//g;
if ($section eq 'gpu' && exists $config{gpu}{$key}) {
$config{gpu}{$key} = $val;
}
elsif ($section eq 'lm_sensors' && exists $config{lm_sensors}{$key}) {
$config{lm_sensors}{$key} = $val;
}
elsif ($section eq 'ups' && exists $config{ups}{$key}) {
$config{ups}{$key} = $val;
}
elsif ($section eq 'system_info' && exists $config{system_info}{$key}) {
$config{system_info}{$key} = $val;
}
}
}
close $fh;
}
_load_ini_file();
1; 1;

View File

@ -0,0 +1,260 @@
#!/usr/bin/env bash
# /usr/lib/pve-mod/apply-patches.sh
#
# Applies pve-mod patches to Proxmox VE system files.
# Reads /etc/pve-mod/pve-mod.conf to determine which modules are enabled.
# Idempotent: safe to call multiple times (e.g. from apt hook after PVE upgrade).
CONF_FILE="/etc/pve-mod/pve-mod.conf"
BACKUP_DIR="/var/lib/pve-mod/backup"
NODES_PM="/usr/share/perl5/PVE/API2/Nodes.pm"
PVE_MANAGER_JS="/usr/share/pve-manager/js/pvemanagerlib.js"
PVE_MOD_JS="/usr/share/pve-manager/js/PveMod_PveNodeStatusView.js"
PROXMOXLIB_JS="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
PROXMOXLIB_MIN_JS="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js"
GPU_RRD_DIR="/var/lib/rrdcached/db/pve-mod-gpu"
info() { echo "[pve-mod] $*"; }
warn() { echo "[pve-mod] WARNING: $*" >&2; }
# Read one value from the INI config file; prints $default if not found.
read_conf() {
local section="$1" key="$2" default="${3:-0}"
if [[ ! -f "$CONF_FILE" ]]; then
echo "$default"
return
fi
local val
val=$(awk -F= -v sec="[$section]" -v k="$key" '
/^\[/ { in_sec = ($0 == sec) }
in_sec && /^[^#=]+=/ {
gsub(/^[[:space:]]+|[[:space:]]+$/, "", $1)
if ($1 == k) {
gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2)
print $2; exit
}
}
' "$CONF_FILE")
echo "${val:-$default}"
}
backup_file() {
local src="$1"
[[ -f "$src" ]] || return 0
local name ts
name=$(basename "$src")
ts=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
cp "$src" "$BACKUP_DIR/${name}.${ts}"
info "Backed up $(basename "$src")$BACKUP_DIR/${name}.${ts}"
}
# ── Read enabled modules ──────────────────────────────────────────────────────
NODE_INFO=$(read_conf modules node_info 0)
NAG_SCREEN=$(read_conf modules nag_screen 0)
GPU_HISTORY=$(read_conf gpu gpu_history 0)
CHANGED=false
# ── node-info: Nodes.pm ───────────────────────────────────────────────────────
if [[ "$NODE_INFO" == "1" ]]; then
if ! grep -qF "use PVE::API2::PVEMod_SensorInfo" "$NODES_PM" 2>/dev/null; then
backup_file "$NODES_PM"
python3 - "$NODES_PM" <<'PYEOF'
import sys, re
path = sys.argv[1]
content = open(path).read()
if 'use PVE::API2::PVEMod_SensorInfo' in content:
sys.exit(0)
m = re.search(r'^([ \t]*)my \$dinfo = df\(\'\/\', 1\);', content, re.MULTILINE)
if not m:
print("ERROR: Anchor 'my $dinfo = df' not found in Nodes.pm", file=sys.stderr)
sys.exit(1)
indent = m.group(1)
insertion = (
f"{indent}# Collect sensor data from PveMod_SensorInfo\n"
f"{indent}use PVE::API2::PVEMod_SensorInfo;\n"
f"{indent}$res->{{PveMod_JsonSensorInfo}} = PVE::API2::PVEMod_SensorInfo::get_sensors_info();\n"
f"{indent}$res->{{PveMod_graphicsInfo}} = PVE::API2::PVEMod_SensorInfo::get_pve_mod_version();\n"
f"{indent}$res->{{PveMod_upsInfo}} = PVE::API2::PVEMod_SensorInfo::get_ups_info();\n"
f"{indent}$res->{{pveMod_sensorInfo_systemInfo}} = PVE::API2::PVEMod_SensorInfo::get_system_information();\n"
)
content = content[:m.start()] + insertion + content[m.start():]
open(path, 'w').write(content)
PYEOF
info "Patched Nodes.pm"
CHANGED=true
fi
# ── node-info: pvemanagerlib.js ───────────────────────────────────────────
if ! grep -qF "PveMod_PveNodeStatusView.js" "$PVE_MANAGER_JS" 2>/dev/null; then
backup_file "$PVE_MANAGER_JS"
python3 - "$PVE_MANAGER_JS" <<'PYEOF'
import sys, re
path = sys.argv[1]
content = open(path).read()
if 'PveMod_PveNodeStatusView.js' in content:
sys.exit(0)
# Comment out original StatusView definition
content = re.sub(
r"(?m)^(Ext\.define\('PVE\.node\.StatusView',.*?^}\);)",
lambda m: '\n'.join('// ' + line for line in m.group(1).split('\n')),
content, flags=re.DOTALL
)
# Comment out original Summary definition
content = re.sub(
r"(?m)^(Ext\.define\('PVE\.node\.Summary',.*?^}\);)",
lambda m: '\n'.join('// ' + line for line in m.group(1).split('\n')),
content, flags=re.DOTALL
)
# Insert dynamic loader before the now-commented StatusView block
loader = (
"// Load custom PVE.node.StatusView from external module\n"
"Ext.Loader.loadScript({\n"
" url: '/pve2/js/PveMod_PveNodeStatusView.js',\n"
" onLoad: function() { },\n"
" onError: function() { console.error('Failed to load PveMod_PveNodeStatusView.js'); }\n"
"});\n"
)
content = re.sub(
r"(// Ext\.define\('PVE\.node\.StatusView',)",
loader + r'\1',
content, count=1
)
open(path, 'w').write(content)
PYEOF
info "Patched pvemanagerlib.js"
CHANGED=true
fi
fi
# ── node-info: GPU RRD history ────────────────────────────────────────────────
if [[ "$NODE_INFO" == "1" && "$GPU_HISTORY" == "1" ]]; then
if ! grep -qF "gpurrddata" "$NODES_PM" 2>/dev/null; then
# Register method in the node sub-path list
sed -i "s/{ name => 'rrddata' },/{ name => 'rrddata' },\n { name => 'gpurrddata' },/" "$NODES_PM"
# Append gpurrddata method definition after the rrddata code block
python3 - "$NODES_PM" <<'PYEOF'
import sys, re
path = sys.argv[1]
content = open(path).read()
if 'gpurrddata' in content:
sys.exit(0)
method = r"""
__PACKAGE__->register_method({
name => 'gpurrddata',
path => 'gpurrddata',
method => 'GET',
protected => 1,
proxyto => 'node',
permissions => {
check => ['perm', '/nodes/{node}', ['Sys.Audit']],
},
description => "Read GPU RRD statistics",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
card => {
description => "The GPU card identifier (e.g. card0, nvidia0).",
type => 'string',
pattern => '[a-zA-Z0-9]+',
},
timeframe => {
description => "Specify the time frame you are interested in.",
type => 'string',
enum => ['hour', 'day', 'week', 'month', 'year', 'decade'],
},
cf => {
description => "The RRD consolidation function",
type => 'string',
enum => ['AVERAGE', 'MAX'],
optional => 1,
},
},
},
returns => {
type => "array",
items => {
type => "object",
properties => {},
},
},
code => sub {
my ($param) = @_;
my $nodename = PVE::INotify::nodename();
my $card = $param->{card};
die "invalid card name\n" unless $card =~ /^[a-zA-Z0-9]+$/;
return PVE::RRD::create_rrd_data(
"pve-mod-gpu/$nodename/$card", $param->{timeframe}, $param->{cf},
);
},
});
"""
# Insert before the final 1; at end of file
content = re.sub(r'\n1;\s*$', method + '\n1;\n', content)
open(path, 'w').write(content)
PYEOF
mkdir -p "$GPU_RRD_DIR"
chown www-data:www-data "$GPU_RRD_DIR" 2>/dev/null || true
info "Installed gpurrddata API endpoint"
CHANGED=true
fi
fi
# ── nag-screen: proxmoxlib.js ─────────────────────────────────────────────────
if [[ "$NAG_SCREEN" == "1" ]]; then
if ! grep -qF "// disable subscription nag screen" "$PROXMOXLIB_JS" 2>/dev/null; then
backup_file "$PROXMOXLIB_JS"
python3 - "$PROXMOXLIB_JS" <<'PYEOF'
import sys, re
path = sys.argv[1]
content = open(path).read()
if '// disable subscription nag screen' in content:
sys.exit(0)
m = re.search(r'(checked_command:\s*function\s*\(orig_cmd\)\s*\{)', content)
if not m:
print("ERROR: checked_command pattern not found in proxmoxlib.js", file=sys.stderr)
sys.exit(1)
insert = "\n\t\t\t// disable subscription nag screen\n\t\t\torig_cmd();\n\t\t\treturn;"
pos = m.end()
content = content[:pos] + insert + content[pos:]
open(path, 'w').write(content)
PYEOF
info "Patched proxmoxlib.js (nag screen)"
CHANGED=true
fi
if [[ ! -L "$PROXMOXLIB_MIN_JS" ]]; then
backup_file "$PROXMOXLIB_MIN_JS"
mv "$PROXMOXLIB_MIN_JS" "$BACKUP_DIR/proxmoxlib.min.js.$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true
ln -sf "$PROXMOXLIB_JS" "$PROXMOXLIB_MIN_JS"
info "Symlinked proxmoxlib.min.js → proxmoxlib.js"
CHANGED=true
fi
fi
# ── restart pveproxy if anything changed ──────────────────────────────────────
if [[ "$CHANGED" == "true" ]]; then
info "Restarting pveproxy..."
systemctl restart pveproxy 2>/dev/null || true
fi

View File

@ -0,0 +1,69 @@
#!/usr/bin/env bash
# /usr/lib/pve-mod/revert-patches.sh
#
# Reverts all patches applied by apply-patches.sh.
# Restores PVE system files from backups in /var/lib/pve-mod/backup/.
# Called by prerm before package files are removed.
BACKUP_DIR="/var/lib/pve-mod/backup"
NODES_PM="/usr/share/perl5/PVE/API2/Nodes.pm"
PVE_MANAGER_JS="/usr/share/pve-manager/js/pvemanagerlib.js"
PVE_MOD_JS="/usr/share/pve-manager/js/PveMod_PveNodeStatusView.js"
PROXMOXLIB_JS="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
PROXMOXLIB_MIN_JS="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js"
info() { echo "[pve-mod] $*"; }
warn() { echo "[pve-mod] WARNING: $*" >&2; }
restore_latest() {
local name="$1" target="$2"
local latest
latest=$(find "$BACKUP_DIR" -name "${name}.*" -type f -printf '%T+ %p\n' 2>/dev/null \
| sort -r | head -n1 | awk '{print $2}')
if [[ -n "$latest" ]]; then
cp "$latest" "$target"
info "Restored $(basename "$target") from backup"
else
warn "No backup found for ${name}; $target not restored"
fi
}
CHANGED=false
# ── node-info: Nodes.pm ───────────────────────────────────────────────────────
if grep -qF "use PVE::API2::PVEMod_SensorInfo" "$NODES_PM" 2>/dev/null; then
restore_latest "Nodes.pm" "$NODES_PM"
CHANGED=true
fi
# ── node-info: pvemanagerlib.js ───────────────────────────────────────────────
if grep -qF "PveMod_PveNodeStatusView.js" "$PVE_MANAGER_JS" 2>/dev/null; then
restore_latest "pvemanagerlib.js" "$PVE_MANAGER_JS"
CHANGED=true
fi
# ── node-info: JS module file ─────────────────────────────────────────────────
if [[ -f "$PVE_MOD_JS" ]]; then
rm -f "$PVE_MOD_JS"
info "Removed PveMod_PveNodeStatusView.js"
CHANGED=true
fi
# ── nag-screen: proxmoxlib.min.js symlink ────────────────────────────────────
if [[ -L "$PROXMOXLIB_MIN_JS" ]]; then
rm -f "$PROXMOXLIB_MIN_JS"
restore_latest "proxmoxlib.min.js" "$PROXMOXLIB_MIN_JS"
CHANGED=true
fi
# ── nag-screen: proxmoxlib.js ────────────────────────────────────────────────
if grep -qF "// disable subscription nag screen" "$PROXMOXLIB_JS" 2>/dev/null; then
restore_latest "proxmoxlib.js" "$PROXMOXLIB_JS"
CHANGED=true
fi
# ── restart pveproxy if anything changed ─────────────────────────────────────
if [[ "$CHANGED" == "true" ]]; then
info "Restarting pveproxy..."
systemctl restart pveproxy 2>/dev/null || true
fi

View File

@ -0,0 +1,438 @@
#!/usr/bin/env bash
#
# pve-mod-configure - Interactive configuration tool for pve-mod
# Detects hardware, asks the user which features to enable, writes
# /etc/pve-mod/pve-mod.conf, and applies patches to PVE system files.
set -euo pipefail
CONF_FILE="/etc/pve-mod/pve-mod.conf"
APT_HOOK_FILE="/etc/apt/apt.conf.d/99-pve-mod"
APPLY_PATCHES="/usr/lib/pve-mod/apply-patches.sh"
NODES_PM="/usr/share/perl5/PVE/API2/Nodes.pm"
KNOWN_CPU_SENSORS=("coretemp-isa-" "k10temp-pci-")
#region helpers
msgb() { echo -e "\e[1m${1}\e[0m"; }
info() { echo -e "\e[0;32m[info] ${1}\e[0m"; }
warn() { echo -e "\e[0;33m[warning] ${1}\e[0m"; }
err() { echo -e "\e[0;31m[error] ${1}\e[0m"; exit 1; }
ask() {
local prompt="$1" response
read -r -p $'\n\e[1;36m'"${prompt}:"$'\e[0m ' response
echo "$response"
}
bool() { [[ "$1" == true ]] && echo 1 || echo 0; }
#endregion helpers
sanitize_sensors_output() {
local input="$1"
echo "$input" | perl -0777 -pe '
s/ERROR:.+\s(\w+):\s(.+)/"$1": 0.000,/g;
s/ERROR:.+\s(\w+)!/"$1": 0.000,/g;
s/,\s*(\})/$1/g;
s/\bNaN\b/null/g;
s/"SODIMM"\s*:\s*\{\s*"temp(\d+)_input"/"SODIMM $1": {\n "temp$1_input"/g;
s/"([^"]*Fan[^"]*)"\s*:\s*\{\s*"fan(\d+)_input"/"$1 $2": {\n "fan$2_input"/g;
' | python3 -m json.tool 2>/dev/null || echo "$input"
}
_check_or_install_tool() {
local cmd="$1" pkg="$2" description="$3"
if command -v "$cmd" &>/dev/null; then
info "$description is installed."
return 0
fi
local choice
choice=$(ask "$description is not installed. Install it now? (y/N)")
case "$choice" in
[yY])
apt-get update -qq
apt-get install -y "$pkg"
command -v "$cmd" &>/dev/null && { info "$description installed."; return 0; } || \
{ warn "$description installation failed. Section will be skipped."; return 1; }
;;
*)
info "Skipping $description."
return 1
;;
esac
}
_check_nvidia_tool() {
if command -v nvidia-smi &>/dev/null; then
info "nvidia-smi is installed."
return 0
fi
warn "nvidia-smi not found. NVIDIA monitoring requires NVIDIA drivers (not installable via apt)."
return 1
}
#region node-info wizard
configure_node_info() {
# Initialize all variables to off
LM_SENSORS_ENABLED=0
ENABLE_CPU=0; CPU_TEMP_TARGET="Core"
ENABLE_RAM_TEMP=0; ENABLE_HDD_TEMP=0; ENABLE_NVME_TEMP=0
ENABLE_FAN_SPEED=0; DISPLAY_ZERO_SPEED_FANS=0; TEMP_UNIT="C"
ENABLE_INTEL_GPU_INFO=0; ENABLE_NVIDIA_GPU_INFO=0; ENABLE_AMD_GPU_INFO=0
ENABLE_GPU_HISTORY=0
ENABLE_UPS=0; UPS_DEVICE_NAME="ups@localhost"
ENABLE_SYSTEM_INFO=0; SYSTEM_INFO_TYPE=1
local lm_sensors_ok=false
local sensors_detected=false
_check_or_install_tool sensors lm-sensors "lm-sensors" && lm_sensors_ok=true && LM_SENSORS_ENABLED=1
if [[ "$lm_sensors_ok" == true ]]; then
local sensorsOutput sanitisedSensorsOutput
sensorsOutput=$(sensors -j 2>/dev/null)
sanitisedSensorsOutput=$(sanitize_sensors_output "$sensorsOutput")
#region CPU
msgb "\n=== Detecting CPU temperature sensors ==="
local cpuList="" cpuCount=0
for pattern in "${KNOWN_CPU_SENSORS[@]}"; do
local found_sensors
found_sensors=$(echo "$sanitisedSensorsOutput" | grep -o "\"${pattern}[^\"]*\"" | sed 's/"//g')
if [[ -n "$found_sensors" ]]; then
while read -r sensor; do
[[ -z "$sensor" ]] && continue
cpuCount=$((cpuCount + 1))
cpuList="${cpuList:+$cpuList,}$sensor"
ENABLE_CPU=1
done <<< "$found_sensors"
fi
done
if [[ "$ENABLE_CPU" -eq 1 ]]; then
info "Detected CPU sensors ($cpuCount): $cpuList"
sensors_detected=true
while true; do
local choice
choice=$(ask "Display temperatures for all cores [C] or average per CPU [a]? (C/a)")
case "$choice" in
[cC]|"") CPU_TEMP_TARGET="Core"; info "Showing per-core temperatures."; break ;;
[aA]) CPU_TEMP_TARGET="Package"; info "Showing average per-CPU temperature."; break ;;
*) warn "Invalid input, choose C or a." ;;
esac
done
else
warn "No CPU temperature sensors found."
fi
#endregion CPU
#region RAM
msgb "\n=== Detecting RAM temperature sensors ==="
local ramCount
ramCount=$(grep -c '"SODIMM[^"]*"' <<<"$sanitisedSensorsOutput" || true)
if [[ "$ramCount" -gt 0 ]]; then
info "Detected $ramCount RAM sensor(s)."
ENABLE_RAM_TEMP=1; sensors_detected=true
else
warn "No RAM temperature sensors found."
fi
#endregion RAM
#region HDD/SSD
msgb "\n=== Detecting HDD/SSD temperature sensors ==="
local hddList
hddList=$(echo "$sanitisedSensorsOutput" | grep -o '"drivetemp-scsi[^"]*"' | sed 's/"//g' | wc -l || true)
if [[ "$hddList" -gt 0 ]]; then
info "Detected $hddList HDD/SSD sensor(s)."
ENABLE_HDD_TEMP=1; sensors_detected=true
else
warn "No HDD/SSD temperature sensors found. (Requires kernel module 'drivetemp'.)"
fi
#endregion HDD/SSD
#region NVMe
msgb "\n=== Detecting NVMe temperature sensors ==="
local nvmeCount
nvmeCount=$(echo "$sanitisedSensorsOutput" | grep -c '"nvme[^"]*"' || true)
if [[ "$nvmeCount" -gt 0 ]]; then
info "Detected $nvmeCount NVMe sensor(s)."
ENABLE_NVME_TEMP=1; sensors_detected=true
else
warn "No NVMe temperature sensors found."
fi
#endregion NVMe
#region Fans
msgb "\n=== Detecting fan speed sensors ==="
local fanCount
fanCount=$(grep -c 'fan[0-9]\+_input' <<<"$sanitisedSensorsOutput" || true)
if [[ "$fanCount" -gt 0 ]]; then
info "Detected $fanCount fan speed reading(s)."
ENABLE_FAN_SPEED=1; sensors_detected=true
local choice
choice=$(ask "Display fans reporting zero speed? (Y/n)")
case "$choice" in
[nN]) DISPLAY_ZERO_SPEED_FANS=0; info "Zero-speed fans will be hidden." ;;
*) DISPLAY_ZERO_SPEED_FANS=1; info "Zero-speed fans will be shown." ;;
esac
else
warn "No fan speed sensors found."
fi
#endregion Fans
#region Temperature unit
if [[ "$sensors_detected" == true ]]; then
msgb "\n=== Temperature unit ==="
local unit
unit=$(ask "Display temperatures in Celsius [C] or Fahrenheit [f]? (C/f)")
case "$unit" in
[fF]) TEMP_UNIT="F"; info "Using Fahrenheit." ;;
*) TEMP_UNIT="C"; info "Using Celsius." ;;
esac
fi
#endregion Temperature unit
fi
#region Intel GPU
msgb "\n=== Detecting Intel GPU ==="
local intelCards=""
if _check_or_install_tool intel_gpu_top igt-gpu-tools "Intel GPU tools (igt-gpu-tools)"; then
intelCards=$(intel_gpu_top -L 2>/dev/null | grep -E '^card[0-9]+' || true)
if [[ -n "$intelCards" ]]; then
info "Intel GPU(s) detected:"
echo "$intelCards" | while IFS= read -r line; do echo " $line"; done
ENABLE_INTEL_GPU_INFO=1
else
warn "No Intel GPUs detected by intel_gpu_top."
fi
fi
#endregion Intel GPU
#region NVIDIA GPU
msgb "\n=== Detecting NVIDIA GPU ==="
if _check_nvidia_tool; then
local nvidiaCards
nvidiaCards=$(nvidia-smi -L 2>/dev/null || true)
if [[ -n "$nvidiaCards" ]]; then
info "NVIDIA GPU(s) detected:"
echo "$nvidiaCards" | while IFS= read -r line; do echo " $line"; done
ENABLE_NVIDIA_GPU_INFO=1
else
warn "No NVIDIA GPUs detected by nvidia-smi."
fi
fi
#endregion NVIDIA GPU
#region AMD GPU (placeholder)
ENABLE_AMD_GPU_INFO=0
#endregion AMD GPU
#region GPU history
if [[ "$ENABLE_INTEL_GPU_INFO" -eq 1 || "$ENABLE_NVIDIA_GPU_INFO" -eq 1 ]]; then
msgb "\n=== GPU Historical Data ==="
local choice
choice=$(ask "Store historical GPU data for graphs? (y/N)")
case "$choice" in
[yY]) ENABLE_GPU_HISTORY=1; info "Historical GPU data will be stored." ;;
*) info "Historical GPU data disabled." ;;
esac
fi
#endregion GPU history
#region UPS
msgb "\n=== UPS Information ==="
local choiceUPS
choiceUPS=$(ask "Enable UPS information? (y/N)")
case "$choiceUPS" in
[yY])
local upsConn modelName upsOutput
upsConn=$(ask "Enter UPS connection string (e.g. upsname@hostname[:port])")
if ! command -v upsc &>/dev/null; then
err "'upsc' is not available. Install 'nut-client' first."
fi
upsOutput=$(upsc "$upsConn" 2>&1)
if echo "$upsOutput" | grep -q "device.model:"; then
modelName=$(echo "$upsOutput" | grep "device.model:" | cut -d: -f2- | xargs)
ENABLE_UPS=1
UPS_DEVICE_NAME="$upsConn"
info "Connected to UPS: $modelName at $upsConn"
else
warn "Could not connect to UPS at '$upsConn'. UPS info will be disabled."
ENABLE_UPS=0
fi
;;
*) info "UPS information disabled." ;;
esac
#endregion UPS
#region System info
msgb "\n=== System Information ==="
echo " type 1) System information (manufacturer, product, serial)"
dmidecode -t 1 2>/dev/null | awk -F': ' '/Manufacturer|Product Name|Serial Number/ {print " "$0}' || true
echo " type 2) Baseboard/Motherboard information"
dmidecode -t 2 2>/dev/null | awk -F': ' '/Manufacturer|Product Name|Serial Number/ {print " "$0}' || true
local choiceSys
choiceSys=$(ask "Enable system information? (1/2/n)")
case "$choiceSys" in
1|"") ENABLE_SYSTEM_INFO=1; SYSTEM_INFO_TYPE=1; info "System information (type 1) will be shown." ;;
2) ENABLE_SYSTEM_INFO=1; SYSTEM_INFO_TYPE=2; info "Baseboard information (type 2) will be shown." ;;
[nN]) info "System information disabled." ;;
*) warn "Invalid selection. Defaulting to type 1."; ENABLE_SYSTEM_INFO=1; SYSTEM_INFO_TYPE=1 ;;
esac
#endregion System info
}
#endregion node-info wizard
#region write config
write_config() {
mkdir -p "$(dirname "$CONF_FILE")"
cat > "$CONF_FILE" <<EOF
# pve-mod configuration file
# Managed by pve-mod-configure. Re-run to update.
[modules]
node_info=${MOD_NODE_INFO}
nag_screen=${MOD_NAG_SCREEN}
[gpu]
intel_enabled=${ENABLE_INTEL_GPU_INFO}
nvidia_enabled=${ENABLE_NVIDIA_GPU_INFO}
amd_enabled=${ENABLE_AMD_GPU_INFO}
gpu_history=${ENABLE_GPU_HISTORY}
[lm_sensors]
enabled=${LM_SENSORS_ENABLED}
enable_cpu=${ENABLE_CPU}
cpu_temp_target=${CPU_TEMP_TARGET}
enable_ram_temp=${ENABLE_RAM_TEMP}
enable_hdd_temp=${ENABLE_HDD_TEMP}
enable_nvme_temp=${ENABLE_NVME_TEMP}
enable_fan_speed=${ENABLE_FAN_SPEED}
display_zero_speed_fans=${DISPLAY_ZERO_SPEED_FANS}
temp_unit=${TEMP_UNIT}
[ups]
enabled=${ENABLE_UPS}
device_name=${UPS_DEVICE_NAME}
[system_info]
enabled=${ENABLE_SYSTEM_INFO}
type=${SYSTEM_INFO_TYPE}
[apt_hook]
enabled=${APT_HOOK_ENABLED}
[service]
mode=embedded
EOF
info "Configuration saved to $CONF_FILE"
}
#endregion write config
#region apt hook
configure_apt_hook() {
msgb "\n=== Automatic Re-patching (apt hook) ==="
echo "After a Proxmox upgrade, patched files (Nodes.pm, pvemanagerlib.js) may be"
echo "overwritten. An apt hook can re-apply patches automatically after every apt run."
echo "Enabling may pose the risk should something go wrong during the re-patching process (e.g. Proxmox changes)."
local choice
choice=$(ask "Enable automatic re-patching after apt/dpkg operations? (y/N)")
case "$choice" in
[yY])
APT_HOOK_ENABLED=1
cat > "$APT_HOOK_FILE" <<'HOOKEOF'
DPkg::Post-Invoke { "/usr/lib/pve-mod/apt-hook.sh || true"; };
HOOKEOF
info "Apt hook installed: $APT_HOOK_FILE"
;;
*)
APT_HOOK_ENABLED=0
if [[ -f "$APT_HOOK_FILE" ]]; then
rm -f "$APT_HOOK_FILE"
info "Apt hook removed."
else
info "Apt hook not enabled."
fi
;;
esac
}
#endregion apt hook
main() {
# ── Root check ────────────────────────────────────────────────────────────
[[ $EUID -eq 0 ]] || err "This script must be run as root."
# ── Prerequisite check ────────────────────────────────────────────────────
if [[ ! -x "$APPLY_PATCHES" ]]; then
err "pve-mod is not installed (cannot find $APPLY_PATCHES).\nInstall it first: dpkg -i pve-mod_*.deb"
fi
# ── Legacy installation check ─────────────────────────────────────────────
if grep -qF '$res->{sensorsJSONOutput}' "$NODES_PM" 2>/dev/null || \
grep -qF '$res->{systemInfo}' "$NODES_PM" 2>/dev/null; then
err "A legacy pve-mod bash-script installation was detected in Nodes.pm.\nRemove it first:\n bash /path/to/pve-mod-gui-sensors.sh uninstall"
fi
# ── Existing config notice ────────────────────────────────────────────────
if [[ -f "$CONF_FILE" ]]; then
warn "Existing configuration found at $CONF_FILE"
local choice
choice=$(ask "Reconfigure? (Y/n)")
case "$choice" in
[nN]) info "Keeping existing configuration."; exit 0 ;;
esac
fi
# ── Initialize all config variables with safe defaults ────────────────────
MOD_NODE_INFO=0; MOD_NAG_SCREEN=0
LM_SENSORS_ENABLED=0
ENABLE_CPU=0; CPU_TEMP_TARGET="Core"
ENABLE_RAM_TEMP=0; ENABLE_HDD_TEMP=0; ENABLE_NVME_TEMP=0
ENABLE_FAN_SPEED=0; DISPLAY_ZERO_SPEED_FANS=0; TEMP_UNIT="C"
ENABLE_INTEL_GPU_INFO=0; ENABLE_NVIDIA_GPU_INFO=0; ENABLE_AMD_GPU_INFO=0
ENABLE_GPU_HISTORY=0
ENABLE_UPS=0; UPS_DEVICE_NAME="ups@localhost"
ENABLE_SYSTEM_INFO=0; SYSTEM_INFO_TYPE=1
APT_HOOK_ENABLED=0
# ── Module selection ──────────────────────────────────────────────────────
msgb "\n=== pve-mod Module Selection ==="
echo "Available modules:"
echo " [1] Node Info — sensor readings, GPU stats, UPS, system information"
echo " [2] Nag Screen — remove Proxmox subscription nag screen"
echo " [3] Both"
echo " [n] None / cancel"
local modChoice
modChoice=$(ask "Select modules to enable (1/2/3/n)")
case "$modChoice" in
1) MOD_NODE_INFO=1 ;;
2) MOD_NAG_SCREEN=1 ;;
3) MOD_NODE_INFO=1; MOD_NAG_SCREEN=1 ;;
[nN]) info "No modules selected. Exiting."; exit 0 ;;
*) warn "Invalid selection. Defaulting to Node Info only."; MOD_NODE_INFO=1 ;;
esac
# ── Per-module wizards ────────────────────────────────────────────────────
if [[ "$MOD_NODE_INFO" -eq 1 ]]; then
msgb "\n=== Node Info Configuration ==="
configure_node_info
fi
if [[ "$MOD_NAG_SCREEN" -eq 1 ]]; then
msgb "\n=== Nag Screen ==="
info "Subscription nag screen removal will be applied."
fi
# ── Apt hook ──────────────────────────────────────────────────────────────
configure_apt_hook
# ── Write config and apply ────────────────────────────────────────────────
write_config
msgb "\n=== Applying patches ==="
"$APPLY_PATCHES"
msgb "\n=== Done ==="
info "pve-mod is configured and active."
info "Clear your browser cache to see the changes."
}
main

8
src/pve-mod-apt-hook.sh Normal file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
# /usr/lib/pve-mod/apt-hook.sh
#
# Installed to /etc/apt/apt.conf.d/99-pve-mod by pve-mod-configure when the
# user enables the apt hook. Re-applies PVE file patches after any dpkg run
# (e.g. after a pve-manager upgrade overwrites patched files).
/usr/lib/pve-mod/apply-patches.sh || true

38
src/pve-mod.conf Normal file
View File

@ -0,0 +1,38 @@
# pve-mod configuration file
# Run 'pve-mod-configure' to set values interactively.
# All flags are 0 (disabled) by default; pve-mod-configure enables them.
[modules]
node_info=0
nag_screen=0
[gpu]
intel_enabled=0
nvidia_enabled=0
amd_enabled=0
gpu_history=0
[lm_sensors]
enabled=0
enable_cpu=0
cpu_temp_target=Core
enable_ram_temp=0
enable_hdd_temp=0
enable_nvme_temp=0
enable_fan_speed=0
display_zero_speed_fans=0
temp_unit=C
[ups]
enabled=0
device_name=ups@localhost
[system_info]
enabled=0
type=1
[apt_hook]
enabled=0
[service]
mode=embedded