mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-07-29 11:58:25 +00:00
Update 1.2.4
This commit is contained in:
@@ -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 ────────────────
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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")"
|
||||
|
||||
@@ -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")"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user