mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-09-14 18:56:52 +00:00
Record GPU/TPU host changes in the change journal
Seven gpu_tpu scripts now write through the journal for host changes only — driver and package installs, vfio binding, modprobe.d, /etc/modules, GRUB and udev — while VM and LXC configuration stays out; add_gpu_vm and install_coral_lxc journal only their host writes. amd_gpu_tools records its install, and pmx_journal.sh gains pmx_record_uninstall while changes_journal.py retires an installed package when ProxMenux removes it. What each function writes is byte-identical to before, verified by tests/journal/verify_journal_migration.py.
This commit is contained in:
@@ -255,6 +255,13 @@ def ingest(limit: int = 5000) -> int:
|
||||
try:
|
||||
conn.execute("BEGIN IMMEDIATE")
|
||||
for r in rows:
|
||||
if r[3] == "uninstall_package":
|
||||
# A removal retires the package's install entry; the current
|
||||
# state is that it is no longer on the host.
|
||||
conn.execute(
|
||||
"DELETE FROM changes WHERE class = ? AND target = ?",
|
||||
(CLASS_INSTALLATION, r[7]))
|
||||
continue
|
||||
if r[2] == CLASS_INSTALLATION:
|
||||
# A package is one entry however often, or by whichever helper,
|
||||
# it is installed.
|
||||
|
||||
@@ -419,6 +419,18 @@ pmx_record_install() {
|
||||
"capture=created" "revert=purge" "exactness=none"
|
||||
}
|
||||
|
||||
# Records that ProxMenux removed a package. On ingest this retires the
|
||||
# package's installation entry, so a package installed and later removed no
|
||||
# longer shows as present — the journal reflects the current state.
|
||||
pmx_record_uninstall() {
|
||||
local packages="$1" version="${2:-1.0}"
|
||||
local -a fields
|
||||
mapfile -t fields < <(_pmx_journal_common)
|
||||
_pmx_journal_record "${fields[@]}" \
|
||||
"class=installation" "operation=uninstall_package" "target=$packages" \
|
||||
"function_version=$version" "capture=none" "revert=none" "exactness=none"
|
||||
}
|
||||
|
||||
pmx_record_applied() {
|
||||
local tool="$1" version="$2" state="${3:-applied}"
|
||||
local -a fields
|
||||
|
||||
@@ -42,6 +42,9 @@ screen_capture="/tmp/proxmenux_add_gpu_vm_screen_$$.txt"
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
if [[ -f "$BASE_DIR/scripts/global/pmx_journal.sh" ]]; then
|
||||
source "$BASE_DIR/scripts/global/pmx_journal.sh"
|
||||
fi
|
||||
if [[ -f "$LOCAL_SCRIPTS_LOCAL/global/pci_passthrough_helpers.sh" ]]; then
|
||||
source "$LOCAL_SCRIPTS_LOCAL/global/pci_passthrough_helpers.sh"
|
||||
elif [[ -f "$LOCAL_SCRIPTS_DEFAULT/global/pci_passthrough_helpers.sh" ]]; then
|
||||
@@ -113,11 +116,12 @@ _get_pci_driver() {
|
||||
}
|
||||
|
||||
_add_line_if_missing() {
|
||||
pmx_journal_context "_add_line_if_missing" "1.1" "add_gpu_vm.sh"
|
||||
local line="$1"
|
||||
local file="$2"
|
||||
touch "$file"
|
||||
[[ -f "$file" ]] || pmx_write_file "$file" < /dev/null
|
||||
if ! grep -qF "$line" "$file"; then
|
||||
echo "$line" >> "$file"
|
||||
echo "$line" | pmx_append_file "$file"
|
||||
HOST_CONFIG_CHANGED=true
|
||||
fi
|
||||
}
|
||||
@@ -670,6 +674,7 @@ check_iommu_enabled() {
|
||||
}
|
||||
|
||||
_enable_iommu_cmdline() {
|
||||
pmx_journal_context "_enable_iommu_cmdline" "1.1" "add_gpu_vm.sh"
|
||||
local cpu_vendor
|
||||
cpu_vendor=$(grep -m1 "vendor_id" /proc/cpuinfo 2>/dev/null | awk '{print $3}')
|
||||
|
||||
@@ -692,8 +697,9 @@ _enable_iommu_cmdline() {
|
||||
# systemd-boot / ZFS
|
||||
if ! grep -q "$iommu_param" "$cmdline_file"; then
|
||||
cp "$cmdline_file" "${cmdline_file}.bak.$(date +%Y%m%d_%H%M%S)"
|
||||
sed -i "s|\\s*$| ${iommu_param} iommu=pt|" "$cmdline_file"
|
||||
pmx_edit_file "$cmdline_file" "s|\\s*$| ${iommu_param} iommu=pt|"
|
||||
proxmox-boot-tool refresh >/dev/null 2>&1 || true
|
||||
pmx_record_execution "refresh Proxmox boot configuration" "proxmox-boot-tool refresh"
|
||||
msg_ok "$(translate 'IOMMU parameters added to /etc/kernel/cmdline')"
|
||||
else
|
||||
msg_ok "$(translate 'IOMMU already configured in /etc/kernel/cmdline')"
|
||||
@@ -702,8 +708,9 @@ _enable_iommu_cmdline() {
|
||||
# GRUB
|
||||
if ! grep -q "$iommu_param" "$grub_file"; then
|
||||
cp "$grub_file" "${grub_file}.bak.$(date +%Y%m%d_%H%M%S)"
|
||||
sed -i "/GRUB_CMDLINE_LINUX_DEFAULT=/ s|\"$| ${iommu_param} iommu=pt\"|" "$grub_file"
|
||||
pmx_edit_file "$grub_file" "/GRUB_CMDLINE_LINUX_DEFAULT=/ s|\"$| ${iommu_param} iommu=pt\"|"
|
||||
update-grub >/dev/null 2>&1 || true
|
||||
pmx_record_execution "update GRUB configuration" "update-grub"
|
||||
msg_ok "$(translate 'IOMMU parameters added to GRUB')"
|
||||
else
|
||||
msg_ok "$(translate 'IOMMU already configured in GRUB')"
|
||||
@@ -1613,9 +1620,12 @@ add_vfio_modules() {
|
||||
|
||||
# ── vfio-pci IDs — merge with existing ones ─────────────
|
||||
configure_vfio_pci_ids() {
|
||||
pmx_journal_context "configure_vfio_pci_ids" "1.1" "add_gpu_vm.sh"
|
||||
msg_info "$(translate 'Configuring vfio-pci binding...')"
|
||||
local vfio_conf="/etc/modprobe.d/vfio.conf"
|
||||
touch "$vfio_conf"
|
||||
local vfio_tmp
|
||||
vfio_tmp=$(mktemp)
|
||||
[[ -f "$vfio_conf" ]] && cat "$vfio_conf" > "$vfio_tmp"
|
||||
|
||||
# ────────────────────────────────────────────────────────────────
|
||||
# NVIDIA: per-BDF binding (multi-GPU safe). The `options vfio-pci
|
||||
@@ -1629,9 +1639,9 @@ configure_vfio_pci_ids() {
|
||||
# Clean up any previous ids= line that captured this NVIDIA
|
||||
# (older versions of this script wrote it; remove to avoid
|
||||
# collateral grabs on sibling GPUs of the same model).
|
||||
if grep -qE '^options vfio-pci ids=' "$vfio_conf" 2>/dev/null; then
|
||||
if grep -qE '^options vfio-pci ids=' "$vfio_tmp" 2>/dev/null; then
|
||||
local existing_line ids_part
|
||||
existing_line=$(grep '^options vfio-pci ids=' "$vfio_conf" | head -1)
|
||||
existing_line=$(grep '^options vfio-pci ids=' "$vfio_tmp" | head -1)
|
||||
ids_part=$(echo "$existing_line" | grep -oE 'ids=[^[:space:]]+' | sed 's/ids=//')
|
||||
|
||||
local kept=()
|
||||
@@ -1644,20 +1654,30 @@ configure_vfio_pci_ids() {
|
||||
$drop || kept+=("$eid")
|
||||
done
|
||||
|
||||
sed -i '/^options vfio-pci ids=/d' "$vfio_conf"
|
||||
sed '/^options vfio-pci ids=/d' "$vfio_tmp" > "${vfio_tmp}.next"
|
||||
mv "${vfio_tmp}.next" "$vfio_tmp"
|
||||
if [[ ${#kept[@]} -gt 0 ]]; then
|
||||
local kept_str
|
||||
kept_str=$(IFS=','; echo "${kept[*]}")
|
||||
echo "options vfio-pci ids=${kept_str} disable_vga=1" >> "$vfio_conf"
|
||||
echo "options vfio-pci ids=${kept_str} disable_vga=1" >> "$vfio_tmp"
|
||||
fi
|
||||
HOST_CONFIG_CHANGED=true
|
||||
fi
|
||||
|
||||
# Ensure vfio loads before nvidia so the per-BDF override wins.
|
||||
_add_line_if_missing "softdep nvidia pre: vfio-pci" "$vfio_conf"
|
||||
_add_line_if_missing "softdep nvidia_drm pre: vfio-pci" "$vfio_conf"
|
||||
_add_line_if_missing "softdep nvidia_modeset pre: vfio-pci" "$vfio_conf"
|
||||
_add_line_if_missing "softdep nvidia_uvm pre: vfio-pci" "$vfio_conf"
|
||||
local softdep
|
||||
for softdep in \
|
||||
"softdep nvidia pre: vfio-pci" \
|
||||
"softdep nvidia_drm pre: vfio-pci" \
|
||||
"softdep nvidia_modeset pre: vfio-pci" \
|
||||
"softdep nvidia_uvm pre: vfio-pci"; do
|
||||
if ! grep -qF "$softdep" "$vfio_tmp"; then
|
||||
echo "$softdep" >> "$vfio_tmp"
|
||||
HOST_CONFIG_CHANGED=true
|
||||
fi
|
||||
done
|
||||
pmx_write_file "$vfio_conf" < "$vfio_tmp"
|
||||
rm -f "$vfio_tmp"
|
||||
|
||||
# Per-BDF binder rule. IOMMU_DEVICES has the BDFs for the GPU
|
||||
# we're passing (and any same-group functions like the audio
|
||||
@@ -1682,7 +1702,7 @@ configure_vfio_pci_ids() {
|
||||
# Collect existing IDs (if any)
|
||||
local existing_ids=()
|
||||
local existing_line
|
||||
existing_line=$(grep "^options vfio-pci ids=" "$vfio_conf" 2>/dev/null | head -1)
|
||||
existing_line=$(grep "^options vfio-pci ids=" "$vfio_tmp" 2>/dev/null | head -1)
|
||||
if [[ -n "$existing_line" ]]; then
|
||||
local ids_part
|
||||
ids_part=$(echo "$existing_line" | grep -oE 'ids=[^[:space:]]+' | sed 's/ids=//')
|
||||
@@ -1703,13 +1723,16 @@ configure_vfio_pci_ids() {
|
||||
ids_str=$(IFS=','; echo "${all_ids[*]}")
|
||||
|
||||
local existing_full_line
|
||||
existing_full_line=$(grep "^options vfio-pci ids=" "$vfio_conf" 2>/dev/null | head -1)
|
||||
existing_full_line=$(grep "^options vfio-pci ids=" "$vfio_tmp" 2>/dev/null | head -1)
|
||||
local new_full_line="options vfio-pci ids=${ids_str} disable_vga=1"
|
||||
if [[ "$existing_full_line" != "$new_full_line" ]]; then
|
||||
sed -i '/^options vfio-pci ids=/d' "$vfio_conf"
|
||||
echo "$new_full_line" >> "$vfio_conf"
|
||||
sed '/^options vfio-pci ids=/d' "$vfio_tmp" > "${vfio_tmp}.next"
|
||||
mv "${vfio_tmp}.next" "$vfio_tmp"
|
||||
echo "$new_full_line" >> "$vfio_tmp"
|
||||
HOST_CONFIG_CHANGED=true
|
||||
fi
|
||||
pmx_write_file "$vfio_conf" < "$vfio_tmp"
|
||||
rm -f "$vfio_tmp"
|
||||
msg_ok "$(translate 'vfio-pci IDs configured') (${ids_str})" | tee -a "$screen_capture"
|
||||
}
|
||||
|
||||
@@ -1787,6 +1810,7 @@ sanitize_nvidia_host_stack_for_vfio() {
|
||||
|
||||
# ── AMD ROM dump: sysfs first, VFCT ACPI table as fallback ───────────────
|
||||
_dump_rom_via_vfct() {
|
||||
pmx_journal_context "_dump_rom_via_vfct" "1.1" "add_gpu_vm.sh"
|
||||
local rom_dest="$1"
|
||||
local vfct_file="/sys/firmware/acpi/tables/VFCT"
|
||||
[[ -f "$vfct_file" ]] || return 1
|
||||
@@ -1808,11 +1832,16 @@ _dump_rom_via_vfct() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
dd if="$vfct_file" bs=1 skip=64 count="$img_length" of="$rom_dest" 2>/dev/null
|
||||
local rom_tmp
|
||||
rom_tmp=$(mktemp)
|
||||
dd if="$vfct_file" bs=1 skip=64 count="$img_length" of="$rom_tmp" 2>/dev/null
|
||||
pmx_write_file "$rom_dest" < "$rom_tmp"
|
||||
rm -f "$rom_tmp"
|
||||
[[ -s "$rom_dest" ]]
|
||||
}
|
||||
|
||||
dump_amd_rom() {
|
||||
pmx_journal_context "dump_amd_rom" "1.1" "add_gpu_vm.sh"
|
||||
local pci_full="$SELECTED_GPU_PCI"
|
||||
local rom_path="/sys/bus/pci/devices/${pci_full}/rom"
|
||||
local kvm_dir="/usr/share/kvm"
|
||||
@@ -1828,14 +1857,19 @@ dump_amd_rom() {
|
||||
if [[ -f "$rom_path" ]]; then
|
||||
msg_info "$(translate 'Dumping AMD GPU ROM BIOS via sysfs...')"
|
||||
echo 1 > "$rom_path" 2>/dev/null
|
||||
if cat "$rom_path" > "$rom_dest" 2>>"$LOG_FILE" && [[ -s "$rom_dest" ]]; then
|
||||
local rom_tmp
|
||||
rom_tmp=$(mktemp)
|
||||
if cat "$rom_path" > "$rom_tmp" 2>>"$LOG_FILE" && [[ -s "$rom_tmp" ]]; then
|
||||
pmx_write_file "$rom_dest" < "$rom_tmp"
|
||||
rm -f "$rom_tmp"
|
||||
echo 0 > "$rom_path" 2>/dev/null
|
||||
AMD_ROM_FILE="$rom_filename"
|
||||
msg_ok "$(translate 'GPU ROM dumped to') ${rom_dest}" | tee -a "$screen_capture"
|
||||
return 0
|
||||
fi
|
||||
rm -f "$rom_tmp"
|
||||
echo 0 > "$rom_path" 2>/dev/null
|
||||
rm -f "$rom_dest"
|
||||
pmx_remove_file "$rom_dest"
|
||||
msg_warn "$(translate 'sysfs ROM dump failed — trying ACPI VFCT table...')"
|
||||
else
|
||||
msg_info "$(translate 'No sysfs ROM entry — trying ACPI VFCT table...')"
|
||||
@@ -1848,7 +1882,7 @@ dump_amd_rom() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
rm -f "$rom_dest"
|
||||
pmx_remove_file "$rom_dest"
|
||||
msg_warn "$(translate 'ROM dump not available — configuring without romfile.')"
|
||||
msg_warn "$(translate 'Passthrough may still work without a ROM file.')"
|
||||
}
|
||||
@@ -2159,11 +2193,14 @@ _configure_nvidia_kvm_hide() {
|
||||
|
||||
# ── Update initramfs ─────────────────────────────────────
|
||||
update_initramfs_host() {
|
||||
pmx_journal_context "update_initramfs_host" "1.1" "add_gpu_vm.sh"
|
||||
msg_info "$(translate 'Updating initramfs (this may take a minute)...')"
|
||||
update-initramfs -u -k all >>"$LOG_FILE" 2>&1
|
||||
pmx_record_execution "rebuild initramfs" "update-initramfs -u -k all"
|
||||
# Copy the freshly-built initramfs to the EFI System Partition.
|
||||
# Without this the bootloader keeps using the previous initramfs.
|
||||
proxmox-boot-tool refresh >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "refresh Proxmox boot configuration" "proxmox-boot-tool refresh"
|
||||
msg_ok "$(translate 'initramfs updated')" | tee -a "$screen_capture"
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@ if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
if [[ -f "$BASE_DIR/scripts/global/pmx_journal.sh" ]]; then
|
||||
source "$BASE_DIR/scripts/global/pmx_journal.sh"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$COMPONENTS_STATUS_FILE" ]]; then
|
||||
echo "{}" > "$COMPONENTS_STATUS_FILE"
|
||||
fi
|
||||
@@ -140,6 +144,9 @@ install_amdgpu_top() {
|
||||
fi
|
||||
|
||||
msg_ok "$(translate 'amdgpu_top installed successfully')"
|
||||
|
||||
pmx_journal_context "install_amdgpu_top" "1.0" "amd_gpu_tools.sh"
|
||||
pmx_record_install "amdgpu-top"
|
||||
|
||||
# Clean up
|
||||
rm -rf "$tmp_dir"
|
||||
@@ -160,6 +167,9 @@ uninstall_amdgpu_top() {
|
||||
|
||||
if dpkg -r amdgpu-top >>"$LOG_FILE" 2>&1 || apt-get remove -y amdgpu-top >>"$LOG_FILE" 2>&1; then
|
||||
msg_ok "$(translate 'amdgpu_top uninstalled successfully')"
|
||||
|
||||
pmx_journal_context "uninstall_amdgpu_top" "1.0" "amd_gpu_tools.sh"
|
||||
pmx_record_uninstall "amdgpu-top"
|
||||
|
||||
if type update_component_status &>/dev/null; then
|
||||
update_component_status "amdgpu_top" "uninstalled" "" "gpu" '{}'
|
||||
|
||||
@@ -52,6 +52,10 @@ if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
if [[ -f "$BASE_DIR/scripts/global/pmx_journal.sh" ]]; then
|
||||
source "$BASE_DIR/scripts/global/pmx_journal.sh"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
@@ -130,16 +134,18 @@ pre_install_prompt() {
|
||||
# ============================================================
|
||||
|
||||
ensure_apex_group_and_udev() {
|
||||
pmx_journal_context "ensure_apex_group_and_udev" "2.0" "install_coral.sh"
|
||||
msg_info "$(translate 'Ensuring apex group and udev rules...')"
|
||||
|
||||
if ! getent group apex >/dev/null; then
|
||||
groupadd --system apex || true
|
||||
pmx_apply_setting "group:apex" "getent group apex 2>/dev/null || true" \
|
||||
groupadd --system apex || true
|
||||
msg_ok "$(translate 'System group apex created.')"
|
||||
else
|
||||
msg_ok "$(translate 'System group apex already exists.')"
|
||||
fi
|
||||
|
||||
cat >/etc/udev/rules.d/99-coral-apex.rules <<'EOF'
|
||||
pmx_write_file /etc/udev/rules.d/99-coral-apex.rules <<'EOF'
|
||||
# Coral / Google APEX TPU (M.2 / PCIe)
|
||||
# Assign group "apex" and safe permissions to device nodes
|
||||
KERNEL=="apex_*", GROUP="apex", MODE="0660"
|
||||
@@ -147,11 +153,14 @@ SUBSYSTEM=="apex", GROUP="apex", MODE="0660"
|
||||
EOF
|
||||
|
||||
if [[ -f /usr/lib/udev/rules.d/60-gasket-dkms.rules ]]; then
|
||||
sed -i 's/GROUP="[^"]*"/GROUP="apex"/g' /usr/lib/udev/rules.d/60-gasket-dkms.rules || true
|
||||
pmx_edit_file /usr/lib/udev/rules.d/60-gasket-dkms.rules \
|
||||
's/GROUP="[^"]*"/GROUP="apex"/g' || true
|
||||
fi
|
||||
|
||||
udevadm control --reload-rules
|
||||
pmx_record_execution "Reload Coral APEX udev rules" "udevadm control --reload-rules"
|
||||
udevadm trigger --subsystem-match=apex || true
|
||||
pmx_record_execution "Trigger Coral APEX udev rules" "udevadm trigger --subsystem-match=apex"
|
||||
|
||||
msg_ok "$(translate 'apex group and udev rules are in place.')"
|
||||
|
||||
@@ -163,6 +172,7 @@ EOF
|
||||
}
|
||||
|
||||
cleanup_broken_gasket_dkms() {
|
||||
pmx_journal_context "cleanup_broken_gasket_dkms" "2.0" "install_coral.sh"
|
||||
# Recover from a broken gasket-dkms .deb state (half-configured, unpacked,
|
||||
# half-installed). This is a common failure mode on PVE 9 kernel upgrades:
|
||||
# dkms autoinstall tries to rebuild against the new kernel, fails, and
|
||||
@@ -175,15 +185,21 @@ cleanup_broken_gasket_dkms() {
|
||||
case "$pkg_state" in
|
||||
ii|rc)
|
||||
msg_info "$(translate 'Removing any pre-existing gasket-dkms package...')"
|
||||
dpkg -r gasket-dkms >>"$LOG_FILE" 2>&1 || true
|
||||
if dpkg -r gasket-dkms >>"$LOG_FILE" 2>&1; then
|
||||
pmx_record_uninstall "gasket-dkms" "2.0"
|
||||
fi
|
||||
dkms remove gasket/1.0 --all >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "Remove gasket DKMS entry" "dkms remove gasket/1.0 --all"
|
||||
msg_ok "$(translate 'Pre-existing gasket-dkms package removed.')"
|
||||
;;
|
||||
*)
|
||||
msg_warn "$(translate 'Detected broken gasket-dkms package state:') ${pkg_state}. $(translate 'Forcing removal...')"
|
||||
dpkg --remove --force-remove-reinstreq gasket-dkms >>"$LOG_FILE" 2>&1 || true
|
||||
if dpkg --remove --force-remove-reinstreq gasket-dkms >>"$LOG_FILE" 2>&1; then
|
||||
pmx_record_uninstall "gasket-dkms" "2.0"
|
||||
fi
|
||||
dpkg --purge --force-all gasket-dkms >>"$LOG_FILE" 2>&1 || true
|
||||
dkms remove gasket/1.0 --all >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "Remove broken gasket DKMS entry" "dkms remove gasket/1.0 --all"
|
||||
apt-get install -f -y >>"$LOG_FILE" 2>&1 || true
|
||||
msg_ok "$(translate 'Broken gasket-dkms package state recovered.')"
|
||||
;;
|
||||
@@ -244,6 +260,7 @@ detect_orphan_gasket_dkms() {
|
||||
}
|
||||
|
||||
cleanup_orphan_gasket_dkms() {
|
||||
pmx_journal_context "cleanup_orphan_gasket_dkms" "2.0" "install_coral.sh"
|
||||
# Return codes:
|
||||
# 0 cleanup completed and every final verification passed
|
||||
# 1 operator cancelled before any change was made
|
||||
@@ -285,6 +302,9 @@ cleanup_orphan_gasket_dkms() {
|
||||
dpkg --remove --force-remove-reinstreq gasket-dkms >>"$LOG_FILE" 2>&1 || true
|
||||
dpkg --purge --force-all gasket-dkms >>"$LOG_FILE" 2>&1 || true
|
||||
fi
|
||||
if ! dpkg-query -W gasket-dkms >/dev/null 2>&1; then
|
||||
pmx_record_uninstall "gasket-dkms" "2.0"
|
||||
fi
|
||||
|
||||
# A host can retain more than the historical gasket/1.0 entry. Read
|
||||
# every version known by DKMS and also include stale version trees
|
||||
@@ -309,6 +329,7 @@ cleanup_orphan_gasket_dkms() {
|
||||
if ! dkms remove -m gasket -v "$version" --all >>"$LOG_FILE" 2>&1; then
|
||||
dkms_remove_failed=1
|
||||
fi
|
||||
pmx_record_execution "Remove orphan gasket DKMS entry" "dkms remove -m gasket -v $version --all"
|
||||
done <<<"$versions"
|
||||
|
||||
if [[ "$dkms_remove_failed" -eq 0 ]]; then
|
||||
@@ -383,7 +404,7 @@ cleanup_orphan_gasket_dkms() {
|
||||
if declare -f update_component_status >/dev/null 2>&1; then
|
||||
update_component_status "coral_driver" "removed" "" "gpu" '{}' >/dev/null 2>&1 || true
|
||||
fi
|
||||
rm -f /var/lib/proxmenux/coral_gasket_version 2>/dev/null || true
|
||||
pmx_remove_file /var/lib/proxmenux/coral_gasket_version || true
|
||||
fi
|
||||
|
||||
if [[ "$repair_failed" -ne 0 ]]; then
|
||||
@@ -450,6 +471,7 @@ show_dkms_build_failure() {
|
||||
}
|
||||
|
||||
install_gasket_apex_dkms() {
|
||||
pmx_journal_context "install_gasket_apex_dkms" "2.0" "install_coral.sh"
|
||||
# Detect running kernel — used both to pull matching headers and to apply
|
||||
# kernel-version-specific patches if we fall back to google/gasket-driver.
|
||||
local KVER KMAJ KMIN
|
||||
@@ -461,7 +483,7 @@ install_gasket_apex_dkms() {
|
||||
|
||||
msg_info "$(translate 'Installing build dependencies...')"
|
||||
apt-get update -qq >>"$LOG_FILE" 2>&1
|
||||
if ! apt-get install -y git dkms build-essential "proxmox-headers-${KVER}" >>"$LOG_FILE" 2>&1; then
|
||||
if ! pmx_install_pkg git dkms build-essential "proxmox-headers-${KVER}"; then
|
||||
msg_error "$(translate 'Error installing build dependencies. Check') ${LOG_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
@@ -478,13 +500,13 @@ install_gasket_apex_dkms() {
|
||||
|
||||
# no_llseek was removed in kernel 6.5 — replace with noop_llseek
|
||||
if [[ "$KMAJ" -gt 6 ]] || [[ "$KMAJ" -eq 6 && "$KMIN" -ge 5 ]]; then
|
||||
sed -i 's/\.llseek = no_llseek/\.llseek = noop_llseek/' src/gasket_core.c
|
||||
sed --in-place 's/\.llseek = no_llseek/\.llseek = noop_llseek/' src/gasket_core.c
|
||||
fi
|
||||
|
||||
# MODULE_IMPORT_NS syntax changed to string-literal in 6.13.
|
||||
# Applying this patch on kernel <6.13 causes a compile error.
|
||||
if [[ "$KMAJ" -gt 6 ]] || [[ "$KMAJ" -eq 6 && "$KMIN" -ge 13 ]]; then
|
||||
sed -i 's/^MODULE_IMPORT_NS(DMA_BUF);/MODULE_IMPORT_NS("DMA_BUF");/' src/gasket_page_table.c
|
||||
sed --in-place 's/^MODULE_IMPORT_NS(DMA_BUF);/MODULE_IMPORT_NS("DMA_BUF");/' src/gasket_page_table.c
|
||||
fi
|
||||
|
||||
msg_ok "$(translate 'Source patched successfully.') (kernel ${KVER})"
|
||||
@@ -506,11 +528,13 @@ install_gasket_apex_dkms() {
|
||||
|
||||
msg_info "$(translate 'Removing previous DKMS source tree...')"
|
||||
dkms remove gasket/1.0 --all >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "Remove previous gasket DKMS entry" "dkms remove gasket/1.0 --all"
|
||||
if [[ -d "$GASKET_SRC" ]]; then
|
||||
if ! rm -rf "$GASKET_SRC" 2>>"$LOG_FILE"; then
|
||||
msg_error "$(translate 'Could not remove previous DKMS tree at') ${GASKET_SRC}. $(translate 'Check') ${LOG_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
pmx_record_execution "Remove previous gasket DKMS source tree" "rm -rf $GASKET_SRC"
|
||||
fi
|
||||
msg_ok "$(translate 'Previous DKMS tree cleared.')"
|
||||
|
||||
@@ -523,6 +547,7 @@ install_gasket_apex_dkms() {
|
||||
msg_error "$(translate 'Failed to copy sources into') ${GASKET_SRC}. $(translate 'Check') ${LOG_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
pmx_record_execution "Copy gasket DKMS source tree" "cp -a /tmp/gasket-driver/src/. ${GASKET_SRC}/"
|
||||
if [[ ! -f "$GASKET_SRC/Makefile" ]]; then
|
||||
msg_error "$(translate 'Makefile missing in') ${GASKET_SRC} $(translate 'after copy; source tree is incomplete.')"
|
||||
exit 1
|
||||
@@ -535,7 +560,7 @@ install_gasket_apex_dkms() {
|
||||
# MAKE[0] passes ${kernelver} to the Makefile so multi-kernel rebuilds
|
||||
# (PVE's autoinstall on new kernel installs) target the right headers.
|
||||
msg_info "$(translate 'Generating dkms.conf...')"
|
||||
cat > "$GASKET_SRC/dkms.conf" <<'EOF'
|
||||
pmx_write_file "$GASKET_SRC/dkms.conf" <<'EOF'
|
||||
PACKAGE_NAME="gasket"
|
||||
PACKAGE_VERSION="1.0"
|
||||
BUILT_MODULE_NAME[0]="gasket"
|
||||
@@ -557,6 +582,7 @@ EOF
|
||||
msg_error "$(translate 'DKMS add failed. Check') ${LOG_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
pmx_record_execution "Register gasket DKMS module" "dkms add $GASKET_SRC"
|
||||
msg_ok "$(translate 'DKMS module registered.')"
|
||||
|
||||
msg_info "$(translate 'Compiling Coral TPU drivers for current kernel...')"
|
||||
@@ -565,11 +591,13 @@ EOF
|
||||
msg_error "$(translate 'DKMS build failed.')"
|
||||
exit 1
|
||||
fi
|
||||
pmx_record_execution "Build gasket DKMS module" "dkms build gasket/1.0 -k $KVER"
|
||||
if ! dkms install gasket/1.0 -k "$KVER" >>"$LOG_FILE" 2>&1; then
|
||||
show_dkms_build_failure
|
||||
msg_error "$(translate 'DKMS install failed.')"
|
||||
exit 1
|
||||
fi
|
||||
pmx_record_execution "Install gasket DKMS module" "dkms install gasket/1.0 -k $KVER"
|
||||
msg_ok "$(translate 'Drivers compiled and installed via DKMS.') (source: ${GASKET_SOURCE_USED})"
|
||||
|
||||
# Track which feranick release was just installed. Without this, the
|
||||
@@ -594,7 +622,7 @@ EOF
|
||||
| sed -E 's/.*"([^"]+)"$/\1/')
|
||||
if [[ -n "$FERANICK_LATEST" ]]; then
|
||||
mkdir -p "$CORAL_MARKER_DIR" >>"$LOG_FILE" 2>&1 || true
|
||||
echo "$FERANICK_LATEST" > "$CORAL_MARKER_FILE" 2>>"$LOG_FILE" || true
|
||||
printf '%s\n' "$FERANICK_LATEST" | pmx_write_file "$CORAL_MARKER_FILE" || true
|
||||
echo "[install_coral] Recorded installed gasket-dkms version: $FERANICK_LATEST" >>"$LOG_FILE" 2>&1
|
||||
else
|
||||
echo "[install_coral] Could not resolve feranick latest tag — marker not written." >>"$LOG_FILE" 2>&1
|
||||
@@ -622,6 +650,7 @@ EOF
|
||||
# ============================================================
|
||||
|
||||
install_libedgetpu_runtime() {
|
||||
pmx_journal_context "install_libedgetpu_runtime" "2.0" "install_coral.sh"
|
||||
local KEYRING=/etc/apt/keyrings/coral-edgetpu.gpg
|
||||
local LIST_FILE=/etc/apt/sources.list.d/coral-edgetpu.list
|
||||
|
||||
@@ -631,15 +660,24 @@ install_libedgetpu_runtime() {
|
||||
mkdir -p /etc/apt/keyrings
|
||||
|
||||
if [[ ! -s "$KEYRING" ]]; then
|
||||
if ! curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg \
|
||||
| gpg --dearmor -o "$KEYRING" 2>>"$LOG_FILE"; then
|
||||
local keyring_tmp
|
||||
local keyring_ready=true
|
||||
keyring_tmp=$(mktemp /tmp/coral-edgetpu-keyring.XXXXXX)
|
||||
curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg \
|
||||
| gpg --dearmor >"$keyring_tmp" 2>>"$LOG_FILE" || keyring_ready=false
|
||||
if $keyring_ready; then
|
||||
pmx_write_file "$KEYRING" <"$keyring_tmp" || keyring_ready=false
|
||||
fi
|
||||
if ! $keyring_ready; then
|
||||
rm -f "$keyring_tmp"
|
||||
msg_error "$(translate 'Failed to fetch the Google Coral GPG key. Check') ${LOG_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
rm -f "$keyring_tmp"
|
||||
chmod 0644 "$KEYRING"
|
||||
fi
|
||||
|
||||
cat > "$LIST_FILE" <<EOF
|
||||
pmx_write_file "$LIST_FILE" <<EOF
|
||||
deb [signed-by=${KEYRING}] https://packages.cloud.google.com/apt coral-edgetpu-stable main
|
||||
EOF
|
||||
|
||||
@@ -652,7 +690,7 @@ EOF
|
||||
# (more heat). We default to -std; users who explicitly want -max can install
|
||||
# it manually. Either way the udev rules come with the package.
|
||||
msg_info "$(translate 'Installing Edge TPU runtime (libedgetpu1-std)...')"
|
||||
if ! apt-get install -y libedgetpu1-std >>"$LOG_FILE" 2>&1; then
|
||||
if ! pmx_install_pkg libedgetpu1-std; then
|
||||
msg_error "$(translate 'Failed to install libedgetpu1-std. Check') ${LOG_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
@@ -661,7 +699,9 @@ EOF
|
||||
# Reload udev so the rules shipped with libedgetpu1-std apply to any USB
|
||||
# Coral already plugged in (otherwise they would only apply after replug).
|
||||
udevadm control --reload-rules >/dev/null 2>&1 || true
|
||||
pmx_record_execution "Reload Coral USB udev rules" "udevadm control --reload-rules"
|
||||
udevadm trigger --subsystem-match=usb >/dev/null 2>&1 || true
|
||||
pmx_record_execution "Trigger Coral USB udev rules" "udevadm trigger --subsystem-match=usb"
|
||||
}
|
||||
|
||||
|
||||
@@ -775,6 +815,7 @@ show_coral_action_menu_if_installed() {
|
||||
# Idempotent: missing pieces are no-ops, never errors.
|
||||
# ============================================================
|
||||
complete_coral_uninstall() {
|
||||
pmx_journal_context "complete_coral_uninstall" "2.0" "install_coral.sh"
|
||||
msg_info "$(translate 'Stopping Coral kernel modules...')"
|
||||
modprobe -r apex 2>>"$LOG_FILE" || true
|
||||
modprobe -r gasket 2>>"$LOG_FILE" || true
|
||||
@@ -791,35 +832,43 @@ complete_coral_uninstall() {
|
||||
while IFS= read -r v; do
|
||||
[[ -z "$v" ]] && continue
|
||||
dkms remove -m gasket -v "$v" --all >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "Remove gasket DKMS module" "dkms remove -m gasket -v $v --all"
|
||||
done <<<"$versions"
|
||||
msg_ok "$(translate 'gasket DKMS entries removed.')"
|
||||
fi
|
||||
fi
|
||||
|
||||
msg_info "$(translate 'Removing Coral packages...')"
|
||||
apt-get -y purge gasket-dkms libedgetpu1-std libedgetpu1-max \
|
||||
>>"$LOG_FILE" 2>&1 || true
|
||||
if apt-get -y purge gasket-dkms libedgetpu1-std libedgetpu1-max \
|
||||
>>"$LOG_FILE" 2>&1; then
|
||||
pmx_record_uninstall "gasket-dkms" "2.0"
|
||||
pmx_record_uninstall "libedgetpu1-std" "2.0"
|
||||
pmx_record_uninstall "libedgetpu1-max" "2.0"
|
||||
fi
|
||||
apt-get -y autoremove --purge >>"$LOG_FILE" 2>&1 || true
|
||||
msg_ok "$(translate 'Coral packages purged.')"
|
||||
|
||||
# udev rules created by our installer.
|
||||
rm -f /etc/udev/rules.d/99-coral-apex.rules
|
||||
pmx_remove_file /etc/udev/rules.d/99-coral-apex.rules
|
||||
# Restore the upstream udev rule group (set it back to its default
|
||||
# GROUP="plugdev") in case dkms-postinstall reinstalls gasket-dkms
|
||||
# later — apex group may not exist next time.
|
||||
if [[ -f /usr/lib/udev/rules.d/60-gasket-dkms.rules ]]; then
|
||||
sed -i 's/GROUP="apex"/GROUP="plugdev"/g' \
|
||||
/usr/lib/udev/rules.d/60-gasket-dkms.rules || true
|
||||
pmx_edit_file /usr/lib/udev/rules.d/60-gasket-dkms.rules \
|
||||
's/GROUP="apex"/GROUP="plugdev"/g' || true
|
||||
fi
|
||||
udevadm control --reload-rules
|
||||
pmx_record_execution "Reload udev rules after Coral removal" "udevadm control --reload-rules"
|
||||
udevadm trigger --subsystem-match=apex >/dev/null 2>&1 || true
|
||||
pmx_record_execution "Trigger udev rules after Coral removal" "udevadm trigger --subsystem-match=apex"
|
||||
|
||||
# Apex system group: only remove if no one else is using it.
|
||||
if getent group apex >/dev/null 2>&1; then
|
||||
local apex_members
|
||||
apex_members=$(getent group apex | cut -d: -f4)
|
||||
if [[ -z "$apex_members" ]]; then
|
||||
groupdel apex >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_apply_setting "group:apex" "getent group apex 2>/dev/null || true" \
|
||||
groupdel apex || true
|
||||
msg_ok "$(translate 'apex group removed.')"
|
||||
else
|
||||
msg_warn "$(translate 'apex group still has members; left in place:') $apex_members"
|
||||
@@ -827,16 +876,19 @@ complete_coral_uninstall() {
|
||||
fi
|
||||
|
||||
# Google Coral APT repo + keyring (only added during USB install).
|
||||
rm -f /etc/apt/sources.list.d/coral-edgetpu.list \
|
||||
local coral_file
|
||||
for coral_file in \
|
||||
/etc/apt/sources.list.d/coral-edgetpu.list \
|
||||
/etc/apt/sources.list.d/coral-cloud.list \
|
||||
/usr/share/keyrings/coral-edgetpu-archive-keyring.gpg \
|
||||
/etc/apt/trusted.gpg.d/coral-edgetpu-archive-keyring.gpg \
|
||||
2>/dev/null || true
|
||||
/etc/apt/trusted.gpg.d/coral-edgetpu-archive-keyring.gpg; do
|
||||
pmx_remove_file "$coral_file" || true
|
||||
done
|
||||
|
||||
# Drop the gasket-dkms version marker written by the install path.
|
||||
# Leaving it around after a full uninstall would let the Monitor
|
||||
# claim a fictional driver version on the next reboot.
|
||||
rm -f /var/lib/proxmenux/coral_gasket_version 2>/dev/null || true
|
||||
pmx_remove_file /var/lib/proxmenux/coral_gasket_version || true
|
||||
|
||||
# Update component status if utils.sh exposes the helper (older
|
||||
# ProxMenux releases didn't have it; uninstall must still work).
|
||||
|
||||
@@ -51,6 +51,10 @@ if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
if [[ -f "$BASE_DIR/scripts/global/pmx_journal.sh" ]]; then
|
||||
source "$BASE_DIR/scripts/global/pmx_journal.sh"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
@@ -161,6 +165,7 @@ suggest_gpu_passthrough_if_needed() {
|
||||
# ==========================================================
|
||||
|
||||
add_udev_rule_for_coral_usb() {
|
||||
pmx_journal_context "add_udev_rule_for_coral_usb" "1.5" "install_coral_lxc.sh"
|
||||
RULE_FILE="/etc/udev/rules.d/99-coral-usb.rules"
|
||||
RULE_CONTENT='# Coral USB Accelerator
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="9302", MODE="0666", TAG+="uaccess", SYMLINK+="coral"
|
||||
@@ -168,14 +173,16 @@ SUBSYSTEM=="usb", ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="9302", MODE="0666"
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="1a6e", ATTRS{idProduct}=="089a", MODE="0666", TAG+="uaccess", SYMLINK+="coral"'
|
||||
|
||||
if [[ ! -f "$RULE_FILE" ]]; then
|
||||
echo "$RULE_CONTENT" > "$RULE_FILE"
|
||||
printf '%s\n' "$RULE_CONTENT" | pmx_write_file "$RULE_FILE"
|
||||
udevadm control --reload-rules && udevadm trigger
|
||||
pmx_record_execution "reload Coral USB udev rules" "udevadm control --reload-rules && udevadm trigger"
|
||||
msg_ok "$(translate 'Udev rules for Coral USB devices added and rules reloaded.')"
|
||||
elif ! grep -q "18d1.*9302\|1a6e.*089a" "$RULE_FILE"; then
|
||||
# Append (>>) instead of overwriting (>) so any user-authored
|
||||
# rules in this file survive.
|
||||
printf '\n%s\n' "$RULE_CONTENT" >> "$RULE_FILE"
|
||||
printf '\n%s\n' "$RULE_CONTENT" | pmx_append_file "$RULE_FILE"
|
||||
udevadm control --reload-rules && udevadm trigger
|
||||
pmx_record_execution "reload Coral USB udev rules" "udevadm control --reload-rules && udevadm trigger"
|
||||
msg_ok "$(translate 'Udev rules for Coral USB devices appended and rules reloaded.')"
|
||||
else
|
||||
msg_ok "$(translate 'Udev rules for Coral USB devices already exist.')"
|
||||
|
||||
@@ -24,6 +24,10 @@ if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
if [[ -f "$BASE_DIR/scripts/global/pmx_journal.sh" ]]; then
|
||||
source "$BASE_DIR/scripts/global/pmx_journal.sh"
|
||||
fi
|
||||
|
||||
if [[ -f "$COMMON_FUNC" ]]; then
|
||||
source "$COMMON_FUNC"
|
||||
fi
|
||||
@@ -73,9 +77,10 @@ check_intel_gpu_tools_installed() {
|
||||
# Install intel-gpu-tools
|
||||
# ==========================================================
|
||||
install_intel_gpu_tools() {
|
||||
pmx_journal_context "install_intel_gpu_tools" "1.0" "intel_gpu_tools.sh"
|
||||
msg_info "$(translate 'Installing intel-gpu-tools...')"
|
||||
|
||||
if apt-get install -y intel-gpu-tools >>"$LOG_FILE" 2>&1; then
|
||||
if pmx_install_pkg intel-gpu-tools >>"$LOG_FILE" 2>&1; then
|
||||
msg_ok "$(translate 'intel-gpu-tools installed successfully')"
|
||||
|
||||
# Get installed version
|
||||
@@ -97,9 +102,11 @@ install_intel_gpu_tools() {
|
||||
# Uninstall intel-gpu-tools
|
||||
# ==========================================================
|
||||
uninstall_intel_gpu_tools() {
|
||||
pmx_journal_context "uninstall_intel_gpu_tools" "1.0" "intel_gpu_tools.sh"
|
||||
msg_info "$(translate 'Uninstalling intel-gpu-tools...')"
|
||||
|
||||
if apt-get remove -y intel-gpu-tools >>"$LOG_FILE" 2>&1; then
|
||||
pmx_record_uninstall "intel-gpu-tools"
|
||||
msg_ok "$(translate 'intel-gpu-tools uninstalled successfully')"
|
||||
|
||||
if type update_component_status &>/dev/null; then
|
||||
@@ -116,6 +123,7 @@ uninstall_intel_gpu_tools() {
|
||||
# Main execution
|
||||
# ==========================================================
|
||||
main() {
|
||||
pmx_journal_context "main" "1.0" "intel_gpu_tools.sh"
|
||||
# Show ProxMenux logo and title
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "$SCRIPT_TITLE")"
|
||||
@@ -149,7 +157,7 @@ main() {
|
||||
if [[ -n "$available_version" && "$available_version" != "$INTEL_GPU_TOOLS_VERSION" ]]; then
|
||||
msg_ok "$(translate 'A newer version is available:') $available_version"
|
||||
|
||||
if apt-get install -y intel-gpu-tools >>"$LOG_FILE" 2>&1; then
|
||||
if pmx_install_pkg intel-gpu-tools >>"$LOG_FILE" 2>&1; then
|
||||
INTEL_GPU_TOOLS_VERSION="$available_version"
|
||||
msg_ok "$(translate 'intel-gpu-tools updated to') $INTEL_GPU_TOOLS_VERSION"
|
||||
|
||||
@@ -219,4 +227,4 @@ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
exit $?
|
||||
fi
|
||||
main
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -53,6 +53,9 @@ export COMPONENTS_STATUS_FILE
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
if [[ -f "$BASE_DIR/scripts/global/pmx_journal.sh" ]]; then
|
||||
source "$BASE_DIR/scripts/global/pmx_journal.sh"
|
||||
fi
|
||||
if [[ -f "$LOCAL_SCRIPTS/global/pci_passthrough_helpers.sh" ]]; then
|
||||
source "$LOCAL_SCRIPTS/global/pci_passthrough_helpers.sh"
|
||||
elif [[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd)/global/pci_passthrough_helpers.sh" ]]; then
|
||||
@@ -134,6 +137,7 @@ check_gpu_not_in_vm_passthrough() {
|
||||
}
|
||||
|
||||
check_stale_vfio_config_for_nvidia() {
|
||||
pmx_journal_context "check_stale_vfio_config_for_nvidia" "1.3" "nvidia_installer.sh"
|
||||
local vfio_conf="/etc/modprobe.d/vfio.conf"
|
||||
[[ ! -f "$vfio_conf" ]] && return 0
|
||||
|
||||
@@ -173,8 +177,10 @@ check_stale_vfio_config_for_nvidia() {
|
||||
|
||||
if declare -F _clean_vfio_conf_ids >/dev/null 2>&1 \
|
||||
&& _clean_vfio_conf_ids "${legacy_ids[@]}"; then
|
||||
pmx_record_execution "Clean stale NVIDIA VFIO IDs" "_clean_vfio_conf_ids ${legacy_ids[*]}"
|
||||
msg_info "$(translate 'Rebuilding initramfs after vfio.conf cleanup...')"
|
||||
update-initramfs -u >/dev/null 2>&1 || true
|
||||
pmx_record_execution "Rebuild initramfs after NVIDIA VFIO cleanup" "update-initramfs -u"
|
||||
msg_ok "$(translate 'Stale VFIO entries removed and initramfs rebuilt.')" | tee -a "$screen_capture"
|
||||
else
|
||||
msg_ok "$(translate 'No changes were needed in vfio.conf.')" | tee -a "$screen_capture"
|
||||
@@ -510,6 +516,7 @@ offer_lxc_updates_if_any() {
|
||||
# System preparation (repos, headers, etc.)
|
||||
# ==========================================================
|
||||
ensure_repos_and_headers() {
|
||||
pmx_journal_context "ensure_repos_and_headers" "1.3" "nvidia_installer.sh"
|
||||
# Bootstrap APT repos FIRST. On a fresh Proxmox install the
|
||||
# pve-no-subscription / debian repos aren't configured by default
|
||||
# → `pve-headers-$(uname -r)` and `build-essential` come back as
|
||||
@@ -537,9 +544,9 @@ ensure_repos_and_headers() {
|
||||
|
||||
if ! dpkg -s "pve-headers-$kver" >/dev/null 2>&1 && \
|
||||
! dpkg -s "proxmox-headers-$kver" >/dev/null 2>&1; then
|
||||
apt-get install -y "pve-headers-$kver" "proxmox-headers-$kver" build-essential dkms >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_install_pkg "pve-headers-$kver" "proxmox-headers-$kver" build-essential dkms || true
|
||||
else
|
||||
apt-get install -y build-essential dkms >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_install_pkg build-essential dkms || true
|
||||
fi
|
||||
|
||||
msg_ok "$(translate 'Kernel headers and build tools verified.')" | tee -a "$screen_capture"
|
||||
@@ -553,14 +560,18 @@ _nouveau_legacy_file_is_proxmenux_shape() {
|
||||
}
|
||||
|
||||
_nouveau_state_set() {
|
||||
pmx_journal_context "_nouveau_state_set" "1.3" "nvidia_installer.sh"
|
||||
local key="$1"
|
||||
mkdir -p "$(dirname "$NVIDIA_NOUVEAU_STATE")"
|
||||
touch "$NVIDIA_NOUVEAU_STATE"
|
||||
if [[ ! -f "$NVIDIA_NOUVEAU_STATE" ]]; then
|
||||
pmx_write_file "$NVIDIA_NOUVEAU_STATE" </dev/null
|
||||
fi
|
||||
grep -qFx "${key}=1" "$NVIDIA_NOUVEAU_STATE" 2>/dev/null \
|
||||
|| echo "${key}=1" >> "$NVIDIA_NOUVEAU_STATE"
|
||||
|| printf '%s\n' "${key}=1" | pmx_append_file "$NVIDIA_NOUVEAU_STATE"
|
||||
}
|
||||
|
||||
restore_nouveau_after_uninstall() {
|
||||
pmx_journal_context "restore_nouveau_after_uninstall" "1.3" "nvidia_installer.sh"
|
||||
local remove_global_line=false
|
||||
|
||||
if [[ -f "$NVIDIA_NOUVEAU_STATE" ]] \
|
||||
@@ -572,29 +583,30 @@ restore_nouveau_after_uninstall() {
|
||||
# version overwrote this exact two-line file and added the matching line
|
||||
# to blacklist.conf, but had no ownership state yet.
|
||||
if _nouveau_legacy_file_is_proxmenux_shape; then
|
||||
rm -f "$NVIDIA_NOUVEAU_LEGACY_BLACKLIST"
|
||||
pmx_remove_file "$NVIDIA_NOUVEAU_LEGACY_BLACKLIST"
|
||||
remove_global_line=true
|
||||
fi
|
||||
|
||||
rm -f "$NVIDIA_NOUVEAU_BLACKLIST"
|
||||
pmx_remove_file "$NVIDIA_NOUVEAU_BLACKLIST"
|
||||
if $remove_global_line && [[ -f "$NVIDIA_GLOBAL_BLACKLIST" ]]; then
|
||||
sed -i '/^blacklist nouveau$/d' "$NVIDIA_GLOBAL_BLACKLIST"
|
||||
pmx_edit_file "$NVIDIA_GLOBAL_BLACKLIST" '/^blacklist nouveau$/d'
|
||||
fi
|
||||
rm -f "$NVIDIA_NOUVEAU_STATE"
|
||||
pmx_remove_file "$NVIDIA_NOUVEAU_STATE"
|
||||
}
|
||||
|
||||
blacklist_nouveau() {
|
||||
pmx_journal_context "blacklist_nouveau" "1.3" "nvidia_installer.sh"
|
||||
msg_info "$(translate 'Blacklisting nouveau driver...')"
|
||||
|
||||
local legacy_owned=false
|
||||
if _nouveau_legacy_file_is_proxmenux_shape; then
|
||||
rm -f "$NVIDIA_NOUVEAU_LEGACY_BLACKLIST"
|
||||
pmx_remove_file "$NVIDIA_NOUVEAU_LEGACY_BLACKLIST"
|
||||
legacy_owned=true
|
||||
_nouveau_state_set "legacy_migrated"
|
||||
fi
|
||||
|
||||
if ! grep -q '^blacklist nouveau$' "$NVIDIA_GLOBAL_BLACKLIST" 2>/dev/null; then
|
||||
echo "blacklist nouveau" >> "$NVIDIA_GLOBAL_BLACKLIST"
|
||||
printf '%s\n' "blacklist nouveau" | pmx_append_file "$NVIDIA_GLOBAL_BLACKLIST"
|
||||
_nouveau_state_set "blacklist_conf_line_added"
|
||||
elif $legacy_owned; then
|
||||
# The legacy ProxMenux file proves ownership of the companion line.
|
||||
@@ -602,7 +614,7 @@ blacklist_nouveau() {
|
||||
fi
|
||||
|
||||
# ProxMenux-owned file: uninstall can now remove only what we created.
|
||||
cat > "$NVIDIA_NOUVEAU_BLACKLIST" <<'EOF'
|
||||
pmx_write_file "$NVIDIA_NOUVEAU_BLACKLIST" <<'EOF'
|
||||
# Managed by ProxMenux NVIDIA installer.
|
||||
blacklist nouveau
|
||||
options nouveau modeset=0
|
||||
@@ -637,8 +649,9 @@ EOF
|
||||
}
|
||||
|
||||
ensure_modules_config() {
|
||||
pmx_journal_context "ensure_modules_config" "1.3" "nvidia_installer.sh"
|
||||
msg_info "$(translate 'Configuring NVIDIA modules...')"
|
||||
cat > /etc/modules-load.d/nvidia-vfio.conf <<'EOF'
|
||||
pmx_write_file /etc/modules-load.d/nvidia-vfio.conf <<'EOF'
|
||||
nvidia
|
||||
nvidia_uvm
|
||||
EOF
|
||||
@@ -646,6 +659,7 @@ EOF
|
||||
}
|
||||
|
||||
stop_and_disable_nvidia_services() {
|
||||
pmx_journal_context "stop_and_disable_nvidia_services" "1.3" "nvidia_installer.sh"
|
||||
local services=(
|
||||
"nvidia-persistenced.service"
|
||||
"nvidia-persistenced"
|
||||
@@ -666,12 +680,7 @@ stop_and_disable_nvidia_services() {
|
||||
msg_info "$(translate 'Stopping and disabling NVIDIA services...')"
|
||||
|
||||
for service in "${services[@]}"; do
|
||||
if systemctl is-active --quiet "$service" 2>/dev/null; then
|
||||
systemctl stop "$service" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if systemctl is-enabled --quiet "$service" 2>/dev/null; then
|
||||
systemctl disable "$service" >/dev/null 2>&1 || true
|
||||
fi
|
||||
pmx_disable_service "$service" || true
|
||||
done
|
||||
|
||||
sleep 2
|
||||
@@ -711,26 +720,36 @@ unload_nvidia_modules() {
|
||||
}
|
||||
|
||||
complete_nvidia_uninstall() {
|
||||
pmx_journal_context "complete_nvidia_uninstall" "1.3" "nvidia_installer.sh"
|
||||
stop_and_disable_nvidia_services
|
||||
unload_nvidia_modules
|
||||
|
||||
if command -v nvidia-uninstall >/dev/null 2>&1; then
|
||||
msg_info "$(translate 'Running NVIDIA uninstaller...')"
|
||||
nvidia-uninstall --silent >>"$LOG_FILE" 2>&1 || true
|
||||
local uninstall_rc=0
|
||||
nvidia-uninstall --silent >>"$LOG_FILE" 2>&1 || uninstall_rc=$?
|
||||
pmx_record_execution "Run NVIDIA uninstaller" "nvidia-uninstall --silent"
|
||||
if [[ "$uninstall_rc" -eq 0 ]]; then
|
||||
pmx_record_uninstall "NVIDIA driver" "1.3"
|
||||
fi
|
||||
msg_ok "$(translate 'NVIDIA uninstaller completed.')"
|
||||
fi
|
||||
msg_ok "$(translate 'NVIDIA uninstallation steps completed.')" | tee -a "$screen_capture"
|
||||
cleanup_nvidia_dkms
|
||||
|
||||
msg_info "$(translate 'Removing NVIDIA packages...')"
|
||||
apt-get -y purge 'nvidia-*' 'libnvidia-*' 'cuda-*' 'libcudnn*' >>"$LOG_FILE" 2>&1 || true
|
||||
if apt-get -y purge 'nvidia-*' 'libnvidia-*' 'cuda-*' 'libcudnn*' >>"$LOG_FILE" 2>&1; then
|
||||
pmx_record_uninstall "nvidia-* libnvidia-* cuda-* libcudnn*" "1.3"
|
||||
fi
|
||||
apt-get -y autoremove --purge >>"$LOG_FILE" 2>&1 || true
|
||||
apt-get -y autoclean >>"$LOG_FILE" 2>&1 || true
|
||||
|
||||
rm -f /etc/modules-load.d/nvidia-vfio.conf
|
||||
rm -f /etc/udev/rules.d/70-nvidia.rules
|
||||
rm -rf /usr/lib/modprobe.d/nvidia*.conf
|
||||
rm -rf /etc/modprobe.d/nvidia*.conf
|
||||
pmx_remove_file /etc/modules-load.d/nvidia-vfio.conf
|
||||
pmx_remove_file /etc/udev/rules.d/70-nvidia.rules
|
||||
local nvidia_conf
|
||||
for nvidia_conf in /usr/lib/modprobe.d/nvidia*.conf /etc/modprobe.d/nvidia*.conf; do
|
||||
[[ -f "$nvidia_conf" ]] && pmx_remove_file "$nvidia_conf"
|
||||
done
|
||||
restore_nouveau_after_uninstall
|
||||
|
||||
if [[ -d "$NVIDIA_WORKDIR" ]]; then
|
||||
@@ -745,6 +764,7 @@ complete_nvidia_uninstall() {
|
||||
}
|
||||
|
||||
cleanup_nvidia_dkms() {
|
||||
pmx_journal_context "cleanup_nvidia_dkms" "1.3" "nvidia_installer.sh"
|
||||
local versions
|
||||
versions=$(dkms status 2>/dev/null | awk -F, '/nvidia/ {gsub(/ /,"",$2); print $2}' || true)
|
||||
|
||||
@@ -754,6 +774,7 @@ cleanup_nvidia_dkms() {
|
||||
while IFS= read -r ver; do
|
||||
[[ -z "$ver" ]] && continue
|
||||
dkms remove -m nvidia -v "$ver" --all >/dev/null 2>&1 || true
|
||||
pmx_record_execution "Remove NVIDIA DKMS entry" "dkms remove -m nvidia -v $ver --all"
|
||||
done <<< "$versions"
|
||||
msg_ok "$(translate 'NVIDIA DKMS entries removed.')"
|
||||
}
|
||||
@@ -1365,6 +1386,7 @@ download_nvidia_installer() {
|
||||
# Installation / uninstallation
|
||||
# ==========================================================
|
||||
run_nvidia_installer() {
|
||||
pmx_journal_context "run_nvidia_installer" "1.3" "nvidia_installer.sh"
|
||||
local installer="$1"
|
||||
|
||||
msg_info2 "$(translate 'Starting NVIDIA installer. This may take several minutes...')"
|
||||
@@ -1377,7 +1399,9 @@ run_nvidia_installer() {
|
||||
if [[ "${NOUVEAU_STILL_LOADED:-false}" == "true" ]]; then
|
||||
msg_info "$(translate 'Rebuilding initramfs to apply nouveau blacklist before installation...')"
|
||||
update-initramfs -u -k all >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "Rebuild initramfs for NVIDIA installation" "update-initramfs -u -k all"
|
||||
proxmox-boot-tool refresh >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "Refresh Proxmox boot configuration for NVIDIA installation" "proxmox-boot-tool refresh"
|
||||
# Try one more time to unload nouveau after initramfs rebuild
|
||||
modprobe -r nouveau 2>/dev/null || true
|
||||
sleep 1
|
||||
@@ -1406,6 +1430,7 @@ run_nvidia_installer() {
|
||||
2>&1 | tee -a "$LOG_FILE"
|
||||
local rc=${PIPESTATUS[0]}
|
||||
echo "" >>"$LOG_FILE"
|
||||
pmx_record_execution "Run NVIDIA driver installer" "sh $installer --no-questions --ui=none --disable-nouveau --no-nouveau-check --dkms"
|
||||
|
||||
rm -rf "$tmp_extract_dir"
|
||||
|
||||
@@ -1415,6 +1440,7 @@ run_nvidia_installer() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
pmx_record_install "NVIDIA driver" "${DRIVER_VERSION:-1.3}"
|
||||
msg_ok "$(translate 'NVIDIA driver installed successfully.')" | tee -a "$screen_capture"
|
||||
return 0
|
||||
}
|
||||
@@ -1424,16 +1450,19 @@ remove_nvidia_driver() {
|
||||
}
|
||||
|
||||
install_udev_rules_and_persistenced() {
|
||||
pmx_journal_context "install_udev_rules_and_persistenced" "1.3" "nvidia_installer.sh"
|
||||
msg_info "$(translate 'Installing NVIDIA udev rules and persistence service...')"
|
||||
|
||||
cat >/etc/udev/rules.d/70-nvidia.rules <<'EOF'
|
||||
pmx_write_file /etc/udev/rules.d/70-nvidia.rules <<'EOF'
|
||||
# /etc/udev/rules.d/70-nvidia.rules
|
||||
KERNEL=="nvidia", RUN+="/bin/bash -c '/usr/bin/nvidia-smi -L'"
|
||||
KERNEL=="nvidia_uvm", RUN+="/bin/bash -c '/usr/bin/nvidia-modprobe -c0 -u'"
|
||||
EOF
|
||||
|
||||
udevadm control --reload-rules
|
||||
pmx_record_execution "Reload NVIDIA udev rules" "udevadm control --reload-rules"
|
||||
udevadm trigger --subsystem-match=drm --subsystem-match=pci || true
|
||||
pmx_record_execution "Trigger NVIDIA udev rules" "udevadm trigger --subsystem-match=drm --subsystem-match=pci"
|
||||
|
||||
ensure_workdir
|
||||
cd "$NVIDIA_WORKDIR" || return 1
|
||||
@@ -1451,13 +1480,19 @@ EOF
|
||||
|
||||
if [[ -d nvidia-persistenced/init ]]; then
|
||||
cd nvidia-persistenced/init || return 1
|
||||
./install.sh >>"$LOG_FILE" 2>&1 || true
|
||||
local persistenced_rc=0
|
||||
./install.sh >>"$LOG_FILE" 2>&1 || persistenced_rc=$?
|
||||
pmx_record_execution "Install NVIDIA persistence service" "$NVIDIA_WORKDIR/nvidia-persistenced/init/install.sh"
|
||||
if [[ "$persistenced_rc" -eq 0 ]]; then
|
||||
pmx_record_install "nvidia-persistenced" "$NVIDIA_PERSISTENCED_TAG"
|
||||
fi
|
||||
fi
|
||||
|
||||
msg_ok "$(translate 'NVIDIA udev rules and persistence service installed.')" | tee -a "$screen_capture"
|
||||
}
|
||||
|
||||
apply_nvidia_patch_if_needed() {
|
||||
pmx_journal_context "apply_nvidia_patch_if_needed" "1.3" "nvidia_installer.sh"
|
||||
# NVIDIA_PATCH_AUTO=yes|no skips the yes/no prompt for non-interactive
|
||||
# callers; unset preserves the interactive menu behavior.
|
||||
case "${NVIDIA_PATCH_AUTO:-}" in
|
||||
@@ -1504,6 +1539,7 @@ apply_nvidia_patch_if_needed() {
|
||||
if [[ -x nvidia-patch/patch.sh ]]; then
|
||||
cd nvidia-patch || return 1
|
||||
./patch.sh >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "Apply NVIDIA patch" "$NVIDIA_WORKDIR/nvidia-patch/patch.sh"
|
||||
msg_ok "$(translate 'NVIDIA patch applied - check README for supported versions.')"
|
||||
update_component_status "nvidia_driver" "installed" "$CURRENT_DRIVER_VERSION" "gpu" '{"patched":true}'
|
||||
else
|
||||
@@ -1739,6 +1775,7 @@ show_version_menu() {
|
||||
# Main flow
|
||||
# ==========================================================
|
||||
main() {
|
||||
pmx_journal_context "main" "1.3" "nvidia_installer.sh"
|
||||
# Rotate the previous run's log instead of truncating — when the
|
||||
# current install fails, the user can compare against the previous
|
||||
# attempt to see what changed. Audit Tier 7 — log truncation.
|
||||
@@ -1846,7 +1883,9 @@ main() {
|
||||
|
||||
msg_info "$(translate 'Updating initramfs for all kernels...')"
|
||||
update-initramfs -u -k all >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "Rebuild initramfs after NVIDIA installation" "update-initramfs -u -k all"
|
||||
proxmox-boot-tool refresh >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "Refresh Proxmox boot configuration after NVIDIA installation" "proxmox-boot-tool refresh"
|
||||
msg_ok "$(translate 'initramfs updated.')"
|
||||
|
||||
msg_info2 "$(translate 'Checking NVIDIA driver status with nvidia-smi')"
|
||||
@@ -1889,7 +1928,9 @@ main() {
|
||||
|
||||
msg_info "$(translate 'Updating initramfs for all kernels...')"
|
||||
update-initramfs -u -k all >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "Rebuild initramfs after NVIDIA removal" "update-initramfs -u -k all"
|
||||
proxmox-boot-tool refresh >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "Refresh Proxmox boot configuration after NVIDIA removal" "proxmox-boot-tool refresh"
|
||||
msg_ok "$(translate 'initramfs updated.')"
|
||||
|
||||
restart_prompt
|
||||
|
||||
@@ -57,6 +57,9 @@ screen_capture="/tmp/proxmenux_gpu_switch_mode_screen_$$.txt"
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
if [[ -f "$BASE_DIR/scripts/global/pmx_journal.sh" ]]; then
|
||||
source "$BASE_DIR/scripts/global/pmx_journal.sh"
|
||||
fi
|
||||
# Both helper libraries are required for the SR-IOV guard and the audio
|
||||
# orphan cascade to work. Surface a loud warning if neither path resolves
|
||||
# — the previous behaviour evaluated `declare -F` later and silently
|
||||
@@ -112,11 +115,12 @@ _set_title() {
|
||||
}
|
||||
|
||||
_add_line_if_missing() {
|
||||
pmx_journal_context "_add_line_if_missing" "1.1" "switch_gpu_mode.sh"
|
||||
local line="$1"
|
||||
local file="$2"
|
||||
touch "$file"
|
||||
[[ -f "$file" ]] || pmx_write_file "$file" < /dev/null
|
||||
if ! grep -qFx "$line" "$file" 2>/dev/null; then
|
||||
echo "$line" >>"$file"
|
||||
echo "$line" | pmx_append_file "$file"
|
||||
HOST_CONFIG_CHANGED=true
|
||||
fi
|
||||
}
|
||||
@@ -199,22 +203,28 @@ _read_vfio_ids() {
|
||||
}
|
||||
|
||||
_write_vfio_ids() {
|
||||
pmx_journal_context "_write_vfio_ids" "1.1" "switch_gpu_mode.sh"
|
||||
local -a ids=("$@")
|
||||
local vfio_conf="/etc/modprobe.d/vfio.conf"
|
||||
touch "$vfio_conf"
|
||||
local vfio_tmp
|
||||
vfio_tmp=$(mktemp)
|
||||
[[ -f "$vfio_conf" ]] && cat "$vfio_conf" > "$vfio_tmp"
|
||||
|
||||
local current_line new_line ids_str
|
||||
current_line=$(grep "^options vfio-pci ids=" "$vfio_conf" 2>/dev/null | head -1)
|
||||
sed -i '/^options vfio-pci ids=/d' "$vfio_conf"
|
||||
sed '/^options vfio-pci ids=/d' "$vfio_tmp" > "${vfio_tmp}.next"
|
||||
mv "${vfio_tmp}.next" "$vfio_tmp"
|
||||
|
||||
if [[ ${#ids[@]} -gt 0 ]]; then
|
||||
ids_str=$(IFS=','; echo "${ids[*]}")
|
||||
new_line="options vfio-pci ids=${ids_str} disable_vga=1"
|
||||
echo "$new_line" >>"$vfio_conf"
|
||||
echo "$new_line" >>"$vfio_tmp"
|
||||
[[ "$current_line" != "$new_line" ]] && HOST_CONFIG_CHANGED=true
|
||||
else
|
||||
[[ -n "$current_line" ]] && HOST_CONFIG_CHANGED=true
|
||||
fi
|
||||
pmx_write_file "$vfio_conf" < "$vfio_tmp"
|
||||
rm -f "$vfio_tmp"
|
||||
}
|
||||
|
||||
_contains_in_array() {
|
||||
@@ -228,6 +238,7 @@ _contains_in_array() {
|
||||
}
|
||||
|
||||
_remove_gpu_blacklist() {
|
||||
pmx_journal_context "_remove_gpu_blacklist" "1.1" "switch_gpu_mode.sh"
|
||||
local gpu_type="$1"
|
||||
local blacklist_file="/etc/modprobe.d/blacklist.conf"
|
||||
[[ ! -f "$blacklist_file" ]] && return
|
||||
@@ -241,12 +252,13 @@ _remove_gpu_blacklist() {
|
||||
;;
|
||||
amd)
|
||||
grep -qE '^blacklist (radeon|amdgpu)$' "$blacklist_file" 2>/dev/null && changed=true
|
||||
sed -i '/^blacklist radeon$/d' "$blacklist_file"
|
||||
sed -i '/^blacklist amdgpu$/d' "$blacklist_file"
|
||||
pmx_edit_file "$blacklist_file" \
|
||||
-e '/^blacklist radeon$/d' \
|
||||
-e '/^blacklist amdgpu$/d'
|
||||
;;
|
||||
intel)
|
||||
grep -qE '^blacklist i915$' "$blacklist_file" 2>/dev/null && changed=true
|
||||
sed -i '/^blacklist i915$/d' "$blacklist_file"
|
||||
pmx_edit_file "$blacklist_file" '/^blacklist i915$/d'
|
||||
;;
|
||||
esac
|
||||
$changed && HOST_CONFIG_CHANGED=true
|
||||
@@ -293,13 +305,15 @@ _add_amd_softdep() {
|
||||
}
|
||||
|
||||
_remove_amd_softdep() {
|
||||
pmx_journal_context "_remove_amd_softdep" "1.1" "switch_gpu_mode.sh"
|
||||
local vfio_conf="/etc/modprobe.d/vfio.conf"
|
||||
[[ ! -f "$vfio_conf" ]] && return
|
||||
local changed=false
|
||||
grep -qE '^softdep (radeon|amdgpu|snd_hda_intel) pre: vfio-pci$' "$vfio_conf" 2>/dev/null && changed=true
|
||||
sed -i '/^softdep radeon pre: vfio-pci$/d' "$vfio_conf"
|
||||
sed -i '/^softdep amdgpu pre: vfio-pci$/d' "$vfio_conf"
|
||||
sed -i '/^softdep snd_hda_intel pre: vfio-pci$/d' "$vfio_conf"
|
||||
pmx_edit_file "$vfio_conf" \
|
||||
-e '/^softdep radeon pre: vfio-pci$/d' \
|
||||
-e '/^softdep amdgpu pre: vfio-pci$/d' \
|
||||
-e '/^softdep snd_hda_intel pre: vfio-pci$/d'
|
||||
$changed && HOST_CONFIG_CHANGED=true
|
||||
$changed
|
||||
}
|
||||
@@ -319,6 +333,7 @@ _add_vfio_modules() {
|
||||
}
|
||||
|
||||
_remove_vfio_modules_if_unused() {
|
||||
pmx_journal_context "_remove_vfio_modules_if_unused" "1.1" "switch_gpu_mode.sh"
|
||||
local vfio_count
|
||||
vfio_count=$(_read_vfio_ids | wc -l | tr -d '[:space:]')
|
||||
[[ "$vfio_count" != "0" ]] && return 1
|
||||
@@ -330,10 +345,11 @@ _remove_vfio_modules_if_unused() {
|
||||
[[ ! -f "$modules_file" ]] && return 1
|
||||
local had_any=false
|
||||
grep -qE '^vfio$|^vfio_iommu_type1$|^vfio_pci$|^vfio_virqfd$' "$modules_file" 2>/dev/null && had_any=true
|
||||
sed -i '/^vfio$/d' "$modules_file"
|
||||
sed -i '/^vfio_iommu_type1$/d' "$modules_file"
|
||||
sed -i '/^vfio_pci$/d' "$modules_file"
|
||||
sed -i '/^vfio_virqfd$/d' "$modules_file"
|
||||
pmx_edit_file "$modules_file" \
|
||||
-e '/^vfio$/d' \
|
||||
-e '/^vfio_iommu_type1$/d' \
|
||||
-e '/^vfio_pci$/d' \
|
||||
-e '/^vfio_virqfd$/d'
|
||||
if $had_any; then
|
||||
HOST_CONFIG_CHANGED=true
|
||||
return 0
|
||||
@@ -422,6 +438,7 @@ _selected_gpu_current_mode() {
|
||||
}
|
||||
|
||||
check_stale_vfio_config_switch_mode() {
|
||||
pmx_journal_context "check_stale_vfio_config_switch_mode" "1.1" "switch_gpu_mode.sh"
|
||||
local vfio_conf="/etc/modprobe.d/vfio.conf"
|
||||
[[ ! -f "$vfio_conf" ]] && return 0
|
||||
|
||||
@@ -461,8 +478,10 @@ check_stale_vfio_config_switch_mode() {
|
||||
|
||||
if declare -F _clean_vfio_conf_ids >/dev/null 2>&1 \
|
||||
&& _clean_vfio_conf_ids "${legacy_ids[@]}"; then
|
||||
pmx_record_execution "remove stale VFIO IDs" "_clean_vfio_conf_ids ${legacy_ids[*]}"
|
||||
msg_info "$(translate 'Rebuilding initramfs after vfio.conf cleanup...')"
|
||||
update-initramfs -u >/dev/null 2>&1 || true
|
||||
pmx_record_execution "rebuild initramfs" "update-initramfs -u"
|
||||
dialog --backtitle "ProxMenux" \
|
||||
--title "$(translate 'Stale VFIO Config Cleaned')" \
|
||||
--msgbox "\n$(translate 'The vfio.conf entries have been removed and initramfs rebuilt.')\n\n$(translate 'A reboot is recommended before the GPU is guaranteed to stay on the native driver.')" \
|
||||
@@ -1056,14 +1075,28 @@ apply_vm_action_for_lxc_mode() {
|
||||
}
|
||||
|
||||
_register_iommu_tool() {
|
||||
pmx_journal_context "_register_iommu_tool" "1.1" "switch_gpu_mode.sh"
|
||||
local tools_json="${BASE_DIR:-/usr/local/share/proxmenux}/installed_tools.json"
|
||||
command -v jq >/dev/null 2>&1 || return 0
|
||||
[[ -f "$tools_json" ]] || echo "{}" > "$tools_json"
|
||||
jq '.vfio_iommu=true' "$tools_json" > "$tools_json.tmp" \
|
||||
&& mv "$tools_json.tmp" "$tools_json" || true
|
||||
local tools_tmp
|
||||
tools_tmp=$(mktemp)
|
||||
if [[ -f "$tools_json" ]]; then
|
||||
jq '.vfio_iommu=true' "$tools_json" > "$tools_tmp" \
|
||||
&& pmx_write_file "$tools_json" < "$tools_tmp" || true
|
||||
else
|
||||
printf '{}\n' > "$tools_tmp"
|
||||
if jq '.vfio_iommu=true' "$tools_tmp" > "${tools_tmp}.next"; then
|
||||
mv "${tools_tmp}.next" "$tools_tmp"
|
||||
else
|
||||
rm -f "${tools_tmp}.next"
|
||||
fi
|
||||
pmx_write_file "$tools_json" < "$tools_tmp" || true
|
||||
fi
|
||||
rm -f "$tools_tmp" "${tools_tmp}.next"
|
||||
}
|
||||
|
||||
_enable_iommu_cmdline() {
|
||||
pmx_journal_context "_enable_iommu_cmdline" "1.1" "switch_gpu_mode.sh"
|
||||
local cpu_vendor
|
||||
cpu_vendor=$(grep -m1 "vendor_id" /proc/cpuinfo 2>/dev/null | awk '{print $3}')
|
||||
|
||||
@@ -1082,14 +1115,16 @@ _enable_iommu_cmdline() {
|
||||
if [[ -f "$cmdline_file" ]] && grep -qE 'root=ZFS=|root=ZFS/' "$cmdline_file" 2>/dev/null; then
|
||||
if ! grep -q "$iommu_param" "$cmdline_file"; then
|
||||
cp "$cmdline_file" "${cmdline_file}.bak.$(date +%Y%m%d_%H%M%S)"
|
||||
sed -i "s|\\s*$| ${iommu_param} iommu=pt|" "$cmdline_file"
|
||||
pmx_edit_file "$cmdline_file" "s|\\s*$| ${iommu_param} iommu=pt|"
|
||||
proxmox-boot-tool refresh >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "refresh Proxmox boot configuration" "proxmox-boot-tool refresh"
|
||||
fi
|
||||
elif [[ -f "$grub_file" ]]; then
|
||||
if ! grep -q "$iommu_param" "$grub_file"; then
|
||||
cp "$grub_file" "${grub_file}.bak.$(date +%Y%m%d_%H%M%S)"
|
||||
sed -i "/GRUB_CMDLINE_LINUX_DEFAULT=/ s|\"$| ${iommu_param} iommu=pt\"|" "$grub_file"
|
||||
pmx_edit_file "$grub_file" "/GRUB_CMDLINE_LINUX_DEFAULT=/ s|\"$| ${iommu_param} iommu=pt\"|"
|
||||
update-grub >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "update GRUB configuration" "update-grub"
|
||||
fi
|
||||
else
|
||||
return 1
|
||||
@@ -1098,6 +1133,7 @@ _enable_iommu_cmdline() {
|
||||
}
|
||||
|
||||
switch_to_vm_mode() {
|
||||
pmx_journal_context "switch_to_vm_mode" "1.1" "switch_gpu_mode.sh"
|
||||
detect_affected_lxc_for_selected
|
||||
prompt_lxc_action_for_vm_mode
|
||||
|
||||
@@ -1177,7 +1213,9 @@ switch_to_vm_mode() {
|
||||
if [[ "$HOST_CONFIG_CHANGED" == "true" ]]; then
|
||||
msg_info "$(translate 'Updating initramfs (this may take a minute)...')"
|
||||
update-initramfs -u -k all >>"$LOG_FILE" 2>&1
|
||||
pmx_record_execution "rebuild initramfs" "update-initramfs -u -k all"
|
||||
proxmox-boot-tool refresh >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "refresh Proxmox boot configuration" "proxmox-boot-tool refresh"
|
||||
msg_ok "$(translate 'initramfs updated')" | tee -a "$screen_capture"
|
||||
fi
|
||||
}
|
||||
@@ -1196,6 +1234,7 @@ _type_has_remaining_vfio_ids() {
|
||||
}
|
||||
|
||||
switch_to_lxc_mode() {
|
||||
pmx_journal_context "switch_to_lxc_mode" "1.1" "switch_gpu_mode.sh"
|
||||
collect_selected_iommu_ids
|
||||
detect_affected_vms_for_selected
|
||||
prompt_vm_action_for_lxc_mode
|
||||
@@ -1256,7 +1295,9 @@ switch_to_lxc_mode() {
|
||||
if [[ "$HOST_CONFIG_CHANGED" == "true" ]]; then
|
||||
msg_info "$(translate 'Updating initramfs (this may take a minute)...')"
|
||||
update-initramfs -u -k all >>"$LOG_FILE" 2>&1
|
||||
pmx_record_execution "rebuild initramfs" "update-initramfs -u -k all"
|
||||
proxmox-boot-tool refresh >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "refresh Proxmox boot configuration" "proxmox-boot-tool refresh"
|
||||
msg_ok "$(translate 'initramfs updated')" | tee -a "$screen_capture"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@ screen_capture="/tmp/proxmenux_gpu_switch_mode_screen_$$.txt"
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
if [[ -f "$BASE_DIR/scripts/global/pmx_journal.sh" ]]; then
|
||||
source "$BASE_DIR/scripts/global/pmx_journal.sh"
|
||||
fi
|
||||
if [[ -f "$LOCAL_SCRIPTS_LOCAL/global/pci_passthrough_helpers.sh" ]]; then
|
||||
source "$LOCAL_SCRIPTS_LOCAL/global/pci_passthrough_helpers.sh"
|
||||
elif [[ -f "$LOCAL_SCRIPTS_DEFAULT/global/pci_passthrough_helpers.sh" ]]; then
|
||||
@@ -92,11 +95,12 @@ _set_title() {
|
||||
}
|
||||
|
||||
_add_line_if_missing() {
|
||||
pmx_journal_context "_add_line_if_missing" "1.1" "switch_gpu_mode_direct.sh"
|
||||
local line="$1"
|
||||
local file="$2"
|
||||
touch "$file"
|
||||
[[ -f "$file" ]] || pmx_write_file "$file" < /dev/null
|
||||
if ! grep -qFx "$line" "$file" 2>/dev/null; then
|
||||
echo "$line" >>"$file"
|
||||
echo "$line" | pmx_append_file "$file"
|
||||
HOST_CONFIG_CHANGED=true
|
||||
fi
|
||||
}
|
||||
@@ -177,22 +181,28 @@ _read_vfio_ids() {
|
||||
}
|
||||
|
||||
_write_vfio_ids() {
|
||||
pmx_journal_context "_write_vfio_ids" "1.1" "switch_gpu_mode_direct.sh"
|
||||
local -a ids=("$@")
|
||||
local vfio_conf="/etc/modprobe.d/vfio.conf"
|
||||
touch "$vfio_conf"
|
||||
local vfio_tmp
|
||||
vfio_tmp=$(mktemp)
|
||||
[[ -f "$vfio_conf" ]] && cat "$vfio_conf" > "$vfio_tmp"
|
||||
|
||||
local current_line new_line ids_str
|
||||
current_line=$(grep "^options vfio-pci ids=" "$vfio_conf" 2>/dev/null | head -1)
|
||||
sed -i '/^options vfio-pci ids=/d' "$vfio_conf"
|
||||
sed '/^options vfio-pci ids=/d' "$vfio_tmp" > "${vfio_tmp}.next"
|
||||
mv "${vfio_tmp}.next" "$vfio_tmp"
|
||||
|
||||
if [[ ${#ids[@]} -gt 0 ]]; then
|
||||
ids_str=$(IFS=','; echo "${ids[*]}")
|
||||
new_line="options vfio-pci ids=${ids_str} disable_vga=1"
|
||||
echo "$new_line" >>"$vfio_conf"
|
||||
echo "$new_line" >>"$vfio_tmp"
|
||||
[[ "$current_line" != "$new_line" ]] && HOST_CONFIG_CHANGED=true
|
||||
else
|
||||
[[ -n "$current_line" ]] && HOST_CONFIG_CHANGED=true
|
||||
fi
|
||||
pmx_write_file "$vfio_conf" < "$vfio_tmp"
|
||||
rm -f "$vfio_tmp"
|
||||
}
|
||||
|
||||
_contains_in_array() {
|
||||
@@ -206,6 +216,7 @@ _contains_in_array() {
|
||||
}
|
||||
|
||||
_remove_gpu_blacklist() {
|
||||
pmx_journal_context "_remove_gpu_blacklist" "1.1" "switch_gpu_mode_direct.sh"
|
||||
local gpu_type="$1"
|
||||
local blacklist_file="/etc/modprobe.d/blacklist.conf"
|
||||
[[ ! -f "$blacklist_file" ]] && return
|
||||
@@ -219,12 +230,13 @@ _remove_gpu_blacklist() {
|
||||
;;
|
||||
amd)
|
||||
grep -qE '^blacklist (radeon|amdgpu)$' "$blacklist_file" 2>/dev/null && changed=true
|
||||
sed -i '/^blacklist radeon$/d' "$blacklist_file"
|
||||
sed -i '/^blacklist amdgpu$/d' "$blacklist_file"
|
||||
pmx_edit_file "$blacklist_file" \
|
||||
-e '/^blacklist radeon$/d' \
|
||||
-e '/^blacklist amdgpu$/d'
|
||||
;;
|
||||
intel)
|
||||
grep -qE '^blacklist i915$' "$blacklist_file" 2>/dev/null && changed=true
|
||||
sed -i '/^blacklist i915$/d' "$blacklist_file"
|
||||
pmx_edit_file "$blacklist_file" '/^blacklist i915$/d'
|
||||
;;
|
||||
esac
|
||||
$changed && HOST_CONFIG_CHANGED=true
|
||||
@@ -271,13 +283,15 @@ _add_amd_softdep() {
|
||||
}
|
||||
|
||||
_remove_amd_softdep() {
|
||||
pmx_journal_context "_remove_amd_softdep" "1.1" "switch_gpu_mode_direct.sh"
|
||||
local vfio_conf="/etc/modprobe.d/vfio.conf"
|
||||
[[ ! -f "$vfio_conf" ]] && return
|
||||
local changed=false
|
||||
grep -qE '^softdep (radeon|amdgpu|snd_hda_intel) pre: vfio-pci$' "$vfio_conf" 2>/dev/null && changed=true
|
||||
sed -i '/^softdep radeon pre: vfio-pci$/d' "$vfio_conf"
|
||||
sed -i '/^softdep amdgpu pre: vfio-pci$/d' "$vfio_conf"
|
||||
sed -i '/^softdep snd_hda_intel pre: vfio-pci$/d' "$vfio_conf"
|
||||
pmx_edit_file "$vfio_conf" \
|
||||
-e '/^softdep radeon pre: vfio-pci$/d' \
|
||||
-e '/^softdep amdgpu pre: vfio-pci$/d' \
|
||||
-e '/^softdep snd_hda_intel pre: vfio-pci$/d'
|
||||
$changed && HOST_CONFIG_CHANGED=true
|
||||
$changed
|
||||
}
|
||||
@@ -297,6 +311,7 @@ _add_vfio_modules() {
|
||||
}
|
||||
|
||||
_remove_vfio_modules_if_unused() {
|
||||
pmx_journal_context "_remove_vfio_modules_if_unused" "1.1" "switch_gpu_mode_direct.sh"
|
||||
local vfio_count
|
||||
vfio_count=$(_read_vfio_ids | wc -l | tr -d '[:space:]')
|
||||
[[ "$vfio_count" != "0" ]] && return 1
|
||||
@@ -308,10 +323,11 @@ _remove_vfio_modules_if_unused() {
|
||||
[[ ! -f "$modules_file" ]] && return 1
|
||||
local had_any=false
|
||||
grep -qE '^vfio$|^vfio_iommu_type1$|^vfio_pci$|^vfio_virqfd$' "$modules_file" 2>/dev/null && had_any=true
|
||||
sed -i '/^vfio$/d' "$modules_file"
|
||||
sed -i '/^vfio_iommu_type1$/d' "$modules_file"
|
||||
sed -i '/^vfio_pci$/d' "$modules_file"
|
||||
sed -i '/^vfio_virqfd$/d' "$modules_file"
|
||||
pmx_edit_file "$modules_file" \
|
||||
-e '/^vfio$/d' \
|
||||
-e '/^vfio_iommu_type1$/d' \
|
||||
-e '/^vfio_pci$/d' \
|
||||
-e '/^vfio_virqfd$/d'
|
||||
if $had_any; then
|
||||
HOST_CONFIG_CHANGED=true
|
||||
return 0
|
||||
@@ -844,14 +860,28 @@ apply_vm_action_for_lxc_mode() {
|
||||
# Switch Mode Functions
|
||||
# ==========================================================
|
||||
_register_iommu_tool() {
|
||||
pmx_journal_context "_register_iommu_tool" "1.1" "switch_gpu_mode_direct.sh"
|
||||
local tools_json="${BASE_DIR:-/usr/local/share/proxmenux}/installed_tools.json"
|
||||
command -v jq >/dev/null 2>&1 || return 0
|
||||
[[ -f "$tools_json" ]] || echo "{}" > "$tools_json"
|
||||
jq '.vfio_iommu=true' "$tools_json" > "$tools_json.tmp" \
|
||||
&& mv "$tools_json.tmp" "$tools_json" || true
|
||||
local tools_tmp
|
||||
tools_tmp=$(mktemp)
|
||||
if [[ -f "$tools_json" ]]; then
|
||||
jq '.vfio_iommu=true' "$tools_json" > "$tools_tmp" \
|
||||
&& pmx_write_file "$tools_json" < "$tools_tmp" || true
|
||||
else
|
||||
printf '{}\n' > "$tools_tmp"
|
||||
if jq '.vfio_iommu=true' "$tools_tmp" > "${tools_tmp}.next"; then
|
||||
mv "${tools_tmp}.next" "$tools_tmp"
|
||||
else
|
||||
rm -f "${tools_tmp}.next"
|
||||
fi
|
||||
pmx_write_file "$tools_json" < "$tools_tmp" || true
|
||||
fi
|
||||
rm -f "$tools_tmp" "${tools_tmp}.next"
|
||||
}
|
||||
|
||||
_enable_iommu_cmdline() {
|
||||
pmx_journal_context "_enable_iommu_cmdline" "1.1" "switch_gpu_mode_direct.sh"
|
||||
local cpu_vendor
|
||||
cpu_vendor=$(grep -m1 "vendor_id" /proc/cpuinfo 2>/dev/null | awk '{print $3}')
|
||||
|
||||
@@ -870,14 +900,16 @@ _enable_iommu_cmdline() {
|
||||
if [[ -f "$cmdline_file" ]] && grep -qE 'root=ZFS=|root=ZFS/' "$cmdline_file" 2>/dev/null; then
|
||||
if ! grep -q "$iommu_param" "$cmdline_file"; then
|
||||
cp "$cmdline_file" "${cmdline_file}.bak.$(date +%Y%m%d_%H%M%S)"
|
||||
sed -i "s|\\s*$| ${iommu_param} iommu=pt|" "$cmdline_file"
|
||||
pmx_edit_file "$cmdline_file" "s|\\s*$| ${iommu_param} iommu=pt|"
|
||||
proxmox-boot-tool refresh >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "refresh Proxmox boot configuration" "proxmox-boot-tool refresh"
|
||||
fi
|
||||
elif [[ -f "$grub_file" ]]; then
|
||||
if ! grep -q "$iommu_param" "$grub_file"; then
|
||||
cp "$grub_file" "${grub_file}.bak.$(date +%Y%m%d_%H%M%S)"
|
||||
sed -i "/GRUB_CMDLINE_LINUX_DEFAULT=/ s|\"$| ${iommu_param} iommu=pt\"|" "$grub_file"
|
||||
pmx_edit_file "$grub_file" "/GRUB_CMDLINE_LINUX_DEFAULT=/ s|\"$| ${iommu_param} iommu=pt\"|"
|
||||
update-grub >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "update GRUB configuration" "update-grub"
|
||||
fi
|
||||
else
|
||||
return 1
|
||||
@@ -886,6 +918,7 @@ _enable_iommu_cmdline() {
|
||||
}
|
||||
|
||||
switch_to_vm_mode() {
|
||||
pmx_journal_context "switch_to_vm_mode" "1.1" "switch_gpu_mode_direct.sh"
|
||||
detect_affected_lxc_for_selected
|
||||
prompt_lxc_action_for_vm_mode
|
||||
|
||||
@@ -956,7 +989,9 @@ switch_to_vm_mode() {
|
||||
if [[ "$HOST_CONFIG_CHANGED" == "true" ]]; then
|
||||
msg_info "$(translate 'Updating initramfs (this may take a minute)...')"
|
||||
update-initramfs -u -k all >>"$LOG_FILE" 2>&1
|
||||
pmx_record_execution "rebuild initramfs" "update-initramfs -u -k all"
|
||||
proxmox-boot-tool refresh >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "refresh Proxmox boot configuration" "proxmox-boot-tool refresh"
|
||||
msg_ok "$(translate 'initramfs updated')" | tee -a "$screen_capture"
|
||||
fi
|
||||
}
|
||||
@@ -975,6 +1010,7 @@ _type_has_remaining_vfio_ids() {
|
||||
}
|
||||
|
||||
switch_to_lxc_mode() {
|
||||
pmx_journal_context "switch_to_lxc_mode" "1.1" "switch_gpu_mode_direct.sh"
|
||||
collect_selected_iommu_ids
|
||||
detect_affected_vms_for_selected
|
||||
prompt_vm_action_for_lxc_mode
|
||||
@@ -1035,7 +1071,9 @@ switch_to_lxc_mode() {
|
||||
if [[ "$HOST_CONFIG_CHANGED" == "true" ]]; then
|
||||
msg_info "$(translate 'Updating initramfs (this may take a minute)...')"
|
||||
update-initramfs -u -k all >>"$LOG_FILE" 2>&1
|
||||
pmx_record_execution "rebuild initramfs" "update-initramfs -u -k all"
|
||||
proxmox-boot-tool refresh >>"$LOG_FILE" 2>&1 || true
|
||||
pmx_record_execution "refresh Proxmox boot configuration" "proxmox-boot-tool refresh"
|
||||
msg_ok "$(translate 'initramfs updated')" | tee -a "$screen_capture"
|
||||
fi
|
||||
}
|
||||
@@ -1154,6 +1192,7 @@ parse_arguments() {
|
||||
# Main Entry Point
|
||||
# ==========================================================
|
||||
check_stale_vfio_config_switch_mode_direct() {
|
||||
pmx_journal_context "check_stale_vfio_config_switch_mode_direct" "1.1" "switch_gpu_mode_direct.sh"
|
||||
local vfio_conf="/etc/modprobe.d/vfio.conf"
|
||||
[[ ! -f "$vfio_conf" ]] && return 0
|
||||
|
||||
@@ -1180,7 +1219,9 @@ check_stale_vfio_config_switch_mode_direct() {
|
||||
msg_info "$(translate 'Removing stale VFIO entries from vfio.conf...')"
|
||||
if declare -F _clean_vfio_conf_ids >/dev/null 2>&1 \
|
||||
&& _clean_vfio_conf_ids "${legacy_ids[@]}"; then
|
||||
pmx_record_execution "remove stale VFIO IDs" "_clean_vfio_conf_ids ${legacy_ids[*]}"
|
||||
update-initramfs -u >/dev/null 2>&1 || true
|
||||
pmx_record_execution "rebuild initramfs" "update-initramfs -u"
|
||||
HOST_CONFIG_CHANGED=true
|
||||
msg_ok "$(translate 'Stale VFIO entries removed and initramfs rebuilt.')" | tee -a "$screen_capture"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user