diff --git a/.github/workflows/install-test.yml b/.github/workflows/install-test.yml
deleted file mode 100644
index 9ba111e..0000000
--- a/.github/workflows/install-test.yml
+++ /dev/null
@@ -1,44 +0,0 @@
-name: Test Patches - Post Build
-
-# Runs automatically after every "Test Release Build" run completes (that build
-# is itself triggered by the 'make-test-build' label on a PR). Reuses the .deb
-# that build produced — no rebuild — installs it on a runner seeded with the
-# latest upstream Proxmox files, and verifies that every mod's patches (and
-# post-apply hooks) apply and revert cleanly. Fails if any mod fails.
-#
-# Wired via workflow_run, so it fires for builds on ANY branch/PR. (GitHub always
-# runs the default-branch copy of this file, so it activates once merged to main.)
-
-on:
- workflow_run:
- workflows: ["Test Release Build"]
- types: [completed]
-
-permissions:
- contents: read
- actions: read # required to download the triggering run's artifact
-
-jobs:
- patch-install-test:
- runs-on: ubuntu-latest
- if: github.event.workflow_run.conclusion == 'success'
- steps:
- - uses: actions/checkout@v4
-
- - name: Download deb from the build run
- uses: actions/download-artifact@v4
- with:
- run-id: ${{ github.event.workflow_run.id }}
- github-token: ${{ secrets.GITHUB_TOKEN }}
- pattern: pve-mods-test-build-*
- merge-multiple: true
- path: deb
-
- - name: Fetch upstream Proxmox files
- run: bash src/Scripts/test/fetch-proxmox-files.sh
-
- - name: Install built package
- run: sudo apt-get install -y ./deb/*.deb
-
- - name: Test all mods
- run: sudo bash src/Scripts/test/test-mods.sh all
diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml
deleted file mode 100644
index 24d2947..0000000
--- a/.github/workflows/test-release.yml
+++ /dev/null
@@ -1,71 +0,0 @@
-name: Test Release Build
-
-# Runs when the 'make-test-build' label is applied to a pull request,
-# or on any subsequent commit while that label is present.
-
-on:
- pull_request:
- types: [labeled, synchronize]
-
-jobs:
- test-release-build:
- runs-on: ubuntu-latest
- if: contains(github.event.pull_request.labels.*.name, 'make-test-build')
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
- ref: ${{ github.event.pull_request.head.sha || github.sha }}
-
- - name: Install build deps
- run: sudo apt-get install -y devscripts debhelper build-essential
-
- - name: Compute build version
- id: version
- run: |
- BASE_VERSION=$(grep -m1 '(' debian/changelog | sed 's/.*(\(.*\)).*/\1/')
- PR_NUMBER="${{ github.event.pull_request.number }}"
- SHORT_SHA=$(git rev-parse --short HEAD)
- BUILD_VERSION="${BASE_VERSION}+pr${PR_NUMBER}.${SHORT_SHA}"
- echo "build_version=$BUILD_VERSION" >> "$GITHUB_OUTPUT"
- echo "short_sha=$SHORT_SHA" >> "$GITHUB_OUTPUT"
- echo "Building version: $BUILD_VERSION"
-
- - name: Inject version into PVEMod_Config.pm
- run: |
- export BUILD_VERSION="${{ steps.version.outputs.build_version }}"
- perl -i -pe 'BEGIN { $v = $ENV{BUILD_VERSION} }
- s/(our \$VERSION\s*=\s*'"'"')[^'"'"']*'"'"';/$1'"'"'$v'"'"';/
- ' src/PVENodeInfo/PVEMod_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: Build package
- run: dpkg-buildpackage -us -uc -b
-
- - name: Verify package contents
- run: |
- echo "=== Built files ==="
- ls -la ../*.deb
- echo ""
- echo "=== Package info ==="
- dpkg-deb --info ../*.deb
- echo ""
- echo "=== Package contents ==="
- dpkg-deb --contents ../*.deb
-
- - name: Collect deb artifacts
- id: collect
- run: |
- mkdir -p artifacts
- cp ../*.deb artifacts/
-
- - name: Upload deb as artifact
- uses: actions/upload-artifact@v4
- with:
- name: pve-mods-test-build-pr${{ github.event.pull_request.number }}-${{ steps.version.outputs.short_sha }}
- path: artifacts/*.deb
- retention-days: 3
\ No newline at end of file
diff --git a/.github/workflows/weekly-patch-test.yml b/.github/workflows/weekly-patch-test.yml
deleted file mode 100644
index 2b239dd..0000000
--- a/.github/workflows/weekly-patch-test.yml
+++ /dev/null
@@ -1,110 +0,0 @@
-name: Test Patches - Weekly
-
-# Weekly regression test of pve-mod against the latest upstream Proxmox VE files.
-# Mods are auto-discovered from the [modules] section of src/pve-mod.conf and
-# tested in parallel - adding a mod needs no change here. If a mod fails, an
-# issue is opened (or updated) per failed mod explaining why.
-
-on:
- schedule:
- - cron: '0 6 * * 1' # Mondays 06:00 UTC
- workflow_dispatch:
-
-permissions:
- contents: read
- issues: write
-
-jobs:
- discover:
- runs-on: ubuntu-latest
- outputs:
- mods: ${{ steps.list.outputs.mods }}
- steps:
- - uses: actions/checkout@v4
-
- - name: Discover mods from [modules]
- id: list
- run: |
- MODS=$(awk -F= '
- /^\[/ { in_sec = ($0 == "[modules]") }
- in_sec && /^[^#=]+=/ {
- gsub(/^[[:space:]]+|[[:space:]]+$/, "", $1); print $1
- }' src/pve-mod.conf | jq -R . | jq -cs .)
- echo "Discovered mods: $MODS"
- echo "mods=$MODS" >> "$GITHUB_OUTPUT"
-
- test:
- needs: discover
- runs-on: ubuntu-latest
- strategy:
- fail-fast: false
- matrix:
- mod: ${{ fromJSON(needs.discover.outputs.mods) }}
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- # ── Build (mirrors test-release.yml) ───────────────────────────────────
- - name: Install build deps
- run: sudo apt-get install -y devscripts debhelper build-essential
-
- - name: Inject version into PVEMod_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/PVENodeInfo/PVEMod_Config.pm
-
- - name: Build package
- run: dpkg-buildpackage -us -uc -b
-
- # ── Test ───────────────────────────────────────────────────────────────
- - name: Fetch upstream Proxmox files
- run: bash src/Scripts/test/fetch-proxmox-files.sh
-
- - name: Install built package
- run: sudo apt-get install -y ../pve-mod_*.deb
-
- - name: Test mod
- run: |
- set -o pipefail
- sudo bash src/Scripts/test/test-mods.sh "${{ matrix.mod }}" 2>&1 | tee test-output.log
-
- - name: Report failure as issue
- if: failure()
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- MOD: ${{ matrix.mod }}
- run: |
- TITLE="[patch-break] ${MOD}"
- {
- echo "The weekly patch test failed for mod \`${MOD}\` against the latest Proxmox VE files."
- echo ""
- echo "Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
- echo ""
- echo "Test log
"
- echo ""
- echo '```'
- tail -n 200 test-output.log 2>/dev/null || echo "(no log captured)"
- echo '```'
- echo " "
- } > issue-body.md
-
- # Ensure the label exists (no-op if it already does).
- gh label create patch-break --color B60205 \
- --description "pve-mod patch failed against upstream Proxmox" 2>/dev/null || true
-
- EXISTING=$(gh issue list --state open --label patch-break \
- --search "$TITLE in:title" --json number,title \
- --jq ".[] | select(.title==\"$TITLE\") | .number" | head -n1)
-
- if [[ -n "$EXISTING" ]]; then
- gh issue comment "$EXISTING" --body-file issue-body.md
- echo "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
diff --git a/src/Scripts/test/fetch-proxmox-files.sh b/src/Scripts/test/fetch-proxmox-files.sh
deleted file mode 100644
index 282cfa7..0000000
--- a/src/Scripts/test/fetch-proxmox-files.sh
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/env bash
-# src/Scripts/test/fetch-proxmox-files.sh
-#
-# Downloads the upstream Proxmox VE files that pve-mod patches and installs them
-# at their real system paths, so the patch engine can be exercised on a plain
-# (non-Proxmox) CI runner.
-#
-# Pulls pve-manager and proxmox-widget-toolkit from the Proxmox no-subscription
-# repository (default: PVE 9 / Debian trixie) and extracts the target files.
-#
-# CI-only helper. The repo is added with [trusted=yes]: we are only fetching
-# public, unmodified UI files to patch against, not installing Proxmox, so
-# managing the signing key would add nothing.
-
-set -euo pipefail
-
-SUITE="${PVE_SUITE:-trixie}"
-WORKDIR="$(mktemp -d)"
-trap 'rm -rf "$WORKDIR"' EXIT
-
-# Files provided by the two packages, relative to the filesystem root. These are
-# exactly the paths the .patch files (and the nag_screen post-apply hook) touch.
-FILES=(
- usr/share/perl5/PVE/API2/Nodes.pm
- usr/share/pve-manager/js/pvemanagerlib.js
- usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
- usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js
-)
-
-echo "[fetch] Configuring Proxmox no-subscription repo ($SUITE)..."
-echo "deb [trusted=yes] http://download.proxmox.com/debian/pve $SUITE pve-no-subscription" \
- | sudo tee /etc/apt/sources.list.d/pve-test.list >/dev/null
-sudo apt-get update -qq
-
-echo "[fetch] Downloading pve-manager and proxmox-widget-toolkit..."
-cd "$WORKDIR"
-apt-get download pve-manager proxmox-widget-toolkit
-
-for deb in *.deb; do
- echo "[fetch] Extracting $deb"
- dpkg-deb -x "$deb" extract
-done
-
-for rel in "${FILES[@]}"; do
- src="extract/$rel"
- dst="/$rel"
- if [[ ! -f "$src" ]]; then
- echo "[fetch] ERROR: expected file not found in packages: $rel" >&2
- exit 1
- fi
- sudo install -Dm644 "$src" "$dst"
- echo "[fetch] Installed $dst"
-done
-
-echo "[fetch] Done."
diff --git a/src/Scripts/test/test-mods.sh b/src/Scripts/test/test-mods.sh
deleted file mode 100644
index 86d3bf1..0000000
--- a/src/Scripts/test/test-mods.sh
+++ /dev/null
@@ -1,206 +0,0 @@
-#!/usr/bin/env bash
-# src/Scripts/test/test-mods.sh
-#
-# Exercises pve-mod's patch engine against the installed Proxmox files.
-#
-# Usage: test-mods.sh | all
-#
-# Mods are auto-discovered from the [modules] section of the main config, so
-# adding a new mod (a new [modules] key plus a patches// directory) is
-# picked up automatically - no edits to this script are required.
-#
-# For each target mod it:
-# 1. enables only that mod in the main config
-# 2. turns on every conditional flag referenced by the mod's patches.list,
-# so all of the mod's patches are exercised (e.g. node_info's gpu_history)
-# 3. runs apply-patches.sh and asserts it exits 0
-# 4. runs revert-patches.sh and asserts it reports no unclean reversions
-# 5. restores the pristine Proxmox files before moving to the next mod
-#
-# Failure detection relies on the apply exit code: the patch engine performs an
-# atomic preflight dry-run and exits non-zero if any mod cannot apply cleanly.
-#
-# Must run as root (it edits /etc/pve-mod and the patched system files).
-
-set -u
-
-MAIN_CONF="${PVE_MOD_MAIN_CONF:-/etc/pve-mod/pve-mod.conf}"
-CONFD_DIR="${PVE_MOD_CONFD_DIR:-/etc/pve-mod/conf.d}"
-PATCHES_DIR="${PVE_MOD_PATCHES_DIR:-/usr/lib/pve-mod/patches}"
-APPLY="${PVE_MOD_APPLY:-/usr/lib/pve-mod/apply-patches.sh}"
-REVERT="${PVE_MOD_REVERT:-/usr/lib/pve-mod/revert-patches.sh}"
-
-# Proxmox files touched by the patches; snapshotted before and restored after
-# each mod so every mod is tested from a pristine baseline.
-PROXMOX_FILES=(
- /usr/share/perl5/PVE/API2/Nodes.pm
- /usr/share/pve-manager/js/pvemanagerlib.js
- /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
- /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js
-)
-
-SNAP_DIR="$(mktemp -d)"
-trap 'rm -rf "$SNAP_DIR"' EXIT
-
-info() { echo "[test] $*"; }
-warn() { echo "[test] WARNING: $*" >&2; }
-
-# List the keys of the [modules] section in the main config, one per line.
-list_modules() {
- [[ -f "$MAIN_CONF" ]] || return 0
- awk -F= '
- /^\[/ { in_sec = ($0 == "[modules]") }
- in_sec && /^[^#=]+=/ {
- gsub(/^[[:space:]]+|[[:space:]]+$/, "", $1)
- print $1
- }
- ' "$MAIN_CONF"
-}
-
-# set_conf : set an existing key within a section.
-set_conf() {
- local file="$1" section="$2" key="$3" value="$4"
- [[ -f "$file" ]] || { warn "config not found: $file"; return 1; }
- awk -v sec="[$section]" -v k="$key" -v v="$value" '
- /^\[/ { in_sec = ($0 == sec) }
- {
- if (in_sec && $0 ~ "^[[:space:]]*"k"[[:space:]]*=") {
- print k"="v
- } else {
- print
- }
- }
- ' "$file" > "$file.tmp" && mv "$file.tmp" "$file"
-}
-
-# Disable every discovered mod in the main config.
-disable_all_modules() {
- local m
- for m in $(list_modules); do
- set_conf "$MAIN_CONF" modules "$m" 0
- done
-}
-
-# Turn on every conditional flag a mod's patches.list references, so all of its
-# patches become active. Conditions look like: section.key=value
-enable_conditions() {
- local mod="$1"
- local manifest="$PATCHES_DIR/$mod/patches.list"
- local mod_conf="$CONFD_DIR/$mod.conf"
- [[ -f "$manifest" ]] || return 0
- local line cond local_key want sect ckey
- while IFS= read -r line; do
- line="${line%%#*}"
- line="$(echo "$line" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')"
- [[ -z "$line" ]] && continue
- # Skip lines without a condition (no whitespace after the patch name).
- [[ "$line" == *[[:space:]]* ]] || continue
- cond="$(echo "${line#*[[:space:]]}" | sed -E 's/^[[:space:]]+//')"
- [[ "$cond" == *=* ]] || continue
- local_key="${cond%%=*}"
- want="${cond#*=}"
- sect="${local_key%%.*}"
- ckey="${local_key#*.}"
- set_conf "$mod_conf" "$sect" "$ckey" "$want" \
- && info " enabled condition $local_key=$want in $(basename "$mod_conf")"
- done < "$manifest"
-}
-
-snapshot_files() {
- rm -rf "$SNAP_DIR"/*; mkdir -p "$SNAP_DIR"
- local i=0 f
- for f in "${PROXMOX_FILES[@]}"; do
- if [[ -e "$f" || -L "$f" ]]; then
- cp -a "$f" "$SNAP_DIR/$i"
- fi
- i=$((i+1))
- done
-}
-
-restore_files() {
- local i=0 f
- for f in "${PROXMOX_FILES[@]}"; do
- if [[ -e "$SNAP_DIR/$i" || -L "$SNAP_DIR/$i" ]]; then
- rm -f "$f"
- cp -a "$SNAP_DIR/$i" "$f"
- fi
- i=$((i+1))
- done
-}
-
-# Test a single mod end to end. Returns 0 on success, 1 on failure.
-test_one_mod() {
- local mod="$1"
- echo "::group::Testing mod: $mod"
- local ok=0
-
- if [[ ! -f "$PATCHES_DIR/$mod/patches.list" ]]; then
- warn "no patch manifest for mod '$mod' ($PATCHES_DIR/$mod/patches.list)"
- echo "::endgroup::"
- return 1
- fi
-
- disable_all_modules
- set_conf "$MAIN_CONF" modules "$mod" 1
- enable_conditions "$mod"
- snapshot_files
-
- info "Applying patches for '$mod'..."
- local apply_log apply_rc
- apply_log="$("$APPLY" 2>&1)"; apply_rc=$?
- echo "$apply_log"
- if [[ $apply_rc -ne 0 ]]; then
- warn "apply-patches.sh failed for mod '$mod' (exit $apply_rc)"
- ok=1
- fi
-
- if [[ $ok -eq 0 ]]; then
- info "Reverting patches for '$mod'..."
- local revert_log
- revert_log="$("$REVERT" 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'"
- ok=1
- fi
- fi
-
- restore_files
-
- if [[ $ok -eq 0 ]]; then
- info "PASS: $mod"
- else
- warn "FAIL: $mod"
- fi
- echo "::endgroup::"
- return $ok
-}
-
-# ── main ──────────────────────────────────────────────────────────────────────
-[[ $# -eq 1 ]] || { echo "Usage: $0 |all" >&2; exit 2; }
-
-if ! command -v patch >/dev/null 2>&1; then
- warn "'patch' command not found; install the 'patch' package."
- exit 1
-fi
-
-targets=()
-if [[ "$1" == "all" ]]; then
- mapfile -t targets < <(list_modules)
- [[ ${#targets[@]} -gt 0 ]] || { warn "no mods found in [modules] of $MAIN_CONF"; exit 1; }
-else
- targets=("$1")
-fi
-
-failed=()
-for mod in "${targets[@]}"; do
- test_one_mod "$mod" || failed+=("$mod")
-done
-
-echo ""
-if [[ ${#failed[@]} -gt 0 ]]; then
- warn "Failed mods: ${failed[*]}"
- exit 1
-fi
-info "All tested mods passed: ${targets[*]}"
-exit 0