mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-09-14 18:56:52 +00:00
ProxMenux modifies the host: it rewrites configuration files, installs packages, enables services. Until now nobody could say afterwards what had changed, and showing the script does not answer that question — a four-hundred-line function may alter two values, and the reader has no way to know which two. This adds the two halves of an answer. The change journal records what ProxMenux does as it does it. Eleven bash primitives capture the previous state, apply the change and record it in the same step, writing to a spool that the Monitor reads back. One hundred and thirteen functions across twenty-five scripts are instrumented, covering post-install, shared storage, security tooling, container conversions, disk operations and the PVE 8 to 9 upgrade path. The page shows the difference — rotate 7 becoming rotate 14 — and never the script. Restore and backup scripts are deliberately left out: a restore puts the host back to a state some other script already recorded. The Audit and reports page answers the other half: what state is this host in, regardless of who put it there. Forty-three checks across seven areas read the host and classify each result as critical, warning, observation, conformant, unverified or not applicable, with the evidence they read attached to each one. A declared policy lets the reader say what this particular host is expected to do — which guests must have a backup, which storages are essential — so the report judges the host against its own intent rather than a generic template. An inventory records the hardware, network and guest topology behind those readings, a comparison shows what moved between two runs, and six report profiles produce a printable document scoped to what the reader needs. Everything is available in the eight supported languages. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1560 lines
56 KiB
Bash
1560 lines
56 KiB
Bash
#!/bin/bash
|
|
# ==========================================================
|
|
# ProxMenux - Automated Post-Install Script
|
|
# ==========================================================
|
|
# Author : MacRimi
|
|
# Copyright : (c) 2024 MacRimi
|
|
# License : GPL-3.0
|
|
# https://github.com/MacRimi/ProxMenux/blob/main/LICENSE
|
|
# Version : 1.1
|
|
# ==========================================================
|
|
# Description:
|
|
# Applies a curated set of 14 safe optimizations to a fresh
|
|
# Proxmox VE host without prompts. Reversible changes are registered
|
|
# in installed_tools.json so they can be restored later from the
|
|
# Uninstall Optimizations menu; package upgrades are intentionally
|
|
# excluded because they cannot be rolled back safely.
|
|
#
|
|
# Features:
|
|
# - Zero-interaction baseline: repos, upgrade, banner, APT
|
|
# IPv4, skip translations, kernel limits, memory tuning,
|
|
# kernel-panic behaviour, network stack tuning, bashrc,
|
|
# Log2RAM (SSD-aware), ZFS autotrim, journald, logrotate,
|
|
# persistent NIC names.
|
|
# - Hardware-aware: auto-detects SSD/NVMe for Log2RAM and sizes
|
|
# its ramdisk according to host RAM (128M/256M/512M).
|
|
# - Registration: tracks each tool in installed_tools.json.
|
|
# - Rollback: every tool has a reverse function in uninstall-tools.sh.
|
|
#
|
|
# Shares the function library with customizable_post_install.sh.
|
|
# ==========================================================
|
|
|
|
|
|
# Configuration
|
|
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
|
BASE_DIR="/usr/local/share/proxmenux"
|
|
UTILS_FILE="$BASE_DIR/utils.sh"
|
|
VENV_PATH="/opt/googletrans-env"
|
|
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
|
|
|
|
if [[ -f "$UTILS_FILE" ]]; then
|
|
source "$UTILS_FILE"
|
|
fi
|
|
|
|
# Pull in ensure_repositories + PROXMENUX_UTILS — customizable already
|
|
# sources this helper; auto did not, so a function that calls
|
|
# `ensure_repositories` (setup_proxmox_repositories below) needs the
|
|
# import here too.
|
|
if [[ -f "$LOCAL_SCRIPTS/global/utils-install-functions.sh" ]]; then
|
|
source "$LOCAL_SCRIPTS/global/utils-install-functions.sh"
|
|
fi
|
|
# Recording is part of writing: sourced before any function runs so a
|
|
# change made without it is a mistake we can find, not one we can make.
|
|
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
|
|
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
|
|
fi
|
|
|
|
load_language
|
|
initialize_cache
|
|
|
|
# Global variables
|
|
OS_CODENAME="$(grep "VERSION_CODENAME=" /etc/os-release | cut -d"=" -f 2 | xargs)"
|
|
RAM_SIZE_GB=$(( $(vmstat -s | grep -i "total memory" | xargs | cut -d" " -f 1) / 1024 / 1000))
|
|
NECESSARY_REBOOT=0
|
|
export SCRIPT_TITLE="ProxMenux Optimization Post-Installation"
|
|
|
|
# Sprint 12A: identify which post-install flow is calling register_tool.
|
|
# auto_post_install.sh always emits source=auto; customizable sets "custom".
|
|
# The detector uses this to know which function to compare against and
|
|
# which one to re-run when applying an update (preserves user's choice
|
|
# between the auto and the custom flow of the same tool).
|
|
SCRIPT_SOURCE="auto"
|
|
|
|
# ==========================================================
|
|
# Tool registration system
|
|
ensure_tools_json() {
|
|
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
|
|
}
|
|
|
|
# Sprint 12A: register_tool accepts (key, state, version, source).
|
|
# key — tool identifier (existing)
|
|
# state — true/false (existing)
|
|
# version — defaults to "1.0"; each function declares its own version as
|
|
# `local FUNC_VERSION="X.Y"` on the first line and passes
|
|
# "$FUNC_VERSION" here. We use a `local` variable rather than a
|
|
# `# version:` comment because bash's `declare -f` strips
|
|
# comments — so a comment-based version was lost when the
|
|
# update wrapper sourced the script and re-ran the function.
|
|
# source — defaults to $SCRIPT_SOURCE (auto/custom) so the detector
|
|
# knows which flow to compare against and which to re-run on
|
|
# update.
|
|
# On install (state=true) the entry becomes a structured object
|
|
# {installed: true, version: "X.Y", source: "auto"|"custom"}
|
|
# On uninstall (state=false) we keep the legacy boolean false so the rest
|
|
# of the pipeline (uninstall-tools.sh, frontend) keeps working.
|
|
register_tool() {
|
|
local tool="$1"
|
|
local state="$2"
|
|
local version="${3:-1.0}"
|
|
local source="${4:-${SCRIPT_SOURCE:-unknown}}"
|
|
# Same as in the customizable script: the one call every function
|
|
# already makes, so an applied tool reaches the journal even where
|
|
# the function itself still writes directly.
|
|
if declare -F pmx_record_applied >/dev/null 2>&1; then
|
|
PMX_JOURNAL_FUNCTION="${FUNCNAME[1]:-$tool}" \
|
|
PMX_JOURNAL_VERSION="$version" \
|
|
PMX_JOURNAL_SOURCE="$source" \
|
|
pmx_record_applied "$tool" "$version" \
|
|
"$([[ "$state" == "true" ]] && echo applied || echo removed)"
|
|
fi
|
|
ensure_tools_json
|
|
if [[ "$state" == "true" ]]; then
|
|
jq --arg t "$tool" --arg ver "$version" --arg src "$source" \
|
|
'.[$t]={"installed": true, "version": $ver, "source": $src}' \
|
|
"$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
|
else
|
|
jq --arg t "$tool" '.[$t]=false' \
|
|
"$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
|
fi
|
|
}
|
|
|
|
|
|
|
|
check_extremeshok_warning() {
|
|
local marker_file="/etc/extremeshok"
|
|
|
|
if [[ -f "$marker_file" ]]; then
|
|
dialog --backtitle "ProxMenux" --title "xshok-proxmox Post-Install Detected" \
|
|
--yesno "\n$(translate "It appears that you have already executed the xshok-proxmox post-install script on this system.")\n\n\
|
|
$(translate "If you continue, some adjustments may be duplicated or conflict with those already made by xshok.")\n\n\
|
|
$(translate "Do you want to continue anyway?")" 13 70
|
|
|
|
local response=$?
|
|
if [[ $response -ne 0 ]]; then
|
|
show_proxmenux_logo
|
|
msg_warn "$(translate "Action cancelled due to previous xshok-proxmox modifications.")"
|
|
echo -e
|
|
msg_success "$(translate "Press Enter to return to menu...")"
|
|
read -r
|
|
exit 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
apt_upgrade() {
|
|
local pve_version
|
|
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
|
|
|
|
if [[ -z "$pve_version" ]]; then
|
|
msg_error "Unable to detect Proxmox version."
|
|
return 1
|
|
fi
|
|
|
|
if [[ "$pve_version" -ge 9 ]]; then
|
|
|
|
bash "$LOCAL_SCRIPTS/global/update-pve9_2.sh"
|
|
else
|
|
|
|
bash "$LOCAL_SCRIPTS/global/update-pve8.sh"
|
|
fi
|
|
}
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
remove_subscription_banner() {
|
|
local FUNC_VERSION="1.0"
|
|
# description: Patch the Proxmox web UI to suppress the subscription dialog and register a successful patch.
|
|
local pve_version
|
|
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
|
|
|
|
|
|
if [[ -z "$pve_version" ]]; then
|
|
msg_error "Unable to detect Proxmox version."
|
|
return 1
|
|
fi
|
|
|
|
kill -TERM "$SPINNER_PID" 2>/dev/null
|
|
sleep 1
|
|
|
|
if [[ "$pve_version" -ge 9 ]]; then
|
|
if ! whiptail --title "Proxmox VE ${pve_version} Subscription Banner Removal" \
|
|
--yesno "$(translate "Do you want to remove the Proxmox subscription banner from the web interface for PVE $pve_version?")\n\n$(translate "Attention: Removing the subscription banner may cause issues in the web interface after a future update.")\n\n$(translate "If this happens, you can restore the backup from the 'Subscription Banner Removal' option in 'Uninstall optimizations'.")\n\n$(translate "Are you sure you want to continue?")" 14 75; then
|
|
msg_warn "Banner removal cancelled by user."
|
|
return 1
|
|
fi
|
|
if ! bash "$LOCAL_SCRIPTS/global/remove-banner-pve-v3.sh"; then
|
|
msg_error "$(translate "Subscription banner removal failed")"
|
|
return 1
|
|
fi
|
|
else
|
|
if ! whiptail --title "Proxmox VE 8.x Subscription Banner Removal" \
|
|
--yesno "Do you want to remove the Proxmox subscription banner from the web interface for PVE $pve_version?" 10 70; then
|
|
msg_warn "Banner removal cancelled by user."
|
|
return 1
|
|
fi
|
|
if ! bash "$LOCAL_SCRIPTS/global/remove-banner-pve8.sh"; then
|
|
msg_error "$(translate "Subscription banner removal failed")"
|
|
return 1
|
|
fi
|
|
fi
|
|
register_tool "subscription_banner" true "$FUNC_VERSION"
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
setup_proxmox_repositories() {
|
|
local FUNC_VERSION="1.1"
|
|
# Description: Configure Proxmox + Debian APT repositories (no-subscription)
|
|
# and set the correct file permissions.
|
|
|
|
msg_info2 "$(translate "Configuring Proxmox APT repositories...")"
|
|
|
|
if ! ensure_repositories; then
|
|
msg_error "$(translate "Failed to configure Proxmox repositories")"
|
|
register_tool "proxmox_repos" false "$FUNC_VERSION"
|
|
return 1
|
|
fi
|
|
|
|
# Defensive re-chmod: if the files already existed with the old 0640
|
|
# permissions there is nothing for `ensure_repositories` to recreate,
|
|
# so we still flip the bits explicitly here.
|
|
local f
|
|
for f in /etc/apt/sources.list.d/proxmox.sources \
|
|
/etc/apt/sources.list.d/debian.sources \
|
|
/etc/apt/sources.list.d/pve-no-subscription.list \
|
|
/etc/apt/sources.list.d/pve-enterprise.list; do
|
|
[[ -f "$f" ]] && chmod 0644 "$f" 2>/dev/null
|
|
done
|
|
|
|
register_tool "proxmox_repos" true "$FUNC_VERSION"
|
|
msg_success "$(translate "Proxmox APT repositories configured")"
|
|
}
|
|
|
|
|
|
configure_time_sync() {
|
|
local FUNC_VERSION="1.0"
|
|
# description: Detect timezone from public IP and enable systemd time sync (NTP).
|
|
msg_info2 "$(translate "Configuring system time settings...")"
|
|
|
|
this_ip=$(dig +short myip.opendns.com @resolver1.opendns.com 2>/dev/null)
|
|
if [ -z "$this_ip" ]; then
|
|
msg_warn "$(translate "Failed to obtain public IP address - keeping current timezone settings")"
|
|
return 0
|
|
fi
|
|
|
|
timezone=$(curl -s --connect-timeout 10 "https://ipapi.co/${this_ip}/timezone" 2>/dev/null | tr -d '[:space:]')
|
|
if [ -z "$timezone" ] || [ "$timezone" = "undefined" ]; then
|
|
msg_warn "$(translate "Failed to determine timezone from IP address - keeping current timezone settings")"
|
|
return 0
|
|
fi
|
|
|
|
# Validate against the system's IANA timezone database before applying.
|
|
# ipapi.co can return rate-limit JSON, an error string, or stale data; the
|
|
# previous code accepted anything that wasn't literally "undefined" and
|
|
# passed it straight to `timedatectl set-timezone`, which silently kept
|
|
# the old TZ on a bad value.
|
|
if ! timedatectl list-timezones 2>/dev/null | grep -Fxq "$timezone"; then
|
|
msg_warn "$(translate "API returned an invalid timezone") ($timezone) - $(translate "keeping current settings")"
|
|
return 0
|
|
fi
|
|
|
|
msg_ok "$(translate "Found timezone $timezone for IP $this_ip")"
|
|
|
|
if timedatectl set-timezone "$timezone"; then
|
|
msg_ok "$(translate "Timezone set to $timezone")"
|
|
|
|
if timedatectl set-ntp true; then
|
|
msg_ok "$(translate "Time settings configured - Timezone:") $timezone"
|
|
register_tool "time_sync" true "$FUNC_VERSION"
|
|
|
|
systemctl restart postfix 2>/dev/null || true
|
|
else
|
|
msg_warn "$(translate "Failed to enable automatic time synchronization")"
|
|
fi
|
|
else
|
|
msg_warn "$(translate "Failed to set timezone - keeping current settings")"
|
|
fi
|
|
}
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
skip_apt_languages() {
|
|
local FUNC_VERSION="1.0"
|
|
pmx_journal_context "skip_apt_languages" "$FUNC_VERSION"
|
|
# description: Stop APT from downloading translation files to speed up updates.
|
|
msg_info "$(translate "Configuring APT to skip downloading additional languages...")"
|
|
pmx_write_file /etc/apt/apt.conf.d/99-disable-translations <<'EOF'
|
|
Acquire::Languages "none";
|
|
EOF
|
|
msg_ok "$(translate "APT configured to skip additional languages")"
|
|
register_tool "apt_languages" true "$FUNC_VERSION"
|
|
}
|
|
|
|
# ==========================================================
|
|
optimize_journald() {
|
|
local FUNC_VERSION="1.0"
|
|
pmx_journal_context "optimize_journald" "$FUNC_VERSION"
|
|
# description: Cap journald size, raise rate limit and force info-level logging so the log viewer and Fail2Ban work.
|
|
if [ -f /etc/log2ram.conf ] || [ -d /var/log.hdd ]; then
|
|
return 0
|
|
fi
|
|
msg_info "$(translate "Limiting size and optimizing journald...")"
|
|
NECESSARY_REBOOT=1
|
|
|
|
local jf="/etc/systemd/journald.conf"
|
|
if ! grep -q "ProxMenux optimized journald" "$jf" 2>/dev/null; then
|
|
cp -a "$jf" "${jf}.bak" 2>/dev/null || true
|
|
fi
|
|
|
|
pmx_write_file /etc/systemd/journald.conf <<EOF
|
|
[Journal]
|
|
Storage=persistent
|
|
SplitMode=none
|
|
RateLimitIntervalSec=30s
|
|
RateLimitBurst=1000
|
|
ForwardToSyslog=no
|
|
ForwardToWall=no
|
|
Seal=no
|
|
Compress=yes
|
|
SystemMaxUse=64M
|
|
RuntimeMaxUse=60M
|
|
# MaxLevelStore=info allows ProxMenux Monitor to display system logs correctly.
|
|
# Using "warning" causes the log viewer to show nearly identical entries across
|
|
# all date ranges (1d/3d/7d) because most activity is info-level.
|
|
# It also prevents Fail2Ban from detecting SSH/Proxmox auth failures via journal.
|
|
MaxLevelStore=info
|
|
MaxLevelSyslog=info
|
|
MaxLevelKMsg=warning
|
|
MaxLevelConsole=notice
|
|
MaxLevelWall=crit
|
|
EOF
|
|
|
|
pmx_record_execution "Restart systemd-journald" "systemctl restart systemd-journald.service"
|
|
systemctl restart systemd-journald.service > /dev/null 2>&1
|
|
pmx_record_execution "Vacuum system journal" "journalctl --vacuum-size=64M --vacuum-time=1d"
|
|
journalctl --vacuum-size=64M --vacuum-time=1d > /dev/null 2>&1
|
|
pmx_record_execution "Rotate system journal" "journalctl --rotate"
|
|
journalctl --rotate > /dev/null 2>&1
|
|
|
|
msg_ok "$(translate "Journald optimized - Max size: 64M")"
|
|
register_tool "journald" true "$FUNC_VERSION"
|
|
}
|
|
|
|
# ==========================================================
|
|
optimize_logrotate() {
|
|
local FUNC_VERSION="1.1"
|
|
pmx_journal_context "optimize_logrotate" "$FUNC_VERSION"
|
|
# description: Replace logrotate.conf with a Log2RAM-friendly profile (daily rotation, copytruncate).
|
|
msg_info "$(translate "Optimizing logrotate configuration...")"
|
|
local logrotate_conf="/etc/logrotate.conf"
|
|
local backup_conf="${logrotate_conf}.bak"
|
|
|
|
cp -n "$logrotate_conf" "$backup_conf" 2>/dev/null || true
|
|
|
|
pmx_write_file "$logrotate_conf" <<EOF
|
|
# ProxMenux optimized configuration (Log2RAM-friendly)
|
|
daily
|
|
su root adm
|
|
rotate 7
|
|
size 10M
|
|
compress
|
|
delaycompress
|
|
missingok
|
|
notifempty
|
|
create 0640 root adm
|
|
copytruncate
|
|
include /etc/logrotate.d
|
|
EOF
|
|
pmx_record_execution "Restart logrotate" "systemctl restart logrotate"
|
|
systemctl restart logrotate > /dev/null 2>&1
|
|
|
|
msg_ok "$(translate "Logrotate optimization completed")"
|
|
register_tool "logrotate" true "$FUNC_VERSION"
|
|
}
|
|
|
|
# ==========================================================
|
|
increase_system_limits() {
|
|
local FUNC_VERSION="1.1"
|
|
pmx_journal_context "increase_system_limits" "$FUNC_VERSION"
|
|
# description: Raise inotify watches, file descriptors, process keys and PID limits to enterprise levels.
|
|
msg_info "$(translate "Increasing various system limits...")"
|
|
NECESSARY_REBOOT=1
|
|
|
|
|
|
pmx_write_file /etc/sysctl.d/99-maxwatches.conf << EOF
|
|
# ProxMenux configuration
|
|
fs.inotify.max_user_watches = 1048576
|
|
fs.inotify.max_user_instances = 1048576
|
|
fs.inotify.max_queued_events = 1048576
|
|
EOF
|
|
|
|
|
|
pmx_write_file /etc/security/limits.d/99-limits.conf << EOF
|
|
# ProxMenux configuration
|
|
* soft nproc 1048576
|
|
* hard nproc 1048576
|
|
* soft nofile 1048576
|
|
* hard nofile 1048576
|
|
root soft nproc unlimited
|
|
root hard nproc unlimited
|
|
root soft nofile unlimited
|
|
root hard nofile unlimited
|
|
EOF
|
|
|
|
|
|
pmx_write_file /etc/sysctl.d/99-maxkeys.conf << EOF
|
|
# ProxMenux configuration
|
|
kernel.keys.root_maxkeys=1000000
|
|
kernel.keys.maxkeys=1000000
|
|
EOF
|
|
|
|
|
|
for file in /etc/systemd/system.conf /etc/systemd/user.conf; do
|
|
if ! grep -q "^DefaultLimitNOFILE=" "$file"; then
|
|
echo "DefaultLimitNOFILE=1048576" | pmx_append_file "$file"
|
|
fi
|
|
done
|
|
|
|
|
|
for file in /etc/pam.d/common-session /etc/pam.d/runuser-l; do
|
|
if ! grep -q "^session required pam_limits.so" "$file"; then
|
|
echo 'session required pam_limits.so' | pmx_append_file "$file"
|
|
fi
|
|
done
|
|
|
|
|
|
if ! grep -q "ulimit -n 1048576" /root/.profile; then
|
|
pmx_edit_file /root/.profile '/ulimit -n 256000/d' 2>/dev/null || true
|
|
echo "ulimit -n 1048576" | pmx_append_file /root/.profile
|
|
fi
|
|
|
|
|
|
pmx_write_file /etc/sysctl.d/99-swap.conf << EOF
|
|
# ProxMenux configuration
|
|
vm.swappiness = 10
|
|
vm.vfs_cache_pressure = 100
|
|
EOF
|
|
|
|
|
|
pmx_write_file /etc/sysctl.d/99-fs.conf << EOF
|
|
# ProxMenux configuration
|
|
fs.nr_open = 2097152
|
|
fs.file-max = 2097152
|
|
fs.aio-max-nr = 1048576
|
|
EOF
|
|
|
|
msg_ok "$(translate "System limits increase completed.")"
|
|
register_tool "system_limits" true "$FUNC_VERSION"
|
|
}
|
|
|
|
# ==========================================================
|
|
optimize_memory_settings() {
|
|
local FUNC_VERSION="1.2"
|
|
pmx_journal_context "optimize_memory_settings" "$FUNC_VERSION"
|
|
# description: Tune swappiness, dirty page ratios and compaction proactiveness for VM hosts without overriding the kernel's memory-overcommit policy.
|
|
msg_info "$(translate "Optimizing memory settings...")"
|
|
NECESSARY_REBOOT=1
|
|
|
|
local memory_settings
|
|
memory_settings="$(cat <<EOF
|
|
# Balanced Memory Optimization
|
|
vm.swappiness = 10
|
|
vm.dirty_ratio = 15
|
|
vm.dirty_background_ratio = 5
|
|
vm.max_map_count = 262144
|
|
EOF
|
|
)"
|
|
|
|
if [ -f /proc/sys/vm/compaction_proactiveness ]; then
|
|
memory_settings+=$'\n''vm.compaction_proactiveness = 20'
|
|
fi
|
|
printf '%s\n' "$memory_settings" | pmx_write_file /etc/sysctl.d/99-memory.conf
|
|
|
|
msg_ok "$(translate "Memory optimization completed.")"
|
|
register_tool "memory_settings" true "$FUNC_VERSION"
|
|
}
|
|
|
|
# ==========================================================
|
|
configure_kernel_panic() {
|
|
local FUNC_VERSION="1.0"
|
|
pmx_journal_context "configure_kernel_panic" "$FUNC_VERSION"
|
|
# description: Auto-reboot on kernel panic / oops / hardlockup; write crash dumps to /var/crash.
|
|
msg_info "$(translate "Configuring kernel panic behavior")"
|
|
NECESSARY_REBOOT=1
|
|
|
|
pmx_write_file /etc/sysctl.d/99-kernelpanic.conf <<EOF
|
|
# Enable restart on kernel panic, kernel oops and hardlockup
|
|
kernel.core_pattern = /var/crash/core.%t.%p
|
|
kernel.panic = 10
|
|
kernel.panic_on_oops = 1
|
|
kernel.hardlockup_panic = 1
|
|
EOF
|
|
|
|
msg_ok "$(translate "Kernel panic behavior configuration completed")"
|
|
register_tool "kernel_panic" true "$FUNC_VERSION"
|
|
}
|
|
|
|
# ==========================================================
|
|
force_apt_ipv4() {
|
|
local FUNC_VERSION="1.0"
|
|
# description: Force APT to use IPv4 to avoid stalls on hosts with broken IPv6 connectivity.
|
|
msg_info "$(translate "Configuring APT to use IPv4...")"
|
|
|
|
echo 'Acquire::ForceIPv4 "true";' > /etc/apt/apt.conf.d/99-force-ipv4
|
|
|
|
msg_ok "$(translate "APT IPv4 configuration completed")"
|
|
register_tool "apt_ipv4" true "$FUNC_VERSION"
|
|
}
|
|
|
|
# ==========================================================
|
|
|
|
apply_network_optimizations() {
|
|
local FUNC_VERSION="1.1"
|
|
pmx_journal_context "apply_network_optimizations" "$FUNC_VERSION"
|
|
# 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
|
|
|
|
pmx_write_file /etc/sysctl.d/99-network.conf <<'EOF'
|
|
# ==========================================================
|
|
# ProxMenux - Network tuning (PVE 9 compatible)
|
|
# ==========================================================
|
|
|
|
# Core buffers & queues
|
|
net.core.netdev_max_backlog = 8192
|
|
net.core.rmem_max = 16777216
|
|
net.core.wmem_max = 16777216
|
|
net.core.somaxconn = 8192
|
|
|
|
# IPv4 hardening
|
|
net.ipv4.conf.all.accept_redirects = 0
|
|
net.ipv4.conf.all.accept_source_route = 0
|
|
net.ipv4.conf.all.secure_redirects = 0
|
|
net.ipv4.conf.all.send_redirects = 0
|
|
net.ipv4.conf.all.log_martians = 0
|
|
|
|
net.ipv4.conf.default.accept_redirects = 0
|
|
net.ipv4.conf.default.accept_source_route = 0
|
|
net.ipv4.conf.default.secure_redirects = 0
|
|
net.ipv4.conf.default.send_redirects = 0
|
|
net.ipv4.conf.default.log_martians = 0
|
|
|
|
# rp_filter:
|
|
net.ipv4.conf.all.rp_filter = 2
|
|
net.ipv4.conf.default.rp_filter = 2
|
|
|
|
# ICMP
|
|
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
|
net.ipv4.icmp_ignore_bogus_error_responses = 1
|
|
|
|
# TCP/IP
|
|
net.ipv4.ip_local_port_range = 1024 65535
|
|
net.ipv4.tcp_mtu_probing = 1
|
|
net.ipv4.tcp_rfc1337 = 1
|
|
net.ipv4.tcp_sack = 1
|
|
net.ipv4.tcp_rmem = 8192 87380 16777216
|
|
net.ipv4.tcp_wmem = 8192 65536 16777216
|
|
|
|
# Unix sockets
|
|
net.unix.max_dgram_qlen = 4096
|
|
EOF
|
|
|
|
pmx_record_execution "Apply network sysctl configuration" "sysctl --system"
|
|
sysctl --system > /dev/null 2>&1
|
|
|
|
pmx_write_file /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
|
|
|
|
pmx_write_file /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
|
|
|
|
pmx_write_file /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
|
|
|
|
pmx_record_execution "Reload systemd configuration" "systemctl daemon-reload"
|
|
systemctl daemon-reload >/dev/null 2>&1 || true
|
|
pmx_record_execution "Reload udev rules" "udevadm control --reload-rules"
|
|
udevadm control --reload-rules >/dev/null 2>&1 || true
|
|
pmx_enable_service proxmenux-fwbr-tune.service || true
|
|
pmx_record_execution "Tune existing Proxmox firewall bridge interfaces" "/usr/local/sbin/proxmenux-fwbr-tune"
|
|
/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
|
|
echo "source /etc/network/interfaces.d/*" | pmx_append_file "$interfaces_file"
|
|
fi
|
|
|
|
msg_ok "$(translate "Network optimization completed")"
|
|
register_tool "network_optimization" true "$FUNC_VERSION"
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
_migrate_proxmenux_bashrc() {
|
|
local bashrc="$1"
|
|
local mode="${2:-migrate}"
|
|
|
|
# Releases up to v1.1.3 appended an unmarked block. A later release
|
|
# accidentally wrote the marker variable names literally because its
|
|
# heredoc was quoted. Remove only those ProxMenux-owned shapes, together
|
|
# with the current marked block, before writing one canonical block.
|
|
python3 - "$bashrc" "$mode" <<'PY'
|
|
import os
|
|
import stat
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
path = Path(sys.argv[1])
|
|
mode = sys.argv[2] if len(sys.argv) > 2 else "migrate"
|
|
if mode not in {"inspect", "migrate"}:
|
|
raise SystemExit(f"invalid Bashrc migration mode: {mode}")
|
|
lines = path.read_text(encoding="utf-8", errors="surrogateescape").splitlines(keepends=True)
|
|
|
|
|
|
def content(line: str) -> str:
|
|
return line.rstrip("\r\n")
|
|
|
|
|
|
def prompt_style(block: list[str]) -> str:
|
|
for line in block:
|
|
if content(line).startswith("export PS1=") and r"\w" in content(line):
|
|
return "full"
|
|
return "short"
|
|
|
|
|
|
legacy_tail = [
|
|
"alias l='ls -CF'",
|
|
"alias la='ls -A'",
|
|
"alias ll='ls -alF'",
|
|
"alias ls='ls --color=auto'",
|
|
"alias grep='grep --color=auto'",
|
|
"alias fgrep='fgrep --color=auto'",
|
|
"alias egrep='egrep --color=auto'",
|
|
"source /etc/profile.d/bash_completion.sh",
|
|
]
|
|
marker_pairs = (
|
|
("# BEGIN PMX_CORE_BASHRC", "# END PMX_CORE_BASHRC"),
|
|
("${marker_begin}", "${marker_end}"),
|
|
)
|
|
|
|
result: list[str] = []
|
|
detected_style = "short"
|
|
changed = False
|
|
i = 0
|
|
while i < len(lines):
|
|
current = content(lines[i])
|
|
|
|
removed = False
|
|
for marker_begin, marker_end in marker_pairs:
|
|
if current != marker_begin:
|
|
continue
|
|
end_index = next(
|
|
(j for j in range(i + 1, len(lines)) if content(lines[j]) == marker_end),
|
|
None,
|
|
)
|
|
# Preserve incomplete ranges rather than risk consuming user content.
|
|
if end_index is None or end_index - i > 32:
|
|
continue
|
|
block = lines[i:end_index + 1]
|
|
if prompt_style(block) == "full":
|
|
detected_style = "full"
|
|
i = end_index + 1
|
|
changed = True
|
|
removed = True
|
|
break
|
|
if removed:
|
|
continue
|
|
|
|
# Exact legacy signature: ProxMenux header, history format, coloured
|
|
# prompt, seven stock aliases and bash-completion. Unrelated user blocks
|
|
# cannot match this complete sequence.
|
|
if current == "# ProxMenux customizations" and i + 10 < len(lines):
|
|
candidate = lines[i:i + 11]
|
|
candidate_text = [content(line) for line in candidate]
|
|
ps1 = candidate_text[2]
|
|
if (
|
|
candidate_text[1] == 'export HISTTIMEFORMAT="%d/%m/%y %T "'
|
|
and ps1.startswith('export PS1="')
|
|
and r"\e[38;5;172m" in ps1
|
|
and r"\e[38;5;153m" in ps1
|
|
and r"\e[38;5;214m" in ps1
|
|
and candidate_text[3:] == legacy_tail
|
|
):
|
|
if prompt_style(candidate) == "full":
|
|
detected_style = "full"
|
|
i += len(candidate)
|
|
changed = True
|
|
continue
|
|
|
|
result.append(lines[i])
|
|
i += 1
|
|
|
|
if changed and mode == "migrate":
|
|
original_stat = path.stat()
|
|
fd, temporary = tempfile.mkstemp(prefix=".bashrc.proxmenux.", dir=str(path.parent))
|
|
try:
|
|
with os.fdopen(fd, "w", encoding="utf-8", errors="surrogateescape", newline="") as handle:
|
|
handle.write("".join(result))
|
|
handle.flush()
|
|
os.fsync(handle.fileno())
|
|
os.chmod(temporary, stat.S_IMODE(original_stat.st_mode))
|
|
try:
|
|
os.chown(temporary, original_stat.st_uid, original_stat.st_gid)
|
|
except PermissionError:
|
|
pass
|
|
os.replace(temporary, path)
|
|
finally:
|
|
if os.path.exists(temporary):
|
|
os.unlink(temporary)
|
|
|
|
print(detected_style)
|
|
PY
|
|
}
|
|
|
|
|
|
customize_bashrc() {
|
|
local FUNC_VERSION="1.2"
|
|
pmx_journal_context "customize_bashrc" "$FUNC_VERSION"
|
|
# description: Install and safely migrate the managed ProxMenux Bash prompt and aliases while preserving or selecting the short/full working-directory style.
|
|
msg_info "$(translate "Customizing bashrc for root user...")"
|
|
local bashrc="/root/.bashrc"
|
|
local bash_profile="/root/.bash_profile"
|
|
local marker_begin="# BEGIN PMX_CORE_BASHRC"
|
|
local marker_end="# END PMX_CORE_BASHRC"
|
|
local prompt_path_escape='\W'
|
|
local detected_path_style="short"
|
|
|
|
[[ -f "$bashrc" ]] || pmx_write_file "$bashrc" < /dev/null
|
|
if ! detected_path_style="$(_migrate_proxmenux_bashrc "$bashrc" inspect)"; then
|
|
msg_error "$(translate "Failed to inspect the existing ProxMenux Bash configuration.")"
|
|
return 1
|
|
fi
|
|
|
|
# Automated installs remain non-interactive. Preserve a previous
|
|
# ProxMenux choice on re-run, including legacy blocks, and use the compact
|
|
# \W style on first use.
|
|
if [[ "$detected_path_style" == "full" ]]; then
|
|
prompt_path_escape='\w'
|
|
fi
|
|
case "${PMX_BASHRC_PATH_STYLE:-}" in
|
|
short) prompt_path_escape='\W' ;;
|
|
"") ;;
|
|
full) prompt_path_escape='\w' ;;
|
|
*)
|
|
msg_error "PMX_BASHRC_PATH_STYLE must be 'short' or 'full'."
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
[ -f "${bashrc}.bak" ] || cp "$bashrc" "${bashrc}.bak" > /dev/null 2>&1
|
|
local migrated_bashrc
|
|
migrated_bashrc="$(mktemp)"
|
|
cp -p "$bashrc" "$migrated_bashrc"
|
|
if ! _migrate_proxmenux_bashrc "$migrated_bashrc" migrate >/dev/null; then
|
|
rm -f "$migrated_bashrc"
|
|
msg_error "$(translate "Failed to migrate the existing ProxMenux Bash configuration.")"
|
|
return 1
|
|
fi
|
|
pmx_write_file "$bashrc" < "$migrated_bashrc"
|
|
rm -f "$migrated_bashrc"
|
|
|
|
|
|
pmx_append_file "$bashrc" << EOF
|
|
${marker_begin}
|
|
# ProxMenux core customizations
|
|
export HISTTIMEFORMAT="%d/%m/%y %T "
|
|
export PS1="\[\e[31m\][\[\e[m\]\[\e[38;5;172m\]\u\[\e[m\]@\[\e[38;5;153m\]\h\[\e[m\] \[\e[38;5;214m\]${prompt_path_escape}\[\e[m\]\[\e[31m\]]\[\e[m\]\\$ "
|
|
alias l='ls -CF'
|
|
alias la='ls -A'
|
|
alias ll='ls -alF'
|
|
alias ls='ls --color=auto'
|
|
alias grep='grep --color=auto'
|
|
alias fgrep='fgrep --color=auto'
|
|
alias egrep='egrep --color=auto'
|
|
source /etc/profile.d/bash_completion.sh
|
|
${marker_end}
|
|
EOF
|
|
|
|
|
|
if ! grep -q "source /root/.bashrc" "$bash_profile" 2>/dev/null; then
|
|
echo "source /root/.bashrc" | pmx_append_file "$bash_profile" 2>/dev/null
|
|
fi
|
|
|
|
msg_ok "$(translate "Bashrc customization completed")"
|
|
msg_info "$(translate "The new prompt will be used in new terminal sessions.")"
|
|
msg_info "$(translate "To apply it to the current shell now, run:") source /root/.bashrc"
|
|
register_tool "bashrc_custom" true "$FUNC_VERSION"
|
|
}
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_update_existing_log2ram_auto() {
|
|
local func_version="$1"
|
|
local log2ram_bin=""
|
|
local candidate resolved tmp_file
|
|
pmx_journal_context "_update_existing_log2ram_auto" "$func_version"
|
|
|
|
msg_ok "$(translate "Log2RAM already registered — updating to latest configuration")"
|
|
|
|
for candidate in \
|
|
"$(command -v log2ram 2>/dev/null)" \
|
|
/usr/local/bin/log2ram \
|
|
/usr/sbin/log2ram \
|
|
/usr/bin/log2ram
|
|
do
|
|
[[ -n "$candidate" && -f "$candidate" ]] || continue
|
|
resolved="$(readlink -f "$candidate" 2>/dev/null || printf '%s' "$candidate")"
|
|
[[ -f "$resolved" ]] || continue
|
|
log2ram_bin="$resolved"
|
|
break
|
|
done
|
|
|
|
if [[ -z "$log2ram_bin" || ! -f /etc/log2ram.conf ]]; then
|
|
msg_error "$(translate "Log2RAM installation verification failed. Check /tmp/log2ram_install.log")"
|
|
return 1
|
|
fi
|
|
|
|
if grep -q 'rsync -aAXv ' "$log2ram_bin" 2>/dev/null; then
|
|
[[ -e "${log2ram_bin}.proxmenux.bak" ]] || cp -a "$log2ram_bin" "${log2ram_bin}.proxmenux.bak"
|
|
sed 's/rsync -aAXv /rsync -aXv --no-acls /g' "$log2ram_bin" | pmx_write_file "$log2ram_bin"
|
|
fi
|
|
|
|
if dpkg-query -W -f='${Status}' proxmox-backup-server 2>/dev/null \
|
|
| grep -q 'install ok installed'; then
|
|
tmp_file="$(mktemp /etc/logrotate.d/.proxmox-backup-api.XXXXXX)" || return 1
|
|
cat > "$tmp_file" <<'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 "$tmp_file"
|
|
chown root:root "$tmp_file"
|
|
pmx_write_file /etc/logrotate.d/proxmox-backup-api < "$tmp_file"
|
|
rm -f "$tmp_file"
|
|
|
|
tmp_file="$(mktemp /etc/cron.hourly/.proxmox-backup-logrotate.XXXXXX)" || return 1
|
|
cat > "$tmp_file" <<'EOF'
|
|
#!/bin/sh
|
|
/usr/sbin/logrotate /etc/logrotate.d/proxmox-backup-api >/dev/null 2>&1
|
|
EOF
|
|
chmod 0755 "$tmp_file"
|
|
chown root:root "$tmp_file"
|
|
pmx_write_file /etc/cron.hourly/proxmox-backup-logrotate < "$tmp_file"
|
|
chmod 0755 /etc/cron.hourly/proxmox-backup-logrotate
|
|
chown root:root /etc/cron.hourly/proxmox-backup-logrotate
|
|
rm -f "$tmp_file"
|
|
msg_ok "$(translate "PBS API log rotation configured (hourly, size-based)")"
|
|
fi
|
|
|
|
tmp_file="$(mktemp /usr/local/bin/.log2ram-check.XXXXXX)" || return 1
|
|
cat > "$tmp_file" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
# 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 existing pveproxy/pveam logs, 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)"
|
|
[[ -z "$L2R_BIN" && -x /usr/sbin/log2ram ]] && L2R_BIN="/usr/sbin/log2ram"
|
|
[[ -z "$L2R_BIN" ]] && exit 0
|
|
|
|
SIZE_MiB="$(grep -E '^SIZE=' "$CONF_FILE" 2>/dev/null | cut -d'=' -f2 | tr -dc '0-9')"
|
|
[[ -z "$SIZE_MiB" ]] && SIZE_MiB=128
|
|
LIMIT_BYTES=$(( SIZE_MiB * 1024 * 1024 ))
|
|
WARN_BYTES=$(( LIMIT_BYTES * 80 / 100 ))
|
|
EMERGENCY_BYTES=$(( LIMIT_BYTES * 92 / 100 ))
|
|
|
|
USED_BYTES="$(df -B1 --output=used /var/log 2>/dev/null | tail -1 | tr -dc '0-9')"
|
|
[[ -z "$USED_BYTES" ]] && exit 0
|
|
|
|
LOCK="/run/log2ram-check.lock"
|
|
exec 9>"$LOCK" 2>/dev/null || exit 0
|
|
flock -n 9 || exit 0
|
|
|
|
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
|
|
[ -e /var/log/pveproxy/access.log ] && : > /var/log/pveproxy/access.log 2>/dev/null || true
|
|
[ -e /var/log/pveproxy/error.log ] && : > /var/log/pveproxy/error.log 2>/dev/null || true
|
|
[ -e /var/log/pveam.log ] && : > /var/log/pveam.log 2>/dev/null || true
|
|
"$L2R_BIN" write 2>/dev/null || true
|
|
elif (( USED_BYTES > WARN_BYTES )); then
|
|
SOFT_JOURNAL_MB=$(( SIZE_MiB * 30 / 100 ))
|
|
[[ "$SOFT_JOURNAL_MB" -lt 32 ]] && SOFT_JOURNAL_MB=32
|
|
journalctl --vacuum-size="${SOFT_JOURNAL_MB}M" >/dev/null 2>&1 || true
|
|
"$L2R_BIN" write 2>/dev/null || true
|
|
fi
|
|
EOF
|
|
chmod 0755 "$tmp_file"
|
|
chown root:root "$tmp_file"
|
|
bash -n "$tmp_file" || return 1
|
|
pmx_write_file /usr/local/bin/log2ram-check.sh < "$tmp_file"
|
|
chmod 0755 /usr/local/bin/log2ram-check.sh
|
|
chown root:root /usr/local/bin/log2ram-check.sh
|
|
rm -f "$tmp_file"
|
|
|
|
tmp_file="$(mktemp /etc/cron.d/.log2ram-auto-sync.XXXXXX)" || return 1
|
|
cat > "$tmp_file" <<'EOF'
|
|
# Log2RAM auto-sync based on /var/log usage - Created by ProxMenux
|
|
SHELL=/bin/bash
|
|
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
MAILTO=""
|
|
# 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 "$tmp_file"
|
|
chown root:root "$tmp_file"
|
|
pmx_write_file /etc/cron.d/log2ram-auto-sync < "$tmp_file"
|
|
chmod 0644 /etc/cron.d/log2ram-auto-sync
|
|
chown root:root /etc/cron.d/log2ram-auto-sync
|
|
rm -f "$tmp_file"
|
|
|
|
register_tool "log2ram" true "$func_version"
|
|
msg_success "$(translate "Log2RAM installation and configuration completed successfully.")"
|
|
}
|
|
|
|
|
|
install_log2ram_auto() {
|
|
local FUNC_VERSION="1.5"
|
|
local existing_log2ram_bin=""
|
|
pmx_journal_context "install_log2ram_auto" "$FUNC_VERSION"
|
|
|
|
# description: Install Log2RAM with size auto-tuned to host RAM (128M/256M/512M); SSD/M.2 detection skips on rotational disks.
|
|
|
|
existing_log2ram_bin="$(command -v log2ram 2>/dev/null || true)"
|
|
if [[ -f /etc/log2ram.conf || -n "$existing_log2ram_bin" \
|
|
|| -e /etc/systemd/system/log2ram.service \
|
|
|| -d /var/hdd.log || -d /var/log.hdd ]]; then
|
|
_update_existing_log2ram_auto "$FUNC_VERSION"
|
|
return $?
|
|
fi
|
|
|
|
if [[ -f "$TOOLS_JSON" ]] && jq -e '.log2ram == true or .log2ram.installed == true' "$TOOLS_JSON" >/dev/null 2>&1; then
|
|
msg_info2 "$(translate "Preparing Log2RAM configuration")"
|
|
else
|
|
# ── First-time install: detect SSD/M.2 ─────────────────────────────────
|
|
msg_info "$(translate "Checking if system disk is SSD or M.2...")"
|
|
|
|
local is_ssd=false
|
|
local pool disks disk byid_path dev rot
|
|
|
|
if grep -qE '^root=ZFS=' /etc/kernel/cmdline 2>/dev/null || mount | grep -q 'on / type zfs'; then
|
|
pool=$(zfs list -Ho name,mountpoint 2>/dev/null | awk '$2=="/"{print $1}' | cut -d/ -f1)
|
|
disks=$(zpool status "$pool" 2>/dev/null | awk '/ONLINE/ && $1 !~ /:|mirror|raidz|log|spare|config|NAME|rpool|state/ {print $1}' | sort -u)
|
|
is_ssd=true
|
|
for disk in $disks; do
|
|
byid_path=$(readlink -f /dev/disk/by-id/*$disk* 2>/dev/null) || continue
|
|
dev=$(basename "$byid_path" | sed -E 's|[0-9]+$||' | sed -E 's|p$||')
|
|
rot=$(cat /sys/block/$dev/queue/rotational 2>/dev/null)
|
|
[[ "$rot" != "0" ]] && is_ssd=false && break
|
|
done
|
|
else
|
|
ROOT_PART=$(lsblk -no NAME,MOUNTPOINT | grep ' /$' | awk '{print $1}')
|
|
SYSTEM_DISK=$(lsblk -no PKNAME /dev/$ROOT_PART 2>/dev/null | grep -E '^[a-z]+' | head -n1)
|
|
SYSTEM_DISK=${SYSTEM_DISK:-sda}
|
|
if [[ "$SYSTEM_DISK" == nvme* || "$(cat /sys/block/$SYSTEM_DISK/queue/rotational 2>/dev/null)" == "0" ]]; then
|
|
is_ssd=true
|
|
fi
|
|
fi
|
|
|
|
if [[ "$is_ssd" == true ]]; then
|
|
msg_ok "$(translate "System disk is SSD or M.2. Proceeding with Log2RAM setup.")"
|
|
else
|
|
kill -TERM "$SPINNER_PID" 2>/dev/null
|
|
sleep 1
|
|
if whiptail --yesno "$(translate "Do you want to install Log2RAM anyway to reduce log write load?")" 10 70 --title "Log2RAM"; then
|
|
msg_ok "$(translate "Proceeding with Log2RAM setup on non-SSD disk as requested by user.")"
|
|
else
|
|
msg_info2 "$(translate "Log2RAM installation cancelled by user")"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
fi # end first-time install block
|
|
|
|
msg_info "$(translate "Cleaning previous Log2RAM installation...")"
|
|
|
|
pmx_disable_service log2ram || true
|
|
pmx_disable_service log2ram-daily.timer || true
|
|
|
|
local obsolete_path
|
|
for obsolete_path in \
|
|
/etc/cron.d/log2ram /etc/cron.d/log2ram-auto-sync \
|
|
/etc/cron.hourly/log2ram /etc/cron.daily/log2ram \
|
|
/etc/cron.weekly/log2ram /etc/cron.monthly/log2ram \
|
|
/usr/local/bin/log2ram-check.sh /usr/local/bin/log2ram /usr/sbin/log2ram \
|
|
/etc/systemd/system/log2ram.service \
|
|
/etc/systemd/system/log2ram-daily.timer \
|
|
/etc/systemd/system/log2ram-daily.service \
|
|
/etc/systemd/system/sysinit.target.wants/log2ram.service \
|
|
/etc/log2ram.conf /etc/log2ram.conf.* /etc/logrotate.d/log2ram
|
|
do
|
|
pmx_remove_file "$obsolete_path" 2>/dev/null || true
|
|
done
|
|
rm -rf /etc/systemd/system/log2ram.service.d 2>/dev/null || true
|
|
rm -rf /var/log.hdd /tmp/log2ram 2>/dev/null || true
|
|
|
|
pmx_record_execution "Re-execute the systemd manager" "systemctl daemon-reexec"
|
|
systemctl daemon-reexec >/dev/null 2>&1 || true
|
|
pmx_record_execution "Reload systemd configuration" "systemctl daemon-reload"
|
|
systemctl daemon-reload >/dev/null 2>&1 || true
|
|
pmx_record_execution "Restart cron" "systemctl restart cron"
|
|
systemctl restart cron >/dev/null 2>&1 || true
|
|
|
|
msg_ok "$(translate "Previous installation cleaned")"
|
|
msg_info "$(translate "Installing Log2RAM from source...")"
|
|
|
|
if ! command -v git >/dev/null 2>&1; then
|
|
pmx_record_execution "Update package lists for Log2RAM" "apt-get update -qq"
|
|
apt-get update -qq >/dev/null 2>&1
|
|
pmx_install_pkg git
|
|
fi
|
|
|
|
rm -rf /tmp/log2ram 2>/dev/null || true
|
|
if ! git clone --depth 1 https://github.com/azlux/log2ram.git /tmp/log2ram >/dev/null 2>>/tmp/log2ram_install.log; then
|
|
msg_error "$(translate "Failed to clone log2ram repository. Check /tmp/log2ram_install.log")"
|
|
return 1
|
|
fi
|
|
|
|
cd /tmp/log2ram || { msg_error "$(translate "Failed to access log2ram directory")"; return 1; }
|
|
|
|
pmx_record_execution "Run the Log2RAM installer" "bash install.sh"
|
|
if ! bash install.sh >>/tmp/log2ram_install.log 2>&1; then
|
|
msg_error "$(translate "Failed to run log2ram installer. Check /tmp/log2ram_install.log")"
|
|
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"
|
|
pmx_edit_file "$_l2r_bin" 's/rsync -aAXv /rsync -aXv --no-acls /g'
|
|
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
|
|
pmx_write_file /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
|
|
pmx_write_file /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
|
|
|
|
pmx_record_execution "Reload systemd configuration" "systemctl daemon-reload"
|
|
systemctl daemon-reload >/dev/null 2>&1 || true
|
|
|
|
if [[ -f /etc/log2ram.conf ]] && command -v log2ram >/dev/null 2>&1; then
|
|
msg_ok "$(translate "Log2RAM installed successfully")"
|
|
else
|
|
msg_error "$(translate "Log2RAM installation verification failed. Check /tmp/log2ram_install.log")"
|
|
return 1
|
|
fi
|
|
|
|
RAM_SIZE_GB=$(free -g | awk '/^Mem:/{print $2}')
|
|
[[ -z "$RAM_SIZE_GB" || "$RAM_SIZE_GB" -eq 0 ]] && RAM_SIZE_GB=4
|
|
|
|
if (( RAM_SIZE_GB <= 8 )); then
|
|
LOG2RAM_SIZE="128M"; CRON_HOURS=1
|
|
elif (( RAM_SIZE_GB <= 16 )); then
|
|
LOG2RAM_SIZE="256M"; CRON_HOURS=3
|
|
else
|
|
LOG2RAM_SIZE="512M"; CRON_HOURS=6
|
|
fi
|
|
|
|
msg_ok "$(translate "Detected RAM:") $RAM_SIZE_GB GB — $(translate "Log2RAM size set to:") $LOG2RAM_SIZE"
|
|
pmx_edit_file /etc/log2ram.conf "s/^SIZE=.*/SIZE=$LOG2RAM_SIZE/"
|
|
|
|
LOG2RAM_BIN="$(command -v log2ram || echo /usr/sbin/log2ram)"
|
|
|
|
pmx_write_file /etc/cron.d/log2ram <<EOF
|
|
# Log2RAM periodic sync - Created by ProxMenux
|
|
SHELL=/bin/bash
|
|
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
MAILTO=""
|
|
0 */$CRON_HOURS * * * root $LOG2RAM_BIN write >/dev/null 2>&1
|
|
EOF
|
|
chmod 0644 /etc/cron.d/log2ram
|
|
chown root:root /etc/cron.d/log2ram
|
|
msg_ok "$(translate "Log2RAM write scheduled every") $CRON_HOURS $(translate "hour(s)")"
|
|
|
|
pmx_write_file /usr/local/bin/log2ram-check.sh <<'EOF'
|
|
#!/usr/bin/env bash
|
|
# 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)"
|
|
[[ -z "$L2R_BIN" && -x /usr/sbin/log2ram ]] && L2R_BIN="/usr/sbin/log2ram"
|
|
[[ -z "$L2R_BIN" ]] && exit 0
|
|
|
|
SIZE_MiB="$(grep -E '^SIZE=' "$CONF_FILE" 2>/dev/null | cut -d'=' -f2 | tr -dc '0-9')"
|
|
[[ -z "$SIZE_MiB" ]] && SIZE_MiB=128
|
|
LIMIT_BYTES=$(( SIZE_MiB * 1024 * 1024 ))
|
|
WARN_BYTES=$(( LIMIT_BYTES * 80 / 100 ))
|
|
EMERGENCY_BYTES=$(( LIMIT_BYTES * 92 / 100 ))
|
|
|
|
USED_BYTES="$(df -B1 --output=used /var/log 2>/dev/null | tail -1 | tr -dc '0-9')"
|
|
[[ -z "$USED_BYTES" ]] && exit 0
|
|
|
|
LOCK="/run/log2ram-check.lock"
|
|
exec 9>"$LOCK" 2>/dev/null || exit 0
|
|
flock -n 9 || exit 0
|
|
|
|
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
|
|
# Only truncate if the file already exists. Creating one from
|
|
# this cron path (running as root, default umask) would leave
|
|
# it as root:root 644 — pveproxy runs as www-data and would then
|
|
# fail to reopen it on the next restart, taking :8006 down.
|
|
[ -e /var/log/pveproxy/access.log ] && : > /var/log/pveproxy/access.log 2>/dev/null || true
|
|
[ -e /var/log/pveproxy/error.log ] && : > /var/log/pveproxy/error.log 2>/dev/null || true
|
|
[ -e /var/log/pveam.log ] && : > /var/log/pveam.log 2>/dev/null || true
|
|
"$L2R_BIN" write 2>/dev/null || true
|
|
elif (( USED_BYTES > WARN_BYTES )); then
|
|
SOFT_JOURNAL_MB=$(( SIZE_MiB * 30 / 100 ))
|
|
[[ "$SOFT_JOURNAL_MB" -lt 32 ]] && SOFT_JOURNAL_MB=32
|
|
journalctl --vacuum-size="${SOFT_JOURNAL_MB}M" >/dev/null 2>&1 || true
|
|
"$L2R_BIN" write 2>/dev/null || true
|
|
fi
|
|
EOF
|
|
chmod +x /usr/local/bin/log2ram-check.sh
|
|
|
|
pmx_write_file /etc/cron.d/log2ram-auto-sync <<'EOF'
|
|
# Log2RAM auto-sync based on /var/log usage - Created by ProxMenux
|
|
SHELL=/bin/bash
|
|
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
MAILTO=""
|
|
# 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
|
|
chown root:root /etc/cron.d/log2ram-auto-sync
|
|
|
|
pmx_record_execution "Restart cron" "systemctl restart cron"
|
|
systemctl restart cron >/dev/null 2>&1 || true
|
|
msg_ok "$(translate "Auto-sync enabled when /var/log exceeds 80% of") $LOG2RAM_SIZE"
|
|
|
|
|
|
msg_info "$(translate "Adjusting systemd-journald limits to match Log2RAM size...")"
|
|
|
|
|
|
if [[ -f /etc/systemd/journald.conf ]]; then
|
|
cp -n /etc/systemd/journald.conf /etc/systemd/journald.conf.bak.$(date +%Y%m%d-%H%M%S)
|
|
|
|
fi
|
|
|
|
SIZE_MB=$(echo "$LOG2RAM_SIZE" | tr -dc '0-9')
|
|
|
|
|
|
USE_MB=$(( SIZE_MB * 55 / 100 ))
|
|
KEEP_MB=$(( SIZE_MB * 10 / 100 ))
|
|
RUNTIME_MB=$(( SIZE_MB * 25 / 100 ))
|
|
|
|
|
|
[ "$USE_MB" -lt 80 ] && USE_MB=80
|
|
[ "$RUNTIME_MB" -lt 32 ] && RUNTIME_MB=32
|
|
[ "$KEEP_MB" -lt 8 ] && KEEP_MB=8
|
|
|
|
|
|
pmx_edit_file /etc/systemd/journald.conf '/^\[Journal\]/,$d' 2>/dev/null || true
|
|
pmx_append_file /etc/systemd/journald.conf <<EOF
|
|
[Journal]
|
|
Storage=persistent
|
|
SplitMode=none
|
|
RateLimitIntervalSec=30s
|
|
RateLimitBurst=1000
|
|
ForwardToSyslog=no
|
|
ForwardToWall=no
|
|
Seal=no
|
|
Compress=yes
|
|
SystemMaxUse=${USE_MB}M
|
|
SystemKeepFree=${KEEP_MB}M
|
|
RuntimeMaxUse=${RUNTIME_MB}M
|
|
# MaxLevelStore=info: required for ProxMenux Monitor log display and Fail2Ban detection.
|
|
# Using "warning" silently discards most system logs making date filters useless.
|
|
MaxLevelStore=info
|
|
MaxLevelSyslog=info
|
|
MaxLevelKMsg=warning
|
|
MaxLevelConsole=notice
|
|
MaxLevelWall=crit
|
|
EOF
|
|
|
|
|
|
mkdir -p /var/log/pveproxy
|
|
chown -R www-data:www-data /var/log/pveproxy
|
|
chmod 0750 /var/log/pveproxy
|
|
|
|
mkdir -p /var/log.hdd/pveproxy
|
|
chown -R www-data:www-data /var/log.hdd/pveproxy
|
|
chmod 0750 /var/log.hdd/pveproxy
|
|
|
|
#msg_ok "$(translate "Backup created:") /etc/systemd/journald.conf.bak.$(date +%Y%m%d-%H%M%S)"
|
|
msg_ok "$(translate "Journald configuration adjusted to") ${USE_MB}M (Log2RAM ${LOG2RAM_SIZE})"
|
|
|
|
pmx_record_execution "Reload systemd configuration" "systemctl daemon-reload"
|
|
systemctl daemon-reload >/dev/null 2>&1 || true
|
|
if ! pmx_apply_setting "service-enabled:log2ram" "systemctl is-enabled log2ram" \
|
|
systemctl enable log2ram; then
|
|
msg_error "$(translate "Log2RAM installation verification failed. Check /tmp/log2ram_install.log")"
|
|
return 1
|
|
fi
|
|
NECESSARY_REBOOT=1
|
|
|
|
register_tool "log2ram" true "$FUNC_VERSION"
|
|
}
|
|
|
|
|
|
# ==========================================================
|
|
enable_zfs_autotrim() {
|
|
local FUNC_VERSION="1.0"
|
|
# description: Enable ZFS autotrim on detected pools and record only pools changed by ProxMenux.
|
|
local state_file="$BASE_DIR/zfs_autotrim_pools"
|
|
local tmp_file="${state_file}.tmp"
|
|
local pools=()
|
|
local pool current
|
|
local changed=false
|
|
|
|
pool_supports_autotrim() {
|
|
local pool_name="$1"
|
|
local vdev dev_path block_device rotational discard_granularity
|
|
local found_device=false
|
|
|
|
while read -r vdev; do
|
|
[[ -z "$vdev" ]] && continue
|
|
found_device=true
|
|
|
|
dev_path=$(readlink -f "$vdev" 2>/dev/null || true)
|
|
if [[ -z "$dev_path" || ! -b "$dev_path" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
block_device=$(lsblk -no PKNAME "$dev_path" 2>/dev/null | head -n1)
|
|
[[ -z "$block_device" ]] && block_device=$(basename "$dev_path")
|
|
|
|
rotational=$(cat "/sys/block/$block_device/queue/rotational" 2>/dev/null || true)
|
|
discard_granularity=$(cat "/sys/block/$block_device/queue/discard_granularity" 2>/dev/null || true)
|
|
|
|
if [[ "$rotational" != "0" || -z "$discard_granularity" || "$discard_granularity" == "0" ]]; then
|
|
return 1
|
|
fi
|
|
done < <(
|
|
zpool status -P "$pool_name" 2>/dev/null |
|
|
awk '
|
|
$1 == "NAME" { in_config=1; next }
|
|
in_config && $1 == "errors:" { exit }
|
|
in_config && $1 ~ /^\// && $2 ~ /^(ONLINE|DEGRADED|FAULTED|OFFLINE|UNAVAIL|REMOVED)$/ { print $1 }
|
|
'
|
|
)
|
|
|
|
[[ "$found_device" == true ]]
|
|
}
|
|
|
|
if ! command -v zpool >/dev/null 2>&1; then
|
|
msg_info2 "$(translate "ZFS not detected. Skipping ZFS autotrim.")"
|
|
return 0
|
|
fi
|
|
|
|
mapfile -t pools < <(zpool list -H -o name 2>/dev/null)
|
|
if [[ ${#pools[@]} -eq 0 ]]; then
|
|
msg_info2 "$(translate "No ZFS pools detected. Skipping ZFS autotrim.")"
|
|
return 0
|
|
fi
|
|
|
|
msg_info "$(translate "Checking ZFS autotrim configuration...")"
|
|
mkdir -p "$BASE_DIR"
|
|
: > "$tmp_file"
|
|
|
|
for pool in "${pools[@]}"; do
|
|
current=$(zpool get -H -o value autotrim "$pool" 2>/dev/null || true)
|
|
|
|
if [[ "$current" == "on" ]]; then
|
|
msg_ok "$(translate "ZFS autotrim already enabled for pool:") $pool"
|
|
continue
|
|
fi
|
|
|
|
if [[ "$current" != "off" ]]; then
|
|
msg_warn "$(translate "ZFS autotrim is not supported for pool:") $pool"
|
|
continue
|
|
fi
|
|
|
|
if ! pool_supports_autotrim "$pool"; then
|
|
stop_spinner
|
|
msg_info2 "$(translate "Pool does not appear to use SSD/NVMe devices with discard support. Skipping ZFS autotrim for pool:") $pool"
|
|
continue
|
|
fi
|
|
|
|
if zpool set autotrim=on "$pool" >/dev/null 2>&1; then
|
|
printf '%s\n' "$pool" >> "$tmp_file"
|
|
changed=true
|
|
msg_ok "$(translate "ZFS autotrim enabled for pool:") $pool"
|
|
else
|
|
msg_warn "$(translate "Failed to enable ZFS autotrim for pool:") $pool"
|
|
fi
|
|
done
|
|
|
|
if [[ "$changed" == true ]]; then
|
|
if [[ -s "$state_file" ]]; then
|
|
sort -u "$state_file" "$tmp_file" > "${tmp_file}.merged"
|
|
mv "${tmp_file}.merged" "$state_file"
|
|
rm -f "$tmp_file"
|
|
else
|
|
mv "$tmp_file" "$state_file"
|
|
fi
|
|
register_tool "zfs_autotrim" true "$FUNC_VERSION"
|
|
else
|
|
rm -f "$tmp_file"
|
|
fi
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
setup_persistent_network() {
|
|
local FUNC_VERSION="1.2"
|
|
# description: Pin NIC names to MAC addresses via systemd .link files so kernel updates don't shuffle interface names.
|
|
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
|
|
|
|
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
|
|
|
|
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")"
|
|
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")"
|
|
fi
|
|
msg_ok "$(translate "Changes will apply after reboot.")"
|
|
else
|
|
msg_warn "$(translate "No physical interfaces found")"
|
|
fi
|
|
register_tool "persistent_network" true "$FUNC_VERSION"
|
|
}
|
|
|
|
|
|
# ==========================================================
|
|
|
|
run_complete_optimization() {
|
|
|
|
show_proxmenux_logo
|
|
msg_title "$(translate "$SCRIPT_TITLE")"
|
|
|
|
ensure_tools_json
|
|
|
|
apt_upgrade
|
|
remove_subscription_banner
|
|
force_apt_ipv4
|
|
#configure_time_sync
|
|
skip_apt_languages
|
|
increase_system_limits
|
|
optimize_memory_settings
|
|
configure_kernel_panic
|
|
apply_network_optimizations
|
|
#disable_rpc
|
|
customize_bashrc
|
|
install_log2ram_auto
|
|
enable_zfs_autotrim
|
|
optimize_journald
|
|
optimize_logrotate
|
|
setup_persistent_network
|
|
|
|
|
|
echo -e
|
|
msg_success "$(translate "Complete post-installation optimization finished!")"
|
|
|
|
if [[ "$NECESSARY_REBOOT" -eq 1 ]]; then
|
|
whiptail --title "Reboot Required" \
|
|
--yesno "$(translate "Some changes require a reboot to take effect. Do you want to restart now?")" 10 60
|
|
if [[ $? -eq 0 ]]; then
|
|
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
|
|
apt-get -y autoremove >/dev/null 2>&1
|
|
apt-get -y autoclean >/dev/null 2>&1
|
|
msg_ok "$(translate "Cleanup finished")"
|
|
msg_success "$(translate "Press Enter to continue...")"
|
|
read -r
|
|
msg_warn "$(translate "Rebooting the system...")"
|
|
reboot
|
|
else
|
|
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
|
|
apt-get -y autoremove >/dev/null 2>&1
|
|
apt-get -y autoclean >/dev/null 2>&1
|
|
msg_ok "$(translate "Cleanup finished")"
|
|
msg_info2 "$(translate "You can reboot later manually.")"
|
|
msg_success "$(translate "Press Enter to continue...")"
|
|
read -r
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
}
|
|
|
|
check_extremeshok_warning
|
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
run_complete_optimization
|
|
fi
|