Update 1.2.4

This commit is contained in:
MacRimi
2026-07-21 18:26:11 +02:00
parent c81230b0f3
commit b2c6a6c536
16 changed files with 967 additions and 342 deletions

View File

@@ -3398,15 +3398,8 @@ _rs_run_custom_restore() {
# user-installed packages is a prerequisite for the restored
# systemd units and config files to actually do anything.
# ─── _rs_import_data_pools ─────────────────────────────────────
# Imports the ZFS data pools that were present in the backup but
# aren't yet imported on this host — typical scenario is a fresh
# PVE install onto new boot disks while the data-pool disks stay
# in place. Never touches the root pool (already imported by the
# system), never forces an import that's missing disks. Retries
# with -f when the pool imports as foreign (hostid mismatch after
# a fresh install regrabs the label) and reports which pools were
# forced so the operator has traceability.
# Import non-root ZFS pools from the backup manifest whose disks are
# all present on this host. Retry with -f on foreign hostid.
_rs_import_data_pools() {
local staging_root="$1"
local manifest="$staging_root/manifest.json"
@@ -3414,7 +3407,6 @@ _rs_import_data_pools() {
command -v zpool >/dev/null 2>&1 || return 0
command -v jq >/dev/null 2>&1 || return 0
# Root pool → skip (mounted at / by the system).
local root_pool=""
if command -v zfs >/dev/null 2>&1; then
local root_dataset
@@ -3423,7 +3415,6 @@ _rs_import_data_pools() {
root_pool="${root_dataset%%/*}"
fi
# Live pools already imported.
local live_pools
live_pools=$(zpool list -H -o name 2>/dev/null || true)
@@ -3437,15 +3428,21 @@ _rs_import_data_pools() {
continue
fi
local devs_all devs_total=0 devs_present=0 devs_missing=0 dev
local devs_all devs_total=0 devs_present=0 devs_missing=0 dev dev_path
devs_all=$(jq -r --arg n "$pool_name" \
'.storage_inventory.zfs_pools[]? | select(.name==$n) | .devices_by_id[]?' \
"$manifest" 2>/dev/null)
[[ -z "$devs_all" ]] && continue
# devices_by_id entries can be a by-id basename or an absolute path.
while IFS= read -r dev; do
[[ -z "$dev" ]] && continue
((devs_total++))
if [[ -e "/dev/disk/by-id/$dev" ]]; then
if [[ "$dev" == /* ]]; then
dev_path="$dev"
else
dev_path="/dev/disk/by-id/$dev"
fi
if [[ -e "$dev_path" ]]; then
((devs_present++))
else
((devs_missing++))
@@ -3483,7 +3480,6 @@ _rs_import_data_pools() {
fi
if (( ${#forced[@]} > 0 )); then
msg_ok "$(translate 'Imported (foreign hostid, forced):') ${forced[*]}"
echo -e "${TAB}${BL}$(translate "New hostid grabbed onto the pool label — next boot imports clean.")${CL}"
fi
if (( ${#partial[@]} > 0 )); then
msg_warn "$(translate 'Skipped (some disks missing):') ${partial[*]}"
@@ -3495,22 +3491,15 @@ _rs_import_data_pools() {
msg_error "$(translate 'Import failed — inspect with `zpool import`:') ${failed[*]}"
fi
# Persist so the post-restore card can show the summary after the
# reboot. The Monitor's ScriptTerminalModal loses its buffer on
# close, and monitor_apply.sh doesn't tee to disk — without this
# the operator has no trace once they dismiss the terminal.
_rs_persist_datapool_import "${ok[@]}" "|FORCED|" "${forced[@]}" \
"|PARTIAL|" "${partial[@]}" "|MISSING|" "${missing[@]}" \
"|FAILED|" "${failed[@]}"
}
# Serialise the auto-import result to restore-state.json (Monitor UI)
# and to a raw log under /var/log/proxmenux. The state file is the
# same one apply_cluster_postboot.sh manages — jq `*` merge preserves
# our section when postboot later seeds its own fields. If the file
# doesn't exist yet (operator hasn't rebooted, or restore ran without
# a pending-reboot step) we seed placeholder fields so the UI can
# still render just our section without crashing on undefined keys.
# Write the data_pools_import section into restore-state.json (which
# the Backups tab polls) and an append-only log under /var/log/proxmenux.
# Seeds the state file with placeholder fields when it doesn't exist yet
# so the UI can render just this section without crashing on undefined keys.
_rs_persist_datapool_import() {
command -v jq >/dev/null 2>&1 || return 0
local mode="OK"
@@ -3540,7 +3529,6 @@ _rs_persist_datapool_import() {
local log_file="$log_dir/restore-datapools-$(date +%Y%m%d_%H%M%S).log"
mkdir -p "$state_dir" "$log_dir" 2>/dev/null || true
# Raw log — appended one line per pool with the resolved action.
{
echo "=== ProxMenux data-pool auto-import at $(date -Iseconds) ==="
local p
@@ -3551,9 +3539,8 @@ _rs_persist_datapool_import() {
for p in "${failed[@]}"; do echo "FAILED $p (zpool import failed — inspect manually)"; done
} >>"$log_file" 2>/dev/null || true
# State-file JSON section. `jq -sR` reads each array from a
# newline-separated stdin so pool names with spaces (partial
# entries include a count fragment) round-trip intact.
# jq -sR reads each array from newline-separated stdin so entries
# containing spaces (partial pools carry a count fragment) round-trip.
local ok_json forced_json partial_json missing_json failed_json section
ok_json=$(printf '%s\n' "${ok[@]:-}" | jq -Rsc 'split("\n") | map(select(length>0))')
forced_json=$(printf '%s\n' "${forced[@]:-}" | jq -Rsc 'split("\n") | map(select(length>0))')
@@ -3578,16 +3565,12 @@ _rs_persist_datapool_import() {
local tmp
tmp=$(mktemp "${state_file}.XXXXXX") || return 0
if [[ -f "$state_file" ]]; then
# Merge into whatever's already there.
if jq -c ". * $section" "$state_file" > "$tmp" 2>/dev/null; then
mv -f "$tmp" "$state_file"
else
rm -f "$tmp"
fi
else
# Seed with placeholders so the card renders without crashing
# if the operator never triggers the postboot flow (hot-only
# restore or a restore path where no reboot is scheduled).
local seed
seed=$(jq -n \
--arg started "$(date -Iseconds)" \
@@ -3832,14 +3815,9 @@ _rs_run_complete_extras() {
fi
# ─ Data ZFS pools — auto-import ───────────────────────────
# Import ZFS pools that were present in the backup and whose
# disks are still on this host. Skips the root pool (already
# imported by the system) and any pool missing disks. When a
# pool imports but ZFS marks it foreign (hostid mismatch from
# a fresh install), retries with -f and flags it for the
# operator. Field-reported by Juan C.: fresh PVE install →
# restore → separate data pool stayed unavailable because
# zfs-import-scan.service failed on hostid mismatch.
# Skips the root pool and any pool whose disks aren't all present.
# Retries with -f when ZFS rejects the import as foreign and flags
# the pool as forced in the report.
_rs_import_data_pools "$staging_root"
# ─ Guest configs — only in full strategies ────────────────

View File

@@ -107,6 +107,7 @@ hb_default_profile_paths() {
# ── Common Proxmox tooling (skipped if not present) ──
"/etc/systemd/system" # custom units (including log2ram.service if installed)
"/etc/systemd/journald.conf" # journal retention tuning from post-install
"/etc/systemd/network" # .link files that pin NIC names to MAC
"/etc/log2ram.conf"
"/etc/logrotate.conf"
"/etc/logrotate.d" # post-install drops log2ram + custom logrotate here

View File

@@ -30,9 +30,16 @@ while IFS= read -r pool_json; do
needed_devs="$(printf '%s' "$pool_json" | jq -r '.devices_by_id[]?')"
present=()
missing=()
# devices_by_id entries can be a by-id basename or an absolute path.
dev_path=""
while IFS= read -r dev; do
[[ -z "$dev" ]] && continue
if [[ -e "/dev/disk/by-id/$dev" ]]; then
if [[ "$dev" == /* ]]; then
dev_path="$dev"
else
dev_path="/dev/disk/by-id/$dev"
fi
if [[ -e "$dev_path" ]]; then
present+=("$dev")
else
missing+=("$dev")

View File

@@ -241,7 +241,12 @@ update_pve_safe() {
lvm_repair_check
fi
# ── 9. Final cleanup ──
# ── 9. DKMS driver rebuild if a new kernel was staged ──
if declare -f pmx_rebuild_dkms_after_kernel >/dev/null 2>&1; then
pmx_rebuild_dkms_after_kernel
fi
# ── 10. Final cleanup ──
apt-get -y autoremove >/dev/null 2>&1 || true
apt-get -y autoclean >/dev/null 2>&1 || true
msg_ok "$(translate "Cleanup finished")"

View File

@@ -153,3 +153,306 @@ install_single_package() {
return 1
fi
}
# ==========================================================
# Persistent NIC naming — shared helpers
# ==========================================================
# ProxMenux-owned .link files carry two identifying marks:
# - filename prefix: 10-proxmenux-<iface>.link
# - first-line marker: `# Managed by ProxMenux — do not edit`
# Both are required for pmx_uninstall_persistent_network to remove a
# file, so user-authored .link files in /etc/systemd/network/ are
# never touched.
readonly PMX_NIC_LINK_DIR="/etc/systemd/network"
readonly PMX_NIC_LINK_MARKER="# Managed by ProxMenux — do not edit"
# Print the MAC address recorded in a .link file, uppercased, or empty
# if the file has no MACAddress= line.
_pmx_link_mac() {
awk -F= '
/^[[:space:]]*MACAddress=/ {
gsub(/[[:space:]]/, "", $2)
print toupper($2)
exit
}
' "$1" 2>/dev/null
}
# Returns 0 if the file exists AND its first line matches the marker.
_pmx_link_is_managed() {
local first
IFS= read -r first < "$1" 2>/dev/null || return 1
[[ "$first" == "$PMX_NIC_LINK_MARKER" ]]
}
# Returns 0 if the file matches the exact template ProxMenux 1.0 used
# to write (no marker, no extra fields). Used once at 1.1 upgrade time
# to reclaim files created by an earlier version and replace them with
# the marked format.
_pmx_link_matches_legacy_10() {
local file="$1" iface mac
iface=$(basename "$file" .link)
iface="${iface#10-}"
mac=$(_pmx_link_mac "$file")
[[ -z "$iface" || -z "$mac" ]] && return 1
local expected
expected="[Match]
MACAddress=$mac
[Link]
Name=$iface"
[[ "$(cat "$file" 2>/dev/null)" == "$expected" ]]
}
# Detect physical NICs and generate 10-proxmenux-<iface>.link for
# each. Idempotent — reruns replace stale ProxMenux entries whose MAC
# is no longer present, migrate 1.0-format files, and leave every
# other .link in the directory alone.
#
# Outputs (via `printf`):
# line "COUNT=<n>" — number of managed files after the run
# line "REMOVED_STALE=<n>" — reconciled entries for missing MACs
# line "REMOVED_LEGACY=<n>" — 1.0-format files replaced
pmx_setup_persistent_network() {
mkdir -p "$PMX_NIC_LINK_DIR"
declare -A current_macs=()
local dev_path iface mac
for dev_path in /sys/class/net/*; do
iface=$(basename "$dev_path")
case "$iface" in
lo|docker*|veth*|br-*|vmbr*|tap*|fwpr*|fwln*|virbr*|bond*|cilium*|zt*|wg*)
continue ;;
esac
if [[ -e "$dev_path/device" || -e "$dev_path/phy80211" ]]; then
mac=$(cat "$dev_path/address" 2>/dev/null | tr '[:lower:]' '[:upper:]')
[[ "$mac" =~ ^([A-F0-9]{2}:){5}[A-F0-9]{2}$ ]] && current_macs["$mac"]=1
fi
done
if compgen -G "$PMX_NIC_LINK_DIR"/*.link >/dev/null; then
local backup_dir
backup_dir="$PMX_NIC_LINK_DIR/backup-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$backup_dir"
cp "$PMX_NIC_LINK_DIR"/*.link "$backup_dir"/ 2>/dev/null || true
fi
local removed_stale=0 removed_legacy=0
local link_file
for link_file in "$PMX_NIC_LINK_DIR"/10-proxmenux-*.link; do
[[ -f "$link_file" ]] || continue
_pmx_link_is_managed "$link_file" || continue
mac=$(_pmx_link_mac "$link_file")
[[ -z "$mac" ]] && continue
if [[ -z "${current_macs[$mac]+x}" ]]; then
rm -f -- "$link_file"
removed_stale=$((removed_stale + 1))
fi
done
for link_file in "$PMX_NIC_LINK_DIR"/10-*.link; do
[[ -f "$link_file" ]] || continue
[[ "$(basename "$link_file")" == 10-proxmenux-*.link ]] && continue
if _pmx_link_matches_legacy_10 "$link_file"; then
rm -f -- "$link_file"
removed_legacy=$((removed_legacy + 1))
fi
done
local count=0
for dev_path in /sys/class/net/*; do
iface=$(basename "$dev_path")
case "$iface" in
lo|docker*|veth*|br-*|vmbr*|tap*|fwpr*|fwln*|virbr*|bond*|cilium*|zt*|wg*)
continue ;;
esac
[[ -e "$dev_path/device" || -e "$dev_path/phy80211" ]] || continue
mac=$(cat "$dev_path/address" 2>/dev/null | tr '[:lower:]' '[:upper:]')
[[ "$mac" =~ ^([A-F0-9]{2}:){5}[A-F0-9]{2}$ ]] || continue
local link_file="$PMX_NIC_LINK_DIR/10-proxmenux-$iface.link"
cat > "$link_file" <<EOF
$PMX_NIC_LINK_MARKER
[Match]
MACAddress=$mac
[Link]
Name=$iface
EOF
chmod 644 "$link_file"
count=$((count + 1))
done
printf 'COUNT=%d\n' "$count"
printf 'REMOVED_STALE=%d\n' "$removed_stale"
printf 'REMOVED_LEGACY=%d\n' "$removed_legacy"
}
# Remove only files that carry BOTH the ProxMenux filename prefix AND
# the marker on the first line. User-authored .link files are left
# intact regardless of their name.
pmx_uninstall_persistent_network() {
local removed=0 link_file
for link_file in "$PMX_NIC_LINK_DIR"/10-proxmenux-*.link; do
[[ -f "$link_file" ]] || continue
_pmx_link_is_managed "$link_file" || continue
rm -f -- "$link_file"
removed=$((removed + 1))
done
printf 'REMOVED=%d\n' "$removed"
}
# ==========================================================
# DKMS driver rebuild after a kernel upgrade
# ==========================================================
# Called from update-pve-safe.sh and proxmox_update.sh after
# apt full-upgrade succeeds. Detects whether a new kernel is
# staged for the next boot and, if so, ensures matching headers
# are installed and rebuilds every DKMS module registered by
# ProxMenux components against that kernel — so drivers keep
# working after the reboot without operator intervention.
readonly PMX_COMPONENTS_STATUS="/usr/local/share/proxmenux/components_status.json"
# component_key : dkms_module_name (empty module = not DKMS)
readonly -a PMX_DKMS_COMPONENTS=(
"nvidia_driver:nvidia"
"coral_driver:gasket"
)
# Print the newest installed pve/proxmox kernel version, or empty.
_pmx_newest_installed_kernel() {
dpkg-query -W -f='${Status}\t${Package}\n' \
'proxmox-kernel-*-pve-signed' 'pve-kernel-*-pve' 2>/dev/null \
| awk -F'\t' '/^install ok installed\t/ { print $2 }' \
| sed -E 's/^(proxmox|pve)-kernel-//; s/-signed$//' \
| sort -V | tail -1
}
# Return the header package name matching a kernel version.
_pmx_header_pkg_for_kernel() {
local kver="$1"
if apt-cache show "proxmox-headers-$kver" >/dev/null 2>&1; then
printf 'proxmox-headers-%s\n' "$kver"
else
printf 'pve-headers-%s\n' "$kver"
fi
}
# Return 0 if the DKMS module is 'installed' for the given kernel.
_pmx_dkms_module_installed_for_kernel() {
local module="$1" kver="$2"
command -v dkms >/dev/null 2>&1 || return 1
dkms status 2>/dev/null | awk -v m="$module" -v k="$kver" '
BEGIN { FS = "[,:]" }
{
gsub(/[[:space:]]/, "")
if (index($0, m "/") == 1 && index($0, k) > 0 && $0 ~ /installed/) {
found = 1; exit
}
}
END { exit found ? 0 : 1 }
'
}
# Best-effort rebuild. Never returns non-zero — the update flow that
# calls this must always complete regardless of driver state.
pmx_rebuild_dkms_after_kernel() {
command -v dkms >/dev/null 2>&1 || return 0
[[ -f "$PMX_COMPONENTS_STATUS" ]] || return 0
command -v jq >/dev/null 2>&1 || return 0
local running_kernel newest_kernel
running_kernel=$(uname -r)
newest_kernel=$(_pmx_newest_installed_kernel)
[[ -z "$newest_kernel" || "$newest_kernel" == "$running_kernel" ]] && return 0
local -a pending_components=() pending_modules=()
local entry key module status
for entry in "${PMX_DKMS_COMPONENTS[@]}"; do
key="${entry%%:*}"
module="${entry##*:}"
status=$(jq -r --arg k "$key" '.[$k].status // ""' "$PMX_COMPONENTS_STATUS" 2>/dev/null)
[[ "$status" == "installed" ]] || continue
pending_components+=("$key")
pending_modules+=("$module")
done
(( ${#pending_components[@]} == 0 )) && return 0
local msg
msg="$(translate 'A new kernel is staged for the next boot:')"$'\n'
msg+=" ${newest_kernel}"$'\n\n'
msg+="$(translate 'The following DKMS-managed drivers will now be rebuilt against it so they keep working after reboot:')"$'\n'
local c
for c in "${pending_components[@]}"; do
msg+="$c"$'\n'
done
msg+=$'\n'"$(translate 'This may take a few minutes. Press OK to proceed.')"
if [[ -t 0 ]] && command -v whiptail >/dev/null 2>&1; then
whiptail --title "$(translate 'DKMS driver rebuild')" --msgbox "$msg" 18 78
else
msg_info "$(translate 'New kernel staged; rebuilding DKMS drivers:') ${pending_components[*]}"
fi
local header_pkg
header_pkg=$(_pmx_header_pkg_for_kernel "$newest_kernel")
if ! dpkg-query -W -f='${Status}' "$header_pkg" 2>/dev/null | grep -q 'install ok installed'; then
msg_info "$(translate 'Installing kernel headers:') $header_pkg"
if DEBIAN_FRONTEND=noninteractive apt-get install -y "$header_pkg" >/dev/null 2>&1; then
msg_ok "$(translate 'Kernel headers installed')"
else
msg_warn "$(translate 'Kernel headers install failed — DKMS rebuild will likely fail:') $header_pkg"
fi
fi
msg_info "$(translate 'Running dkms autoinstall for kernel') ${newest_kernel}..."
dkms autoinstall -k "$newest_kernel" >/dev/null 2>&1 || true
local -a failed_components=() failed_modules=()
local i
for i in "${!pending_components[@]}"; do
if ! _pmx_dkms_module_installed_for_kernel "${pending_modules[$i]}" "$newest_kernel"; then
failed_components+=("${pending_components[$i]}")
failed_modules+=("${pending_modules[$i]}")
fi
done
if (( ${#failed_components[@]} == 0 )); then
msg_ok "$(translate 'DKMS drivers rebuilt for kernel') ${newest_kernel}: ${pending_components[*]}"
return 0
fi
msg_warn "$(translate 'dkms autoinstall did not activate:') ${failed_components[*]}"
msg_info "$(translate 'Falling back to each installer with --auto-reinstall...')"
declare -A installer_for=(
[nvidia_driver]="/usr/local/share/proxmenux/scripts/gpu_tpu/nvidia_installer.sh"
[coral_driver]="/usr/local/share/proxmenux/scripts/gpu_tpu/install_coral.sh"
)
local comp installer still_failing=()
for comp in "${failed_components[@]}"; do
installer="${installer_for[$comp]:-}"
[[ -n "$installer" && -x "$installer" ]] || {
still_failing+=("$comp")
continue
}
msg_info "$(translate 'Reinstalling') $comp..."
if bash "$installer" --auto-reinstall >/dev/null 2>&1; then
msg_ok "$(translate 'Reinstalled') $comp"
else
still_failing+=("$comp")
fi
done
if (( ${#still_failing[@]} > 0 )); then
msg_warn "$(translate 'The following drivers could not be rebuilt for the new kernel — run their installer manually after reboot:') ${still_failing[*]}"
else
msg_ok "$(translate 'DKMS drivers reinstalled for kernel') ${newest_kernel}"
fi
return 0
}

View File

@@ -751,57 +751,45 @@ guided_configuration_cleanup() {
setup_persistent_network() {
local LINK_DIR="/etc/systemd/network"
local BACKUP_DIR="/etc/systemd/network/backup-$(date +%Y%m%d-%H%M%S)"
if ! dialog --title "$(translate "Network Interface Setup")" \
--yesno "\n$(translate "Create persistent network interface names?")" 8 60; then
return 1
fi
show_proxmenux_logo
show_proxmenux_logo
msg_info "$(translate "Setting up persistent network interfaces")"
sleep 2
# Create directory
mkdir -p "$LINK_DIR"
# Backup existing files
if ls "$LINK_DIR"/*.link >/dev/null 2>&1; then
mkdir -p "$BACKUP_DIR"
cp "$LINK_DIR"/*.link "$BACKUP_DIR"/ 2>/dev/null || true
fi
# Process physical interfaces
local count=0
for iface in $(ls /sys/class/net/ | grep -vE "lo|docker|veth|br-|vmbr|tap|fwpr|fwln|virbr|bond|cilium|zt|wg"); do
if [[ -e "/sys/class/net/$iface/device" ]] || [[ -e "/sys/class/net/$iface/phy80211" ]]; then
local MAC=$(cat /sys/class/net/$iface/address 2>/dev/null)
if [[ "$MAC" =~ ^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$ ]]; then
local LINK_FILE="$LINK_DIR/10-$iface.link"
cat > "$LINK_FILE" <<EOF
[Match]
MACAddress=$MAC
[Link]
Name=$iface
EOF
chmod 644 "$LINK_FILE"
((count++))
fi
if ! type pmx_setup_persistent_network &>/dev/null; then
if [[ -f "$LOCAL_SCRIPTS/global/utils-install-functions.sh" ]]; then
source "$LOCAL_SCRIPTS/global/utils-install-functions.sh"
fi
done
if [[ $count -gt 0 ]]; then
fi
local count=0 removed_stale=0 removed_legacy=0
while IFS='=' read -r key value; do
case "$key" in
COUNT) count="$value" ;;
REMOVED_STALE) removed_stale="$value" ;;
REMOVED_LEGACY) removed_legacy="$value" ;;
esac
done < <(pmx_setup_persistent_network)
if (( removed_legacy > 0 )); then
msg_ok "$(translate "Migrated") $removed_legacy $(translate "legacy .link file(s) to the ProxMenux-managed format")"
fi
if (( removed_stale > 0 )); then
msg_ok "$(translate "Reconciled") $removed_stale $(translate "stale entry/entries for interfaces no longer present")"
fi
if (( count > 0 )); then
msg_ok "$(translate "Created persistent names for") $count $(translate "interfaces")"
msg_ok "$(translate "Changes will apply after reboot.")"
else
msg_warn "$(translate "No physical interfaces found")"
fi
register_tool "persistent_network" true
echo -e
msg_success "$(translate "Press ENTER to continue...")"
read -r
register_tool "persistent_network" true
echo -e
msg_success "$(translate "Press ENTER to continue...")"
read -r
}

View File

@@ -497,7 +497,7 @@ force_apt_ipv4() {
# ==========================================================
apply_network_optimizations() {
local FUNC_VERSION="1.0"
local FUNC_VERSION="1.1"
# description: Tune TCP buffers, somaxconn, IPv4 hardening and disable rp_filter on fw bridges (PVE 9 compatible).
msg_info "$(translate "Optimizing network settings...")"
NECESSARY_REBOOT=1
@@ -548,8 +548,38 @@ EOF
sysctl --system > /dev/null 2>&1
cat > /usr/local/sbin/proxmenux-fwbr-tune <<'EOF'
#!/usr/bin/env bash
# Set rp_filter=0 and log_martians=0 on Proxmox fw bridge interfaces.
# No arg → sweep every interface currently under /proc/sys/net/ipv4/conf/.
# One arg → tune only that interface (used by the udev rule).
set -u
cat >/etc/systemd/system/proxmenux-fwbr-tune.service <<'EOF'
tune_interface() {
local iface="$1"
local sysctl_path="/proc/sys/net/ipv4/conf/${iface}"
case "$iface" in
fwbr*|fwln*|fwpr*|tap*)
[[ -d "$sysctl_path" ]] || return 0
[[ -w "$sysctl_path/rp_filter" ]] && printf '0\n' > "$sysctl_path/rp_filter"
[[ -w "$sysctl_path/log_martians" ]] && printf '0\n' > "$sysctl_path/log_martians"
;;
esac
}
if [[ $# -gt 0 ]]; then
tune_interface "$1"
else
for sysctl_path in /proc/sys/net/ipv4/conf/*; do
[[ -d "$sysctl_path" ]] || continue
tune_interface "${sysctl_path##*/}"
done
fi
EOF
chmod 0755 /usr/local/sbin/proxmenux-fwbr-tune
chown root:root /usr/local/sbin/proxmenux-fwbr-tune
cat > /etc/systemd/system/proxmenux-fwbr-tune.service <<'EOF'
[Unit]
Description=ProxMenux - Tune rp_filter/log_martians on virtual fw bridges
After=network-online.target
@@ -557,14 +587,26 @@ Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'for i in /proc/sys/net/ipv4/conf/*; do n=${i##*/}; case "$n" in fwbr*|fwln*|fwpr*|tap*) echo 0 > /proc/sys/net/ipv4/conf/$n/rp_filter; echo 0 > /proc/sys/net/ipv4/conf/$n/log_martians; esac; done'
ExecStart=/usr/local/sbin/proxmenux-fwbr-tune
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
cat > /etc/udev/rules.d/99-proxmenux-fwbr-tune.rules <<'EOF'
ACTION=="add", SUBSYSTEM=="net", KERNEL=="fwbr*", RUN+="/usr/local/sbin/proxmenux-fwbr-tune %k"
ACTION=="add", SUBSYSTEM=="net", KERNEL=="fwln*", RUN+="/usr/local/sbin/proxmenux-fwbr-tune %k"
ACTION=="add", SUBSYSTEM=="net", KERNEL=="fwpr*", RUN+="/usr/local/sbin/proxmenux-fwbr-tune %k"
ACTION=="add", SUBSYSTEM=="net", KERNEL=="tap*", RUN+="/usr/local/sbin/proxmenux-fwbr-tune %k"
EOF
chmod 0644 /etc/udev/rules.d/99-proxmenux-fwbr-tune.rules
chown root:root /etc/udev/rules.d/99-proxmenux-fwbr-tune.rules
systemctl daemon-reload >/dev/null 2>&1 || true
udevadm control --reload-rules >/dev/null 2>&1 || true
systemctl enable --now proxmenux-fwbr-tune.service >/dev/null 2>&1 || true
/usr/local/sbin/proxmenux-fwbr-tune >/dev/null 2>&1 || true
local interfaces_file="/etc/network/interfaces"
@@ -638,7 +680,7 @@ EOF
install_log2ram_auto() {
local FUNC_VERSION="1.2"
local FUNC_VERSION="1.3"
# description: Install Log2RAM with size auto-tuned to host RAM (128M/256M/512M); SSD/M.2 detection skips on rotational disks.
@@ -727,6 +769,52 @@ install_log2ram_auto() {
return 1
fi
# Drop ACL preservation from the upstream rsync call: some
# /var/log.hdd filesystems reject POSIX ACLs and log2ram write
# exits 23 with `set_acl: Operation not supported`. xattrs stay.
local _l2r_bin
for _l2r_bin in \
"$(command -v log2ram 2>/dev/null)" \
/usr/local/bin/log2ram \
/usr/sbin/log2ram \
/usr/bin/log2ram
do
[[ -n "$_l2r_bin" && -f "$_l2r_bin" ]] || continue
if grep -q 'rsync -aAXv ' "$_l2r_bin" 2>/dev/null; then
cp -a "$_l2r_bin" "${_l2r_bin}.proxmenux.bak"
sed -i 's/rsync -aAXv /rsync -aXv --no-acls /g' "$_l2r_bin"
fi
break
done
# Size-based rotation for the PBS API logs — the upstream package
# ships no logrotate rule and pvestatd's local-datastore poll fills
# them fast enough to saturate a tmpfs /var/log.
if dpkg-query -W -f='${Status}' proxmox-backup-server 2>/dev/null \
| grep -q 'install ok installed'; then
mkdir -p /var/log/proxmox-backup/api 2>/dev/null || true
cat > /etc/logrotate.d/proxmox-backup-api <<'EOF'
/var/log/proxmox-backup/api/access.log /var/log/proxmox-backup/api/auth.log {
size 20M
rotate 3
compress
delaycompress
missingok
notifempty
copytruncate
}
EOF
chmod 0644 /etc/logrotate.d/proxmox-backup-api
chown root:root /etc/logrotate.d/proxmox-backup-api
cat > /etc/cron.hourly/proxmox-backup-logrotate <<'EOF'
#!/bin/sh
/usr/sbin/logrotate /etc/logrotate.d/proxmox-backup-api >/dev/null 2>&1
EOF
chmod 0755 /etc/cron.hourly/proxmox-backup-logrotate
chown root:root /etc/cron.hourly/proxmox-backup-logrotate
msg_ok "$(translate "PBS API log rotation configured (hourly, size-based)")"
fi
systemctl enable --now log2ram >/dev/null 2>&1 || true
systemctl daemon-reload >/dev/null 2>&1 || true
@@ -766,13 +854,10 @@ EOF
cat > /usr/local/bin/log2ram-check.sh <<'EOF'
#!/usr/bin/env bash
# v1.2 — `log2ram write` only copies tmpfs→disk; it does NOT shrink
# the tmpfs. When journald or pveproxy/access.log grow past their
# limits the tmpfs hit 100% and PVE crashed with "No space left on
# device" on Shell open (community-reported: JC Miñarro, Nicolás P.
# de A., 17-18/05). We now vacuum the journal and truncate the
# non-rotating logs that actually consume the tmpfs before calling
# `log2ram write`.
# Watch /var/log usage on Log2RAM's tmpfs and act at two thresholds:
# > 80% → vacuum journald down to ~30% of SIZE, then log2ram write
# > 92% → aggressive: journal to ~5%, rotate PBS API logs if present,
# truncate pveproxy/pveam, then log2ram write
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
CONF_FILE="/etc/log2ram.conf"
@@ -793,15 +878,13 @@ LOCK="/run/log2ram-check.lock"
exec 9>"$LOCK" 2>/dev/null || exit 0
flock -n 9 || exit 0
# `log2ram write` alone leaves the tmpfs full. Real recovery requires:
# (a) journal vacuum — journald respects --vacuum-size unconditionally,
# unlike SystemMaxUse which only enforces on rotation boundaries;
# (b) truncating logs that aren't rotated by logrotate (pveproxy, pveam);
# (c) THEN syncing to disk so the persistent copy reflects reality.
if (( USED_BYTES > EMERGENCY_BYTES )); then
SAFE_JOURNAL_MB=$(( SIZE_MiB * 5 / 100 ))
[[ "$SAFE_JOURNAL_MB" -lt 16 ]] && SAFE_JOURNAL_MB=16
journalctl --vacuum-size="${SAFE_JOURNAL_MB}M" >/dev/null 2>&1 || true
if [[ -x /usr/sbin/logrotate && -f /etc/logrotate.d/proxmox-backup-api ]]; then
/usr/sbin/logrotate -f /etc/logrotate.d/proxmox-backup-api >/dev/null 2>&1 || true
fi
: > /var/log/pveproxy/access.log 2>/dev/null || true
: > /var/log/pveproxy/error.log 2>/dev/null || true
: > /var/log/pveam.log 2>/dev/null || true
@@ -820,8 +903,7 @@ EOF
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
# Runs every 10 min starting at :03 to avoid overlap with debian-sa1 (:00/:10/:20...)
# nice -n 19 + ionice -c 3 ensures minimum CPU/IO priority (no visible spikes)
# nice/ionice keep the check off the priority queue for scheduled tasks.
3-59/10 * * * * root nice -n 19 ionice -c 3 /usr/local/bin/log2ram-check.sh >/dev/null 2>&1
EOF
chmod 0644 /etc/cron.d/log2ram-auto-sync
@@ -1011,57 +1093,37 @@ enable_zfs_autotrim() {
setup_persistent_network() {
local FUNC_VERSION="1.0"
local FUNC_VERSION="1.1"
# description: Pin NIC names to MAC addresses via systemd .link files so kernel updates don't shuffle interface names.
local LINK_DIR="/etc/systemd/network"
local BACKUP_DIR="/etc/systemd/network/backup-$(date +%Y%m%d-%H%M%S)"
local pve_version
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
msg_info "$(translate "Setting up persistent network interfaces")"
sleep 2
# Same legacy-conflict warning as in customizable_post_install.sh.
if [[ -f /etc/network/interfaces ]]; then
if grep -qE '^[[:space:]]*allow-hotplug[[:space:]]' /etc/network/interfaces 2>/dev/null; then
msg_warn "$(translate '/etc/network/interfaces uses allow-hotplug. Renaming interfaces via systemd .link can break that flow — review the file after reboot.')"
fi
fi
mkdir -p "$LINK_DIR"
local count=0 removed_stale=0 removed_legacy=0
while IFS='=' read -r key value; do
case "$key" in
COUNT) count="$value" ;;
REMOVED_STALE) removed_stale="$value" ;;
REMOVED_LEGACY) removed_legacy="$value" ;;
esac
done < <(pmx_setup_persistent_network)
if ls "$LINK_DIR"/*.link >/dev/null 2>&1; then
mkdir -p "$BACKUP_DIR"
cp "$LINK_DIR"/*.link "$BACKUP_DIR"/ 2>/dev/null || true
if (( removed_legacy > 0 )); then
msg_ok "$(translate "Migrated") $removed_legacy $(translate "legacy .link file(s) to the ProxMenux-managed format")"
fi
local count=0
for iface in $(ls /sys/class/net/ | grep -vE "lo|docker|veth|br-|vmbr|tap|fwpr|fwln|virbr|bond|cilium|zt|wg"); do
if [[ -e "/sys/class/net/$iface/device" ]] || [[ -e "/sys/class/net/$iface/phy80211" ]]; then
local MAC=$(cat /sys/class/net/$iface/address 2>/dev/null)
if [[ "$MAC" =~ ^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$ ]]; then
local LINK_FILE="$LINK_DIR/10-$iface.link"
cat > "$LINK_FILE" <<EOF
[Match]
MACAddress=$MAC
[Link]
Name=$iface
EOF
chmod 644 "$LINK_FILE"
((count++))
fi
fi
done
if [[ $count -gt 0 ]]; then
if (( removed_stale > 0 )); then
msg_ok "$(translate "Reconciled") $removed_stale $(translate "stale entry/entries for interfaces no longer present")"
fi
if (( count > 0 )); then
msg_ok "$(translate "Created persistent names for") $count $(translate "interfaces")"
# In PVE9, systemd-networkd is the native network backend and udev processes
# .link files directly. Reloading udev rules makes the new .link files effective
# immediately for any interface added later (hotplug, new NICs) without waiting
# for a full reboot. On PVE8 (ifupdown2), names are resolved at boot anyway.
if [[ "$pve_version" -ge 9 ]]; then
udevadm control --reload-rules 2>/dev/null || true
msg_ok "$(translate "PVE9: udev rules reloaded — new interfaces will get correct names without reboot")"

View File

@@ -778,7 +778,7 @@ force_apt_ipv4() {
apply_network_optimizations() {
local FUNC_VERSION="1.0"
local FUNC_VERSION="1.1"
# description: Tune TCP buffers, somaxconn, IPv4 hardening and disable rp_filter on fw bridges (PVE 9 compatible).
msg_info "$(translate "Optimizing network settings...")"
NECESSARY_REBOOT=1
@@ -839,6 +839,66 @@ EOF
sysctl --system > /dev/null 2>&1
cat > /usr/local/sbin/proxmenux-fwbr-tune <<'EOF'
#!/usr/bin/env bash
# Set rp_filter=0 and log_martians=0 on Proxmox fw bridge interfaces.
# No arg → sweep every interface currently under /proc/sys/net/ipv4/conf/.
# One arg → tune only that interface (used by the udev rule).
set -u
tune_interface() {
local iface="$1"
local sysctl_path="/proc/sys/net/ipv4/conf/${iface}"
case "$iface" in
fwbr*|fwln*|fwpr*|tap*)
[[ -d "$sysctl_path" ]] || return 0
[[ -w "$sysctl_path/rp_filter" ]] && printf '0\n' > "$sysctl_path/rp_filter"
[[ -w "$sysctl_path/log_martians" ]] && printf '0\n' > "$sysctl_path/log_martians"
;;
esac
}
if [[ $# -gt 0 ]]; then
tune_interface "$1"
else
for sysctl_path in /proc/sys/net/ipv4/conf/*; do
[[ -d "$sysctl_path" ]] || continue
tune_interface "${sysctl_path##*/}"
done
fi
EOF
chmod 0755 /usr/local/sbin/proxmenux-fwbr-tune
chown root:root /usr/local/sbin/proxmenux-fwbr-tune
cat > /etc/systemd/system/proxmenux-fwbr-tune.service <<'EOF'
[Unit]
Description=ProxMenux - Tune rp_filter/log_martians on virtual fw bridges
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/proxmenux-fwbr-tune
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
cat > /etc/udev/rules.d/99-proxmenux-fwbr-tune.rules <<'EOF'
ACTION=="add", SUBSYSTEM=="net", KERNEL=="fwbr*", RUN+="/usr/local/sbin/proxmenux-fwbr-tune %k"
ACTION=="add", SUBSYSTEM=="net", KERNEL=="fwln*", RUN+="/usr/local/sbin/proxmenux-fwbr-tune %k"
ACTION=="add", SUBSYSTEM=="net", KERNEL=="fwpr*", RUN+="/usr/local/sbin/proxmenux-fwbr-tune %k"
ACTION=="add", SUBSYSTEM=="net", KERNEL=="tap*", RUN+="/usr/local/sbin/proxmenux-fwbr-tune %k"
EOF
chmod 0644 /etc/udev/rules.d/99-proxmenux-fwbr-tune.rules
chown root:root /etc/udev/rules.d/99-proxmenux-fwbr-tune.rules
systemctl daemon-reload >/dev/null 2>&1 || true
udevadm control --reload-rules >/dev/null 2>&1 || true
systemctl enable --now proxmenux-fwbr-tune.service >/dev/null 2>&1 || true
/usr/local/sbin/proxmenux-fwbr-tune >/dev/null 2>&1 || true
local interfaces_file="/etc/network/interfaces"
if ! grep -q 'source /etc/network/interfaces.d/*' "$interfaces_file"; then
@@ -1147,86 +1207,58 @@ EOF
optimize_zfs_arc() {
local FUNC_VERSION="1.0"
# description: Cap ZFS ARC to a sensible fraction of host RAM so VMs don't fight the kernel for memory.
msg_info2 "$(translate "Optimizing ZFS ARC size according to available memory...")"
local FUNC_VERSION="1.1"
# description: Cap ZFS ARC max to a sensible fraction of host RAM so VMs don't fight the kernel for memory. Only sets zfs_arc_max; other OpenZFS tunables stay at their defaults.
local zfs_conf="/etc/modprobe.d/99-zfsarc.conf"
local ram_bytes arc_max
# Check if ZFS is installed
if ! command -v zfs > /dev/null; then
msg_info2 "$(translate "Optimizing ZFS ARC maximum size...")"
if ! command -v zpool >/dev/null 2>&1; then
msg_warn "$(translate "ZFS not detected. Skipping ZFS ARC optimization.")"
return 0
fi
# Ensure RAM_SIZE_GB is set
if [ -z "$RAM_SIZE_GB" ]; then
RAM_SIZE_GB=$(free -g | awk '/^Mem:/{print $2}')
if [ -z "$RAM_SIZE_GB" ] || [ "$RAM_SIZE_GB" -eq 0 ]; then
msg_warn "$(translate "Failed to detect RAM size. Using default value of 16GB for ZFS ARC optimization.")"
RAM_SIZE_GB=16 # Default to 16GB if detection fails
fi
if ! zpool list -H -o name 2>/dev/null | grep -q .; then
msg_warn "$(translate "No ZFS pools detected. Skipping ZFS ARC optimization.")"
return 0
fi
msg_ok "$(translate "Detected RAM size: ${RAM_SIZE_GB} GB")"
ram_bytes=$(awk '/MemTotal:/ { print $2 * 1024 }' /proc/meminfo)
if [[ -z "$ram_bytes" || "$ram_bytes" -le 0 ]]; then
msg_error "$(translate "Unable to determine the installed memory.")"
return 1
fi
# Calculate ZFS ARC sizes
if [[ "$RAM_SIZE_GB" -le 16 ]]; then
MY_ZFS_ARC_MIN=536870911 # 512MB
MY_ZFS_ARC_MAX=536870912 # 512MB
elif [[ "$RAM_SIZE_GB" -le 32 ]]; then
MY_ZFS_ARC_MIN=1073741823 # 1GB
MY_ZFS_ARC_MAX=1073741824 # 1GB
if (( ram_bytes <= 16 * 1024 * 1024 * 1024 )); then
arc_max=$((512 * 1024 * 1024))
elif (( ram_bytes <= 32 * 1024 * 1024 * 1024 )); then
arc_max=$((1024 * 1024 * 1024))
else
# Use 1/16 of RAM for min and 1/8 for max
MY_ZFS_ARC_MIN=$((RAM_SIZE_GB * 1073741824 / 16))
MY_ZFS_ARC_MAX=$((RAM_SIZE_GB * 1073741824 / 8))
arc_max=$((ram_bytes / 8))
fi
(( arc_max < 512 * 1024 * 1024 )) && arc_max=$((512 * 1024 * 1024))
if [[ -f "$zfs_conf" && ! -f "${zfs_conf}.bak" ]]; then
cp -p "$zfs_conf" "${zfs_conf}.bak"
fi
# Enforce the minimum values
MY_ZFS_ARC_MIN=$((MY_ZFS_ARC_MIN > 536870911 ? MY_ZFS_ARC_MIN : 536870911))
MY_ZFS_ARC_MAX=$((MY_ZFS_ARC_MAX > 536870912 ? MY_ZFS_ARC_MAX : 536870912))
# Apply ZFS tuning parameters
local zfs_conf="/etc/modprobe.d/99-zfsarc.conf"
local config_changed=false
if [ -f "$zfs_conf" ]; then
msg_info "$(translate "Checking existing ZFS ARC configuration...")"
if ! grep -q "zfs_arc_min=$MY_ZFS_ARC_MIN" "$zfs_conf" || \
! grep -q "zfs_arc_max=$MY_ZFS_ARC_MAX" "$zfs_conf"; then
msg_ok "$(translate "Changes detected. Updating ZFS ARC configuration...")"
cp "$zfs_conf" "${zfs_conf}.bak"
config_changed=true
else
msg_ok "$(translate "ZFS ARC configuration is up to date")"
fi
else
msg_info "$(translate "Creating new ZFS ARC configuration...")"
config_changed=true
fi
if $config_changed; then
cat <<EOF > "$zfs_conf"
# ZFS tuning
# Use 1/8 RAM for MAX cache, 1/16 RAM for MIN cache, or 512MB/1GB for systems with <= 32GB RAM
options zfs zfs_arc_min=$MY_ZFS_ARC_MIN
options zfs zfs_arc_max=$MY_ZFS_ARC_MAX
# Enable prefetch method
options zfs l2arc_noprefetch=0
# Set max write speed to L2ARC (500MB)
options zfs l2arc_write_max=524288000
options zfs zfs_txg_timeout=60
cat > "$zfs_conf" <<EOF
# ProxMenux ZFS ARC configuration
# Only zfs_arc_max is set; zfs_arc_min stays at the OpenZFS default (auto).
options zfs zfs_arc_max=$arc_max
EOF
if [ $? -eq 0 ]; then
msg_ok "$(translate "ZFS ARC configuration file created/updated successfully")"
NECESSARY_REBOOT=1
else
msg_error "$(translate "Failed to create/update ZFS ARC configuration file")"
fi
msg_info "$(translate "Updating initramfs so the ARC cap applies at next boot...")"
if ! update-initramfs -u -k all >/dev/null 2>&1; then
msg_error "$(translate "Failed to update initramfs.")"
return 1
fi
if command -v proxmox-boot-tool >/dev/null 2>&1; then
proxmox-boot-tool refresh >/dev/null 2>&1 || true
fi
NECESSARY_REBOOT=1
msg_ok "$(translate "ZFS ARC maximum configured:") $arc_max $(translate "bytes")"
msg_success "$(translate "ZFS ARC optimization completed")"
register_tool "zfs_arc" true "$FUNC_VERSION"
}
@@ -2493,7 +2525,7 @@ update_pve_appliance_manager() {
configure_log2ram() {
local FUNC_VERSION="1.2"
local FUNC_VERSION="1.3"
# description: Install Log2RAM with user-chosen RAM size; prompts for size and SSD/M.2 awareness before applying.
msg_info2 "$(translate "Preparing Log2RAM configuration")"
sleep 1
@@ -2591,6 +2623,52 @@ configure_log2ram() {
return 1
fi
# Drop ACL preservation from the upstream rsync call: some
# /var/log.hdd filesystems reject POSIX ACLs and log2ram write
# exits 23 with `set_acl: Operation not supported`. xattrs stay.
local _l2r_bin
for _l2r_bin in \
"$(command -v log2ram 2>/dev/null)" \
/usr/local/bin/log2ram \
/usr/sbin/log2ram \
/usr/bin/log2ram
do
[[ -n "$_l2r_bin" && -f "$_l2r_bin" ]] || continue
if grep -q 'rsync -aAXv ' "$_l2r_bin" 2>/dev/null; then
cp -a "$_l2r_bin" "${_l2r_bin}.proxmenux.bak"
sed -i 's/rsync -aAXv /rsync -aXv --no-acls /g' "$_l2r_bin"
fi
break
done
# Size-based rotation for the PBS API logs — the upstream package
# ships no logrotate rule and pvestatd's local-datastore poll fills
# them fast enough to saturate a tmpfs /var/log.
if dpkg-query -W -f='${Status}' proxmox-backup-server 2>/dev/null \
| grep -q 'install ok installed'; then
mkdir -p /var/log/proxmox-backup/api 2>/dev/null || true
cat > /etc/logrotate.d/proxmox-backup-api <<'EOF'
/var/log/proxmox-backup/api/access.log /var/log/proxmox-backup/api/auth.log {
size 20M
rotate 3
compress
delaycompress
missingok
notifempty
copytruncate
}
EOF
chmod 0644 /etc/logrotate.d/proxmox-backup-api
chown root:root /etc/logrotate.d/proxmox-backup-api
cat > /etc/cron.hourly/proxmox-backup-logrotate <<'EOF'
#!/bin/sh
/usr/sbin/logrotate /etc/logrotate.d/proxmox-backup-api >/dev/null 2>&1
EOF
chmod 0755 /etc/cron.hourly/proxmox-backup-logrotate
chown root:root /etc/cron.hourly/proxmox-backup-logrotate
msg_ok "$(translate "PBS API log rotation configured (hourly, size-based)")"
fi
systemctl daemon-reload >/dev/null 2>&1 || true
systemctl enable --now log2ram >/dev/null 2>&1 || true
@@ -2620,13 +2698,10 @@ EOF
if [[ "$ENABLE_AUTOSYNC" == true ]]; then
cat > /usr/local/bin/log2ram-check.sh <<'EOF'
#!/usr/bin/env bash
# v1.2 — `log2ram write` only copies tmpfs→disk; it does NOT shrink
# the tmpfs. When journald or pveproxy/access.log grow past their
# limits the tmpfs hit 100% and PVE crashed with "No space left on
# device" on Shell open (community-reported: JC Miñarro, Nicolás P.
# de A., 17-18/05). We now vacuum the journal and truncate the
# non-rotating logs that actually consume the tmpfs before calling
# `log2ram write`.
# Watch /var/log usage on Log2RAM's tmpfs and act at two thresholds:
# > 80% → vacuum journald down to ~30% of SIZE, then log2ram write
# > 92% → aggressive: journal to ~5%, rotate PBS API logs if present,
# truncate pveproxy/pveam, then log2ram write
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
CONF_FILE="/etc/log2ram.conf"
L2R_BIN="$(command -v log2ram || true)"
@@ -2646,15 +2721,13 @@ LOCK="/run/log2ram-check.lock"
exec 9>"$LOCK" 2>/dev/null || exit 0
flock -n 9 || exit 0
# `log2ram write` alone leaves the tmpfs full. Real recovery requires:
# (a) journal vacuum — journald respects --vacuum-size unconditionally,
# unlike SystemMaxUse which only enforces on rotation boundaries;
# (b) truncating logs that aren't rotated by logrotate (pveproxy, pveam);
# (c) THEN syncing to disk so the persistent copy reflects reality.
if (( USED_BYTES > EMERGENCY_BYTES )); then
SAFE_JOURNAL_MB=$(( SIZE_MiB * 5 / 100 ))
[[ "$SAFE_JOURNAL_MB" -lt 16 ]] && SAFE_JOURNAL_MB=16
journalctl --vacuum-size="${SAFE_JOURNAL_MB}M" >/dev/null 2>&1 || true
if [[ -x /usr/sbin/logrotate && -f /etc/logrotate.d/proxmox-backup-api ]]; then
/usr/sbin/logrotate -f /etc/logrotate.d/proxmox-backup-api >/dev/null 2>&1 || true
fi
: > /var/log/pveproxy/access.log 2>/dev/null || true
: > /var/log/pveproxy/error.log 2>/dev/null || true
: > /var/log/pveam.log 2>/dev/null || true
@@ -2763,64 +2836,37 @@ EOF
setup_persistent_network() {
local FUNC_VERSION="1.0"
local FUNC_VERSION="1.1"
# description: Pin NIC names to MAC addresses via systemd .link files so kernel updates don't shuffle interface names.
local LINK_DIR="/etc/systemd/network"
local BACKUP_DIR="/etc/systemd/network/backup-$(date +%Y%m%d-%H%M%S)"
local pve_version
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
msg_info "$(translate "Setting up persistent network interfaces")"
sleep 2
# Detect legacy `/etc/network/interfaces` setups that depend on the
# default udev naming. If the file references `allow-hotplug` rules
# or uses physical interface names directly, the .link rules below
# could rename interfaces and break network on reboot. Warn loudly so
# the user can review before continuing. Audit Tier 6 —
# `setup_persistent_network` no detecta conflicto con legacy.
if [[ -f /etc/network/interfaces ]]; then
if grep -qE '^[[:space:]]*allow-hotplug[[:space:]]' /etc/network/interfaces 2>/dev/null; then
msg_warn "$(translate '/etc/network/interfaces uses allow-hotplug. Renaming interfaces via systemd .link can break that flow — review the file after reboot.')"
fi
fi
mkdir -p "$LINK_DIR"
local count=0 removed_stale=0 removed_legacy=0
while IFS='=' read -r key value; do
case "$key" in
COUNT) count="$value" ;;
REMOVED_STALE) removed_stale="$value" ;;
REMOVED_LEGACY) removed_legacy="$value" ;;
esac
done < <(pmx_setup_persistent_network)
if ls "$LINK_DIR"/*.link >/dev/null 2>&1; then
mkdir -p "$BACKUP_DIR"
cp "$LINK_DIR"/*.link "$BACKUP_DIR"/ 2>/dev/null || true
if (( removed_legacy > 0 )); then
msg_ok "$(translate "Migrated") $removed_legacy $(translate "legacy .link file(s) to the ProxMenux-managed format")"
fi
# Process physical interfaces
local count=0
for iface in $(ls /sys/class/net/ | grep -vE "lo|docker|veth|br-|vmbr|tap|fwpr|fwln|virbr|bond|cilium|zt|wg"); do
if [[ -e "/sys/class/net/$iface/device" ]] || [[ -e "/sys/class/net/$iface/phy80211" ]]; then
local MAC=$(cat /sys/class/net/$iface/address 2>/dev/null)
if [[ "$MAC" =~ ^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$ ]]; then
local LINK_FILE="$LINK_DIR/10-$iface.link"
cat > "$LINK_FILE" <<EOF
[Match]
MACAddress=$MAC
[Link]
Name=$iface
EOF
chmod 644 "$LINK_FILE"
((count++))
fi
fi
done
if [[ $count -gt 0 ]]; then
if (( removed_stale > 0 )); then
msg_ok "$(translate "Reconciled") $removed_stale $(translate "stale entry/entries for interfaces no longer present")"
fi
if (( count > 0 )); then
msg_ok "$(translate "Created persistent names for") $count $(translate "interfaces")"
# In PVE9, systemd-networkd is the native network backend and udev processes
# .link files directly. Reloading udev rules makes the new .link files effective
# immediately for any interface added later (hotplug, new NICs) without waiting
# for a full reboot. On PVE8 (ifupdown2), names are resolved at boot anyway.
if [[ "$pve_version" -ge 9 ]]; then
udevadm control --reload-rules 2>/dev/null || true
msg_ok "$(translate "PVE9: udev rules reloaded — new interfaces will get correct names without reboot")"

View File

@@ -455,11 +455,14 @@ uninstall_network_optimization() {
systemctl disable --now proxmenux-fwbr-tune.service >/dev/null 2>&1 || true
rm -f /etc/systemd/system/proxmenux-fwbr-tune.service
rm -f /usr/local/sbin/proxmenux-fwbr-tune
rm -f /etc/udev/rules.d/99-proxmenux-fwbr-tune.rules
udevadm control --reload-rules >/dev/null 2>&1 || true
systemctl daemon-reload >/dev/null 2>&1 || true
sysctl --system >/dev/null 2>&1 || true
msg_ok "$(translate "Network optimizations removed")"
register_tool "network_optimization" false
}
@@ -573,22 +576,28 @@ uninstall_log2ram() {
################################################################
uninstall_persistent_network() {
local LINK_DIR="/etc/systemd/network"
msg_info "$(translate "Removing all .link files from") $LINK_DIR"
sleep 2
if ! ls "$LINK_DIR"/*.link >/dev/null 2>&1; then
msg_warn "$(translate "No .link files found in") $LINK_DIR"
return 0
msg_info "$(translate "Removing ProxMenux persistent NIC .link files...")"
sleep 1
if ! type pmx_uninstall_persistent_network &>/dev/null; then
if [[ -f "$LOCAL_SCRIPTS/global/utils-install-functions.sh" ]]; then
source "$LOCAL_SCRIPTS/global/utils-install-functions.sh"
fi
fi
rm -f "$LINK_DIR"/*.link
local removed=0
while IFS='=' read -r key value; do
[[ "$key" == "REMOVED" ]] && removed="$value"
done < <(pmx_uninstall_persistent_network)
msg_ok "$(translate "Removed all .link files from") $LINK_DIR"
msg_info "$(translate "Interface names will return to default systemd behavior.")"
if (( removed > 0 )); then
msg_ok "$(translate "Removed") $removed $(translate "ProxMenux-managed .link file(s). User-authored .link files were left in place.")"
msg_info "$(translate "Interface names will return to default systemd behavior.")"
NECESSARY_REBOOT=1
else
msg_warn "$(translate "No ProxMenux-managed .link files found — nothing to remove.")"
fi
register_tool "persistent_network" false
NECESSARY_REBOOT=1
}
@@ -906,6 +915,10 @@ uninstall_zfs_arc() {
rm -f /etc/modprobe.d/99-zfsarc.conf
msg_ok "$(translate 'ZFS ARC config removed (kernel defaults will apply on reboot)')"
fi
update-initramfs -u -k all >/dev/null 2>&1 || true
if command -v proxmox-boot-tool >/dev/null 2>&1; then
proxmox-boot-tool refresh >/dev/null 2>&1 || true
fi
register_tool "zfs_arc" false
}