mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-09-15 19:26:45 +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>
3944 lines
137 KiB
Bash
3944 lines
137 KiB
Bash
#!/bin/bash
|
|
|
|
# ==========================================================
|
|
# ProxMenux - Customizable Post-Install Script
|
|
# ==========================================================
|
|
# Author : MacRimi
|
|
# Copyright : (c) 2024 MacRimi
|
|
# License : GPL-3.0
|
|
# https://github.com/MacRimi/ProxMenux/blob/main/LICENSE
|
|
# Version : 1.4
|
|
# ==========================================================
|
|
# Description:
|
|
# Interactive post-installation configurator for Proxmox VE.
|
|
# Groups ~30 optimizations into 10 categories (Basic Settings,
|
|
# System, Virtualization, Network, Storage, Security,
|
|
# Customization, Monitoring, Performance, Optional) and presents
|
|
# a checklist per category so the user picks exactly what to
|
|
# apply. Reversible changes are registered in installed_tools.json
|
|
# for later restoration from Uninstall Optimizations; package upgrades
|
|
# are intentionally excluded because they cannot be rolled back safely.
|
|
#
|
|
# Features:
|
|
# - Checklist UI per category (10 categories, ~30 tools total).
|
|
# - Superset of the Automated script: includes the 13 baseline
|
|
# optimizations plus opt-in items (IOMMU/VFIO, Fastfetch,
|
|
# Figurine, Ceph repo, HA, AMD fixes, pigz, ZFS ARC, …).
|
|
# - Idempotent: safe to run repeatedly.
|
|
# - Registration + rollback: every registered tool has a reverse
|
|
# function in uninstall-tools.sh.
|
|
#
|
|
# Credits:
|
|
# Incorporates ideas and snippets originally published under BSD
|
|
# by Adrian Jon Kriel (eXtremeSHOK) in xshok-proxmox, and elements
|
|
# of the Proxmox VE Post Install script from the Proxmox VE
|
|
# Helper-Scripts Community (MIT).
|
|
# https://github.com/extremeshok/xshok-proxmox
|
|
# https://github.com/community-scripts/ProxmoxVE
|
|
# ==========================================================
|
|
|
|
|
|
# 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"
|
|
|
|
if [[ -f "$UTILS_FILE" ]]; then
|
|
source "$UTILS_FILE"
|
|
fi
|
|
|
|
load_language
|
|
initialize_cache
|
|
|
|
# Load shared global functions
|
|
if [[ -f "$LOCAL_SCRIPTS/global/common-functions.sh" ]]; then
|
|
source "$LOCAL_SCRIPTS/global/common-functions.sh"
|
|
fi
|
|
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
|
|
# ==========================================================
|
|
|
|
|
|
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
|
|
SCRIPT_TITLE="Customizable post-installation optimization script"
|
|
|
|
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
|
|
|
|
# Sprint 12A: customizable_post_install.sh always emits source=custom so
|
|
# the update detector knows to re-run the customizable flow (which asks
|
|
# the user for parameters again) instead of the silent auto flow.
|
|
SCRIPT_SOURCE="custom"
|
|
|
|
ensure_tools_json() {
|
|
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
|
|
}
|
|
|
|
# Sprint 12A: register_tool accepts (key, state, version, source).
|
|
# Each function declares `local FUNC_VERSION="X.Y"` on its first line and
|
|
# passes "$FUNC_VERSION" here. We use `local` rather than a `# version:`
|
|
# comment because bash's `declare -f` strips comments — comments would be
|
|
# silently lost when the update wrapper sourced the script and re-ran a
|
|
# function, leaving the registered version stuck at the default. See
|
|
# auto_post_install.sh for the full contract.
|
|
register_tool() {
|
|
local tool="$1"
|
|
local state="$2"
|
|
local version="${3:-1.0}"
|
|
local source="${4:-${SCRIPT_SOURCE:-unknown}}"
|
|
# Recorded here rather than in each function: this is the one call the
|
|
# whole of post-install already makes, so every applied tool reaches
|
|
# the journal even where the function itself still writes directly.
|
|
# Such an entry says what was applied and admits it cannot say what
|
|
# changed, which is the honest account for anything not yet migrated.
|
|
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
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
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")"
|
|
}
|
|
|
|
|
|
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
|
|
}
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
enable_kexec() {
|
|
local FUNC_VERSION="1.1"
|
|
pmx_journal_context "enable_kexec" "$FUNC_VERSION"
|
|
# description: Install kexec-tools and add a Ctrl+Alt+K hotkey + systemd unit for fast reboots that skip BIOS/POST.
|
|
msg_info2 "$(translate "Configuring kexec for quick reboots...")"
|
|
NECESSARY_REBOOT=1
|
|
|
|
# Set default answers for debconf
|
|
pmx_apply_setting "kexec-tools/load_kexec" \
|
|
"debconf-show kexec-tools 2>/dev/null | grep -E '^[* ]*kexec-tools/load_kexec:'" \
|
|
bash -c 'echo "kexec-tools kexec-tools/load_kexec boolean false" | debconf-set-selections'
|
|
|
|
msg_info "$(translate "Installing kexec-tools...")"
|
|
# Install kexec-tools without showing output
|
|
if ! dpkg -s kexec-tools >/dev/null 2>&1; then
|
|
pmx_install_pkg kexec-tools
|
|
msg_ok "$(translate "kexec-tools installed successfully")"
|
|
else
|
|
msg_ok "$(translate "kexec-tools is already installed")"
|
|
fi
|
|
|
|
# Create systemd service file
|
|
local service_file="/etc/systemd/system/kexec-pve.service"
|
|
if [ ! -f "$service_file" ]; then
|
|
pmx_write_file "$service_file" <<'EOF'
|
|
[Unit]
|
|
Description=Loading new kernel into memory
|
|
Documentation=man:kexec(8)
|
|
DefaultDependencies=no
|
|
Before=reboot.target
|
|
RequiresMountsFor=/boot
|
|
#Before=shutdown.target umount.target final.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
RemainAfterExit=yes
|
|
ExecStart=/sbin/kexec -d -l /boot/pve/vmlinuz --initrd=/boot/pve/initrd.img --reuse-cmdline
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
EOF
|
|
msg_ok "$(translate "kexec-pve service file created")"
|
|
else
|
|
msg_ok "$(translate "kexec-pve service file is already configured")"
|
|
fi
|
|
|
|
# Enable the service
|
|
if ! systemctl is-enabled kexec-pve.service > /dev/null 2>&1; then
|
|
pmx_apply_setting "kexec-pve.service enablement" \
|
|
"systemctl is-enabled kexec-pve.service 2>/dev/null" \
|
|
systemctl enable kexec-pve.service
|
|
msg_ok "$(translate "kexec-pve service enabled")"
|
|
else
|
|
msg_ok "$(translate "kexec-pve service is already enabled")"
|
|
fi
|
|
|
|
if [ ! -f /root/.bash_profile ]; then
|
|
pmx_write_file /root/.bash_profile < /dev/null
|
|
fi
|
|
|
|
if ! grep -q "alias reboot-quick='systemctl kexec'" /root/.bash_profile; then
|
|
echo "alias reboot-quick='systemctl kexec'" | pmx_append_file /root/.bash_profile
|
|
msg_ok "$(translate "reboot-quick alias added")"
|
|
else
|
|
msg_ok "$(translate "reboot-quick alias is already configured")"
|
|
fi
|
|
|
|
msg_success "$(translate "kexec configured successfully. Use the command: reboot-quick")"
|
|
register_tool "kexec" true "$FUNC_VERSION"
|
|
}
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
msg_success "$(translate "Proxmox repository configuration completed")"
|
|
}
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
optimize_journald() {
|
|
local FUNC_VERSION="1.0"
|
|
# description: Cap journald size, raise rate limit and force info-level logging so the log viewer and Fail2Ban work.
|
|
msg_info2 "$(translate "Limiting size and optimizing journald")"
|
|
NECESSARY_REBOOT=1
|
|
local journald_conf="/etc/systemd/journald.conf"
|
|
local config_changed=false
|
|
|
|
msg_info "$(translate "Configuring journald...")"
|
|
|
|
# Create a temporary configuration
|
|
cat <<EOF > /tmp/journald.conf.new
|
|
[Journal]
|
|
# Store on disk
|
|
Storage=persistent
|
|
# Don't split Journald logs by user
|
|
SplitMode=none
|
|
# Reasonable rate limits (evita spam en kernel/auditd)
|
|
RateLimitIntervalSec=30s
|
|
RateLimitBurst=1000
|
|
# Disable Journald forwarding to syslog
|
|
ForwardToSyslog=no
|
|
# Don't forward to wall (para evitar mensajes en terminales)
|
|
ForwardToWall=no
|
|
# Disable signing of the logs, save cpu resources
|
|
Seal=no
|
|
Compress=yes
|
|
# Fix the log size
|
|
SystemMaxUse=64M
|
|
RuntimeMaxUse=60M
|
|
# Optimize the logging and speed up tasks
|
|
# 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
|
|
|
|
# This function already declines to write when nothing differs; the
|
|
# journal replaces the move so the audit sees what was replaced.
|
|
if ! cmp -s "$journald_conf" "/tmp/journald.conf.new"; then
|
|
pmx_journal_context "optimize_journald" "$FUNC_VERSION"
|
|
pmx_write_file "$journald_conf" < /tmp/journald.conf.new
|
|
rm -f "/tmp/journald.conf.new"
|
|
config_changed=true
|
|
else
|
|
rm "/tmp/journald.conf.new"
|
|
fi
|
|
|
|
if [ "$config_changed" = true ]; then
|
|
systemctl restart systemd-journald.service > /dev/null 2>&1
|
|
msg_ok "$(translate "Journald configuration updated and service restarted")"
|
|
else
|
|
msg_ok "$(translate "Journald configuration is already optimized")"
|
|
fi
|
|
|
|
# Clean and rotate logs
|
|
journalctl --vacuum-size=64M --vacuum-time=1d > /dev/null 2>&1
|
|
journalctl --rotate > /dev/null 2>&1
|
|
|
|
msg_success "$(translate "Journald optimization completed")"
|
|
register_tool "journald" true "$FUNC_VERSION"
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
configure_kernel_panic() {
|
|
local FUNC_VERSION="1.0"
|
|
# description: Auto-reboot on kernel panic / oops / hardlockup; write crash dumps to /var/crash.
|
|
msg_info2 "$(translate "Configuring kernel panic behavior")"
|
|
NECESSARY_REBOOT=1
|
|
|
|
local config_file="/etc/sysctl.d/99-kernelpanic.conf"
|
|
|
|
msg_info "$(translate "Updating kernel panic configuration...")"
|
|
|
|
# Written through the journal, so the audit can show what this
|
|
# replaced — or that the file did not exist before.
|
|
pmx_journal_context "configure_kernel_panic" "$FUNC_VERSION"
|
|
pmx_write_file "$config_file" <<EOF
|
|
# Enable restart on kernel panic, kernel oops and hardlockup
|
|
kernel.core_pattern = /var/crash/core.%t.%p
|
|
# Reboot on kernel panic after 10s
|
|
kernel.panic = 10
|
|
# Panic on kernel oops, kernel exploits generally create an oops
|
|
kernel.panic_on_oops = 1
|
|
# Panic on a hardlockup
|
|
kernel.hardlockup_panic = 1
|
|
EOF
|
|
|
|
|
|
msg_ok "$(translate "Kernel panic configuration updated and applied")"
|
|
register_tool "kernel_panic" true "$FUNC_VERSION"
|
|
msg_success "$(translate "Kernel panic behavior configuration completed")"
|
|
}
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
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_info2 "$(translate "Increasing various system limits...")"
|
|
NECESSARY_REBOOT=1
|
|
|
|
# Function to safely append or replace configuration
|
|
append_or_replace() {
|
|
local file="$1"
|
|
local content="$2"
|
|
local temp_file=$(mktemp)
|
|
|
|
if [ -f "$file" ]; then
|
|
grep -vF "# ProxMenux configuration" "$file" > "$temp_file"
|
|
fi
|
|
echo -e "# ProxMenux configuration\n$content" >> "$temp_file"
|
|
pmx_write_file "$file" < "$temp_file"
|
|
rm -f "$temp_file"
|
|
}
|
|
|
|
# Increase max user watches
|
|
msg_info "$(translate "Configuring max user watches...")"
|
|
append_or_replace "/etc/sysctl.d/99-maxwatches.conf" "
|
|
fs.inotify.max_user_watches = 1048576
|
|
fs.inotify.max_user_instances = 1048576
|
|
fs.inotify.max_queued_events = 1048576"
|
|
msg_ok "$(translate "Max user watches configured")"
|
|
|
|
# Increase max FD limit / ulimit
|
|
msg_info "$(translate "Configuring max FD limit / ulimit...")"
|
|
append_or_replace "/etc/security/limits.d/99-limits.conf" "
|
|
* 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"
|
|
msg_ok "$(translate "Max FD limit / ulimit configured")"
|
|
|
|
# Increase kernel max Key limit
|
|
msg_info "$(translate "Configuring kernel max Key limit...")"
|
|
append_or_replace "/etc/sysctl.d/99-maxkeys.conf" "
|
|
kernel.keys.root_maxkeys=1000000
|
|
kernel.keys.maxkeys=1000000"
|
|
msg_ok "$(translate "Kernel max Key limit configured")"
|
|
|
|
# Set systemd ulimits
|
|
msg_info "$(translate "Setting systemd ulimits...")"
|
|
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
|
|
msg_ok "$(translate "Systemd ulimits set")"
|
|
|
|
# Configure PAM limits
|
|
msg_info "$(translate "Configuring PAM limits...")"
|
|
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
|
|
msg_ok "$(translate "PAM limits configured")"
|
|
|
|
# Set ulimit for the shell user
|
|
msg_info "$(translate "Setting ulimit for the shell user...")"
|
|
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
|
|
msg_ok "$(translate "Shell user ulimit set")"
|
|
|
|
# Configure swappiness
|
|
msg_info "$(translate "Configuring kernel swappiness...")"
|
|
append_or_replace "/etc/sysctl.d/99-swap.conf" "
|
|
vm.swappiness = 10
|
|
vm.vfs_cache_pressure = 100"
|
|
msg_ok "$(translate "Swappiness configuration created successfully")"
|
|
|
|
# Increase Max FS open files
|
|
msg_info "$(translate "Increasing maximum file system open files...")"
|
|
append_or_replace "/etc/sysctl.d/99-fs.conf" "
|
|
fs.nr_open = 2097152
|
|
fs.file-max = 2097152
|
|
fs.aio-max-nr = 1048576"
|
|
|
|
msg_ok "$(translate "Max FS open files configuration created successfully")"
|
|
register_tool "system_limits" true "$FUNC_VERSION"
|
|
msg_success "$(translate "System limits increase completed.")"
|
|
}
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
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_info2 "$(translate "Configuring APT to skip downloading additional languages")"
|
|
|
|
# 1. Detect locale
|
|
local default_locale=""
|
|
if [ -f /etc/default/locale ]; then
|
|
default_locale=$(grep '^LANG=' /etc/default/locale | cut -d= -f2 | tr -d '"')
|
|
elif [ -f /etc/environment ]; then
|
|
default_locale=$(grep '^LANG=' /etc/environment | cut -d= -f2 | tr -d '"')
|
|
fi
|
|
|
|
# Fallback
|
|
default_locale="${default_locale:-en_US.UTF-8}"
|
|
|
|
# Normalize for comparison (en_US.UTF-8 → en_US.utf8)
|
|
local normalized_locale
|
|
normalized_locale=$(echo "$default_locale" | tr 'A-Z' 'a-z' | sed 's/utf-8/utf8/;s/-/_/')
|
|
|
|
# 2. Only generate if missing
|
|
if ! locale -a | grep -qi "^$normalized_locale$"; then
|
|
# Only add to locale.gen if missing
|
|
if ! grep -qE "^${default_locale}[[:space:]]+UTF-8" /etc/locale.gen; then
|
|
echo "$default_locale UTF-8" | pmx_append_file /etc/locale.gen
|
|
fi
|
|
msg_info "$(translate "Generating missing locale:") $default_locale"
|
|
pmx_record_execution "Generate locale" "locale-gen $default_locale"
|
|
locale-gen "$default_locale"
|
|
msg_ok "$(translate "Locale generated")"
|
|
fi
|
|
|
|
# 3. Set APT to skip language downloads
|
|
local config_file="/etc/apt/apt.conf.d/99-disable-translations"
|
|
local config_content='Acquire::Languages "none";'
|
|
|
|
msg_info "$(translate "Setting APT language configuration...")"
|
|
if [ -f "$config_file" ] && grep -Fxq "$config_content" "$config_file"; then
|
|
msg_ok "$(translate "APT language configuration already set")"
|
|
else
|
|
printf '%s\n' "$config_content" | pmx_write_file "$config_file"
|
|
msg_ok "$(translate "APT language configuration updated")"
|
|
fi
|
|
|
|
register_tool "apt_languages" true "$FUNC_VERSION"
|
|
msg_success "$(translate "APT configured to skip downloading additional languages")"
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
configure_time_sync() {
|
|
local FUNC_VERSION="1.0"
|
|
pmx_journal_context "configure_time_sync" "$FUNC_VERSION"
|
|
# 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")"
|
|
|
|
pmx_apply_setting "timezone" "timedatectl show -p Timezone --value" \
|
|
timedatectl set-timezone "$timezone"
|
|
if timedatectl set-timezone "$timezone"; then
|
|
msg_ok "$(translate "Timezone set to $timezone")"
|
|
|
|
pmx_apply_setting "ntp" "timedatectl show -p NTP --value" \
|
|
timedatectl set-ntp true
|
|
if timedatectl set-ntp true; then
|
|
msg_ok "$(translate "Time settings configured - Timezone:") $timezone"
|
|
register_tool "time_sync" true "$FUNC_VERSION"
|
|
|
|
pmx_record_execution "Restart Postfix" "systemctl restart postfix"
|
|
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
|
|
}
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
# configure_entropy removed — modern kernels (5.6+) have built-in entropy generation
|
|
# haveged is no longer needed and adds unnecessary overhead
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
apply_amd_fixes() {
|
|
local FUNC_VERSION="1.0"
|
|
pmx_journal_context "apply_amd_fixes" "$FUNC_VERSION"
|
|
# description: Detect AMD EPYC/Ryzen CPUs and apply microcode + IOMMU + KVM-specific kernel boot params.
|
|
msg_info2 "$(translate "Detecting AMD CPU and applying fixes if necessary...")"
|
|
NECESSARY_REBOOT=1
|
|
|
|
local cpu_model
|
|
cpu_model=$(grep -i -m 1 "model name" /proc/cpuinfo || true)
|
|
|
|
if echo "$cpu_model" | grep -qiE "EPYC|Ryzen"; then
|
|
msg_ok "$(translate "AMD CPU detected")"
|
|
else
|
|
msg_ok "$(translate "No AMD CPU detected. Skipping AMD fixes.")"
|
|
return 0
|
|
fi
|
|
|
|
msg_info "$(translate "Applying AMD-specific fixes...")"
|
|
|
|
|
|
local cmdline_file="/etc/kernel/cmdline"
|
|
local grub_file="/etc/default/grub"
|
|
local added_param="idle=nomwait"
|
|
local uses_zfs=false
|
|
|
|
if grep -q "root=ZFS=" "$cmdline_file" 2>/dev/null; then
|
|
uses_zfs=true
|
|
fi
|
|
|
|
if $uses_zfs && [[ -f "$cmdline_file" ]]; then
|
|
# ZFS/systemd-boot
|
|
if ! grep -qw "$added_param" "$cmdline_file"; then
|
|
cp "$cmdline_file" "${cmdline_file}.bak"
|
|
|
|
pmx_edit_file "$cmdline_file" "s|\s*$| $added_param|"
|
|
msg_ok "$(translate "Added '$added_param' to /etc/kernel/cmdline")"
|
|
else
|
|
msg_ok "$(translate "'$added_param' already present in /etc/kernel/cmdline")"
|
|
fi
|
|
|
|
if command -v proxmox-boot-tool >/dev/null 2>&1; then
|
|
pmx_record_execution "Refresh Proxmox boot configuration" "proxmox-boot-tool refresh"
|
|
proxmox-boot-tool refresh >/dev/null 2>&1 && \
|
|
msg_ok "$(translate "proxmox-boot-tool refreshed")" || \
|
|
msg_warn "$(translate "Failed to refresh proxmox-boot-tool")"
|
|
fi
|
|
else
|
|
# GRUB (no ZFS)
|
|
if [[ -f "$grub_file" ]]; then
|
|
|
|
grep -q '^GRUB_CMDLINE_LINUX_DEFAULT="' "$grub_file" || echo 'GRUB_CMDLINE_LINUX_DEFAULT=""' | pmx_append_file "$grub_file"
|
|
|
|
if ! grep -q 'GRUB_CMDLINE_LINUX_DEFAULT=' "$grub_file"; then
|
|
msg_warn "$(translate "GRUB_CMDLINE_LINUX_DEFAULT not found in GRUB config")"
|
|
else
|
|
if ! grep -q "GRUB_CMDLINE_LINUX_DEFAULT=.*\b$added_param\b" "$grub_file"; then
|
|
cp "$grub_file" "${grub_file}.bak"
|
|
pmx_edit_file "$grub_file" "s/^\(GRUB_CMDLINE_LINUX_DEFAULT=\"[^\"]*\)\"/\1 $added_param\"/"
|
|
msg_ok "$(translate "Added '$added_param' to GRUB_CMDLINE_LINUX_DEFAULT")"
|
|
else
|
|
msg_ok "$(translate "'$added_param' already present in GRUB_CMDLINE_LINUX_DEFAULT")"
|
|
fi
|
|
pmx_record_execution "Regenerate GRUB configuration" "update-grub"
|
|
update-grub >/dev/null 2>&1 && \
|
|
msg_ok "$(translate "GRUB configuration updated")" || \
|
|
msg_warn "$(translate "Failed to update GRUB")"
|
|
fi
|
|
else
|
|
msg_warn "$(translate "GRUB config file not found; skipping GRUB changes")"
|
|
fi
|
|
fi
|
|
|
|
|
|
local kvm_conf="/etc/modprobe.d/kvm.conf"
|
|
[[ -f "$kvm_conf" ]] || pmx_write_file "$kvm_conf" < /dev/null
|
|
|
|
if ! grep -q "^options kvm " "$kvm_conf"; then
|
|
echo "options kvm ignore_msrs=Y report_ignored_msrs=N" | pmx_append_file "$kvm_conf"
|
|
msg_ok "$(translate "KVM MSR options added to /etc/modprobe.d/kvm.conf")"
|
|
else
|
|
|
|
if ! grep -q "ignore_msrs=" "$kvm_conf"; then
|
|
pmx_edit_file "$kvm_conf" 's/^options kvm /options kvm ignore_msrs=Y /'
|
|
else
|
|
pmx_edit_file "$kvm_conf" 's/ignore_msrs=[YNyn]/ignore_msrs=Y/'
|
|
fi
|
|
if ! grep -q "report_ignored_msrs=" "$kvm_conf"; then
|
|
pmx_edit_file "$kvm_conf" 's/^options kvm .*/& report_ignored_msrs=N/'
|
|
else
|
|
pmx_edit_file "$kvm_conf" 's/report_ignored_msrs=[YNyn]/report_ignored_msrs=N/'
|
|
fi
|
|
msg_ok "$(translate "KVM MSR options ensured in /etc/modprobe.d/kvm.conf")"
|
|
fi
|
|
|
|
msg_success "$(translate "AMD CPU fixes applied successfully")"
|
|
register_tool "amd_fixes" 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_info2 "$(translate "Configuring APT to use IPv4...")"
|
|
|
|
local config_file="/etc/apt/apt.conf.d/99-force-ipv4"
|
|
local config_content="Acquire::ForceIPv4 \"true\";"
|
|
|
|
if [ -f "$config_file" ] && grep -q "$config_content" "$config_file"; then
|
|
msg_ok "$(translate "APT configured to use IPv4")"
|
|
else
|
|
msg_info "$(translate "Creating APT configuration to force IPv4...")"
|
|
if echo -e "$config_content\n" > "$config_file"; then
|
|
msg_ok "$(translate "APT configured to use IPv4")"
|
|
fi
|
|
fi
|
|
|
|
register_tool "apt_ipv4" true "$FUNC_VERSION"
|
|
msg_success "$(translate "APT IPv4 configuration completed")"
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
apply_network_optimizations() {
|
|
local FUNC_VERSION="1.2"
|
|
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
|
|
|
|
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=2 (loose) instead of the kernel default 1 (strict). Loose
|
|
# allows asymmetric routing typical of a Proxmox host with VMs on
|
|
# multiple bridges — strict mode would drop legitimate VM traffic when
|
|
# the reverse path differs. Trade-off: slightly weaker spoofing
|
|
# protection from local LAN. Document this choice rather than the
|
|
# default.
|
|
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
|
|
# Wider ephemeral port range than Debian's default 32768-60999 so
|
|
# Proxmox hosts handing out NAT/forward ports for many VMs/CTs don't
|
|
# run out under load. Privileged-port range (1-1023) is still protected
|
|
# by capabilities — the kernel won't actually pick one for an
|
|
# unprivileged ephemeral allocation.
|
|
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_remove_file /etc/udev/rules.d/99-proxmenux-fwbr-tune.rules
|
|
|
|
pmx_write_file /etc/udev/rules.d/99-zz-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-zz-proxmenux-fwbr-tune.rules
|
|
chown root:root /etc/udev/rules.d/99-zz-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"
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
install_openvswitch() {
|
|
local FUNC_VERSION="1.0"
|
|
# description: Install OpenVSwitch for software-defined networking inside VMs and containers.
|
|
msg_info2 "$(translate "Installing OpenVSwitch for virtual internal network...")"
|
|
|
|
|
|
|
|
# Install OpenVSwitch
|
|
msg_info "$(translate "Installing OpenVSwitch packages...")"
|
|
(
|
|
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install openvswitch-switch openvswitch-common 2>&1 | \
|
|
while IFS= read -r line; do
|
|
if [[ $line == *"Installing"* ]] || [[ $line == *"Unpacking"* ]]; then
|
|
printf "\r%-$(($(tput cols)-1))s\r" " " # Clear current line
|
|
printf "\r%s" "$line"
|
|
fi
|
|
done
|
|
)
|
|
|
|
if [ $? -eq 0 ]; then
|
|
printf "\r%-$(($(tput cols)-1))s\r" " " # Clear final line
|
|
msg_ok "$(translate "OpenVSwitch installed successfully")"
|
|
else
|
|
printf "\r%-$(($(tput cols)-1))s\r" " " # Clear final line
|
|
msg_warn "$(translate "Failed to install OpenVSwitch")"
|
|
fi
|
|
|
|
# Verify installation
|
|
if command -v ovs-vsctl >/dev/null 2>&1; then
|
|
msg_success "$(translate "OpenVSwitch is ready to use")"
|
|
register_tool "openvswitch" true "$FUNC_VERSION"
|
|
else
|
|
msg_warn "$(translate "OpenVSwitch installation could not be verified")"
|
|
fi
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
enable_tcp_fast_open() {
|
|
local FUNC_VERSION="1.0"
|
|
pmx_journal_context "enable_tcp_fast_open" "$FUNC_VERSION"
|
|
# description: Enable TCP Fast Open (clients + server) and BBR congestion control for better latency under load.
|
|
msg_info2 "$(translate "Configuring TCP optimizations...")"
|
|
|
|
local bbr_conf="/etc/sysctl.d/99-kernel-bbr.conf"
|
|
local tfo_conf="/etc/sysctl.d/99-tcp-fastopen.conf"
|
|
local reboot_needed=0
|
|
|
|
# Enable Google TCP BBR congestion control
|
|
msg_info "$(translate "Enabling Google TCP BBR congestion control...")"
|
|
if [ ! -f "$bbr_conf" ] || ! grep -q "net.ipv4.tcp_congestion_control = bbr" "$bbr_conf"; then
|
|
pmx_write_file "$bbr_conf" <<EOF
|
|
# TCP BBR congestion control
|
|
net.core.default_qdisc = fq
|
|
net.ipv4.tcp_congestion_control = bbr
|
|
EOF
|
|
msg_ok "$(translate "TCP BBR configuration created successfully")"
|
|
reboot_needed=1
|
|
else
|
|
msg_ok "$(translate "TCP BBR configuration created successfully")"
|
|
fi
|
|
|
|
# Enable TCP Fast Open
|
|
msg_info "$(translate "Enabling TCP Fast Open...")"
|
|
if [ ! -f "$tfo_conf" ] || ! grep -q "net.ipv4.tcp_fastopen = 3" "$tfo_conf"; then
|
|
pmx_write_file "$tfo_conf" <<EOF
|
|
# TCP Fast Open (TFO)
|
|
net.ipv4.tcp_fastopen = 3
|
|
EOF
|
|
msg_ok "$(translate "TCP Fast Open configuration created successfully")"
|
|
else
|
|
msg_ok "$(translate "TCP Fast Open configuration created successfully")"
|
|
fi
|
|
|
|
# Apply changes
|
|
pmx_record_execution "Apply sysctl configuration" "sysctl --system"
|
|
sysctl --system > /dev/null 2>&1
|
|
|
|
if [ "$reboot_needed" -eq 1 ]; then
|
|
NECESSARY_REBOOT=1
|
|
fi
|
|
|
|
msg_success "$(translate "TCP optimizations configuration completed")"
|
|
register_tool "tcp_optimizations" true "$FUNC_VERSION"
|
|
}
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
install_ceph() {
|
|
local FUNC_VERSION="1.1"
|
|
pmx_journal_context "install_ceph" "$FUNC_VERSION"
|
|
# description: Install Ceph (client + server packages) for distributed RBD/CephFS storage; PVE 8/9 aware repo selection.
|
|
msg_info2 "$(translate "Installing Ceph support...")"
|
|
|
|
|
|
local pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
|
|
local current_codename=$(lsb_release -cs 2>/dev/null || echo "bookworm")
|
|
local is_pve9=false
|
|
local ceph_version="squid"
|
|
local target_codename="bookworm"
|
|
|
|
|
|
if [ "$pve_version" -ge 9 ] 2>/dev/null || [ "$current_codename" = "trixie" ]; then
|
|
is_pve9=true
|
|
target_codename="trixie"
|
|
ceph_version="squid"
|
|
msg_info2 "$(translate "Detected Proxmox VE 9.x - Installing Ceph Squid for Debian Trixie")"
|
|
else
|
|
target_codename="$current_codename"
|
|
ceph_version="squid"
|
|
msg_info2 "$(translate "Detected Proxmox VE 8.x - Installing Ceph Squid for Debian") $target_codename"
|
|
fi
|
|
|
|
if pveceph status &>/dev/null; then
|
|
msg_ok "$(translate "Ceph is already installed")"
|
|
msg_success "$(translate "Ceph installation check completed")"
|
|
return 0
|
|
fi
|
|
|
|
if [[ ! -r /usr/share/keyrings/proxmox-archive-keyring.gpg ]]; then
|
|
msg_error "$(translate "The Proxmox archive keyring is missing; Ceph installation cannot continue safely")"
|
|
return 1
|
|
fi
|
|
|
|
# Configure Ceph repository based on version
|
|
msg_info "$(translate "Configuring Ceph repository for PVE") $pve_version..."
|
|
|
|
if [ "$is_pve9" = true ]; then
|
|
# ==========================================
|
|
# CEPH CONFIGURATION FOR PROXMOX VE 9
|
|
# ==========================================
|
|
|
|
|
|
[ -f /etc/apt/sources.list.d/ceph-squid.list ] && pmx_remove_file /etc/apt/sources.list.d/ceph-squid.list
|
|
[ -f /etc/apt/sources.list.d/ceph.list ] && pmx_remove_file /etc/apt/sources.list.d/ceph.list
|
|
|
|
# Create new deb822 format Ceph repository for PVE 9
|
|
msg_info "$(translate "Creating Ceph repository for PVE 9 (deb822 format)...")"
|
|
pmx_write_file /etc/apt/sources.list.d/ceph.sources << EOF
|
|
Types: deb
|
|
URIs: https://download.proxmox.com/debian/ceph-${ceph_version}
|
|
Suites: ${target_codename}
|
|
Components: no-subscription
|
|
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
|
|
EOF
|
|
chmod 0644 /etc/apt/sources.list.d/ceph.sources
|
|
msg_ok "$(translate "Ceph repository configured for PVE 9")"
|
|
|
|
else
|
|
# ==========================================
|
|
# CEPH CONFIGURATION FOR PROXMOX VE 8
|
|
# ==========================================
|
|
|
|
# Use legacy format for PVE 8
|
|
msg_info "$(translate "Creating Ceph repository for PVE 8 (legacy format)...")"
|
|
echo "deb [signed-by=/usr/share/keyrings/proxmox-archive-keyring.gpg] https://download.proxmox.com/debian/ceph-${ceph_version} ${target_codename} no-subscription" | pmx_write_file /etc/apt/sources.list.d/ceph-${ceph_version}.list
|
|
msg_ok "$(translate "Ceph repository configured for PVE 8")"
|
|
fi
|
|
|
|
|
|
msg_info "$(translate "Updating package lists...")"
|
|
|
|
pmx_record_execution "Update package lists for Ceph" "apt-get update"
|
|
update_output=$(apt-get update 2>&1)
|
|
update_exit_code=$?
|
|
|
|
if [ $update_exit_code -eq 0 ]; then
|
|
msg_ok "$(translate "Package lists updated successfully")"
|
|
else
|
|
msg_warn "$(translate "Package update had issues, checking details...")"
|
|
|
|
|
|
if echo "$update_output" | grep -Eqi 'NO_PUBKEY|GPG error|EXPKEYSIG|BADSIG|not signed|signatures? (could not|couldn.t) be verified'; then
|
|
msg_error "$(translate "Ceph repository signature verification failed; installation has been stopped")"
|
|
return 1
|
|
elif echo "$update_output" | grep -q "404\|Failed to fetch"; then
|
|
msg_warn "$(translate "Some repositories are not available, continuing with available ones...")"
|
|
else
|
|
msg_warn "$(translate "Package update completed with warnings, continuing...")"
|
|
fi
|
|
fi
|
|
|
|
|
|
msg_info "$(translate "Verifying Ceph packages availability...")"
|
|
if apt-cache search ceph-common | grep -q "ceph-common"; then
|
|
msg_ok "$(translate "Ceph packages are available")"
|
|
else
|
|
msg_warn "$(translate "Ceph packages may not be available, but continuing installation...")"
|
|
fi
|
|
|
|
|
|
|
|
tput civis
|
|
tput sc
|
|
|
|
pmx_record_execution "Install Ceph packages" "pveceph install"
|
|
(pveceph install 2>&1 | \
|
|
while IFS= read -r line; do
|
|
if [[ $line == *"Installing"* ]] || [[ $line == *"Unpacking"* ]] || [[ $line == *"Setting up"* ]] || [[ $line == *"Processing"* ]]; then
|
|
|
|
package_name=$(echo "$line" | sed -E 's/.*(Installing|Unpacking|Setting up|Processing) ([^ ]+).*/\2/' | head -c 30)
|
|
[ -z "$package_name" ] && package_name="$(translate "Ceph components")"
|
|
|
|
tput rc
|
|
tput ed
|
|
row=$(( $(tput lines) - 4 ))
|
|
tput cup $row 0; echo "$(translate "Installing Ceph packages...")"
|
|
tput cup $((row + 1)) 0; echo "──────────────────────────────────────────────"
|
|
tput cup $((row + 2)) 0; echo "$(translate "Current"): $package_name"
|
|
tput cup $((row + 3)) 0; echo "──────────────────────────────────────────────"
|
|
fi
|
|
done)
|
|
|
|
ceph_install_exit_code=$?
|
|
tput rc
|
|
tput ed
|
|
tput cnorm
|
|
|
|
|
|
msg_info "$(translate "Verifying Ceph installation...")"
|
|
|
|
|
|
sleep 3
|
|
|
|
if pveceph status &>/dev/null; then
|
|
msg_ok "$(translate "Ceph packages installed and verified successfully")"
|
|
|
|
local ceph_version_info=$(ceph --version 2>/dev/null | head -1 || echo "$(translate "Version info not available")")
|
|
msg_ok "$(translate "Installed"): $ceph_version_info"
|
|
|
|
|
|
if [ "$is_pve9" = true ]; then
|
|
if pveceph pool ls &>/dev/null 2>&1 || [ $? -eq 2 ]; then
|
|
msg_ok "$(translate "Ceph integration with PVE 9 verified")"
|
|
else
|
|
msg_warn "$(translate "Ceph installed but integration may need configuration")"
|
|
fi
|
|
msg_success "$(translate "Ceph installation completed successfully")"
|
|
fi
|
|
|
|
|
|
|
|
elif command -v ceph >/dev/null 2>&1; then
|
|
|
|
msg_warn "$(translate "Ceph packages installed but service verification failed")"
|
|
msg_info2 "$(translate "This may be normal for a fresh installation")"
|
|
msg_success "$(translate "Ceph installation process completed")"
|
|
|
|
else
|
|
msg_warn "$(translate "Ceph installation could not be verified")"
|
|
msg_info2 "$(translate "You may need to run 'pveceph install' manually")"
|
|
msg_success "$(translate "Ceph installation process finished with warnings")"
|
|
fi
|
|
|
|
# Track install in the registry so the Uninstall menu can offer
|
|
# `apt purge ceph-*` + repo removal. Audit Tier 6 — `install_ceph` /
|
|
# `enable_ha` sin `register_tool` ni uninstall.
|
|
register_tool "ceph" true "$FUNC_VERSION"
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
_reconcile_external_zfs_arc_settings() {
|
|
local managed_conf="$1"
|
|
local backup_dir="$BASE_DIR/backups/zfs_arc"
|
|
local manifest="$backup_dir/manifest.tsv"
|
|
local conf_file backup_file tmp_file tmp_manifest post_hash
|
|
|
|
while IFS= read -r -d '' conf_file; do
|
|
[[ "$conf_file" == "$managed_conf" ]] && continue
|
|
|
|
if ! grep -Eq \
|
|
'^[[:space:]]*options[[:space:]]+zfs([[:space:]]|$).*zfs_arc_(min|max)=' \
|
|
"$conf_file" 2>/dev/null; then
|
|
continue
|
|
fi
|
|
|
|
msg_warn "$(translate "Conflicting ZFS ARC settings detected in:") $conf_file"
|
|
mkdir -p "$backup_dir" || return 1
|
|
touch "$manifest" || return 1
|
|
backup_file="$backup_dir/$(basename "$conf_file").before-proxmenux"
|
|
if [[ ! -f "$backup_file" ]]; then
|
|
cp -p "$conf_file" "$backup_file" || return 1
|
|
fi
|
|
|
|
tmp_file=$(mktemp "${conf_file}.proxmenux.XXXXXX") || return 1
|
|
cp -p "$conf_file" "$tmp_file" || {
|
|
rm -f "$tmp_file"
|
|
return 1
|
|
}
|
|
|
|
# Remove only zfs_arc_min/max tokens from active `options zfs`
|
|
# directives. Other ZFS module options and comments stay untouched.
|
|
awk '
|
|
/^[[:space:]]*options[[:space:]]+zfs([[:space:]]|$)/ {
|
|
line = $0
|
|
gsub(/[[:space:]]+zfs_arc_(min|max)=[^[:space:]#]+/, "", line)
|
|
if (line ~ /^[[:space:]]*options[[:space:]]+zfs[[:space:]]*$/) {
|
|
next
|
|
}
|
|
if (line ~ /^[[:space:]]*options[[:space:]]+zfs[[:space:]]*#/) {
|
|
sub(/^[[:space:]]*options[[:space:]]+zfs[[:space:]]*/, "", line)
|
|
}
|
|
print line
|
|
next
|
|
}
|
|
{ print }
|
|
' "$conf_file" > "$tmp_file" || {
|
|
rm -f "$tmp_file"
|
|
return 1
|
|
}
|
|
|
|
mv -f "$tmp_file" "$conf_file" || return 1
|
|
post_hash=$(sha256sum "$conf_file" | awk '{print $1}')
|
|
|
|
tmp_manifest=$(mktemp "${manifest}.XXXXXX") || return 1
|
|
awk -F '\t' -v path="$conf_file" '$1 != path' "$manifest" > "$tmp_manifest"
|
|
printf '%s\t%s\t%s\n' "$conf_file" "$backup_file" "$post_hash" >> "$tmp_manifest"
|
|
mv -f "$tmp_manifest" "$manifest" || return 1
|
|
|
|
msg_ok "$(translate "Conflicting ZFS ARC settings backed up and reconciled:") $conf_file"
|
|
done < <(find /etc/modprobe.d -maxdepth 1 -type f -name '*.conf' -print0 2>/dev/null)
|
|
}
|
|
|
|
|
|
optimize_zfs_arc() {
|
|
local FUNC_VERSION="1.3"
|
|
# description: Cap ZFS ARC max using Proxmox VE's 10%-of-RAM policy (16 GiB ceiling), safely reconcile conflicting module settings and report the pool-size guideline before applying it.
|
|
local zfs_conf="/etc/modprobe.d/99-zfsarc.conf"
|
|
local gib=$((1024 * 1024 * 1024))
|
|
local tib=$((1024 * 1024 * 1024 * 1024))
|
|
local ram_kib ram_bytes arc_max current_arc_max pool_bytes=0 pool_size
|
|
local pool_tib pool_guideline arc_max_human current_arc_max_human pool_guideline_human
|
|
|
|
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
|
|
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
|
|
|
|
ram_kib=$(awk '/MemTotal:/ { print $2; exit }' /proc/meminfo)
|
|
if [[ ! "$ram_kib" =~ ^[0-9]+$ || "$ram_kib" -le 0 ]]; then
|
|
msg_error "$(translate "Unable to determine the installed memory.")"
|
|
return 1
|
|
fi
|
|
ram_bytes=$((ram_kib * 1024))
|
|
|
|
arc_max=$((ram_bytes / 10))
|
|
(( arc_max > 16 * gib )) && arc_max=$((16 * gib))
|
|
(( arc_max < 64 * 1024 * 1024 )) && arc_max=$((64 * 1024 * 1024))
|
|
|
|
while read -r pool_size; do
|
|
[[ "$pool_size" =~ ^[0-9]+$ ]] || continue
|
|
pool_bytes=$((pool_bytes + pool_size))
|
|
done < <(zpool list -H -p -o size 2>/dev/null)
|
|
|
|
pool_tib=$(((pool_bytes + tib - 1) / tib))
|
|
pool_guideline=$((2 * gib + pool_tib * gib))
|
|
current_arc_max=$(awk '$1 == "c_max" { print $3; exit }' /proc/spl/kstat/zfs/arcstats 2>/dev/null || true)
|
|
if [[ ! "$current_arc_max" =~ ^[0-9]+$ ]]; then
|
|
current_arc_max=$(cat /sys/module/zfs/parameters/zfs_arc_max 2>/dev/null || true)
|
|
fi
|
|
|
|
if command -v numfmt >/dev/null 2>&1; then
|
|
arc_max_human=$(numfmt --to=iec-i --suffix=B "$arc_max")
|
|
pool_guideline_human=$(numfmt --to=iec-i --suffix=B "$pool_guideline")
|
|
if [[ "$current_arc_max" =~ ^[0-9]+$ ]]; then
|
|
current_arc_max_human=$(numfmt --to=iec-i --suffix=B "$current_arc_max")
|
|
fi
|
|
fi
|
|
arc_max_human=${arc_max_human:-"$arc_max bytes"}
|
|
pool_guideline_human=${pool_guideline_human:-"$pool_guideline bytes"}
|
|
current_arc_max_human=${current_arc_max_human:-${current_arc_max:-unknown}}
|
|
|
|
msg_info "$(translate "Current effective ZFS ARC maximum:") $current_arc_max_human"
|
|
msg_info "$(translate "Proposed ZFS ARC maximum:") $arc_max_human"
|
|
if (( arc_max < pool_guideline )); then
|
|
msg_warn "$(translate "The proposed ARC maximum is below Proxmox VE's pool-size guideline:") $pool_guideline_human"
|
|
msg_info2 "$(translate "Consider adding RAM or reducing the host workload if ZFS performance is insufficient.")"
|
|
fi
|
|
|
|
if ! _reconcile_external_zfs_arc_settings "$zfs_conf"; then
|
|
msg_error "$(translate "Failed to reconcile conflicting ZFS ARC settings.")"
|
|
return 1
|
|
fi
|
|
|
|
if [[ -f "$zfs_conf" && ! -f "${zfs_conf}.bak" ]]; then
|
|
cp -p "$zfs_conf" "${zfs_conf}.bak"
|
|
fi
|
|
|
|
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
|
|
|
|
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_human"
|
|
msg_success "$(translate "ZFS ARC optimization completed")"
|
|
register_tool "zfs_arc" 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
|
|
|
|
msg_success "$(translate "ZFS autotrim setup completed")"
|
|
}
|
|
|
|
|
|
install_zfs_auto_snapshot() {
|
|
local FUNC_VERSION="1.0"
|
|
# description: Install zfs-auto-snapshot with cron schedules for hourly/daily/weekly/monthly snapshots.
|
|
msg_info2 "$(translate "Installing and configuring ZFS auto-snapshot...")"
|
|
|
|
# Check if zfs-auto-snapshot is already installed
|
|
if command -v zfs-auto-snapshot >/dev/null 2>&1; then
|
|
msg_ok "$(translate "zfs-auto-snapshot is already installed")"
|
|
else
|
|
# Install zfs-auto-snapshot
|
|
msg_info "$(translate "Installing zfs-auto-snapshot package...")"
|
|
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install zfs-auto-snapshot > /dev/null 2>&1; then
|
|
msg_ok "$(translate "zfs-auto-snapshot installed successfully")"
|
|
else
|
|
msg_error "$(translate "Failed to install zfs-auto-snapshot")"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# Configure snapshot schedules
|
|
config_zfs_auto_snapshot
|
|
|
|
msg_success "$(translate "ZFS auto-snapshot installation and configuration completed")"
|
|
register_tool "zfs_auto_snapshot" true "$FUNC_VERSION"
|
|
}
|
|
|
|
config_zfs_auto_snapshot() {
|
|
msg_info "$(translate "Configuring snapshot schedules...")"
|
|
|
|
# Update 15-minute snapshots
|
|
update_snapshot_schedule "/etc/cron.d/zfs-auto-snapshot" "frequent" "4" "*/15"
|
|
|
|
# Update other snapshot schedules
|
|
update_snapshot_schedule "/etc/cron.hourly/zfs-auto-snapshot" "hourly" "1"
|
|
update_snapshot_schedule "/etc/cron.daily/zfs-auto-snapshot" "daily" "1"
|
|
update_snapshot_schedule "/etc/cron.weekly/zfs-auto-snapshot" "weekly" "1"
|
|
update_snapshot_schedule "/etc/cron.monthly/zfs-auto-snapshot" "monthly" "1"
|
|
}
|
|
|
|
update_snapshot_schedule() {
|
|
local config_file="$1"
|
|
local schedule_type="$2"
|
|
local keep_value="$3"
|
|
local frequency="$4"
|
|
pmx_journal_context "update_snapshot_schedule" "$FUNC_VERSION"
|
|
|
|
if [ -f "$config_file" ]; then
|
|
if ! grep -q ".*--keep=$keep_value" "$config_file"; then
|
|
if [ -n "$frequency" ]; then
|
|
pmx_edit_file "$config_file" "s|^\*/[0-9]*.*--keep=[0-9]*|$frequency * * * * root /usr/sbin/zfs-auto-snapshot --quiet --syslog --label=$schedule_type --keep=$keep_value|"
|
|
else
|
|
pmx_edit_file "$config_file" "s|--keep=[0-9]*|--keep=$keep_value|g"
|
|
fi
|
|
msg_ok "$(translate "Updated $schedule_type snapshot schedule")"
|
|
else
|
|
msg_ok "$(translate "$schedule_type snapshot schedule already configured")"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
disable_rpc() {
|
|
local FUNC_VERSION="1.1"
|
|
# description: Disable rpcbind service/socket while preserving their exact previous systemd state for rollback.
|
|
local state_file="$BASE_DIR/rpcbind.state"
|
|
local state_tmp="${state_file}.tmp.$$"
|
|
local unit load_state enabled_state active_state
|
|
|
|
msg_info2 "$(translate "Disabling portmapper/rpcbind for security...")"
|
|
|
|
mkdir -p "$BASE_DIR"
|
|
if [[ ! -s "$state_file" ]]; then
|
|
: > "$state_tmp"
|
|
for unit in rpcbind.socket rpcbind.service; do
|
|
load_state="$(systemctl show -p LoadState --value "$unit" 2>/dev/null || true)"
|
|
[[ -z "$load_state" || "$load_state" == "not-found" ]] && continue
|
|
enabled_state="$(systemctl is-enabled "$unit" 2>/dev/null || true)"
|
|
active_state="$(systemctl is-active "$unit" 2>/dev/null || true)"
|
|
printf '%s|%s|%s\n' "$unit" "${enabled_state:-unknown}" "${active_state:-unknown}" >> "$state_tmp"
|
|
done
|
|
|
|
if [[ ! -s "$state_tmp" ]]; then
|
|
rm -f "$state_tmp"
|
|
msg_warn "$(translate "rpcbind units were not found; no changes were made")"
|
|
return 0
|
|
fi
|
|
mv "$state_tmp" "$state_file"
|
|
fi
|
|
|
|
# Register as soon as the original state is safely persisted. If a
|
|
# later systemd operation fails, Uninstall Optimizations must still
|
|
# expose the recovery path instead of leaving a hidden partial change.
|
|
register_tool "rpc" true "$FUNC_VERSION"
|
|
|
|
msg_info "$(translate "Disabling and stopping rpcbind service and socket...")"
|
|
|
|
pmx_journal_context "disable_rpc" "$FUNC_VERSION"
|
|
pmx_disable_service rpcbind.socket || true
|
|
pmx_disable_service rpcbind.service || true
|
|
|
|
for unit in rpcbind.socket rpcbind.service; do
|
|
active_state="$(systemctl is-active "$unit" 2>/dev/null || true)"
|
|
enabled_state="$(systemctl is-enabled "$unit" 2>/dev/null || true)"
|
|
if [[ "$active_state" == "active" || "$active_state" == "activating" ||
|
|
"$enabled_state" == "enabled" || "$enabled_state" == "enabled-runtime" ]]; then
|
|
msg_error "$(translate "rpcbind could not be disabled completely")"
|
|
return 1
|
|
fi
|
|
done
|
|
|
|
msg_ok "$(translate "rpcbind service and socket have been disabled and stopped")"
|
|
|
|
msg_success "$(translate "portmapper/rpcbind has been disabled")"
|
|
}
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
configure_pigz() {
|
|
local FUNC_VERSION="1.0"
|
|
pmx_journal_context "configure_pigz" "$FUNC_VERSION"
|
|
# description: Replace gzip with pigz (parallel implementation) for faster vzdump backup compression.
|
|
msg_info2 "$(translate "Configuring pigz as a faster replacement for gzip...")"
|
|
|
|
# Enable pigz in vzdump configuration
|
|
msg_info "$(translate "Enabling pigz in vzdump configuration...")"
|
|
if ! grep -q "^pigz: 1" /etc/vzdump.conf; then
|
|
pmx_edit_file /etc/vzdump.conf "s/#pigz:.*/pigz: 1/"
|
|
msg_ok "$(translate "pigz enabled in vzdump configuration")"
|
|
else
|
|
msg_ok "$(translate "pigz enabled in vzdump configuration")"
|
|
fi
|
|
|
|
# Install pigz
|
|
if ! dpkg -s pigz >/dev/null 2>&1; then
|
|
msg_info "$(translate "Installing pigz...")"
|
|
if pmx_install_pkg pigz; then
|
|
msg_ok "$(translate "pigz installed successfully")"
|
|
else
|
|
msg_error "$(translate "Failed to install pigz")"
|
|
return 1
|
|
fi
|
|
else
|
|
msg_ok "$(translate "pigz installed successfully")"
|
|
fi
|
|
|
|
# Create pigz wrapper script
|
|
msg_info "$(translate "Creating pigz wrapper script...")"
|
|
if [ ! -f /bin/pigzwrapper ] || ! cmp -s /bin/pigzwrapper - <<EOF
|
|
#!/bin/sh
|
|
PATH=/bin:\$PATH
|
|
GZIP="-1"
|
|
exec /usr/bin/pigz "\$@"
|
|
EOF
|
|
then
|
|
pmx_write_file /bin/pigzwrapper <<EOF
|
|
#!/bin/sh
|
|
PATH=/bin:\$PATH
|
|
GZIP="-1"
|
|
exec /usr/bin/pigz "\$@"
|
|
EOF
|
|
chmod +x /bin/pigzwrapper
|
|
msg_ok "$(translate "pigz wrapper script created")"
|
|
else
|
|
msg_ok "$(translate "pigz wrapper script created")"
|
|
fi
|
|
|
|
# Replace gzip with pigz wrapper
|
|
msg_info "$(translate "Replacing gzip with pigz wrapper...")"
|
|
if [ ! -f /bin/gzip.original ]; then
|
|
mv -f /bin/gzip /bin/gzip.original && \
|
|
pmx_write_file /bin/gzip < /bin/pigzwrapper && \
|
|
chmod +x /bin/gzip
|
|
msg_ok "$(translate "gzip replaced with pigz wrapper successfully")"
|
|
else
|
|
msg_ok "$(translate "gzip replaced with pigz wrapper successfully")"
|
|
fi
|
|
|
|
msg_success "$(translate "pigz configuration completed")"
|
|
register_tool "pigz" true "$FUNC_VERSION"
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
install_guest_agent() {
|
|
local FUNC_VERSION="1.0"
|
|
# description: Detect the host's hypervisor (qemu/vmware/hyperv/virtualbox) and install the matching guest agent.
|
|
msg_info2 "$(translate "Detecting virtualization and installing guest agent...")"
|
|
NECESSARY_REBOOT=1
|
|
|
|
local virt_env=""
|
|
local guest_agent=""
|
|
|
|
# Detect virtualization environment
|
|
if [ "$(dmidecode -s system-manufacturer | xargs)" == "QEMU" ] || [ "$(systemd-detect-virt | xargs)" == "kvm" ]; then
|
|
virt_env="QEMU/KVM"
|
|
guest_agent="qemu-guest-agent"
|
|
elif [ "$(systemd-detect-virt | xargs)" == "vmware" ]; then
|
|
virt_env="VMware"
|
|
guest_agent="open-vm-tools"
|
|
elif [ "$(systemd-detect-virt | xargs)" == "oracle" ]; then
|
|
virt_env="VirtualBox"
|
|
guest_agent="virtualbox-guest-utils"
|
|
else
|
|
msg_ok "$(translate "Guest agent detection completed")"
|
|
msg_success "$(translate "Guest agent installation process completed")"
|
|
return
|
|
fi
|
|
|
|
# Install guest agent
|
|
if [ -n "$guest_agent" ]; then
|
|
msg_info "$(translate "Installing $guest_agent for $virt_env...")"
|
|
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install $guest_agent > /dev/null 2>&1; then
|
|
msg_ok "$(translate "$guest_agent installed successfully")"
|
|
# Persist which package was installed so the uninstaller knows
|
|
# what to apt purge later (different per hypervisor).
|
|
mkdir -p /usr/local/share/proxmenux 2>/dev/null
|
|
echo "$guest_agent" > /usr/local/share/proxmenux/guest_agent.pkg
|
|
register_tool "guest_agent" true "$FUNC_VERSION"
|
|
else
|
|
msg_error "$(translate "Failed to install $guest_agent")"
|
|
fi
|
|
fi
|
|
|
|
msg_success "$(translate "Guest agent installation process completed")"
|
|
}
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
enable_vfio_iommu() {
|
|
local FUNC_VERSION="1.0"
|
|
pmx_journal_context "enable_vfio_iommu" "$FUNC_VERSION"
|
|
# description: Enable IOMMU and load VFIO modules to allow GPU/PCI passthrough into VMs.
|
|
msg_info2 "$(translate "Enabling IOMMU and configuring VFIO for PCI passthrough...")"
|
|
NECESSARY_REBOOT=1
|
|
|
|
# Detect if system uses ZFS/systemd-boot (Proxmox)
|
|
local uses_zfs=false
|
|
local cmdline_file="/etc/kernel/cmdline"
|
|
if [[ -f "$cmdline_file" ]] && grep -qE 'root=ZFS=|root=ZFS/' "$cmdline_file"; then
|
|
uses_zfs=true
|
|
fi
|
|
|
|
if [[ "$uses_zfs" == true ]] && [[ -f "$cmdline_file" ]]; then
|
|
msg_info "$(translate "Cleaning up duplicate parameters...")"
|
|
cp "$cmdline_file" "${cmdline_file}.cleanup.bak"
|
|
|
|
pmx_edit_file "$cmdline_file" 's/intel_iommu=on[[:space:]]*intel_iommu=on/intel_iommu=on/g'
|
|
pmx_edit_file "$cmdline_file" 's/amd_iommu=on[[:space:]]*amd_iommu=on/amd_iommu=on/g'
|
|
pmx_edit_file "$cmdline_file" 's/iommu=pt[[:space:]]*iommu=pt/iommu=pt/g'
|
|
|
|
msg_ok "$(translate "Duplicate parameters cleaned")"
|
|
fi
|
|
|
|
# Detect CPU type and set IOMMU parameter
|
|
local cpu_info=$(cat /proc/cpuinfo)
|
|
local iommu_param=""
|
|
local grub_file="/etc/default/grub"
|
|
local additional_params="pcie_acs_override=downstream,multifunction"
|
|
|
|
if [[ "$cpu_info" == *"GenuineIntel"* ]]; then
|
|
msg_info "$(translate "Detected Intel CPU")"
|
|
iommu_param="intel_iommu=on"
|
|
elif [[ "$cpu_info" == *"AuthenticAMD"* ]]; then
|
|
msg_info "$(translate "Detected AMD CPU")"
|
|
iommu_param="amd_iommu=on"
|
|
else
|
|
msg_warning "$(translate "Unknown CPU type. IOMMU might not be properly enabled.")"
|
|
return 1
|
|
fi
|
|
|
|
# Configure /etc/kernel/cmdline or GRUB
|
|
if [[ "$uses_zfs" == true ]]; then
|
|
# SYSTEMD-BOOT - Verificación mejorada
|
|
local needs_iommu_param=false
|
|
local needs_iommu_pt=false
|
|
local needs_additional=false
|
|
|
|
# Verificar qué parámetros faltan
|
|
if ! grep -q "$iommu_param" "$cmdline_file"; then
|
|
needs_iommu_param=true
|
|
fi
|
|
|
|
if ! grep -q "iommu=pt" "$cmdline_file"; then
|
|
needs_iommu_pt=true
|
|
fi
|
|
|
|
if ! grep -q "pcie_acs_override=" "$cmdline_file"; then
|
|
needs_additional=true
|
|
fi
|
|
|
|
# Solo agregar lo que falta
|
|
if [[ "$needs_iommu_param" == true ]] || [[ "$needs_iommu_pt" == true ]] || [[ "$needs_additional" == true ]]; then
|
|
cp "$cmdline_file" "${cmdline_file}.bak"
|
|
|
|
local params_to_add=""
|
|
[[ "$needs_iommu_param" == true ]] && params_to_add+=" $iommu_param"
|
|
[[ "$needs_iommu_pt" == true ]] && params_to_add+=" iommu=pt"
|
|
[[ "$needs_additional" == true ]] && params_to_add+=" $additional_params"
|
|
|
|
pmx_edit_file "$cmdline_file" "s|\s*$|$params_to_add|"
|
|
msg_ok "$(translate "IOMMU parameters added to /etc/kernel/cmdline")"
|
|
else
|
|
msg_ok "$(translate "IOMMU already configured in /etc/kernel/cmdline")"
|
|
fi
|
|
|
|
else
|
|
# GRUB - Verificación mejorada
|
|
local needs_update=false
|
|
|
|
if ! grep -q "$iommu_param" "$grub_file" || ! grep -q "iommu=pt" "$grub_file"; then
|
|
needs_update=true
|
|
fi
|
|
|
|
if [[ "$needs_update" == true ]]; then
|
|
cp "$grub_file" "${grub_file}.bak"
|
|
|
|
# Agregar parámetros que falten
|
|
local current_line=$(grep "GRUB_CMDLINE_LINUX_DEFAULT=" "$grub_file")
|
|
local params_to_add=""
|
|
|
|
if ! echo "$current_line" | grep -q "$iommu_param"; then
|
|
params_to_add+=" $iommu_param"
|
|
fi
|
|
|
|
if ! echo "$current_line" | grep -q "iommu=pt"; then
|
|
params_to_add+=" iommu=pt"
|
|
fi
|
|
|
|
if ! echo "$current_line" | grep -q "pcie_acs_override="; then
|
|
params_to_add+=" $additional_params"
|
|
fi
|
|
|
|
pmx_edit_file "$grub_file" "/GRUB_CMDLINE_LINUX_DEFAULT=/ s|\"$|$params_to_add\"|"
|
|
msg_ok "$(translate "IOMMU enabled in GRUB configuration")"
|
|
else
|
|
msg_ok "$(translate "IOMMU already enabled in GRUB configuration")"
|
|
fi
|
|
fi
|
|
|
|
# Configure VFIO modules
|
|
local modules_file="/etc/modules"
|
|
msg_info "$(translate "Checking VFIO modules...")"
|
|
# vfio_virqfd was merged into the vfio module in kernel 6.2+
|
|
# Adding it as a separate module on kernel >= 6.2 generates warnings
|
|
local kernel_major kernel_minor
|
|
kernel_major=$(uname -r | cut -d. -f1)
|
|
kernel_minor=$(uname -r | cut -d. -f2)
|
|
local vfio_modules=("vfio" "vfio_iommu_type1" "vfio_pci")
|
|
if (( kernel_major < 6 || ( kernel_major == 6 && kernel_minor < 2 ) )); then
|
|
vfio_modules+=("vfio_virqfd")
|
|
fi
|
|
|
|
for module in "${vfio_modules[@]}"; do
|
|
if ! grep -q "^$module" "$modules_file"; then
|
|
echo "$module" | pmx_append_file "$modules_file"
|
|
fi
|
|
done
|
|
msg_ok "$(translate "VFIO modules configured.")"
|
|
|
|
# Blacklist conflicting drivers (sin cambios)
|
|
local blacklist_file="/etc/modprobe.d/blacklist.conf"
|
|
msg_info "$(translate "Checking conflicting drivers blacklist...")"
|
|
[[ -f "$blacklist_file" ]] || pmx_write_file "$blacklist_file" < /dev/null
|
|
|
|
local blacklist_drivers=("nouveau" "lbm-nouveau" "radeon" "nvidia" "nvidiafb")
|
|
for driver in "${blacklist_drivers[@]}"; do
|
|
if ! grep -q "^blacklist $driver" "$blacklist_file"; then
|
|
echo "blacklist $driver" | pmx_append_file "$blacklist_file"
|
|
fi
|
|
done
|
|
|
|
if ! grep -q "options nouveau modeset=0" "$blacklist_file"; then
|
|
echo "options nouveau modeset=0" | pmx_append_file "$blacklist_file"
|
|
fi
|
|
msg_ok "$(translate "Conflicting drivers blacklisted successfully.")"
|
|
|
|
# Update initramfs and bootloader
|
|
msg_info "$(translate "Updating initramfs, GRUB, and EFI boot, patience...")"
|
|
pmx_record_execution "Regenerate initramfs" "update-initramfs -u -k all"
|
|
update-initramfs -u -k all > /dev/null 2>&1
|
|
|
|
if [[ "$uses_zfs" == true ]]; then
|
|
pmx_record_execution "Refresh Proxmox boot configuration" "proxmox-boot-tool refresh"
|
|
proxmox-boot-tool refresh > /dev/null 2>&1
|
|
else
|
|
pmx_record_execution "Regenerate GRUB configuration" "update-grub"
|
|
update-grub > /dev/null 2>&1
|
|
fi
|
|
|
|
msg_success "$(translate "IOMMU and VFIO setup completed")"
|
|
register_tool "vfio_iommu" 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_info2 "$(translate "Customizing bashrc for root user...")"
|
|
|
|
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 prompt_path_style="${PMX_BASHRC_PATH_STYLE:-}"
|
|
local short_state="on"
|
|
local full_state="off"
|
|
local choice=""
|
|
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
|
|
|
|
# Preserve an existing ProxMenux-managed choice when the function is
|
|
# re-run, including legacy blocks. \W shows only the current directory;
|
|
# \w shows the full path.
|
|
if [[ "$detected_path_style" == "full" ]]; then
|
|
prompt_path_escape='\w'
|
|
short_state="off"
|
|
full_state="on"
|
|
fi
|
|
|
|
case "$prompt_path_style" in
|
|
short)
|
|
prompt_path_escape='\W'
|
|
;;
|
|
full)
|
|
prompt_path_escape='\w'
|
|
;;
|
|
"")
|
|
if [[ -t 0 && -t 1 ]] && command -v whiptail >/dev/null 2>&1; then
|
|
if ! choice=$(whiptail \
|
|
--title "$(translate "Bash prompt path")" \
|
|
--radiolist "$(translate "Choose how the current directory is shown in the Bash prompt:")" \
|
|
14 76 2 \
|
|
"short" "$(translate "Current directory only") (\\W)" "$short_state" \
|
|
"full" "$(translate "Full path") (\\w)" "$full_state" \
|
|
3>&1 1>&2 2>&3); then
|
|
msg_warn "$(translate "Cancelled by user.")"
|
|
return 1
|
|
fi
|
|
[[ "$choice" == "full" ]] && prompt_path_escape='\w' || prompt_path_escape='\W'
|
|
fi
|
|
;;
|
|
*)
|
|
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"
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
setup_motd() {
|
|
local FUNC_VERSION="1.0"
|
|
pmx_journal_context "setup_motd" "$FUNC_VERSION"
|
|
# description: Add the ProxMenux MOTD banner while preserving the original file contents or absence for rollback.
|
|
msg_info2 "$(translate "Configuring MOTD (Message of the Day) banner...")"
|
|
|
|
local motd_file="${PROXMENUX_MOTD_FILE:-/etc/motd}"
|
|
local custom_message=" This system is optimised by: ProxMenux"
|
|
local state_file="$BASE_DIR/motd.state"
|
|
local original_file="$BASE_DIR/motd.original"
|
|
local changes_made=false
|
|
|
|
msg_info "$(translate "Checking MOTD configuration...")"
|
|
|
|
mkdir -p "$BASE_DIR"
|
|
if [[ ! -f "$state_file" ]]; then
|
|
if grep -Fqx "$custom_message" "$motd_file" 2>/dev/null; then
|
|
if [[ -f "${motd_file}.bak" ]]; then
|
|
cp -a "${motd_file}.bak" "$original_file"
|
|
printf 'present\n' > "$state_file"
|
|
else
|
|
printf 'legacy-marker\n' > "$state_file"
|
|
fi
|
|
elif [[ -e "$motd_file" ]]; then
|
|
cp -a "$motd_file" "$original_file"
|
|
printf 'present\n' > "$state_file"
|
|
else
|
|
printf 'absent\n' > "$state_file"
|
|
fi
|
|
fi
|
|
|
|
# Check if the custom message already exists
|
|
if grep -Fqx "$custom_message" "$motd_file" 2>/dev/null; then
|
|
msg_ok "$(translate "Custom MOTD message is already configured")"
|
|
else
|
|
# Add the custom message at the beginning of the file
|
|
[[ -f "$motd_file" ]] || pmx_write_file "$motd_file" < /dev/null
|
|
local motd_tmp
|
|
motd_tmp="$(mktemp)"
|
|
{
|
|
printf '%s\n\n' "$custom_message"
|
|
cat "$motd_file"
|
|
} > "$motd_tmp"
|
|
pmx_write_file "$motd_file" < "$motd_tmp"
|
|
rm -f "$motd_tmp"
|
|
changes_made=true
|
|
msg_ok "$(translate "Custom message added to MOTD")"
|
|
fi
|
|
|
|
pmx_edit_file "$motd_file" '/^$/N;/^\n$/D'
|
|
|
|
if $changes_made; then
|
|
msg_success "$(translate "MOTD configuration updated successfully")"
|
|
else
|
|
msg_success "$(translate "MOTD configuration was already up to date")"
|
|
fi
|
|
register_tool "motd" true "$FUNC_VERSION"
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
optimize_logrotate() {
|
|
local FUNC_VERSION="1.1"
|
|
# description: Replace logrotate.conf with a Log2RAM-friendly profile (daily rotation, copytruncate).
|
|
msg_info2 "$(translate "Optimizing logrotate configuration...")"
|
|
|
|
local logrotate_conf="/etc/logrotate.conf"
|
|
local backup_conf="${logrotate_conf}.bak"
|
|
|
|
# The .bak stays until reverting from the journal exists:
|
|
# uninstall_logrotate restores from it, and migrating the write must
|
|
# not quietly disable the rollback that is already shipping.
|
|
cp -n "$logrotate_conf" "$backup_conf" 2>/dev/null || true
|
|
|
|
msg_info "$(translate "Applying optimized logrotate configuration...")"
|
|
pmx_journal_context "optimize_logrotate" "$FUNC_VERSION"
|
|
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
|
|
|
|
systemctl restart logrotate > /dev/null 2>&1
|
|
msg_ok "$(translate "Logrotate service restarted successfully")"
|
|
register_tool "logrotate" true "$FUNC_VERSION"
|
|
msg_success "$(translate "Logrotate optimization completed")"
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
remove_subscription_banner() {
|
|
local FUNC_VERSION="1.1"
|
|
# description: Patch the Proxmox web UI to suppress the "no valid subscription" dialog (PVE 8 + 9 variants supported).
|
|
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
|
|
if ! bash "$LOCAL_SCRIPTS/global/remove-banner-pve-v3.sh"; then
|
|
msg_error "$(translate "Subscription banner removal failed")"
|
|
return 1
|
|
fi
|
|
else
|
|
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"
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
optimize_memory_settings() {
|
|
local FUNC_VERSION="1.2"
|
|
# description: Tune swappiness, dirty page ratios and compaction proactiveness for VM hosts without overriding the kernel's memory-overcommit policy.
|
|
msg_info2 "$(translate "Optimizing memory settings...")"
|
|
NECESSARY_REBOOT=1
|
|
|
|
local sysctl_conf="/etc/sysctl.d/99-memory.conf"
|
|
|
|
|
|
if [ -f "$sysctl_conf" ] && grep -q "Memory Optimising" "$sysctl_conf"; then
|
|
msg_info "$(translate "Old memory configuration detected. Replacing with balanced optimization...")"
|
|
else
|
|
msg_info "$(translate "Applying balanced memory optimization settings...")"
|
|
fi
|
|
|
|
# Composed in full before writing: the journal records the file as
|
|
# it ends up, not a write followed by an append.
|
|
local memory_settings
|
|
memory_settings="$(cat <<EOF
|
|
# Balanced Memory Optimization
|
|
# Improve responsiveness without excessive memory reservation
|
|
|
|
# Avoid unnecessary swapping
|
|
vm.swappiness = 10
|
|
|
|
# Lower dirty memory thresholds to free memory faster
|
|
vm.dirty_ratio = 15
|
|
vm.dirty_background_ratio = 5
|
|
|
|
# Avoid excessive virtual memory areas (safe for most applications)
|
|
vm.max_map_count = 262144
|
|
EOF
|
|
)"
|
|
|
|
if [ -f /proc/sys/vm/compaction_proactiveness ]; then
|
|
memory_settings+=$'\n''vm.compaction_proactiveness = 20'
|
|
msg_ok "$(translate "Enabled memory compaction proactiveness")"
|
|
fi
|
|
|
|
pmx_journal_context "optimize_memory_settings" "$FUNC_VERSION"
|
|
printf '%s\n' "$memory_settings" | pmx_write_file "$sysctl_conf"
|
|
|
|
msg_ok "$(translate "Memory settings optimized successfully")"
|
|
msg_success "$(translate "Memory optimization completed.")"
|
|
register_tool "memory_settings" true "$FUNC_VERSION"
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
optimize_vzdump() {
|
|
local FUNC_VERSION="1.0"
|
|
pmx_journal_context "optimize_vzdump" "$FUNC_VERSION"
|
|
# description: Lift vzdump bandwidth/IO limits so backups run at the storage's real throughput.
|
|
msg_info2 "$(translate "Optimizing vzdump backup speed...")"
|
|
|
|
local vzdump_conf="/etc/vzdump.conf"
|
|
|
|
# Backup the current config so the uninstall path can restore the
|
|
# user's original values. The previous code edited in-place with no
|
|
# backup; users with custom bwlimit/ionice lost them silently.
|
|
if [[ -f "$vzdump_conf" && ! -f "${vzdump_conf}.bak" ]]; then
|
|
cp -p "$vzdump_conf" "${vzdump_conf}.bak"
|
|
fi
|
|
|
|
# Configure bandwidth limit
|
|
msg_info "$(translate "Configuring bandwidth limit for vzdump...")"
|
|
if ! grep -q "^bwlimit: 0" "$vzdump_conf"; then
|
|
pmx_edit_file "$vzdump_conf" '/^#*bwlimit:/d'
|
|
echo "bwlimit: 0" | pmx_append_file "$vzdump_conf"
|
|
fi
|
|
msg_ok "$(translate "Bandwidth limit configured")"
|
|
|
|
# Configure I/O priority
|
|
msg_info "$(translate "Configuring I/O priority for vzdump...")"
|
|
if ! grep -q "^ionice: 5" "$vzdump_conf"; then
|
|
pmx_edit_file "$vzdump_conf" '/^#*ionice:/d'
|
|
echo "ionice: 5" | pmx_append_file "$vzdump_conf"
|
|
fi
|
|
msg_ok "$(translate "I/O priority configured")"
|
|
|
|
msg_success "$(translate "vzdump backup speed optimization completed")"
|
|
register_tool "vzdump_speed" true "$FUNC_VERSION"
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
install_ovh_rtm() {
|
|
local FUNC_VERSION="1.0"
|
|
# description: Detect OVH-rented hardware via whois lookup and install OVH Real-Time Monitoring (no-op on non-OVH).
|
|
msg_info2 "$(translate "Detecting if this is an OVH server and installing OVH RTM if necessary...")"
|
|
|
|
# Get the public IP and check if it belongs to OVH
|
|
msg_info "$(translate "Checking if the server belongs to OVH...")"
|
|
public_ip=$(curl -s ipinfo.io/ip)
|
|
# `--` ends whois client option parsing so "-t IP" reaches the cymru server
|
|
# as the query string. Previous form had a leading space inside the quotes
|
|
# ("\" -t IP\"") that mangled the query and caused detection to never match.
|
|
is_ovh=$(whois -h v4.whois.cymru.com -- "-t $public_ip" | tail -n 1 | cut -d'|' -f3 | grep -i "ovh")
|
|
|
|
if [ -n "$is_ovh" ]; then
|
|
msg_ok "$(translate "OVH server detected")"
|
|
|
|
msg_info "$(translate "Installing OVH RTM (Real Time Monitoring)...")"
|
|
if wget -qO - https://last-public-ovh-infra-yak.snap.mirrors.ovh.net/yak/archives/apply.sh | OVH_PUPPET_MANIFEST=distribyak/catalog/master/puppet/manifests/common/rtmv2.pp bash > /dev/null 2>&1; then
|
|
msg_ok "$(translate "OVH RTM installed successfully")"
|
|
register_tool "ovh_rtm" true "$FUNC_VERSION"
|
|
else
|
|
msg_error "$(translate "Failed to install OVH RTM")"
|
|
fi
|
|
else
|
|
msg_ok "$(translate "Not an OVH server, skipping RTM installation")"
|
|
fi
|
|
msg_success "$(translate "OVH server detection and RTM installation process completed")"
|
|
}
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
enable_ha() {
|
|
local FUNC_VERSION="1.0"
|
|
# description: Enable the Proxmox HA stack (pve-ha-lrm, pve-ha-crm, corosync) for cluster failover.
|
|
msg_info2 "$(translate "Enabling High Availability (HA) services...")"
|
|
NECESSARY_REBOOT=1
|
|
|
|
msg_info "$(translate "Enabling High Availability (HA) services...")"
|
|
# Enable all necessary services
|
|
systemctl enable -q --now pve-ha-lrm pve-ha-crm corosync &>/dev/null
|
|
|
|
|
|
msg_ok "$(translate "High Availability services have been enabled successfully")"
|
|
msg_success "$(translate "High Availability setup completed")"
|
|
|
|
register_tool "ha" true "$FUNC_VERSION"
|
|
}
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
configure_fastfetch() {
|
|
local FUNC_VERSION="1.1"
|
|
pmx_journal_context "configure_fastfetch" "$FUNC_VERSION"
|
|
# description: Install Fastfetch system summary tool with the ProxMenux logo + status block as the SSH login banner.
|
|
msg_info2 "$(translate "Installing and configuring Fastfetch...")"
|
|
|
|
|
|
# Define paths
|
|
local fastfetch_bin="/usr/local/bin/fastfetch"
|
|
local fastfetch_config_dir="$HOME/.config/fastfetch"
|
|
local logos_dir="/usr/local/share/fastfetch/logos"
|
|
local fastfetch_config="$fastfetch_config_dir/config.jsonc"
|
|
|
|
apply_fastfetch_config() {
|
|
local config_tmp status
|
|
config_tmp="$(mktemp)"
|
|
if jq "$@" "$fastfetch_config" > "$config_tmp"; then
|
|
pmx_write_file "$fastfetch_config" < "$config_tmp"
|
|
status=$?
|
|
else
|
|
status=$?
|
|
fi
|
|
rm -f "$config_tmp"
|
|
return "$status"
|
|
}
|
|
|
|
download_fastfetch_logo() {
|
|
local path="$1" url="$2"
|
|
local -a statuses
|
|
wget -qO - "$url" | pmx_write_file "$path"
|
|
statuses=("${PIPESTATUS[@]}")
|
|
[[ "${statuses[0]}" -eq 0 && "${statuses[1]}" -eq 0 ]]
|
|
}
|
|
|
|
# Ensure directories exist
|
|
mkdir -p "$fastfetch_config_dir"
|
|
mkdir -p "$logos_dir"
|
|
|
|
|
|
if command -v fastfetch &> /dev/null; then
|
|
pmx_record_execution "Remove existing Fastfetch package" "apt-get remove --purge -y fastfetch"
|
|
apt-get remove --purge -y fastfetch > /dev/null 2>&1
|
|
pmx_remove_file /usr/bin/fastfetch
|
|
pmx_remove_file /usr/local/bin/fastfetch
|
|
fi
|
|
|
|
|
|
msg_info "$(translate "Downloading the latest Fastfetch release...")"
|
|
# `--connect-timeout`/`--max-time` so a slow GitHub API call doesn't
|
|
# hang the menu indefinitely. `FASTFETCH_PIN_TAG` env var lets the
|
|
# caller pin to a known release if upstream introduces a breaking
|
|
# change. Audit Tier 6 — recursos remotos sin pinning de versión.
|
|
local fastfetch_deb_url=""
|
|
if [[ -n "${FASTFETCH_PIN_TAG:-}" ]]; then
|
|
fastfetch_deb_url="https://github.com/fastfetch-cli/fastfetch/releases/download/${FASTFETCH_PIN_TAG}/fastfetch-linux-amd64.deb"
|
|
else
|
|
fastfetch_deb_url=$(curl -s --connect-timeout 5 --max-time 15 https://api.github.com/repos/fastfetch-cli/fastfetch/releases/latest |
|
|
jq -r '.assets[] | select(.name | test("fastfetch-linux-amd64.deb")) | .browser_download_url')
|
|
fi
|
|
|
|
if [[ -z "$fastfetch_deb_url" ]]; then
|
|
msg_error "$(translate "Failed to retrieve Fastfetch download URL.")"
|
|
return 1
|
|
fi
|
|
msg_ok "$(translate "Fastfetch download URL retrieved successfully.")"
|
|
|
|
|
|
wget -qO /tmp/fastfetch.deb "$fastfetch_deb_url"
|
|
pmx_record_execution "Install Fastfetch package" "dpkg -i /tmp/fastfetch.deb"
|
|
if dpkg -i /tmp/fastfetch.deb > /dev/null 2>&1; then
|
|
pmx_record_execution "Resolve Fastfetch package dependencies" "apt-get install -f -y"
|
|
apt-get install -f -y > /dev/null 2>&1
|
|
msg_ok "$(translate "Fastfetch installed successfully")"
|
|
else
|
|
msg_error "$(translate "Failed to install Fastfetch.")"
|
|
return 1
|
|
fi
|
|
|
|
|
|
rm -f /tmp/fastfetch.deb
|
|
|
|
|
|
if ! command -v fastfetch &> /dev/null; then
|
|
msg_error "$(translate "Fastfetch is not installed correctly.")"
|
|
return 1
|
|
fi
|
|
|
|
|
|
if [ ! -f "$fastfetch_config" ]; then
|
|
echo '{"$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", "modules": []}' | pmx_write_file "$fastfetch_config"
|
|
fi
|
|
|
|
pmx_record_execution "Generate Fastfetch configuration" "fastfetch --gen-config-force"
|
|
fastfetch --gen-config-force > /dev/null 2>&1
|
|
|
|
while true; do
|
|
# Define logo options
|
|
local logo_options=("ProxMenux" "Proxmox (default)" "JC Channel" "Comunidad Helper-Scripts" "Home-Labs-Club" "Proxmology" "Custom")
|
|
local choice
|
|
|
|
choice=$(whiptail --title "$(translate "Fastfetch Logo Selection")" --menu "$(translate "Choose a logo for Fastfetch:")" 20 78 7 \
|
|
"1" "${logo_options[0]}" \
|
|
"2" "${logo_options[1]}" \
|
|
"3" "${logo_options[2]}" \
|
|
"4" "${logo_options[3]}" \
|
|
"5" "${logo_options[4]}" \
|
|
"6" "${logo_options[5]}" \
|
|
"7" "${logo_options[6]}" \
|
|
3>&1 1>&2 2>&3)
|
|
|
|
case $choice in
|
|
1)
|
|
msg_info "$(translate "Downloading ProxMenux logo...")"
|
|
local proxmenux_logo_path="$logos_dir/ProxMenux.txt"
|
|
if download_fastfetch_logo "$proxmenux_logo_path" "https://raw.githubusercontent.com/MacRimi/ProxMenux/main/images/logos_txt/logo.txt"; then
|
|
apply_fastfetch_config --arg path "$proxmenux_logo_path" '. + {logo: $path}'
|
|
msg_ok "$(translate "ProxMenux logo applied")"
|
|
else
|
|
msg_error "$(translate "Failed to download ProxMenux logo")"
|
|
fi
|
|
break
|
|
;;
|
|
2)
|
|
msg_info "$(translate "Using default Proxmox logo...")"
|
|
apply_fastfetch_config 'del(.logo)'
|
|
msg_ok "$(translate "Default Proxmox logo applied")"
|
|
break
|
|
;;
|
|
3)
|
|
msg_info "$(translate "Downloading JC Channel logo...")"
|
|
local jc_channel_logo_path="$logos_dir/jc_channel.txt"
|
|
if download_fastfetch_logo "$jc_channel_logo_path" "https://raw.githubusercontent.com/MacRimi/ProxMenux/main/images/logos_txt/jc_channel.txt"; then
|
|
apply_fastfetch_config --arg path "$jc_channel_logo_path" '. + {logo: $path}'
|
|
msg_ok "$(translate "JC Channel logo applied")"
|
|
else
|
|
msg_error "$(translate "Failed to download JC Channel logo")"
|
|
fi
|
|
break
|
|
;;
|
|
4)
|
|
msg_info "$(translate "Downloading Helper-Scripts logo...")"
|
|
local helper_scripts_logo_path="$logos_dir/Helper_Scripts.txt"
|
|
if download_fastfetch_logo "$helper_scripts_logo_path" "https://raw.githubusercontent.com/MacRimi/ProxMenux/main/images/logos_txt/Helper_Scripts.txt"; then
|
|
apply_fastfetch_config --arg path "$helper_scripts_logo_path" '. + {logo: $path}'
|
|
msg_ok "$(translate "Helper-Scripts logo applied")"
|
|
else
|
|
msg_error "$(translate "Failed to download Helper-Scripts logo")"
|
|
fi
|
|
break
|
|
;;
|
|
5)
|
|
msg_info "$(translate "Downloading Home-Labs-Club logo...")"
|
|
local home_lab_club_logo_path="$logos_dir/home_labsclub.txt"
|
|
if download_fastfetch_logo "$home_lab_club_logo_path" "https://raw.githubusercontent.com/MacRimi/ProxMenux/main/images/logos_txt/home_labsclub.txt"; then
|
|
apply_fastfetch_config --arg path "$home_lab_club_logo_path" '. + {logo: $path}'
|
|
msg_ok "$(translate "Home-Lab-Club logo applied")"
|
|
else
|
|
msg_error "$(translate "Failed to download Home-Lab-Club logo")"
|
|
fi
|
|
break
|
|
;;
|
|
6)
|
|
msg_info "$(translate "Downloading Proxmology logo...")"
|
|
local proxmology_logo_path="$logos_dir/proxmology.txt"
|
|
if download_fastfetch_logo "$proxmology_logo_path" "https://raw.githubusercontent.com/MacRimi/ProxMenux/main/images/logos_txt/proxmology.txt"; then
|
|
apply_fastfetch_config --arg path "$proxmology_logo_path" '. + {logo: $path}'
|
|
msg_ok "$(translate "Proxmology logo applied")"
|
|
else
|
|
msg_error "$(translate "Failed to download Proxmology logo")"
|
|
fi
|
|
break
|
|
;;
|
|
7)
|
|
whiptail --title "$(translate "Custom Logo Instructions")" --msgbox "$(translate "To use a custom Fastfetch logo, place your ASCII logo file in:\n\n/usr/local/share/fastfetch/logos/\n\nThe file should not exceed 35 lines to fit properly in the terminal.\n\nPress OK to continue and select your logo.")" 15 70
|
|
|
|
local logo_files=($(ls "$logos_dir"/*.txt 2>/dev/null))
|
|
|
|
if [ ${#logo_files[@]} -eq 0 ]; then
|
|
whiptail --title "$(translate "No Custom Logos Found")" --msgbox "$(translate "No custom logos were found in /usr/local/share/fastfetch/logos/.\n\nPlease add a logo and try again.")" 10 60
|
|
continue
|
|
fi
|
|
|
|
local menu_items=()
|
|
local index=1
|
|
for file in "${logo_files[@]}"; do
|
|
menu_items+=("$index" "$(basename "$file")")
|
|
index=$((index+1))
|
|
done
|
|
|
|
local selected_logo_index
|
|
selected_logo_index=$(whiptail --title "$(translate "Select a Custom Logo")" --menu "$(translate "Choose a custom logo:")" 20 70 10 "${menu_items[@]}" 3>&1 1>&2 2>&3)
|
|
|
|
if [ -z "$selected_logo_index" ]; then
|
|
continue
|
|
fi
|
|
|
|
local selected_logo="${logo_files[$((selected_logo_index-1))]}"
|
|
apply_fastfetch_config --arg path "$selected_logo" '. + {logo: $path}'
|
|
msg_ok "$(translate "Custom logo applied: $(basename "$selected_logo")")"
|
|
break
|
|
;;
|
|
*)
|
|
msg_warn "$(translate "You must select a logo to continue.")"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Modify Fastfetch modules to display custom title
|
|
msg_info "$(translate "Modifying Fastfetch configuration...")"
|
|
|
|
apply_fastfetch_config '.modules |= map(select(. != "title"))'
|
|
|
|
apply_fastfetch_config 'del(.modules[] | select(type == "object" and .type == "custom"))'
|
|
|
|
apply_fastfetch_config '.modules |= [{"type": "custom", "format": "\u001b[1;38;5;166mSystem optimised by ProxMenux\u001b[0m"}] + .'
|
|
|
|
msg_ok "$(translate "Fastfetch now displays: System optimised by: ProxMenux")"
|
|
|
|
pmx_record_execution "Generate Fastfetch configuration" "fastfetch --gen-config"
|
|
fastfetch --gen-config > /dev/null 2>&1
|
|
msg_ok "$(translate "Fastfetch configuration updated")"
|
|
|
|
|
|
pmx_edit_file "$HOME/.profile" '/fastfetch/d' 2>/dev/null || true
|
|
pmx_edit_file /etc/profile '/fastfetch/d' 2>/dev/null || true
|
|
pmx_remove_file /etc/update-motd.d/99-fastfetch
|
|
|
|
pmx_edit_file "$HOME/.bashrc" '/# BEGIN FASTFETCH/,/# END FASTFETCH/d' 2>/dev/null || true
|
|
|
|
if ! grep -q '# BEGIN FASTFETCH' "$HOME/.bashrc"; then
|
|
pmx_append_file "$HOME/.bashrc" << 'EOF'
|
|
|
|
# BEGIN FASTFETCH
|
|
# Run Fastfetch only in interactive sessions
|
|
if [[ $- == *i* ]] && command -v fastfetch &>/dev/null; then
|
|
clear
|
|
fastfetch
|
|
fi
|
|
# END FASTFETCH
|
|
EOF
|
|
fi
|
|
|
|
msg_ok "$(translate "Fastfetch will start automatically in the console")"
|
|
msg_success "$(translate "Fastfetch installation and configuration completed")"
|
|
register_tool "fastfetch" true "$FUNC_VERSION"
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
configure_figurine() {
|
|
local FUNC_VERSION="1.1"
|
|
pmx_journal_context "configure_figurine" "$FUNC_VERSION"
|
|
# description: Install Figurine (ASCII-art hostname banner) and wire it into the SSH login flow.
|
|
msg_info2 "$(translate "Installing and configuring Figurine...")"
|
|
# `FIGURINE_VERSION` env var allows pinning to a specific release;
|
|
# default tracks the last tested upstream tag. Audit Tier 6 —
|
|
# recursos remotos sin pinning de versión.
|
|
local version="${FIGURINE_VERSION:-2.0.0}"
|
|
local file="figurine_linux_amd64_v${version}.tar.gz"
|
|
local url="https://github.com/arsham/figurine/releases/download/v${version}/${file}"
|
|
local temp_dir; temp_dir=$(mktemp -d)
|
|
local install_dir="/usr/local/bin"
|
|
local profile_script="/etc/profile.d/figurine.sh"
|
|
local bin_path="${install_dir}/figurine"
|
|
local bashrc="/root/.bashrc"
|
|
|
|
cleanup_dir() { rm -rf "$temp_dir" 2>/dev/null || true; }
|
|
trap cleanup_dir EXIT
|
|
|
|
[[ -f "$bashrc" ]] || pmx_write_file "$bashrc" < /dev/null
|
|
|
|
if command -v figurine &>/dev/null; then
|
|
msg_info "$(translate "Updating Figurine binary...")"
|
|
else
|
|
msg_info "$(translate "Downloading Figurine v${version}...")"
|
|
fi
|
|
|
|
if ! wget -qO "${temp_dir}/${file}" "$url"; then
|
|
msg_error "$(translate "Failed to download Figurine")"
|
|
return 1
|
|
fi
|
|
|
|
if ! tar -xf "${temp_dir}/${file}" -C "${temp_dir}"; then
|
|
msg_error "$(translate "Failed to extract package")"
|
|
return 1
|
|
fi
|
|
msg_ok "$(translate "Extraction successful")"
|
|
|
|
if [[ ! -f "${temp_dir}/deploy/figurine" ]]; then
|
|
msg_error "$(translate "Binary not found in extracted content.")"
|
|
return 1
|
|
fi
|
|
|
|
msg_info "$(translate "Installing binary to ${install_dir}...")"
|
|
pmx_write_file "$bin_path" < "${temp_dir}/deploy/figurine"
|
|
chmod 0755 "$bin_path"
|
|
chown root:root "$bin_path"
|
|
|
|
|
|
pmx_write_file "$profile_script" << 'EOF'
|
|
/usr/local/bin/figurine -f "3d.flf" $(hostname)
|
|
EOF
|
|
chmod +x "$profile_script"
|
|
|
|
|
|
ensure_aliases() {
|
|
local bashrc="/root/.bashrc"
|
|
[[ -f "$bashrc" ]] || pmx_write_file "$bashrc" < /dev/null
|
|
|
|
if ! grep -q "shopt -s expand_aliases" "$bashrc" 2>/dev/null; then
|
|
echo "shopt -s expand_aliases" | pmx_append_file "$bashrc"
|
|
fi
|
|
|
|
local -a ALIASES=(
|
|
"aptup=apt update && apt dist-upgrade"
|
|
"lxcclean=bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/clean-lxcs.sh)\""
|
|
"lxcupdate=bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/update-lxcs.sh)\""
|
|
"kernelclean=bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/kernel-clean.sh)\""
|
|
"cpugov=bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/scaling-governor.sh)\""
|
|
"lxctrim=bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/fstrim.sh)\""
|
|
"updatecerts=pvecm updatecerts"
|
|
"seqwrite=sync; fio --randrepeat=1 --ioengine=libaio --direct=1 --name=test --filename=test --bs=4M --size=32G --readwrite=write --ramp_time=4"
|
|
"seqread=sync; fio --randrepeat=1 --ioengine=libaio --direct=1 --name=test --filename=test --bs=4M --size=32G --readwrite=read --ramp_time=4"
|
|
"ranwrite=sync; fio --randrepeat=1 --ioengine=libaio --direct=1 --name=test --filename=test --bs=4k --size=4G --readwrite=randwrite --ramp_time=4"
|
|
"ranread=sync; fio --randrepeat=1 --ioengine=libaio --direct=1 --name=test --filename=test --bs=4k --size=4G --readwrite=randread --ramp_time=4"
|
|
)
|
|
|
|
for entry in "${ALIASES[@]}"; do
|
|
local name="${entry%%=*}"
|
|
local cmd="${entry#*=}"
|
|
|
|
local safe_cmd=${cmd//\'/\'\\\'\'}
|
|
|
|
pmx_edit_file "$bashrc" -E "/^[[:space:]]*alias[[:space:]]+${name}=.*/d"
|
|
|
|
printf "alias %s='%s'\n" "$name" "$safe_cmd" | pmx_append_file "$bashrc"
|
|
done
|
|
|
|
. "$bashrc"
|
|
}
|
|
|
|
|
|
ensure_aliases
|
|
msg_ok "$(translate "Aliases added to .bashrc")"
|
|
|
|
msg_success "$(translate "Figurine installation and configuration completed successfully.")"
|
|
register_tool "figurine" true "$FUNC_VERSION"
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
update_pve_appliance_manager() {
|
|
msg_info "$(translate "Updating PVE application manager...")"
|
|
if pveam update > /dev/null 2>&1; then
|
|
msg_ok "$(translate "PVE application manager updated")"
|
|
else
|
|
msg_warn "$(translate "No updates or failed to fetch templates")"
|
|
fi
|
|
}
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_update_existing_log2ram_custom() {
|
|
local func_version="$1"
|
|
local log2ram_bin=""
|
|
local candidate resolved tmp_file
|
|
pmx_journal_context "_update_existing_log2ram_custom" "$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
|
|
|
|
if [[ -f /usr/local/bin/log2ram-check.sh ]]; then
|
|
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"
|
|
fi
|
|
|
|
register_tool "log2ram" true "$func_version"
|
|
msg_success "$(translate "Log2RAM installation and configuration completed successfully.")"
|
|
}
|
|
|
|
|
|
configure_log2ram() {
|
|
local FUNC_VERSION="1.5"
|
|
local existing_log2ram_bin=""
|
|
pmx_journal_context "configure_log2ram" "$FUNC_VERSION"
|
|
# description: Install Log2RAM with user-chosen RAM size; prompts for size and SSD/M.2 awareness before applying.
|
|
|
|
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_custom "$FUNC_VERSION"
|
|
return $?
|
|
fi
|
|
|
|
msg_info2 "$(translate "Preparing Log2RAM configuration")"
|
|
sleep 1
|
|
|
|
|
|
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
|
|
DEFAULT_SIZE="128" # MiB
|
|
DEFAULT_HOURS="1"
|
|
elif (( RAM_SIZE_GB <= 16 )); then
|
|
DEFAULT_SIZE="256"
|
|
DEFAULT_HOURS="3"
|
|
else
|
|
DEFAULT_SIZE="512"
|
|
DEFAULT_HOURS="6"
|
|
fi
|
|
|
|
|
|
USER_SIZE=$(whiptail --title "Log2RAM" --inputbox \
|
|
"$(translate "Enter the maximum size (in MB) to allocate for /var/log in RAM (e.g. 128, 256, 512):")\n\n$(translate "Recommended for $RAM_SIZE_GB GB RAM:") ${DEFAULT_SIZE}M" \
|
|
12 70 "$DEFAULT_SIZE" 3>&1 1>&2 2>&3) || return 0
|
|
|
|
if ! [[ "$USER_SIZE" =~ ^[0-9]+$ ]]; then
|
|
msg_error "$(translate "Invalid size. Please enter a number in MB (e.g., 128, 256, 512).")"
|
|
return 1
|
|
fi
|
|
(( USER_SIZE < 64 )) && USER_SIZE=64 # mínimo razonable
|
|
(( USER_SIZE > 8192 )) && USER_SIZE=8192 # límite de seguridad
|
|
LOG2RAM_SIZE="${USER_SIZE}M"
|
|
|
|
|
|
CRON_HOURS=$(whiptail --title "Log2RAM" --radiolist \
|
|
"$(translate "Select the sync interval (in hours):")\n\n$(translate "Suggested interval: every $DEFAULT_HOURS hour(s)")" \
|
|
15 70 5 \
|
|
"1" "$(translate "Every hour")" $([[ "$DEFAULT_HOURS" = "1" ]] && echo ON || echo OFF) \
|
|
"3" "$(translate "Every 3 hours")" $([[ "$DEFAULT_HOURS" = "3" ]] && echo ON || echo OFF) \
|
|
"6" "$(translate "Every 6 hours")" $([[ "$DEFAULT_HOURS" = "6" ]] && echo ON || echo OFF) \
|
|
"12" "$(translate "Every 12 hours")" OFF \
|
|
3>&1 1>&2 2>&3) || return 0
|
|
|
|
|
|
if whiptail --title "Log2RAM" --yesno "$(translate "Enable auto-sync if /var/log exceeds 90% of its size?")" 10 60; then
|
|
ENABLE_AUTOSYNC=true
|
|
else
|
|
ENABLE_AUTOSYNC=false
|
|
fi
|
|
|
|
|
|
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 "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 GitHub...")"
|
|
if ! command -v git >/dev/null 2>&1; then
|
|
msg_info "$(translate "Installing required package: git")"
|
|
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
|
|
|
|
|
|
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)")"
|
|
|
|
|
|
if [[ "$ENABLE_AUTOSYNC" == true ]]; then
|
|
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 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 +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=""
|
|
*/5 * * * * root /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
|
|
msg_ok "$(translate "Auto-sync enabled when /var/log exceeds 80% of") $LOG2RAM_SIZE"
|
|
else
|
|
pmx_remove_file /usr/local/bin/log2ram-check.sh 2>/dev/null || true
|
|
pmx_remove_file /etc/cron.d/log2ram-auto-sync 2>/dev/null || true
|
|
msg_info2 "$(translate "Auto-sync was not enabled")"
|
|
fi
|
|
|
|
# --- Ajuste de systemd-journald proporcional al tamaño de Log2RAM ---
|
|
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)"
|
|
BAK_OK=$?
|
|
fi
|
|
|
|
SIZE_MB=$(echo "$LOG2RAM_SIZE" | tr -dc '0-9')
|
|
# Repartos: 55% persistente / 10% libre / 25% runtime (pisos mínimos)
|
|
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
|
|
|
|
# Reescribir bloque [Journal] de forma segura
|
|
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
|
|
|
|
[[ "$BAK_OK" = "0" ]] && 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})"
|
|
|
|
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
|
|
|
|
pmx_record_execution "Restart cron" "systemctl restart cron"
|
|
systemctl restart cron >/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
|
|
|
|
msg_success "$(translate "Log2RAM installation and configuration completed successfully.")"
|
|
register_tool "log2ram" true "$FUNC_VERSION"
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
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
|
|
msg_success "$(translate "Setting up persistent network interfaces successfully.")"
|
|
register_tool "persistent_network" true "$FUNC_VERSION"
|
|
NECESSARY_REBOOT=1
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
# Auxiliary help functions
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==========================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
install_system_utils() {
|
|
local FUNC_VERSION="1.1"
|
|
# description: Install selected system utilities and track only packages that were newly added by ProxMenux.
|
|
local state_file="$BASE_DIR/system_utils.packages"
|
|
local new_packages_tmp=""
|
|
msg_info2 "$(translate "Installing system utilities...")"
|
|
|
|
# Build checklist from global PROXMENUX_UTILS array
|
|
local checklist_items=()
|
|
for util_entry in "${PROXMENUX_UTILS[@]}"; do
|
|
IFS=':' read -r pkg cmd desc <<< "$util_entry"
|
|
checklist_items+=("$pkg" "$(translate "$desc")" "OFF")
|
|
done
|
|
|
|
exec 3>&1
|
|
local selected
|
|
selected=$(dialog --clear --backtitle "ProxMenux" \
|
|
--title "$(translate "Select utilities to install")" \
|
|
--checklist "$(translate "Use SPACE to select, ENTER to confirm")" \
|
|
25 80 20 "${checklist_items[@]}" 2>&1 1>&3)
|
|
local dialog_exit=$?
|
|
exec 3>&-
|
|
|
|
if [[ $dialog_exit -ne 0 || -z "$selected" ]]; then
|
|
msg_warn "$(translate "No utilities selected")"
|
|
return 0
|
|
fi
|
|
|
|
clear
|
|
show_proxmenux_logo
|
|
|
|
if ! ensure_repositories; then
|
|
msg_error "$(translate "Failed to configure repositories. Installation aborted.")"
|
|
return 1
|
|
fi
|
|
|
|
new_packages_tmp="$(mktemp)"
|
|
|
|
local success=0 failed=0 warning=0
|
|
local selected_array
|
|
IFS=' ' read -ra selected_array <<< "$selected"
|
|
|
|
for util in "${selected_array[@]}"; do
|
|
util=$(echo "$util" | tr -d '"')
|
|
local pkg_cmd="$util" pkg_desc="$util"
|
|
local was_installed=false
|
|
if dpkg-query -W -f='${Status}' "$util" 2>/dev/null | grep -q '^install ok installed$'; then
|
|
was_installed=true
|
|
fi
|
|
for util_entry in "${PROXMENUX_UTILS[@]}"; do
|
|
IFS=':' read -r epkg ecmd edesc <<< "$util_entry"
|
|
if [[ "$epkg" == "$util" ]]; then
|
|
pkg_cmd="$ecmd"
|
|
pkg_desc="$edesc"
|
|
break
|
|
fi
|
|
done
|
|
install_single_package "$util" "$pkg_cmd" "$pkg_desc"
|
|
local install_result=$?
|
|
case $install_result in
|
|
0) success=$((success + 1)) ;;
|
|
1) failed=$((failed + 1)) ;;
|
|
2) warning=$((warning + 1)) ;;
|
|
esac
|
|
|
|
if [[ "$was_installed" == false ]] &&
|
|
dpkg-query -W -f='${Status}' "$util" 2>/dev/null | grep -q '^install ok installed$'; then
|
|
printf '%s\n' "$util" >> "$new_packages_tmp"
|
|
fi
|
|
done
|
|
|
|
if [[ -s "$new_packages_tmp" ]]; then
|
|
mkdir -p "$BASE_DIR"
|
|
{
|
|
[[ -f "$state_file" ]] && cat "$state_file"
|
|
cat "$new_packages_tmp"
|
|
} | sort -u > "${state_file}.tmp"
|
|
mv "${state_file}.tmp" "$state_file"
|
|
fi
|
|
rm -f "$new_packages_tmp"
|
|
|
|
hash -r 2>/dev/null
|
|
echo
|
|
msg_info2 "$(translate "Installation summary"):"
|
|
[[ $success -gt 0 ]] && msg_ok "$(translate "Successful"): $success"
|
|
[[ $warning -gt 0 ]] && msg_warn "$(translate "With warnings"): $warning"
|
|
[[ $failed -gt 0 ]] && msg_error "$(translate "Failed"): $failed"
|
|
if [[ -s "$state_file" ]]; then
|
|
register_tool "system_utils" true "$FUNC_VERSION"
|
|
fi
|
|
msg_success "$(translate "Utilities installation completed")"
|
|
}
|
|
|
|
|
|
|
|
|
|
custom_post_category_label() {
|
|
case "$1" in
|
|
"Basic Settings") translate "Basic Settings" ;;
|
|
"System") translate "System" ;;
|
|
"Virtualization") translate "Virtualization" ;;
|
|
"Network") translate "Network" ;;
|
|
"Storage") translate "Storage" ;;
|
|
"Security") translate "Security" ;;
|
|
"Customization") translate "Customization" ;;
|
|
"Monitoring") translate "Monitoring" ;;
|
|
"Performance") translate "Performance" ;;
|
|
"Optional") translate "Optional" ;;
|
|
*) echo "$1" ;;
|
|
esac
|
|
}
|
|
|
|
custom_post_description_label() {
|
|
case "$1" in
|
|
"Configure Proxmox APT repositories") translate "Configure Proxmox APT repositories" ;;
|
|
"Update and upgrade system") translate "Update and upgrade system" ;;
|
|
"Synchronize time automatically") translate "Synchronize time automatically" ;;
|
|
"Skip downloading additional languages") translate "Skip downloading additional languages" ;;
|
|
"Install common system utilities") translate "Install common system utilities" ;;
|
|
"Optimize journald") translate "Optimize journald" ;;
|
|
"Optimize logrotate") translate "Optimize logrotate" ;;
|
|
"Increase various system limits") translate "Increase various system limits" ;;
|
|
"Optimize Memory") translate "Optimize Memory" ;;
|
|
"Enable fast reboots") translate "Enable fast reboots" ;;
|
|
"Enable restart on kernel panic") translate "Enable restart on kernel panic" ;;
|
|
"Apply AMD CPU fixes") translate "Apply AMD CPU fixes" ;;
|
|
"Install relevant guest agent") translate "Install relevant guest agent" ;;
|
|
"Enable VFIO IOMMU support") translate "Enable VFIO IOMMU support" ;;
|
|
"Force APT to use IPv4") translate "Force APT to use IPv4" ;;
|
|
"Apply network optimizations") translate "Apply network optimizations" ;;
|
|
"Install Open vSwitch") translate "Install Open vSwitch" ;;
|
|
"Enable TCP BBR/Fast Open control") translate "Enable TCP BBR/Fast Open control" ;;
|
|
"Interface Names (persistent)") translate "Interface Names (persistent)" ;;
|
|
"Optimize ZFS ARC size") translate "Optimize ZFS ARC size" ;;
|
|
"Install ZFS auto-snapshot") translate "Install ZFS auto-snapshot" ;;
|
|
"Enable ZFS autotrim (SSD/NVMe pools)") translate "Enable ZFS autotrim (SSD/NVMe pools)" ;;
|
|
"Increase vzdump backup speed") translate "Increase vzdump backup speed" ;;
|
|
"Disable portmapper/rpcbind") translate "Disable portmapper/rpcbind" ;;
|
|
"Customize bashrc") translate "Customize bashrc" ;;
|
|
"Set up custom MOTD banner") translate "Set up custom MOTD banner" ;;
|
|
"Remove subscription banner") translate "Remove subscription banner" ;;
|
|
"Install OVH Real Time Monitoring") translate "Install OVH Real Time Monitoring" ;;
|
|
"Use pigz for faster gzip compression") translate "Use pigz for faster gzip compression" ;;
|
|
"Install and configure Fastfetch") translate "Install and configure Fastfetch" ;;
|
|
"Update Proxmox VE Appliance Manager") translate "Update Proxmox VE Appliance Manager" ;;
|
|
"Add latest Ceph support") translate "Add latest Ceph support" ;;
|
|
"Enable High Availability services") translate "Enable High Availability services" ;;
|
|
"Install Figurine") translate "Install Figurine" ;;
|
|
"Install and configure Log2RAM") translate "Install and configure Log2RAM" ;;
|
|
*) echo "$1" ;;
|
|
esac
|
|
}
|
|
|
|
format_custom_post_line() {
|
|
local description="$1"
|
|
local category="$2"
|
|
local max_description_length=52
|
|
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
python3 - "$description" "$category" "$max_description_length" <<'PY'
|
|
import sys
|
|
import unicodedata
|
|
|
|
description = sys.argv[1]
|
|
category = sys.argv[2]
|
|
max_width = int(sys.argv[3])
|
|
|
|
def display_width(text):
|
|
width = 0
|
|
for char in text:
|
|
if unicodedata.combining(char):
|
|
continue
|
|
width += 2 if unicodedata.east_asian_width(char) in ("F", "W") else 1
|
|
return width
|
|
|
|
def fit_text(text, width):
|
|
result = ""
|
|
used = 0
|
|
for char in text:
|
|
char_width = 0 if unicodedata.combining(char) else (2 if unicodedata.east_asian_width(char) in ("F", "W") else 1)
|
|
if used + char_width > width:
|
|
break
|
|
result += char
|
|
used += char_width
|
|
return result, used
|
|
|
|
if display_width(description) > max_width:
|
|
description, used_width = fit_text(description, max_width - 3)
|
|
description += "..."
|
|
used_width += 3
|
|
else:
|
|
used_width = display_width(description)
|
|
|
|
print(f"{description}{' ' * (max_width - used_width)} | {category}")
|
|
PY
|
|
return
|
|
fi
|
|
|
|
if [ ${#description} -gt $max_description_length ]; then
|
|
description="${description:0:$((max_description_length - 3))}..."
|
|
fi
|
|
printf '%-*s | %s' "$max_description_length" "$description" "$category"
|
|
}
|
|
|
|
format_custom_post_header() {
|
|
local description_label
|
|
local category_label
|
|
local checklist_prefix=" "
|
|
|
|
description_label="$(translate "Description")"
|
|
category_label="$(translate "Category")"
|
|
printf '%s%s' "$checklist_prefix" "$(format_custom_post_line "$description_label" "$category_label")"
|
|
}
|
|
|
|
# Main menu function
|
|
main_menu() {
|
|
local header_line
|
|
local HEADER
|
|
if [[ "$LANGUAGE" == "es" ]]; then
|
|
HEADER="Seleccione las opciones a configurar:\n\n Descripción | Categoría"
|
|
else
|
|
HEADER="$(translate "Choose options to configure:")\n\n Description | Category"
|
|
fi
|
|
|
|
header_line="$(format_custom_post_header)"
|
|
HEADER="$(translate "Choose options to configure:")\n\n${header_line}"
|
|
|
|
declare -A category_order=(
|
|
["Basic Settings"]=1 ["System"]=2 ["Virtualization"]=3
|
|
["Network"]=4 ["Storage"]=5 ["Security"]=6 ["Customization"]=7
|
|
["Monitoring"]=8 ["Performance"]=9 ["Optional"]=10
|
|
)
|
|
|
|
local options=(
|
|
"Basic Settings|Configure Proxmox APT repositories|REPOS"
|
|
"Basic Settings|Update and upgrade system|APTUPGRADE"
|
|
"Basic Settings|Synchronize time automatically|TIMESYNC"
|
|
"Basic Settings|Skip downloading additional languages|NOAPTLANG"
|
|
"Basic Settings|Install common system utilities|UTILS"
|
|
"System|Optimize journald|JOURNALD"
|
|
"System|Optimize logrotate|LOGROTATE"
|
|
"System|Increase various system limits|LIMITS"
|
|
# Entropy (haveged) removed — modern kernels 5.6+ have built-in entropy generation
|
|
"System|Optimize Memory|MEMORYFIXES"
|
|
"System|Enable fast reboots|KEXEC"
|
|
"System|Enable restart on kernel panic|KERNELPANIC"
|
|
"Optional|Apply AMD CPU fixes|AMDFIXES"
|
|
"Virtualization|Install relevant guest agent|GUESTAGENT"
|
|
"Virtualization|Enable VFIO IOMMU support|VFIO_IOMMU"
|
|
"Network|Force APT to use IPv4|APTIPV4"
|
|
"Network|Apply network optimizations|NET"
|
|
"Network|Install Open vSwitch|OPENVSWITCH"
|
|
"Network|Enable TCP BBR/Fast Open control|TCPFASTOPEN"
|
|
"Network|Interface Names (persistent)|PERSISNET"
|
|
"Storage|Optimize ZFS ARC size|ZFSARC"
|
|
"Storage|Install ZFS auto-snapshot|ZFSAUTOSNAPSHOT"
|
|
"Storage|Enable ZFS autotrim (SSD/NVMe pools)|ZFSAUTOTRIM"
|
|
"Storage|Increase vzdump backup speed|VZDUMP"
|
|
"Security|Disable portmapper/rpcbind|DISABLERPC"
|
|
"Customization|Customize bashrc|BASHRC"
|
|
"Customization|Set up custom MOTD banner|MOTD"
|
|
"Customization|Remove subscription banner|NOSUBBANNER"
|
|
"Monitoring|Install OVH Real Time Monitoring|OVHRTM"
|
|
"Performance|Use pigz for faster gzip compression|PIGZ"
|
|
"Optional|Install and configure Fastfetch|FASTFETCH"
|
|
"Optional|Update Proxmox VE Appliance Manager|PVEAM"
|
|
"Optional|Add latest Ceph support|CEPH"
|
|
"Optional|Enable High Availability services|ENABLE_HA"
|
|
"Optional|Install Figurine|FIGURINE"
|
|
"Optional|Install and configure Log2RAM|LOG2RAM"
|
|
)
|
|
|
|
IFS=$'\n' sorted_options=($(for option in "${options[@]}"; do
|
|
IFS='|' read -r category description function_name <<< "$option"
|
|
printf "%d|%s|%s|%s\n" "${category_order[$category]:-999}" "$category" "$description" "$function_name"
|
|
done | sort -n | cut -d'|' -f2-))
|
|
unset IFS
|
|
|
|
local temp_descriptions=()
|
|
local temp_categories=()
|
|
|
|
for option in "${sorted_options[@]}"; do
|
|
IFS='|' read -r category description function_name <<< "$option"
|
|
local desc_translated
|
|
local category_translated
|
|
desc_translated="$(custom_post_description_label "$description")"
|
|
category_translated="$(custom_post_category_label "$category")"
|
|
temp_descriptions+=("$desc_translated")
|
|
temp_categories+=("$category_translated")
|
|
done
|
|
|
|
local checklist_items=()
|
|
local i=1
|
|
local desc_index=0
|
|
local previous_category=""
|
|
|
|
for option in "${sorted_options[@]}"; do
|
|
IFS='|' read -r category description function_name <<< "$option"
|
|
|
|
|
|
if [[ "$category" != "$previous_category" && "$category" == "Optional" && -n "$previous_category" ]]; then
|
|
checklist_items+=("" "==============================================================" "")
|
|
fi
|
|
|
|
local desc_translated="${temp_descriptions[$desc_index]}"
|
|
local category_translated="${temp_categories[$desc_index]}"
|
|
desc_index=$((desc_index + 1))
|
|
|
|
local line
|
|
line="$(format_custom_post_line "$desc_translated" "$category_translated")"
|
|
|
|
checklist_items+=("$i" "$line" "off")
|
|
i=$((i + 1))
|
|
previous_category="$category"
|
|
done
|
|
|
|
exec 3>&1
|
|
selected_indices=$(dialog --clear \
|
|
--backtitle "ProxMenux" \
|
|
--title "$(translate "Post-Installation Options")" \
|
|
--checklist "$HEADER" 22 88 15 \
|
|
"${checklist_items[@]}" \
|
|
2>&1 1>&3)
|
|
|
|
local dialog_exit=$?
|
|
exec 3>&-
|
|
|
|
if [[ $dialog_exit -ne 0 || -z "$selected_indices" ]]; then
|
|
exit 0
|
|
fi
|
|
|
|
|
|
|
|
|
|
declare -A selected_functions
|
|
read -ra indices_array <<< "$selected_indices"
|
|
|
|
for index in "${indices_array[@]}"; do
|
|
if [[ -z "$index" ]] || ! [[ "$index" =~ ^[0-9]+$ ]]; then
|
|
continue
|
|
fi
|
|
|
|
|
|
local item_index=$(( (index - 1) * 3 + 1 ))
|
|
if [[ $item_index -lt ${#checklist_items[@]} ]]; then
|
|
local selected_line="${checklist_items[$item_index]}"
|
|
if [[ "$selected_line" =~ ^.*(\-\-\-|===+).*$ ]]; then
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
|
|
option=${sorted_options[$((index - 1))]}
|
|
IFS='|' read -r _ description function_name <<< "$option"
|
|
selected_functions[$function_name]=1
|
|
[[ "$function_name" == "FASTFETCH" ]] && selected_functions[MOTD]=0
|
|
done
|
|
|
|
|
|
|
|
|
|
|
|
clear
|
|
show_proxmenux_logo
|
|
msg_title "$SCRIPT_TITLE"
|
|
|
|
for option in "${sorted_options[@]}"; do
|
|
IFS='|' read -r _ description function_name <<< "$option"
|
|
if [[ ${selected_functions[$function_name]} -eq 1 ]]; then
|
|
case $function_name in
|
|
REPOS) setup_proxmox_repositories ;;
|
|
APTUPGRADE) apt_upgrade ;;
|
|
TIMESYNC) configure_time_sync ;;
|
|
NOAPTLANG) skip_apt_languages ;;
|
|
UTILS) install_system_utils ;;
|
|
JOURNALD) optimize_journald ;;
|
|
LOGROTATE) optimize_logrotate ;;
|
|
LIMITS) increase_system_limits ;;
|
|
# ENTROPY removed — modern kernels 5.6+ have built-in entropy
|
|
MEMORYFIXES) optimize_memory_settings ;;
|
|
KEXEC) enable_kexec ;;
|
|
KERNELPANIC) configure_kernel_panic ;;
|
|
AMDFIXES) apply_amd_fixes ;;
|
|
GUESTAGENT) install_guest_agent ;;
|
|
VFIO_IOMMU) enable_vfio_iommu ;;
|
|
APTIPV4) force_apt_ipv4 ;;
|
|
NET) apply_network_optimizations ;;
|
|
OPENVSWITCH) install_openvswitch ;;
|
|
TCPFASTOPEN) enable_tcp_fast_open ;;
|
|
ZFSARC) optimize_zfs_arc ;;
|
|
ZFSAUTOSNAPSHOT) install_zfs_auto_snapshot ;;
|
|
ZFSAUTOTRIM) enable_zfs_autotrim ;;
|
|
VZDUMP) optimize_vzdump ;;
|
|
DISABLERPC) disable_rpc ;;
|
|
BASHRC) customize_bashrc ;;
|
|
MOTD) setup_motd ;;
|
|
NOSUBBANNER) remove_subscription_banner ;;
|
|
OVHRTM) install_ovh_rtm ;;
|
|
PIGZ) configure_pigz ;;
|
|
FASTFETCH) configure_fastfetch ;;
|
|
CEPH) install_ceph ;;
|
|
ENABLE_HA) enable_ha ;;
|
|
FIGURINE) configure_figurine ;;
|
|
LOG2RAM) configure_log2ram ;;
|
|
PVEAM) update_pve_appliance_manager ;;
|
|
PERSISNET) setup_persistent_network ;;
|
|
*) echo "Option $function_name not implemented yet" ;;
|
|
esac
|
|
fi
|
|
done
|
|
|
|
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
|
|
|
|
msg_success "$(translate "All changes applied. No reboot required.")"
|
|
msg_success "$(translate "Press Enter to return to menu...")"
|
|
read -r
|
|
clear
|
|
}
|
|
|
|
|
|
|
|
# Sprint 12B: only run the interactive menu when this script is invoked
|
|
# directly. When sourced from another script (e.g. the post-install
|
|
# update wrapper that re-runs a single function), don't trigger the
|
|
# extremeshok warning or the main menu.
|
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
check_extremeshok_warning
|
|
main_menu
|
|
fi
|