Add audit and reports page, and a change journal

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>
This commit is contained in:
MacRimi
2026-09-08 21:06:04 +02:00
co-authored by Claude Opus 5
parent ae75508eff
commit da8a480eff
102 changed files with 24118 additions and 1403 deletions
+9 -2
View File
@@ -22,6 +22,9 @@ BACKUP_DIR="/var/backups/proxmenux"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$BASE_DIR/scripts/global/pmx_journal.sh" ]]; then
source "$BASE_DIR/scripts/global/pmx_journal.sh"
fi
load_language
initialize_cache
@@ -328,6 +331,8 @@ analyze_bridge_configuration() {
}
guided_bridge_repair() {
local FUNC_VERSION="1.0"
pmx_journal_context "guided_bridge_repair" "$FUNC_VERSION"
local step=1
local total_steps=5
@@ -420,7 +425,7 @@ guided_bridge_repair() {
# Apply the change
if [ "$new_ports" != "$current_ports" ]; then
sed -i "/iface $bridge/,/bridge-ports/ s/bridge-ports.*/bridge-ports $new_ports/" /etc/network/interfaces
pmx_edit_file /etc/network/interfaces "/iface $bridge/,/bridge-ports/ s/bridge-ports.*/bridge-ports $new_ports/"
fi
fi
done
@@ -567,6 +572,8 @@ analyze_network_configuration() {
}
guided_configuration_cleanup() {
local FUNC_VERSION="1.0"
pmx_journal_context "guided_configuration_cleanup" "$FUNC_VERSION"
local step=1
local total_steps=5
@@ -645,7 +652,7 @@ guided_configuration_cleanup() {
--infobox "$(translate "Removing invalid configurations...")\n\n$(translate "This may take a few seconds...")" 8 50
for iface in $interfaces_to_remove; do
sed -i "/^iface $iface/,/^$/d" /etc/network/interfaces
pmx_edit_file /etc/network/interfaces "/^iface $iface/,/^$/d"
done
((step++))
+17 -6
View File
@@ -12,6 +12,9 @@ TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
load_language
initialize_cache
@@ -84,6 +87,8 @@ lvm_repair_check() {
cleanup_duplicate_repos_pve9() {
local FUNC_VERSION="1.0"
pmx_journal_context "cleanup_duplicate_repos_pve9" "$FUNC_VERSION"
msg_info "$(translate "Cleaning up duplicate repositories...")"
local sources_file="/etc/apt/sources.list"
@@ -152,7 +157,8 @@ cleanup_duplicate_repos_pve9() {
if [[ "$file_changed" -eq 1 ]]; then
_backup_once "$sources_file"
mv "$temp_file" "$sources_file"
pmx_write_file "$sources_file" < "$temp_file"
rm -f "$temp_file"
chmod 644 "$sources_file"
else
rm -f "$temp_file"
@@ -201,7 +207,7 @@ cleanup_duplicate_repos_pve9() {
esc_uri=$(printf '%s' "$uri" | sed 's/[][\.^$*/]/\\&/g')
esc_suite=$(printf '%s' "$suite" | sed 's/[][\.^$*/]/\\&/g')
esc_comp=$(printf '%s' "$first_comp" | sed 's/[][\.^$*/]/\\&/g')
sed -i -E "/^deb[[:space:]]+${esc_uri}[[:space:]]+${esc_suite}[[:space:]]+.*(^| )${esc_comp}( |$)/s/^/# /" "$target_file"
pmx_edit_file "$target_file" -E "/^deb[[:space:]]+${esc_uri}[[:space:]]+${esc_suite}[[:space:]]+.*(^| )${esc_comp}( |$)/s/^/# /"
cleaned_count=$((cleaned_count + 1))
fi
}
@@ -240,7 +246,7 @@ cleanup_duplicate_repos_pve9() {
for old_file in /etc/apt/sources.list.d/pve-public-repo.list /etc/apt/sources.list.d/pve-install-repo.list; do
if [ -f "$old_file" ]; then
_backup_once "$old_file"
rm -f "$old_file"
pmx_remove_file "$old_file"
cleaned_count=$((cleaned_count + 1))
fi
done
@@ -248,6 +254,7 @@ cleanup_duplicate_repos_pve9() {
if [ $cleaned_count -gt 0 ]; then
msg_ok "$(translate "Cleaned up $cleaned_count duplicate/old repositories")"
pmx_record_execution "Update package lists after repository cleanup" "apt-get update"
apt-get update > /dev/null 2>&1 || true
else
msg_ok "$(translate "No duplicate repositories found")"
@@ -257,6 +264,8 @@ cleanup_duplicate_repos_pve9() {
cleanup_duplicate_repos_pve9_() {
local FUNC_VERSION="1.0"
pmx_journal_context "cleanup_duplicate_repos_pve9_" "$FUNC_VERSION"
msg_info "$(translate "Cleaning up duplicate repositories...")"
local sources_file="/etc/apt/sources.list"
@@ -285,7 +294,8 @@ cleanup_duplicate_repos_pve9_() {
fi
done < "$sources_file"
mv "$temp_file" "$sources_file"
pmx_write_file "$sources_file" < "$temp_file"
rm -f "$temp_file"
chmod 644 "$sources_file"
for src in proxmox debian ceph; do
@@ -308,7 +318,7 @@ cleanup_duplicate_repos_pve9_() {
if [[ -n "$url_match" ]]; then
if grep -q "^deb.*$url_match" "$sources_file"; then
sed -i "/^deb.*$url_match/s/^/# /" "$sources_file"
pmx_edit_file "$sources_file" "/^deb.*$url_match/s/^/# /"
cleaned_count=$((cleaned_count + 1))
fi
fi
@@ -316,7 +326,7 @@ cleanup_duplicate_repos_pve9_() {
for list_file in /etc/apt/sources.list.d/*.list; do
[[ -f "$list_file" ]] || continue
if grep -q "^deb.*$url_match" "$list_file"; then
sed -i "/^deb.*$url_match/s/^/# /" "$list_file"
pmx_edit_file "$list_file" "/^deb.*$url_match/s/^/# /"
cleaned_count=$((cleaned_count + 1))
fi
done
@@ -325,6 +335,7 @@ cleanup_duplicate_repos_pve9_() {
if [ $cleaned_count -gt 0 ]; then
msg_ok "$(translate "Cleaned up $cleaned_count duplicate/old repositories")"
pmx_record_execution "Update package lists after repository cleanup" "apt-get update"
apt-get update > /dev/null 2>&1 || true
else
msg_ok "$(translate "No duplicate repositories found")"
+31 -12
View File
@@ -5,6 +5,10 @@ if [[ -n "${__PROXMENUX_PCI_PASSTHROUGH_HELPERS__:-}" ]]; then
fi
__PROXMENUX_PCI_PASSTHROUGH_HELPERS__=1
if [[ -f /usr/local/share/proxmenux/scripts/global/pmx_journal.sh ]]; then
source /usr/local/share/proxmenux/scripts/global/pmx_journal.sh
fi
function _pci_is_iommu_active() {
grep -qE 'intel_iommu=on|amd_iommu=on' /proc/cmdline 2>/dev/null || return 1
[[ -d /sys/kernel/iommu_groups ]] || return 1
@@ -497,6 +501,8 @@ _proxmenux_vfio_bind_add_bdfs() {
}
_proxmenux_vfio_bind_remove_bdfs() {
local FUNC_VERSION="1.0"
pmx_journal_context "_proxmenux_vfio_bind_remove_bdfs" "$FUNC_VERSION"
# Args: any number of BDFs to remove from the binder list
[[ -f "$PROXMENUX_VFIO_BIND_STATE" ]] || return 0
_proxmenux_vfio_bind_cleanup_legacy
@@ -511,13 +517,14 @@ _proxmenux_vfio_bind_remove_bdfs() {
else
normalized="0000:${bdf}"
fi
sed -i "\|^${normalized}\$|d" "$tmp"
sed "\|^${normalized}\$|d" "$tmp" > "${tmp}.next" && mv "${tmp}.next" "$tmp"
done
if ! cmp -s "$tmp" "$PROXMENUX_VFIO_BIND_STATE"; then
mv "$tmp" "$PROXMENUX_VFIO_BIND_STATE"
pmx_write_file "$PROXMENUX_VFIO_BIND_STATE" < "$tmp"
rm -f "$tmp"
_proxmenux_vfio_bind_write_udev_rule
# If empty, remove state file too (keeps host clean)
[[ ! -s "$PROXMENUX_VFIO_BIND_STATE" ]] && rm -f "$PROXMENUX_VFIO_BIND_STATE"
[[ ! -s "$PROXMENUX_VFIO_BIND_STATE" ]] && pmx_remove_file "$PROXMENUX_VFIO_BIND_STATE"
_proxmenux_nvidia_vfio_policy_sync || true
_proxmenux_mark_host_config_changed
else
@@ -598,9 +605,11 @@ EOF
}
_proxmenux_nvidia_vfio_softdeps_sync() {
local FUNC_VERSION="1.0"
pmx_journal_context "_proxmenux_nvidia_vfio_softdeps_sync" "$FUNC_VERSION"
local changed=1
mkdir -p "$(dirname "$PROXMENUX_VFIO_CONF")"
touch "$PROXMENUX_VFIO_CONF"
[[ -f "$PROXMENUX_VFIO_CONF" ]] || pmx_write_file "$PROXMENUX_VFIO_CONF" < /dev/null
local -a softdeps=(
"softdep nvidia pre: vfio-pci"
@@ -612,14 +621,14 @@ _proxmenux_nvidia_vfio_softdeps_sync() {
if _proxmenux_vfio_bind_state_has_vendor "10de"; then
for line in "${softdeps[@]}"; do
if ! grep -qFx "$line" "$PROXMENUX_VFIO_CONF" 2>/dev/null; then
echo "$line" >> "$PROXMENUX_VFIO_CONF"
echo "$line" | pmx_append_file "$PROXMENUX_VFIO_CONF"
changed=0
fi
done
else
for line in "${softdeps[@]}"; do
if grep -qFx "$line" "$PROXMENUX_VFIO_CONF" 2>/dev/null; then
sed -i "\|^${line}$|d" "$PROXMENUX_VFIO_CONF"
pmx_edit_file "$PROXMENUX_VFIO_CONF" "\|^${line}$|d"
changed=0
fi
done
@@ -779,6 +788,8 @@ _proxmenux_vfio_bind_migrate_legacy_nvidia_ids() {
# passed through.
# ──────────────────────────────────────────────────────────────────────
_proxmenux_nvidia_migrate_legacy_blacklist() {
local FUNC_VERSION="1.0"
pmx_journal_context "_proxmenux_nvidia_migrate_legacy_blacklist" "$FUNC_VERSION"
local changed=false
local blacklist_file="${PROXMENUX_ETC_ROOT}/modprobe.d/blacklist.conf"
local nvidia_blacklist="${PROXMENUX_ETC_ROOT}/modprobe.d/nvidia-blacklist.conf"
@@ -788,29 +799,37 @@ _proxmenux_nvidia_migrate_legacy_blacklist() {
local modules_load_active="${PROXMENUX_ETC_ROOT}/modules-load.d/nvidia-vfio.conf"
if [[ -f "$blacklist_file" ]] && grep -qE '^blacklist (nvidia|nvidia_drm|nvidia_modeset|nvidia_uvm|nvidiafb)$' "$blacklist_file"; then
sed -i \
pmx_edit_file "$blacklist_file" \
-e '/^blacklist nvidia$/d' \
-e '/^blacklist nvidia_drm$/d' \
-e '/^blacklist nvidia_modeset$/d' \
-e '/^blacklist nvidia_uvm$/d' \
-e '/^blacklist nvidiafb$/d' \
"$blacklist_file"
-e '/^blacklist nvidiafb$/d'
changed=true
fi
if [[ -f "$nvidia_blacklist" ]]; then
rm -f "$nvidia_blacklist"
pmx_remove_file "$nvidia_blacklist"
changed=true
fi
if [[ -f "$udev_disabled" ]]; then
mv "$udev_disabled" "$udev_rules" >/dev/null 2>&1 || true
if pmx_write_file "$udev_rules" < "$udev_disabled"; then
chmod --reference="$udev_disabled" "$udev_rules" 2>/dev/null || true
chown --reference="$udev_disabled" "$udev_rules" 2>/dev/null || true
pmx_remove_file "$udev_disabled" || true
fi
pmx_record_execution "Reload udev rules" "udevadm control --reload-rules"
udevadm control --reload-rules >/dev/null 2>&1 || true
changed=true
fi
if [[ -f "$modules_load_disabled" ]]; then
mv "$modules_load_disabled" "$modules_load_active" >/dev/null 2>&1 || true
if pmx_write_file "$modules_load_active" < "$modules_load_disabled"; then
chmod --reference="$modules_load_disabled" "$modules_load_active" 2>/dev/null || true
chown --reference="$modules_load_disabled" "$modules_load_active" 2>/dev/null || true
pmx_remove_file "$modules_load_disabled" || true
fi
changed=true
fi
+416
View File
@@ -0,0 +1,416 @@
#!/usr/bin/env bash
# ProxMenux change journal — recording side.
#
# What a sysadmin holds against a tool like this one is not that it
# changes things: it is that afterwards nobody can say what it changed.
# Reading the script does not answer it either — a function of four
# hundred lines may alter two values, and the reader has no way to know
# which two.
#
# So the rule here is that a change is recorded because it could not be
# made any other way. These helpers are the writing path: they capture
# what was there, make the change, and record both. A function that uses
# them is auditable without its author having remembered anything, and a
# function that writes directly is a bug we can find by grepping.
#
# Nothing here needs sqlite, python or network access. Each entry is one
# small JSON file written whole into a spool directory, which the Monitor
# reads and consolidates. One file per entry means no two concurrent
# scripts can interleave a line, and an interrupted write leaves a file
# the reader skips rather than a corrupted log.
#
# Usage:
# source /usr/local/share/proxmenux/scripts/pmx_journal.sh
# pmx_journal_context "optimize_logrotate" "1.1"
# pmx_write_file /etc/logrotate.conf <<EOF
# ...
# EOF
# pmx_enable_service log2ram
#
# Everything degrades quietly: if the journal cannot be written, the
# change still happens. Recording must never be the reason an operation
# fails on somebody's host.
PMX_JOURNAL_ROOT="${PMX_JOURNAL_ROOT:-/usr/local/share/proxmenux/changes}"
PMX_JOURNAL_SPOOL="$PMX_JOURNAL_ROOT/spool"
PMX_JOURNAL_OBJECTS="$PMX_JOURNAL_ROOT/objects"
# Set by pmx_journal_context; every entry carries them.
PMX_JOURNAL_FUNCTION="${PMX_JOURNAL_FUNCTION:-}"
PMX_JOURNAL_VERSION="${PMX_JOURNAL_VERSION:-}"
PMX_JOURNAL_SOURCE="${PMX_JOURNAL_SOURCE:-${SCRIPT_SOURCE:-}}"
# Which function is making the changes that follow. Called once at the
# top of a function, so the entries it produces are attributable to it
# rather than to whichever script happened to source this file.
pmx_journal_context() {
PMX_JOURNAL_FUNCTION="${1:-unknown}"
PMX_JOURNAL_VERSION="${2:-}"
PMX_JOURNAL_SOURCE="${3:-${SCRIPT_SOURCE:-$(basename "${BASH_SOURCE[-1]:-unknown}")}}"
}
_pmx_journal_ready() {
mkdir -p "$PMX_JOURNAL_SPOOL" "$PMX_JOURNAL_OBJECTS" 2>/dev/null || return 1
chmod 700 "$PMX_JOURNAL_ROOT" 2>/dev/null || true
return 0
}
# JSON string escaping in pure bash: no jq dependency on the recording
# side, because the recording side runs before anything is installed.
_pmx_json_escape() {
local text="$1"
text="${text//\\/\\\\}"
text="${text//\"/\\\"}"
text="${text//$'\n'/\\n}"
text="${text//$'\r'/\\r}"
text="${text//$'\t'/\\t}"
printf '%s' "$text"
}
# The largest file whose contents are worth keeping. Configuration is
# measured in kilobytes; a binary is measured in megabytes and shows no
# useful difference, so past this the journal records that the file was
# there and what it hashed to, and stops short of copying it. A host that
# fills its disk with captured binaries is a worse outcome than a change
# whose contents cannot be shown.
PMX_JOURNAL_MAX_OBJECT="${PMX_JOURNAL_MAX_OBJECT:-1048576}"
# Set by _pmx_store_object. Reported through globals rather than printed
# because a command substitution runs in a subshell: anything the helper
# set there would be lost on the way back, and the caller would record
# every capture as unrecoverable.
PMX_LAST_DIGEST=""
PMX_LAST_OBJECT_STORED=false
# Stores a file's contents and returns its digest, so an entry references
# the bytes rather than embedding them. Content is kept once however many
# times it is captured.
_pmx_store_object() {
local path="$1"
PMX_LAST_DIGEST=""
PMX_LAST_OBJECT_STORED=false
[ -f "$path" ] || return 1
local digest
digest="$(sha256sum "$path" 2>/dev/null | cut -d' ' -f1)" || return 1
[ -n "$digest" ] || return 1
PMX_LAST_DIGEST="$digest"
local size
size="$(stat -c %s "$path" 2>/dev/null || echo 0)"
if [ "$size" -gt "$PMX_JOURNAL_MAX_OBJECT" ] 2>/dev/null; then
# The digest still identifies what was there; the bytes are not
# kept, and the entry will say the change cannot be undone from
# the journal alone.
return 0
fi
local target="$PMX_JOURNAL_OBJECTS/${digest:0:2}/$digest"
if [ ! -f "$target" ]; then
mkdir -p "$(dirname "$target")" 2>/dev/null || return 1
cp "$path" "$target.tmp.$$" 2>/dev/null || return 1
chmod 600 "$target.tmp.$$" 2>/dev/null || true
mv "$target.tmp.$$" "$target" 2>/dev/null || return 1
fi
PMX_LAST_OBJECT_STORED=true
}
# Writes one entry. Callers pass key=value pairs; values are escaped
# here so no caller has to think about JSON.
_pmx_journal_record() {
_pmx_journal_ready || return 0
local entry="" key value first=1
for pair in "$@"; do
key="${pair%%=*}"
value="${pair#*=}"
[ "$first" = 1 ] && first=0 || entry+=","
# A key ending in _raw carries a number or a literal such as
# true/false/null and is written unquoted.
if [ "${key%_raw}" != "$key" ]; then
entry+="\"${key%_raw}\":${value}"
else
entry+="\"$key\":\"$(_pmx_json_escape "$value")\""
fi
done
local file
file="$PMX_JOURNAL_SPOOL/$(date +%s)-$$-${RANDOM}.json"
printf '{%s}\n' "$entry" > "$file.tmp" 2>/dev/null || return 0
chmod 600 "$file.tmp" 2>/dev/null || true
mv "$file.tmp" "$file" 2>/dev/null || true
return 0
}
_pmx_journal_common() {
printf '%s\n' \
"recorded_at_raw=$(date +%s)" \
"function=${PMX_JOURNAL_FUNCTION:-unknown}" \
"function_version=${PMX_JOURNAL_VERSION:-}" \
"source=${PMX_JOURNAL_SOURCE:-unknown}"
}
# ---------------------------------------------------------------------
# Configuration: files this host had, and what they became
# ---------------------------------------------------------------------
# Replaces a file with what arrives on stdin, capturing what was there.
#
# pmx_write_file /etc/logrotate.conf <<EOF
# ...
# EOF
pmx_write_file() {
local path="$1"
local temp before after existed="false"
temp="$(mktemp)" || { cat > "$path"; return $?; }
cat > "$temp"
local kept="true"
if [ -f "$path" ]; then
existed="true"
_pmx_store_object "$path"
before="$PMX_LAST_DIGEST"; kept="$PMX_LAST_OBJECT_STORED"
fi
# The change itself. Permissions of an existing file are preserved by
# writing through it rather than replacing the inode.
if ! cat "$temp" > "$path" 2>/dev/null; then
rm -f "$temp"
return 1
fi
_pmx_store_object "$path"; after="$PMX_LAST_DIGEST"
rm -f "$temp"
# Writing the same bytes back is not a change. Recording it would
# fill the journal with entries a reader has to open to discover
# nothing happened — which is exactly what re-running an idempotent
# post-install does.
[ "$before" = "$after" ] && return 0
local -a fields
mapfile -t fields < <(_pmx_journal_common)
_pmx_journal_record "${fields[@]}" \
"class=configuration" "operation=write_file" "target=$path" \
"before=${before:-}" "after=${after:-}" \
"existed_raw=$existed" \
"capture=$([ "$existed" = true ] && echo present || echo created)" \
"revert=$([ "$existed" = true ] && echo restore || echo remove)" \
"exactness=$([ "$existed" != true ] || [ "$kept" = true ] && echo exact || echo none)"
}
# Applies a sed expression in place, capturing the file first.
#
# pmx_edit_file /etc/default/grub 's/^X=.*/X=1/'
pmx_edit_file() {
local path="$1"; shift
[ -f "$path" ] || return 1
local before after kept
_pmx_store_object "$path"
before="$PMX_LAST_DIGEST"; kept="$PMX_LAST_OBJECT_STORED"
sed -i "$@" "$path" || return 1
_pmx_store_object "$path"; after="$PMX_LAST_DIGEST"
# An expression that matched nothing is not a change, and recording
# it would fill the journal with entries a reader has to dismiss.
[ "$before" = "$after" ] && return 0
local -a fields
mapfile -t fields < <(_pmx_journal_common)
_pmx_journal_record "${fields[@]}" \
"class=configuration" "operation=edit_file" "target=$path" \
"before=${before:-}" "after=${after:-}" \
"expression=$*" "capture=present" "revert=restore" \
"exactness=$([ "$kept" = true ] && echo exact || echo none)"
}
# Removes a file, keeping its contents so the removal can be undone.
pmx_remove_file() {
local path="$1"
[ -e "$path" ] || return 0
local before kept
_pmx_store_object "$path"
before="$PMX_LAST_DIGEST"; kept="$PMX_LAST_OBJECT_STORED"
rm -f "$path" || return 1
local -a fields
mapfile -t fields < <(_pmx_journal_common)
_pmx_journal_record "${fields[@]}" \
"class=configuration" "operation=remove_file" "target=$path" \
"before=${before:-}" "after=" "capture=present" \
"revert=restore" \
"exactness=$([ "$kept" = true ] && echo exact || echo none)"
}
# Adds to a file, keeping what was there.
#
# Appending looks like it needs no capture — the previous content is
# still in the file — but the journal shows a change as the difference
# between two states, and a reader asking what a function did to a file
# should not have to reconstruct the first state by subtracting.
#
# printf 'ulimit -n 1048576\n' | pmx_append_file /root/.profile
pmx_append_file() {
local path="$1"
local temp before after existed="false"
temp="$(mktemp)" || { cat >> "$path"; return $?; }
cat > "$temp"
local kept="true"
if [ -f "$path" ]; then
existed="true"
_pmx_store_object "$path"
before="$PMX_LAST_DIGEST"; kept="$PMX_LAST_OBJECT_STORED"
fi
if ! cat "$temp" >> "$path" 2>/dev/null; then
rm -f "$temp"
return 1
fi
_pmx_store_object "$path"; after="$PMX_LAST_DIGEST"
rm -f "$temp"
[ "$before" = "$after" ] && return 0
local -a fields
mapfile -t fields < <(_pmx_journal_common)
_pmx_journal_record "${fields[@]}" \
"class=configuration" "operation=append_file" "target=$path" \
"before=${before:-}" "after=${after:-}" \
"capture=$([ "$existed" = true ] && echo present || echo created)" \
"revert=$([ "$existed" = true ] && echo restore || echo remove)" \
"exactness=$([ "$existed" != true ] || [ "$kept" = true ] && echo exact || echo none)"
}
# Applies a setting through the command that owns it, capturing the
# state that command reports before and after.
#
# Some settings have no file to write: the timezone, whether the clock is
# disciplined, a bootloader entry. The tool that owns them is the only
# thing that can read them back, so it is asked twice — before and after
# — and the journal records the two answers.
#
# pmx_apply_setting "timezone" "timedatectl show -p Timezone --value" \
# timedatectl set-timezone "$timezone"
pmx_apply_setting() {
local name="$1" reader="$2"; shift 2
local before after
before="$(eval "$reader" 2>/dev/null | head -c 400)"
"$@" >/dev/null 2>&1
local status=$?
after="$(eval "$reader" 2>/dev/null | head -c 400)"
# A setting already at the wanted value is not a change.
[ "$before" = "$after" ] && return $status
local -a fields
mapfile -t fields < <(_pmx_journal_common)
_pmx_journal_record "${fields[@]}" \
"class=configuration" "operation=apply_setting" "target=$name" \
"before_state=$before" "after_state=$after" "command=$*" \
"capture=present" "revert=reapply" \
"result=$([ $status -eq 0 ] && echo ok || echo failed)" \
"exactness=exact"
return $status
}
# ---------------------------------------------------------------------
# Installation: what was not on this host and now is
# ---------------------------------------------------------------------
# Installs packages, recording which ones actually arrived.
#
# What is recorded is the difference the operation made, not what was
# asked for: a package already present is not a change, and the
# dependencies apt pulled in are, even though nobody named them.
pmx_install_pkg() {
local -a requested=("$@")
[ ${#requested[@]} -gt 0 ] || return 0
local before_list after_list added
before_list="$(dpkg-query -W -f='${binary:Package}\n' 2>/dev/null | sort -u)"
DEBIAN_FRONTEND=noninteractive apt-get install -y "${requested[@]}" >/dev/null 2>&1
local status=$?
after_list="$(dpkg-query -W -f='${binary:Package}\n' 2>/dev/null | sort -u)"
added="$(comm -13 <(printf '%s\n' "$before_list") <(printf '%s\n' "$after_list") | tr '\n' ' ')"
local -a fields
mapfile -t fields < <(_pmx_journal_common)
_pmx_journal_record "${fields[@]}" \
"class=installation" "operation=install_package" \
"target=${requested[*]}" "installed=${added% }" \
"result=$([ $status -eq 0 ] && echo ok || echo failed)" \
"capture=present" "revert=purge" \
"exactness=$([ -n "${added// /}" ] && echo partial || echo none)"
return $status
}
# ---------------------------------------------------------------------
# Services: what was running, and what runs now
# ---------------------------------------------------------------------
_pmx_service_state() {
local unit="$1"
printf '%s/%s' \
"$(systemctl is-enabled "$unit" 2>/dev/null || echo unknown)" \
"$(systemctl is-active "$unit" 2>/dev/null || echo unknown)"
}
pmx_enable_service() {
local unit="$1"
local before after
before="$(_pmx_service_state "$unit")"
systemctl enable --now "$unit" >/dev/null 2>&1
local status=$?
after="$(_pmx_service_state "$unit")"
[ "$before" = "$after" ] && return $status
local -a fields
mapfile -t fields < <(_pmx_journal_common)
_pmx_journal_record "${fields[@]}" \
"class=configuration" "operation=enable_service" "target=$unit" \
"before_state=$before" "after_state=$after" \
"capture=present" "revert=disable" "exactness=exact"
return $status
}
pmx_disable_service() {
local unit="$1"
local before after
before="$(_pmx_service_state "$unit")"
systemctl disable --now "$unit" >/dev/null 2>&1
local status=$?
after="$(_pmx_service_state "$unit")"
[ "$before" = "$after" ] && return $status
local -a fields
mapfile -t fields < <(_pmx_journal_common)
_pmx_journal_record "${fields[@]}" \
"class=configuration" "operation=disable_service" "target=$unit" \
"before_state=$before" "after_state=$after" \
"capture=present" "revert=enable" "exactness=exact"
return $status
}
# ---------------------------------------------------------------------
# Execution: what ProxMenux ran on the user's behalf
# ---------------------------------------------------------------------
# For work ProxMenux launches but does not decide: a system upgrade, a
# rebuild. Recording it as a change of ours would claim authorship of
# whatever apt decided; recording nothing would leave a host that changed
# under the reader's feet with no trace of why.
pmx_record_execution() {
local description="$1"; shift
local command="$*"
local -a fields
mapfile -t fields < <(_pmx_journal_common)
_pmx_journal_record "${fields[@]}" \
"class=execution" "operation=run_command" \
"target=$description" "command=$command" \
"capture=none" "revert=none" "exactness=none"
}
# Records that a function was applied without being able to say what it
# changed — the state before it ran is not knowable. Used by the
# registration path so a host carries an honest account of what was
# applied before the journal existed.
pmx_record_applied() {
local tool="$1" version="$2" state="${3:-applied}"
local -a fields
mapfile -t fields < <(_pmx_journal_common)
_pmx_journal_record "${fields[@]}" \
"class=registration" "operation=$state" "target=$tool" \
"function_version=$version" "capture=unknown" \
"revert=none" "exactness=none"
}
+6 -1
View File
@@ -17,6 +17,9 @@ TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f /usr/local/share/proxmenux/scripts/global/pmx_journal.sh ]]; then
source /usr/local/share/proxmenux/scripts/global/pmx_journal.sh
fi
load_language
initialize_cache
@@ -77,7 +80,9 @@ create_backup() {
# Create the patch script that will be called by APT hook
create_patch_script() {
cat > "$PATCH_BIN" <<'EOFPATCH'
local FUNC_VERSION="1.0"
pmx_journal_context "create_patch_script" "$FUNC_VERSION"
pmx_write_file "$PATCH_BIN" <<'EOFPATCH'
#!/usr/bin/env bash
# ==========================================================
# Proxmox Subscription Banner Patch (v3 - Minimal)
+14 -6
View File
@@ -10,6 +10,9 @@ TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
load_language
initialize_cache
@@ -27,6 +30,8 @@ register_tool() {
}
remove_subscription_banner_pve8() {
local FUNC_VERSION="1.0"
pmx_journal_context "remove_subscription_banner_pve8" "$FUNC_VERSION"
local JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
local GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
local APT_HOOK="/etc/apt/apt.conf.d/no-nag-script"
@@ -50,17 +55,20 @@ remove_subscription_banner_pve8() {
cp "$JS_FILE" "$BACKUP_FILE"
sed -i "s/No valid subscription/Subscription active/g" "$JS_FILE"
sed -i "s/Ext.Msg.WARNING/Ext.Msg.INFO/g" "$JS_FILE"
sed -i "s/res.data.status.toLowerCase() !== 'active'/false/g" "$JS_FILE"
sed -i "s/subscriptionActive: ''/subscriptionActive: true/g" "$JS_FILE"
pmx_edit_file "$JS_FILE" \
-e "s/No valid subscription/Subscription active/g" \
-e "s/Ext.Msg.WARNING/Ext.Msg.INFO/g" \
-e "s/res.data.status.toLowerCase() !== 'active'/false/g" \
-e "s/subscriptionActive: ''/subscriptionActive: true/g"
[[ -f "$GZ_FILE" ]] && rm -f "$GZ_FILE"
[[ -f "$GZ_FILE" ]] && pmx_remove_file "$GZ_FILE"
pmx_record_execution "Clear cached Proxmox JavaScript files" "find /var/cache/pve-manager/ -name *.js* -delete"
find /var/cache/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
pmx_record_execution "Clear generated Proxmox JavaScript files" "find /var/lib/pve-manager/ -name *.js* -delete"
find /var/lib/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
[[ -f "$APT_HOOK" ]] && rm -f "$APT_HOOK"
[[ -f "$APT_HOOK" ]] && pmx_remove_file "$APT_HOOK"
msg_ok "Subscription banner removed successfully."
+25 -10
View File
@@ -12,6 +12,9 @@ TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
load_language
initialize_cache
@@ -34,6 +37,8 @@ download_common_functions() {
}
update_pve8() {
local FUNC_VERSION="1.0"
pmx_journal_context "update_pve8" "$FUNC_VERSION"
local start_time=$(date +%s)
local log_file="/var/log/proxmox-update-$(date +%Y%m%d-%H%M%S).log"
local changes_made=false
@@ -67,20 +72,20 @@ update_pve8() {
if [ -f /etc/apt/sources.list.d/pve-enterprise.list ] && grep -q "^deb" /etc/apt/sources.list.d/pve-enterprise.list; then
sed -i "s/^deb/#deb/g" /etc/apt/sources.list.d/pve-enterprise.list
pmx_edit_file /etc/apt/sources.list.d/pve-enterprise.list "s/^deb/#deb/g"
msg_ok "$(translate "Enterprise Proxmox repository disabled")"
changes_made=true
fi
if [ -f /etc/apt/sources.list.d/ceph.list ] && grep -q "^deb" /etc/apt/sources.list.d/ceph.list; then
sed -i "s/^deb/#deb/g" /etc/apt/sources.list.d/ceph.list
pmx_edit_file /etc/apt/sources.list.d/ceph.list "s/^deb/#deb/g"
msg_ok "$(translate "Enterprise Proxmox Ceph repository disabled")"
changes_made=true
fi
if [ ! -f /etc/apt/sources.list.d/pve-public-repo.list ] || ! grep -q "pve-no-subscription" /etc/apt/sources.list.d/pve-public-repo.list; then
echo "deb http://download.proxmox.com/debian/pve $OS_CODENAME pve-no-subscription" > /etc/apt/sources.list.d/pve-public-repo.list
echo "deb http://download.proxmox.com/debian/pve $OS_CODENAME pve-no-subscription" | pmx_write_file /etc/apt/sources.list.d/pve-public-repo.list
msg_ok "$(translate "Free public Proxmox repository enabled")"
changes_made=true
fi
@@ -90,14 +95,15 @@ update_pve8() {
cp "$sources_file" "${sources_file}.backup.$(date +%Y%m%d_%H%M%S)"
if grep -q -E "(debian-security -security|debian main$|debian -updates)" "$sources_file"; then
sed -i '/^deb.*debian-security -security/d' "$sources_file"
sed -i '/^deb.*debian main$/d' "$sources_file"
sed -i '/^deb.*debian -updates/d' "$sources_file"
pmx_edit_file "$sources_file" \
-e '/^deb.*debian-security -security/d' \
-e '/^deb.*debian main$/d' \
-e '/^deb.*debian -updates/d'
changes_made=true
msg_ok "$(translate "Malformed repository entries cleaned")"
fi
cat > "$sources_file" << EOF
pmx_write_file "$sources_file" << EOF
# Debian $OS_CODENAME repositories
deb http://deb.debian.org/debian $OS_CODENAME main contrib non-free non-free-firmware
deb http://deb.debian.org/debian $OS_CODENAME-updates main contrib non-free non-free-firmware
@@ -108,12 +114,13 @@ EOF
local firmware_conf="/etc/apt/apt.conf.d/no-firmware-warnings.conf"
if [ ! -f "$firmware_conf" ]; then
echo 'APT::Get::Update::SourceListWarnings::NonFreeFirmware "false";' > "$firmware_conf"
echo 'APT::Get::Update::SourceListWarnings::NonFreeFirmware "false";' | pmx_write_file "$firmware_conf"
fi
cleanup_duplicate_repos
msg_info "$(translate "Updating package lists...")"
pmx_record_execution "Update package lists" "apt-get update"
if apt-get update > "$log_file" 2>&1; then
msg_ok "$(translate "Package lists updated successfully")"
else
@@ -159,12 +166,16 @@ EOF
if [[ $MENU_RESULT -eq 1 ]]; then
msg_info2 "$(translate "Update cancelled by user")"
pmx_record_execution "Remove unused packages" "apt-get -y autoremove"
apt-get -y autoremove > /dev/null 2>&1 || true
pmx_record_execution "Clean downloaded package cache" "apt-get -y autoclean"
apt-get -y autoclean > /dev/null 2>&1 || true
return 0
elif [[ $MENU_RESULT -eq 2 ]]; then
msg_ok "$(translate "System is already up to date. No update needed.")"
pmx_record_execution "Remove unused packages" "apt-get -y autoremove"
apt-get -y autoremove > /dev/null 2>&1 || true
pmx_record_execution "Clean downloaded package cache" "apt-get -y autoclean"
apt-get -y autoclean > /dev/null 2>&1 || true
return 0
fi
@@ -173,6 +184,7 @@ EOF
local conflicting_packages=$(dpkg -l 2>/dev/null | grep -E "^ii.*(ntp|openntpd|systemd-timesyncd)" | awk '{print $2}')
if [ -n "$conflicting_packages" ]; then
msg_info "$(translate "Removing conflicting utilities...")"
pmx_record_execution "Purge conflicting time services" "apt-get -y purge $conflicting_packages"
DEBIAN_FRONTEND=noninteractive apt-get -y purge $conflicting_packages >> "$log_file" 2>&1
msg_ok "$(translate "Conflicting utilities removed")"
fi
@@ -185,7 +197,7 @@ EOF
export DPKG_OPTIONS="--force-confdef --force-confold"
msg_info "$(translate "Performing packages upgrade...")"
apt-get install pv -y > /dev/null 2>&1
pmx_install_pkg pv
total_packages=$(apt-get -s dist-upgrade | grep "^Inst" | wc -l)
msg_ok "$(translate "Packages upgrade successfull")"
@@ -196,6 +208,7 @@ EOF
tput civis
tput sc
pmx_record_execution "Upgrade Proxmox VE 8 packages" "apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold dist-upgrade"
(
/usr/bin/env \
DEBIAN_FRONTEND=noninteractive \
@@ -250,7 +263,7 @@ EOF
if [ ${#missing_packages[@]} -gt 0 ]; then
msg_info "$(translate "Installing essential Proxmox packages...")"
DEBIAN_FRONTEND=noninteractive apt-get -y install "${missing_packages[@]}" >> "$log_file" 2>&1
pmx_install_pkg "${missing_packages[@]}"
msg_ok "$(translate "Essential Proxmox packages installed")"
fi
@@ -258,7 +271,9 @@ EOF
cleanup_duplicate_repos
msg_info "$(translate "Performing system cleanup...")"
pmx_record_execution "Remove unused packages" "apt-get -y autoremove"
apt-get -y autoremove > /dev/null 2>&1 || true
pmx_record_execution "Clean downloaded package cache" "apt-get -y autoclean"
apt-get -y autoclean > /dev/null 2>&1 || true
msg_ok "$(translate "Cleanup finished")"
+28 -10
View File
@@ -13,6 +13,9 @@ APT_ENV="env DEBIAN_FRONTEND=noninteractive LC_ALL=C LANG=C"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
load_language
initialize_cache
@@ -35,6 +38,8 @@ download_common_functions() {
}
update_pve9() {
local FUNC_VERSION="1.0"
pmx_journal_context "update_pve9" "$FUNC_VERSION"
local pve_version
pve_version=$(pveversion | awk -F'/' '{print $2}' | cut -d'-' -f1)
local start_time
@@ -79,17 +84,17 @@ update_pve9() {
disable_sources_repo() {
local file="$1"
if [[ -f "$file" ]]; then
sed -i ':a;/^\n*$/{$d;N;ba}' "$file"
pmx_edit_file "$file" ':a;/^\n*$/{$d;N;ba}'
if grep -q "^Enabled:" "$file"; then
sed -i 's/^Enabled:.*$/Enabled: false/' "$file"
pmx_edit_file "$file" 's/^Enabled:.*$/Enabled: false/'
else
echo "Enabled: false" >> "$file"
echo "Enabled: false" | pmx_append_file "$file"
fi
if ! grep -q "^Types: " "$file"; then
msg_warn "$(translate "Malformed .sources file detected, removing: $(basename "$file")")"
rm -f "$file"
pmx_remove_file "$file"
fi
return 0
fi
@@ -110,18 +115,18 @@ update_pve9() {
/etc/apt/sources.list.d/pve-install-repo.list \
/etc/apt/sources.list.d/debian.list; do
if [[ -f "$legacy_file" ]]; then
rm -f "$legacy_file"
pmx_remove_file "$legacy_file"
msg_ok "$(translate "Removed legacy repository: $(basename "$legacy_file")")" | tee -a "$screen_capture"
fi
done
if [[ -f /etc/apt/sources.list.d/debian.sources ]]; then
rm -f /etc/apt/sources.list.d/debian.sources
pmx_remove_file /etc/apt/sources.list.d/debian.sources
msg_ok "$(translate "Old debian.sources file removed to prevent duplication")" | tee -a "$screen_capture"
fi
msg_info "$(translate "Creating Proxmox VE 9.x no-subscription repository...")"
cat > /etc/apt/sources.list.d/proxmox.sources << EOF
pmx_write_file /etc/apt/sources.list.d/proxmox.sources << EOF
Enabled: true
Types: deb
URIs: http://download.proxmox.com/debian/pve
@@ -134,7 +139,7 @@ EOF
changes_made=true
msg_info "$(translate "Creating Debian ${TARGET_CODENAME} sources file...")"
cat > /etc/apt/sources.list.d/debian.sources << EOF
pmx_write_file /etc/apt/sources.list.d/debian.sources << EOF
Types: deb
URIs: http://deb.debian.org/debian/
Suites: ${TARGET_CODENAME} ${TARGET_CODENAME}-updates
@@ -154,11 +159,12 @@ EOF
local firmware_conf="/etc/apt/apt.conf.d/no-firmware-warnings.conf"
if [ ! -f "$firmware_conf" ]; then
msg_info "$(translate "Disabling non-free firmware warnings...")"
echo 'APT::Get::Update::SourceListWarnings::NonFreeFirmware "false";' > "$firmware_conf"
echo 'APT::Get::Update::SourceListWarnings::NonFreeFirmware "false";' | pmx_write_file "$firmware_conf"
msg_ok "$(translate "Non-free firmware warnings disabled")"
fi
# UPDATE: no progress bar here (dpkg is not involved); capture output to parse errors
pmx_record_execution "Update package lists" "apt-get update"
update_output=$(apt-get update 2>&1)
update_exit_code=$?
@@ -176,21 +182,25 @@ EOF
if command -v gpg >/dev/null 2>&1; then
# Modern approach: receive -> export -> dearmor into /etc/apt/keyrings/<KEY>.gpg
pmx_record_execution "Import missing repository signing key" "gpg --batch --keyserver keyserver.ubuntu.com --recv-keys $key"
if gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" \
&& gpg --batch --export "$key" | gpg --dearmor -o "/etc/apt/keyrings/${key}.gpg"; then
msg_ok "$(translate "Imported missing GPG key: $key")"
else
msg_warn "$(translate "Keyrings method failed; trying apt-key fallback")"
pmx_record_execution "Import missing repository signing key with apt-key" "apt-key adv --keyserver keyserver.ubuntu.com --recv-keys $key"
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys "$key" >/dev/null 2>&1 || true
fi
else
# Fallback for minimal systems without gpg installed
msg_warn "$(translate "gpg not found; trying apt-key fallback")"
pmx_record_execution "Import missing repository signing key with apt-key" "apt-key adv --keyserver keyserver.ubuntu.com --recv-keys $key"
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys "$key" >/dev/null 2>&1 || true
fi
fi
# Retry update after importing the key
pmx_record_execution "Retry package list update" "apt-get update"
if apt-get update > "$log_file" 2>&1; then
msg_ok "$(translate "Package lists updated after GPG fix")" | tee -a "$screen_capture"
else
@@ -270,19 +280,24 @@ EOF
if [[ $MENU_RESULT -eq 1 ]]; then
msg_info2 "$(translate "Update cancelled by user")"
pmx_record_execution "Remove unused packages" "apt-get -y autoremove"
apt-get -y autoremove > /dev/null 2>&1 || true
pmx_record_execution "Clean downloaded package cache" "apt-get -y autoclean"
apt-get -y autoclean > /dev/null 2>&1 || true
rm -f "$screen_capture"
return 0
elif [[ $MENU_RESULT -eq 2 ]]; then
msg_ok "$(translate "System is already up to date. No update needed.")"
pmx_record_execution "Remove unused packages" "apt-get -y autoremove"
apt-get -y autoremove > /dev/null 2>&1 || true
pmx_record_execution "Clean downloaded package cache" "apt-get -y autoclean"
apt-get -y autoclean > /dev/null 2>&1 || true
rm -f "$screen_capture"
return 0
fi
msg_info "$(translate "Cleaning up unused time synchronization services...")"
pmx_record_execution "Purge unused time synchronization services" "apt-get -y -o Dpkg::Options::=--force-confdef purge ntp openntpd systemd-timesyncd"
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' purge ntp openntpd systemd-timesyncd > /dev/null 2>&1; then
msg_ok "$(translate "Old time services removed successfully")"
else
@@ -292,6 +307,7 @@ EOF
echo -e
pmx_record_execution "Upgrade Proxmox VE 9 packages" "apt -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold full-upgrade"
DEBIAN_FRONTEND=noninteractive apt -y \
-o Dpkg::Options::='--force-confdef' \
-o Dpkg::Options::='--force-confold' \
@@ -314,7 +330,7 @@ EOF
msg_info "$(translate "Installing essential Proxmox packages...")"
local additional_packages="zfsutils-linux proxmox-backup-restore-image chrony"
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install $additional_packages >> "$log_file" 2>&1; then
if pmx_install_pkg $additional_packages; then
msg_ok "$(translate "Essential Proxmox packages installed")"
else
msg_warn "$(translate "Some essential Proxmox packages may not have been installed")"
@@ -323,7 +339,9 @@ EOF
lvm_repair_check
cleanup_duplicate_repos
pmx_record_execution "Remove unused packages" "apt-get -y autoremove"
apt-get -y autoremove > /dev/null 2>&1 || true
pmx_record_execution "Clean downloaded package cache" "apt-get -y autoclean"
apt-get -y autoclean > /dev/null 2>&1 || true
msg_ok "$(translate "Cleanup finished")"
+24 -5
View File
@@ -41,7 +41,16 @@ PROXMENUX_UTILS=(
# Ensure APT repositories are configured for the current PVE version.
# Creates missing no-subscription repo entries for PVE8 (bookworm) or PVE9 (trixie).
# Shared journal helpers, so any script sourcing this file records what
# it installs without arranging for it.
if [[ -f "${LOCAL_SCRIPTS:-/usr/local/share/proxmenux/scripts}/global/pmx_journal.sh" ]]; then
source "${LOCAL_SCRIPTS:-/usr/local/share/proxmenux/scripts}/global/pmx_journal.sh"
fi
ensure_repositories() {
local FUNC_VERSION="1.0"
pmx_journal_context "ensure_repositories" "$FUNC_VERSION"
local pve_version need_update=false
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
@@ -57,7 +66,7 @@ ensure_repositories() {
# 0640, which the PVE 9 webgui's repository manager treats as
# unparseable and silently hides the source — issue #230.
if [[ ! -f /etc/apt/sources.list.d/proxmox.sources ]]; then
cat > /etc/apt/sources.list.d/proxmox.sources <<'EOF'
pmx_write_file /etc/apt/sources.list.d/proxmox.sources <<'EOF'
Enabled: true
Types: deb
URIs: http://download.proxmox.com/debian/pve
@@ -70,7 +79,7 @@ EOF
fi
if [[ ! -f /etc/apt/sources.list.d/debian.sources ]]; then
cat > /etc/apt/sources.list.d/debian.sources <<'EOF'
pmx_write_file /etc/apt/sources.list.d/debian.sources <<'EOF'
Types: deb
URIs: http://deb.debian.org/debian/
Suites: trixie trixie-updates
@@ -96,19 +105,20 @@ EOF
echo "deb http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware"
echo "deb http://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware"
echo "deb http://security.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware"
} >> "$sources_file"
} | pmx_append_file "$sources_file"
need_update=true
fi
if [[ ! -f /etc/apt/sources.list.d/pve-no-subscription.list ]]; then
echo "deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription" \
> /etc/apt/sources.list.d/pve-no-subscription.list
| pmx_write_file /etc/apt/sources.list.d/pve-no-subscription.list
need_update=true
fi
fi
if [[ "$need_update" == true ]] || [[ ! -d /var/lib/apt/lists || -z "$(ls -A /var/lib/apt/lists 2>/dev/null)" ]]; then
msg_info "$(translate "Updating APT package lists...")"
pmx_record_execution "Update APT package lists" "apt-get update"
apt-get update >/dev/null 2>&1 || apt-get update
# Spinner pair: msg_info must be closed before returning.
# Without this the next `msg_info` caller spawns a second
@@ -132,7 +142,16 @@ install_single_package() {
msg_info "$(translate "Installing") $package${description:+ ($description)}..."
local install_success=false
if DEBIAN_FRONTEND=noninteractive apt-get install -y "$package" >/dev/null 2>&1; then
# Every script that installs anything comes through here, so this is
# where an installation becomes visible in the audit. What gets
# recorded is the difference the operation made — the packages that
# were not on the host and now are, dependencies included — rather
# than the name that was asked for.
if declare -F pmx_install_pkg >/dev/null 2>&1; then
PMX_JOURNAL_FUNCTION="${PMX_JOURNAL_FUNCTION:-install_single_package}" \
PMX_JOURNAL_SOURCE="${PMX_JOURNAL_SOURCE:-${SCRIPT_SOURCE:-utils-install-functions.sh}}" \
pmx_install_pkg "$package" && install_success=true
elif DEBIAN_FRONTEND=noninteractive apt-get install -y "$package" >/dev/null 2>&1; then
install_success=true
fi
cleanup 2>/dev/null || true
+10 -2
View File
@@ -5,6 +5,10 @@ if [[ -n "${__PROXMENUX_VM_STORAGE_HELPERS__}" ]]; then
fi
__PROXMENUX_VM_STORAGE_HELPERS__=1
if [[ -f "/usr/local/share/proxmenux/scripts/global/pmx_journal.sh" ]]; then
source "/usr/local/share/proxmenux/scripts/global/pmx_journal.sh"
fi
function _array_contains() {
local needle="$1"
shift
@@ -371,6 +375,8 @@ function _vm_storage_register_vfio_iommu_tool() {
}
function _vm_storage_enable_iommu_cmdline() {
local FUNC_VERSION="1.0"
pmx_journal_context "_vm_storage_enable_iommu_cmdline" "$FUNC_VERSION"
local cpu_vendor iommu_param
cpu_vendor=$(grep -m1 "vendor_id" /proc/cpuinfo 2>/dev/null | awk '{print $3}')
@@ -388,13 +394,15 @@ function _vm_storage_enable_iommu_cmdline() {
if [[ -f "$cmdline_file" ]] && grep -qE 'root=ZFS=|root=ZFS/' "$cmdline_file" 2>/dev/null; then
if ! grep -q "$iommu_param" "$cmdline_file"; then
cp "$cmdline_file" "${cmdline_file}.bak.$(date +%Y%m%d_%H%M%S)"
sed -i "s|\\s*$| ${iommu_param} iommu=pt|" "$cmdline_file"
pmx_edit_file "$cmdline_file" "s|\\s*$| ${iommu_param} iommu=pt|"
pmx_record_execution "refresh Proxmox boot entries" "proxmox-boot-tool refresh"
proxmox-boot-tool refresh >/dev/null 2>&1 || true
fi
elif [[ -f "$grub_file" ]]; then
if ! grep -q "$iommu_param" "$grub_file"; then
cp "$grub_file" "${grub_file}.bak.$(date +%Y%m%d_%H%M%S)"
sed -i "/GRUB_CMDLINE_LINUX_DEFAULT=/ s|\"$| ${iommu_param} iommu=pt\"|" "$grub_file"
pmx_edit_file "$grub_file" "/GRUB_CMDLINE_LINUX_DEFAULT=/ s|\"$| ${iommu_param} iommu=pt\"|"
pmx_record_execution "regenerate GRUB configuration" "update-grub"
update-grub >/dev/null 2>&1 || true
fi
else
+19 -2
View File
@@ -23,6 +23,10 @@ if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
load_language
initialize_cache
@@ -53,6 +57,9 @@ select_privileged_container() {
}
validate_container_id() {
local FUNC_VERSION="1.1"
pmx_journal_context "validate_container_id" "$FUNC_VERSION"
if [ -z "$CONTAINER_ID" ]; then
msg_error "$(translate 'Container ID not defined. Make sure to select a container first.')"
exit 1
@@ -66,6 +73,8 @@ validate_container_id() {
if pct status "$CONTAINER_ID" | grep -q "running"; then
msg_info "$(translate 'Stopping the container before conversion...')"
pmx_record_execution "stop CT ${CONTAINER_ID} for privileged-to-unprivileged conversion" \
"pct stop ${CONTAINER_ID}"
pct stop "$CONTAINER_ID"
msg_ok "$(translate 'Container stopped.')"
fi
@@ -89,7 +98,12 @@ show_backup_warning() {
}
convert_direct_method() {
local FUNC_VERSION="1.1"
pmx_journal_context "convert_direct_method" "$FUNC_VERSION"
msg_info2 "$(translate 'Starting direct conversion of container') $CONTAINER_ID..."
pmx_record_execution "convert CT ${CONTAINER_ID} filesystem ownership to unprivileged IDs" \
"mount rootfs, remap ownership by 100000, and update CT configuration"
TEMP_DIR="/tmp/lxc_convert_$CONTAINER_ID"
mkdir -p "$TEMP_DIR"
@@ -225,9 +239,9 @@ convert_direct_method() {
CONFIG_FILE="/etc/pve/lxc/$CONTAINER_ID.conf"
if ! grep -q "^unprivileged:" "$CONFIG_FILE"; then
echo "unprivileged: 1" >> "$CONFIG_FILE"
echo "unprivileged: 1" | pmx_append_file "$CONFIG_FILE"
else
sed -i 's/^unprivileged:.*/unprivileged: 1/' "$CONFIG_FILE"
pmx_edit_file "$CONFIG_FILE" 's/^unprivileged:.*/unprivileged: 1/'
fi
msg_ok "$(translate 'Direct conversion completed for container') $CONTAINER_ID"
@@ -238,9 +252,12 @@ convert_direct_method() {
}
cleanup_and_finalize() {
local FUNC_VERSION="1.1"
pmx_journal_context "cleanup_and_finalize" "$FUNC_VERSION"
if whiptail --yesno "$(translate 'Do you want to start the converted unprivileged container') $CONTAINER_ID $(translate 'now?')" 10 60; then
msg_info2 "$(translate 'Starting unprivileged container...')"
pmx_record_execution "start converted unprivileged CT ${CONTAINER_ID}" "pct start ${CONTAINER_ID}"
pct start "$CONTAINER_ID"
msg_ok "$(translate 'Unprivileged container') $CONTAINER_ID $(translate 'started successfully.')"
fi
+16 -2
View File
@@ -25,6 +25,10 @@ if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
load_language
initialize_cache
@@ -69,12 +73,19 @@ show_backup_warning() {
}
convert_to_privileged() {
local FUNC_VERSION="2.0"
pmx_journal_context "convert_to_privileged" "$FUNC_VERSION"
CONF_FILE="/etc/pve/lxc/$CONTAINER_ID.conf"
pmx_record_execution "convert CT ${CONTAINER_ID} to privileged mode" \
"stop CT if running and update ${CONF_FILE}"
CONTAINER_STATUS=$(pct status "$CONTAINER_ID" | awk '{print $2}')
if [ "$CONTAINER_STATUS" == "running" ]; then
msg_info "$(translate 'Stopping container') $CONTAINER_ID..."
pmx_record_execution "stop CT ${CONTAINER_ID} for unprivileged-to-privileged conversion" \
"pct shutdown ${CONTAINER_ID}"
pct shutdown "$CONTAINER_ID"
# Wait for container to stop
@@ -101,8 +112,8 @@ convert_to_privileged() {
msg_ok "$(translate 'Configuration backup created:') $CONF_FILE.bak"
msg_info "$(translate 'Converting container to privileged...')"
sed -i '/^unprivileged: 1/d' "$CONF_FILE"
echo "unprivileged: 0" >> "$CONF_FILE"
pmx_edit_file "$CONF_FILE" '/^unprivileged: 1/d'
echo "unprivileged: 0" | pmx_append_file "$CONF_FILE"
msg_ok "$(translate 'Container successfully converted to privileged.')"
@@ -112,9 +123,12 @@ convert_to_privileged() {
}
finalize_conversion() {
local FUNC_VERSION="2.0"
pmx_journal_context "finalize_conversion" "$FUNC_VERSION"
if whiptail --yesno "$(translate 'Do you want to start the privileged container') $CONTAINER_ID $(translate 'now?')" 10 60; then
msg_info "$(translate 'Starting privileged container...')"
pmx_record_execution "start converted privileged CT ${CONTAINER_ID}" "pct start ${CONTAINER_ID}"
pct start "$CONTAINER_ID"
msg_ok "$(translate 'Privileged container') $CONTAINER_ID $(translate 'started successfully.')"
fi
+19 -4
View File
@@ -61,6 +61,9 @@ MONITOR_PORT=8008
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
load_language
initialize_cache
@@ -749,6 +752,8 @@ show_version_info() {
# ==========================================================
uninstall_proxmenu() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_proxmenu" "$FUNC_VERSION"
if ! dialog --clear --backtitle "$BACKTITLE" \
--title "Uninstall ProxMenux" \
--yesno "\n$(translate "Are you sure you want to uninstall ProxMenux?")" 8 60; then
@@ -773,11 +778,13 @@ uninstall_proxmenu() {
# a pre-static-translations install. Cheap idempotent check.
if [ -d "/opt/googletrans-env" ]; then
echo "30" ; echo "Removing legacy googletrans virtualenv..."
pmx_record_execution "Remove legacy googletrans virtual environment" "rm -rf /opt/googletrans-env"
rm -rf "/opt/googletrans-env"
fi
echo "50" ; echo "Removing ProxMenu files..."
rm -f "$INSTALL_DIR/$MENU_SCRIPT"
pmx_remove_file "$INSTALL_DIR/$MENU_SCRIPT"
pmx_record_execution "Remove ProxMenux application directory" "rm -rf $BASE_DIR"
rm -rf "$BASE_DIR"
# Remove selected dependencies
@@ -785,22 +792,30 @@ uninstall_proxmenu() {
echo "70" ; echo "Removing selected dependencies..."
read -r -a DEPS_ARRAY <<< "$(echo "$deps_to_remove" | tr -d '"')"
for dep in "${DEPS_ARRAY[@]}"; do
pmx_record_execution "Mark ProxMenux dependency as automatic" "apt-mark auto $dep"
apt-mark auto "$dep" >/dev/null 2>&1
pmx_record_execution "Remove selected ProxMenux dependency" "apt-get -y --purge autoremove $dep"
apt-get -y --purge autoremove "$dep" >/dev/null 2>&1
done
pmx_record_execution "Remove unused ProxMenux dependencies" "apt-get autoremove -y --purge"
apt-get autoremove -y --purge >/dev/null 2>&1
fi
echo "80" ; echo "Removing ProxMenux Monitor..."
pmx_record_execution "Uninstall ProxMenux Monitor" "uninstall_proxmenux_monitor"
uninstall_proxmenux_monitor
echo "90" ; echo "Restoring system files..."
# Restore .bashrc and motd
[ -f /root/.bashrc.bak ] && mv /root/.bashrc.bak /root/.bashrc
if [ -f /root/.bashrc.bak ]; then
pmx_write_file /root/.bashrc < /root/.bashrc.bak
pmx_remove_file /root/.bashrc.bak
fi
if [ -f /etc/motd.bak ]; then
mv /etc/motd.bak /etc/motd
pmx_write_file /etc/motd < /etc/motd.bak
pmx_remove_file /etc/motd.bak
else
sed -i '/This system is optimised by: ProxMenux/d' /etc/motd
pmx_edit_file /etc/motd '/This system is optimised by: ProxMenux/d'
fi
echo "100" ; echo "Uninstallation complete!"
+10 -2
View File
@@ -41,6 +41,9 @@ TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
load_language
initialize_cache
@@ -389,6 +392,8 @@ analyze_bridge_configuration() {
guided_bridge_repair() {
local FUNC_VERSION="1.0"
pmx_journal_context "guided_bridge_repair" "$FUNC_VERSION"
local step=1
local total_steps=5
@@ -482,7 +487,7 @@ guided_bridge_repair() {
# Apply the change
if [ "$new_ports" != "$current_ports" ]; then
sed -i "/iface $bridge/,/bridge-ports/ s/bridge-ports.*/bridge-ports $new_ports/" /etc/network/interfaces
pmx_edit_file /etc/network/interfaces "/iface $bridge/,/bridge-ports/ s/bridge-ports.*/bridge-ports $new_ports/"
fi
fi
done
@@ -520,6 +525,7 @@ guided_bridge_repair() {
clear
msg_info "$(translate "Restarting network service...")"
pmx_record_execution "Restart networking service" "systemctl restart networking"
if systemctl restart networking; then
msg_ok "$(translate "Network service restarted successfully")"
else
@@ -635,6 +641,8 @@ analyze_network_configuration() {
}
guided_configuration_cleanup() {
local FUNC_VERSION="1.0"
pmx_journal_context "guided_configuration_cleanup" "$FUNC_VERSION"
local step=1
local total_steps=5
@@ -714,7 +722,7 @@ guided_configuration_cleanup() {
--infobox "$(translate "Removing invalid configurations...")\n\n$(translate "This may take a few seconds...")" 8 50
for iface in $interfaces_to_remove; do
sed -i "/^iface $iface/,/^$/d" /etc/network/interfaces
pmx_edit_file /etc/network/interfaces "/^iface $iface/,/^$/d"
done
((step++))
+120 -57
View File
@@ -48,6 +48,11 @@ 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
load_language
initialize_cache
@@ -92,6 +97,16 @@ register_tool() {
local state="$2"
local version="${3:-1.0}"
local source="${4:-${SCRIPT_SOURCE:-unknown}}"
# Same as in the customizable script: the one call every function
# already makes, so an applied tool reaches the journal even where
# the function itself still writes directly.
if declare -F pmx_record_applied >/dev/null 2>&1; then
PMX_JOURNAL_FUNCTION="${FUNCNAME[1]:-$tool}" \
PMX_JOURNAL_VERSION="$version" \
PMX_JOURNAL_SOURCE="$source" \
pmx_record_applied "$tool" "$version" \
"$([[ "$state" == "true" ]] && echo applied || echo removed)"
fi
ensure_tools_json
if [[ "$state" == "true" ]]; then
jq --arg t "$tool" --arg ver "$version" --arg src "$source" \
@@ -290,9 +305,10 @@ configure_time_sync() {
skip_apt_languages() {
local FUNC_VERSION="1.0"
pmx_journal_context "skip_apt_languages" "$FUNC_VERSION"
# description: Stop APT from downloading translation files to speed up updates.
msg_info "$(translate "Configuring APT to skip downloading additional languages...")"
cat > /etc/apt/apt.conf.d/99-disable-translations <<'EOF'
pmx_write_file /etc/apt/apt.conf.d/99-disable-translations <<'EOF'
Acquire::Languages "none";
EOF
msg_ok "$(translate "APT configured to skip additional languages")"
@@ -302,6 +318,7 @@ EOF
# ==========================================================
optimize_journald() {
local FUNC_VERSION="1.0"
pmx_journal_context "optimize_journald" "$FUNC_VERSION"
# description: Cap journald size, raise rate limit and force info-level logging so the log viewer and Fail2Ban work.
if [ -f /etc/log2ram.conf ] || [ -d /var/log.hdd ]; then
return 0
@@ -314,7 +331,7 @@ optimize_journald() {
cp -a "$jf" "${jf}.bak" 2>/dev/null || true
fi
cat <<EOF > /etc/systemd/journald.conf
pmx_write_file /etc/systemd/journald.conf <<EOF
[Journal]
Storage=persistent
SplitMode=none
@@ -337,8 +354,11 @@ MaxLevelConsole=notice
MaxLevelWall=crit
EOF
pmx_record_execution "Restart systemd-journald" "systemctl restart systemd-journald.service"
systemctl restart systemd-journald.service > /dev/null 2>&1
pmx_record_execution "Vacuum system journal" "journalctl --vacuum-size=64M --vacuum-time=1d"
journalctl --vacuum-size=64M --vacuum-time=1d > /dev/null 2>&1
pmx_record_execution "Rotate system journal" "journalctl --rotate"
journalctl --rotate > /dev/null 2>&1
msg_ok "$(translate "Journald optimized - Max size: 64M")"
@@ -348,6 +368,7 @@ EOF
# ==========================================================
optimize_logrotate() {
local FUNC_VERSION="1.1"
pmx_journal_context "optimize_logrotate" "$FUNC_VERSION"
# description: Replace logrotate.conf with a Log2RAM-friendly profile (daily rotation, copytruncate).
msg_info "$(translate "Optimizing logrotate configuration...")"
local logrotate_conf="/etc/logrotate.conf"
@@ -355,7 +376,7 @@ optimize_logrotate() {
cp -n "$logrotate_conf" "$backup_conf" 2>/dev/null || true
cat <<EOF > "$logrotate_conf"
pmx_write_file "$logrotate_conf" <<EOF
# ProxMenux optimized configuration (Log2RAM-friendly)
daily
su root adm
@@ -369,6 +390,7 @@ create 0640 root adm
copytruncate
include /etc/logrotate.d
EOF
pmx_record_execution "Restart logrotate" "systemctl restart logrotate"
systemctl restart logrotate > /dev/null 2>&1
msg_ok "$(translate "Logrotate optimization completed")"
@@ -378,12 +400,13 @@ EOF
# ==========================================================
increase_system_limits() {
local FUNC_VERSION="1.1"
pmx_journal_context "increase_system_limits" "$FUNC_VERSION"
# description: Raise inotify watches, file descriptors, process keys and PID limits to enterprise levels.
msg_info "$(translate "Increasing various system limits...")"
NECESSARY_REBOOT=1
cat > /etc/sysctl.d/99-maxwatches.conf << EOF
pmx_write_file /etc/sysctl.d/99-maxwatches.conf << EOF
# ProxMenux configuration
fs.inotify.max_user_watches = 1048576
fs.inotify.max_user_instances = 1048576
@@ -391,7 +414,7 @@ fs.inotify.max_queued_events = 1048576
EOF
cat > /etc/security/limits.d/99-limits.conf << EOF
pmx_write_file /etc/security/limits.d/99-limits.conf << EOF
# ProxMenux configuration
* soft nproc 1048576
* hard nproc 1048576
@@ -404,7 +427,7 @@ root hard nofile unlimited
EOF
cat > /etc/sysctl.d/99-maxkeys.conf << EOF
pmx_write_file /etc/sysctl.d/99-maxkeys.conf << EOF
# ProxMenux configuration
kernel.keys.root_maxkeys=1000000
kernel.keys.maxkeys=1000000
@@ -413,32 +436,32 @@ EOF
for file in /etc/systemd/system.conf /etc/systemd/user.conf; do
if ! grep -q "^DefaultLimitNOFILE=" "$file"; then
echo "DefaultLimitNOFILE=1048576" >> "$file"
echo "DefaultLimitNOFILE=1048576" | pmx_append_file "$file"
fi
done
for file in /etc/pam.d/common-session /etc/pam.d/runuser-l; do
if ! grep -q "^session required pam_limits.so" "$file"; then
echo 'session required pam_limits.so' >> "$file"
echo 'session required pam_limits.so' | pmx_append_file "$file"
fi
done
if ! grep -q "ulimit -n 1048576" /root/.profile; then
sed -i '/ulimit -n 256000/d' /root/.profile 2>/dev/null
echo "ulimit -n 1048576" >> /root/.profile
pmx_edit_file /root/.profile '/ulimit -n 256000/d' 2>/dev/null || true
echo "ulimit -n 1048576" | pmx_append_file /root/.profile
fi
cat > /etc/sysctl.d/99-swap.conf << EOF
pmx_write_file /etc/sysctl.d/99-swap.conf << EOF
# ProxMenux configuration
vm.swappiness = 10
vm.vfs_cache_pressure = 100
EOF
cat > /etc/sysctl.d/99-fs.conf << EOF
pmx_write_file /etc/sysctl.d/99-fs.conf << EOF
# ProxMenux configuration
fs.nr_open = 2097152
fs.file-max = 2097152
@@ -452,21 +475,25 @@ EOF
# ==========================================================
optimize_memory_settings() {
local FUNC_VERSION="1.2"
pmx_journal_context "optimize_memory_settings" "$FUNC_VERSION"
# description: Tune swappiness, dirty page ratios and compaction proactiveness for VM hosts without overriding the kernel's memory-overcommit policy.
msg_info "$(translate "Optimizing memory settings...")"
NECESSARY_REBOOT=1
cat <<EOF > /etc/sysctl.d/99-memory.conf
local memory_settings
memory_settings="$(cat <<EOF
# Balanced Memory Optimization
vm.swappiness = 10
vm.dirty_ratio = 15
vm.dirty_background_ratio = 5
vm.max_map_count = 262144
EOF
)"
if [ -f /proc/sys/vm/compaction_proactiveness ]; then
echo "vm.compaction_proactiveness = 20" >> /etc/sysctl.d/99-memory.conf
memory_settings+=$'\n''vm.compaction_proactiveness = 20'
fi
printf '%s\n' "$memory_settings" | pmx_write_file /etc/sysctl.d/99-memory.conf
msg_ok "$(translate "Memory optimization completed.")"
register_tool "memory_settings" true "$FUNC_VERSION"
@@ -475,11 +502,12 @@ EOF
# ==========================================================
configure_kernel_panic() {
local FUNC_VERSION="1.0"
pmx_journal_context "configure_kernel_panic" "$FUNC_VERSION"
# description: Auto-reboot on kernel panic / oops / hardlockup; write crash dumps to /var/crash.
msg_info "$(translate "Configuring kernel panic behavior")"
NECESSARY_REBOOT=1
cat <<EOF > /etc/sysctl.d/99-kernelpanic.conf
pmx_write_file /etc/sysctl.d/99-kernelpanic.conf <<EOF
# Enable restart on kernel panic, kernel oops and hardlockup
kernel.core_pattern = /var/crash/core.%t.%p
kernel.panic = 10
@@ -507,11 +535,12 @@ force_apt_ipv4() {
apply_network_optimizations() {
local FUNC_VERSION="1.1"
pmx_journal_context "apply_network_optimizations" "$FUNC_VERSION"
# description: Tune TCP buffers, somaxconn, IPv4 hardening and disable rp_filter on fw bridges (PVE 9 compatible).
msg_info "$(translate "Optimizing network settings...")"
NECESSARY_REBOOT=1
cat <<'EOF' > /etc/sysctl.d/99-network.conf
pmx_write_file /etc/sysctl.d/99-network.conf <<'EOF'
# ==========================================================
# ProxMenux - Network tuning (PVE 9 compatible)
# ==========================================================
@@ -555,9 +584,10 @@ net.ipv4.tcp_wmem = 8192 65536 16777216
net.unix.max_dgram_qlen = 4096
EOF
pmx_record_execution "Apply network sysctl configuration" "sysctl --system"
sysctl --system > /dev/null 2>&1
cat > /usr/local/sbin/proxmenux-fwbr-tune <<'EOF'
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/.
@@ -588,7 +618,7 @@ EOF
chmod 0755 /usr/local/sbin/proxmenux-fwbr-tune
chown root:root /usr/local/sbin/proxmenux-fwbr-tune
cat > /etc/systemd/system/proxmenux-fwbr-tune.service <<'EOF'
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
@@ -603,7 +633,7 @@ RemainAfterExit=yes
WantedBy=multi-user.target
EOF
cat > /etc/udev/rules.d/99-proxmenux-fwbr-tune.rules <<'EOF'
pmx_write_file /etc/udev/rules.d/99-proxmenux-fwbr-tune.rules <<'EOF'
ACTION=="add", SUBSYSTEM=="net", KERNEL=="fwbr*", RUN+="/usr/local/sbin/proxmenux-fwbr-tune %k"
ACTION=="add", SUBSYSTEM=="net", KERNEL=="fwln*", RUN+="/usr/local/sbin/proxmenux-fwbr-tune %k"
ACTION=="add", SUBSYSTEM=="net", KERNEL=="fwpr*", RUN+="/usr/local/sbin/proxmenux-fwbr-tune %k"
@@ -612,15 +642,18 @@ EOF
chmod 0644 /etc/udev/rules.d/99-proxmenux-fwbr-tune.rules
chown root:root /etc/udev/rules.d/99-proxmenux-fwbr-tune.rules
pmx_record_execution "Reload systemd configuration" "systemctl daemon-reload"
systemctl daemon-reload >/dev/null 2>&1 || true
pmx_record_execution "Reload udev rules" "udevadm control --reload-rules"
udevadm control --reload-rules >/dev/null 2>&1 || true
systemctl enable --now proxmenux-fwbr-tune.service >/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/*" >> "$interfaces_file"
echo "source /etc/network/interfaces.d/*" | pmx_append_file "$interfaces_file"
fi
msg_ok "$(translate "Network optimization completed")"
@@ -759,6 +792,7 @@ PY
customize_bashrc() {
local FUNC_VERSION="1.2"
pmx_journal_context "customize_bashrc" "$FUNC_VERSION"
# description: Install and safely migrate the managed ProxMenux Bash prompt and aliases while preserving or selecting the short/full working-directory style.
msg_info "$(translate "Customizing bashrc for root user...")"
local bashrc="/root/.bashrc"
@@ -768,7 +802,7 @@ customize_bashrc() {
local prompt_path_escape='\W'
local detected_path_style="short"
[[ -f "$bashrc" ]] || touch "$bashrc"
[[ -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
@@ -791,13 +825,19 @@ customize_bashrc() {
esac
[ -f "${bashrc}.bak" ] || cp "$bashrc" "${bashrc}.bak" > /dev/null 2>&1
if ! _migrate_proxmenux_bashrc "$bashrc" migrate >/dev/null; then
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"
cat >> "$bashrc" << EOF
pmx_append_file "$bashrc" << EOF
${marker_begin}
# ProxMenux core customizations
export HISTTIMEFORMAT="%d/%m/%y %T "
@@ -815,7 +855,7 @@ EOF
if ! grep -q "source /root/.bashrc" "$bash_profile" 2>/dev/null; then
echo "source /root/.bashrc" >> "$bash_profile" 2>/dev/null
echo "source /root/.bashrc" | pmx_append_file "$bash_profile" 2>/dev/null
fi
msg_ok "$(translate "Bashrc customization completed")"
@@ -839,6 +879,7 @@ _update_existing_log2ram_auto() {
local func_version="$1"
local log2ram_bin=""
local candidate resolved tmp_file
pmx_journal_context "_update_existing_log2ram_auto" "$func_version"
msg_ok "$(translate "Log2RAM already registered — updating to latest configuration")"
@@ -862,10 +903,7 @@ _update_existing_log2ram_auto() {
if grep -q 'rsync -aAXv ' "$log2ram_bin" 2>/dev/null; then
[[ -e "${log2ram_bin}.proxmenux.bak" ]] || cp -a "$log2ram_bin" "${log2ram_bin}.proxmenux.bak"
tmp_file="$(mktemp "${log2ram_bin}.proxmenux.XXXXXX")" || return 1
cp -a "$log2ram_bin" "$tmp_file"
sed -i 's/rsync -aAXv /rsync -aXv --no-acls /g' "$tmp_file"
mv -f "$tmp_file" "$log2ram_bin"
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 \
@@ -884,7 +922,8 @@ _update_existing_log2ram_auto() {
EOF
chmod 0644 "$tmp_file"
chown root:root "$tmp_file"
mv -f "$tmp_file" /etc/logrotate.d/proxmox-backup-api
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'
@@ -893,7 +932,10 @@ EOF
EOF
chmod 0755 "$tmp_file"
chown root:root "$tmp_file"
mv -f "$tmp_file" /etc/cron.hourly/proxmox-backup-logrotate
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
@@ -945,7 +987,10 @@ EOF
chmod 0755 "$tmp_file"
chown root:root "$tmp_file"
bash -n "$tmp_file" || return 1
mv -f "$tmp_file" /usr/local/bin/log2ram-check.sh
pmx_write_file /usr/local/bin/log2ram-check.sh < "$tmp_file"
chmod 0755 /usr/local/bin/log2ram-check.sh
chown root:root /usr/local/bin/log2ram-check.sh
rm -f "$tmp_file"
tmp_file="$(mktemp /etc/cron.d/.log2ram-auto-sync.XXXXXX)" || return 1
cat > "$tmp_file" <<'EOF'
@@ -958,7 +1003,10 @@ MAILTO=""
EOF
chmod 0644 "$tmp_file"
chown root:root "$tmp_file"
mv -f "$tmp_file" /etc/cron.d/log2ram-auto-sync
pmx_write_file /etc/cron.d/log2ram-auto-sync < "$tmp_file"
chmod 0644 /etc/cron.d/log2ram-auto-sync
chown root:root /etc/cron.d/log2ram-auto-sync
rm -f "$tmp_file"
register_tool "log2ram" true "$func_version"
msg_success "$(translate "Log2RAM installation and configuration completed successfully.")"
@@ -968,6 +1016,7 @@ EOF
install_log2ram_auto() {
local FUNC_VERSION="1.5"
local existing_log2ram_bin=""
pmx_journal_context "install_log2ram_auto" "$FUNC_VERSION"
# description: Install Log2RAM with size auto-tuned to host RAM (128M/256M/512M); SSD/M.2 detection skips on rotational disks.
@@ -1024,31 +1073,40 @@ install_log2ram_auto() {
msg_info "$(translate "Cleaning previous Log2RAM installation...")"
systemctl stop log2ram log2ram-daily.timer >/dev/null 2>&1 || true
systemctl disable log2ram log2ram-daily.timer >/dev/null 2>&1 || true
pmx_disable_service log2ram || true
pmx_disable_service log2ram-daily.timer || true
rm -f /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 2>/dev/null || true
rm -f /usr/local/bin/log2ram-check.sh /usr/local/bin/log2ram /usr/sbin/log2ram 2>/dev/null || true
rm -f /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 2>/dev/null || 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 -f /etc/log2ram.conf* 2>/dev/null || true
rm -rf /etc/logrotate.d/log2ram /var/log.hdd /tmp/log2ram 2>/dev/null || true
rm -rf /var/log.hdd /tmp/log2ram 2>/dev/null || true
pmx_record_execution "Re-execute the systemd manager" "systemctl daemon-reexec"
systemctl daemon-reexec >/dev/null 2>&1 || true
pmx_record_execution "Reload systemd configuration" "systemctl daemon-reload"
systemctl daemon-reload >/dev/null 2>&1 || true
pmx_record_execution "Restart cron" "systemctl restart cron"
systemctl restart cron >/dev/null 2>&1 || true
msg_ok "$(translate "Previous installation cleaned")"
msg_info "$(translate "Installing Log2RAM from source...")"
if ! command -v git >/dev/null 2>&1; then
pmx_record_execution "Update package lists for Log2RAM" "apt-get update -qq"
apt-get update -qq >/dev/null 2>&1
apt-get install -y git >/dev/null 2>&1
pmx_install_pkg git
fi
rm -rf /tmp/log2ram 2>/dev/null || true
@@ -1059,6 +1117,7 @@ install_log2ram_auto() {
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
@@ -1077,7 +1136,7 @@ install_log2ram_auto() {
[[ -n "$_l2r_bin" && -f "$_l2r_bin" ]] || continue
if grep -q 'rsync -aAXv ' "$_l2r_bin" 2>/dev/null; then
cp -a "$_l2r_bin" "${_l2r_bin}.proxmenux.bak"
sed -i 's/rsync -aAXv /rsync -aXv --no-acls /g' "$_l2r_bin"
pmx_edit_file "$_l2r_bin" 's/rsync -aAXv /rsync -aXv --no-acls /g'
fi
break
done
@@ -1088,7 +1147,7 @@ install_log2ram_auto() {
if dpkg-query -W -f='${Status}' proxmox-backup-server 2>/dev/null \
| grep -q 'install ok installed'; then
mkdir -p /var/log/proxmox-backup/api 2>/dev/null || true
cat > /etc/logrotate.d/proxmox-backup-api <<'EOF'
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
@@ -1101,7 +1160,7 @@ install_log2ram_auto() {
EOF
chmod 0644 /etc/logrotate.d/proxmox-backup-api
chown root:root /etc/logrotate.d/proxmox-backup-api
cat > /etc/cron.hourly/proxmox-backup-logrotate <<'EOF'
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
@@ -1110,6 +1169,7 @@ EOF
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
@@ -1131,11 +1191,11 @@ EOF
fi
msg_ok "$(translate "Detected RAM:") $RAM_SIZE_GB GB — $(translate "Log2RAM size set to:") $LOG2RAM_SIZE"
sed -i "s/^SIZE=.*/SIZE=$LOG2RAM_SIZE/" /etc/log2ram.conf
pmx_edit_file /etc/log2ram.conf "s/^SIZE=.*/SIZE=$LOG2RAM_SIZE/"
LOG2RAM_BIN="$(command -v log2ram || echo /usr/sbin/log2ram)"
cat > /etc/cron.d/log2ram <<EOF
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
@@ -1146,7 +1206,7 @@ EOF
chown root:root /etc/cron.d/log2ram
msg_ok "$(translate "Log2RAM write scheduled every") $CRON_HOURS $(translate "hour(s)")"
cat > /usr/local/bin/log2ram-check.sh <<'EOF'
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
@@ -1196,7 +1256,7 @@ fi
EOF
chmod +x /usr/local/bin/log2ram-check.sh
cat > /etc/cron.d/log2ram-auto-sync <<'EOF'
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
@@ -1207,6 +1267,7 @@ EOF
chmod 0644 /etc/cron.d/log2ram-auto-sync
chown root:root /etc/cron.d/log2ram-auto-sync
pmx_record_execution "Restart cron" "systemctl restart cron"
systemctl restart cron >/dev/null 2>&1 || true
msg_ok "$(translate "Auto-sync enabled when /var/log exceeds 80% of") $LOG2RAM_SIZE"
@@ -1232,8 +1293,8 @@ EOF
[ "$KEEP_MB" -lt 8 ] && KEEP_MB=8
sed -i '/^\[Journal\]/,$d' /etc/systemd/journald.conf 2>/dev/null || true
tee -a /etc/systemd/journald.conf >/dev/null <<EOF
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
@@ -1267,8 +1328,10 @@ EOF
#msg_ok "$(translate "Backup created:") /etc/systemd/journald.conf.bak.$(date +%Y%m%d-%H%M%S)"
msg_ok "$(translate "Journald configuration adjusted to") ${USE_MB}M (Log2RAM ${LOG2RAM_SIZE})"
pmx_record_execution "Reload systemd configuration" "systemctl daemon-reload"
systemctl daemon-reload >/dev/null 2>&1 || true
if ! systemctl enable log2ram >/dev/null 2>&1; then
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
File diff suppressed because it is too large Load Diff
+121 -58
View File
@@ -34,6 +34,9 @@ TOOLS_JSON="$BASE_DIR/installed_tools.json"
if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
load_language
initialize_cache
@@ -53,18 +56,28 @@ register_tool() {
################################################################
uninstall_fastfetch() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_fastfetch" "$FUNC_VERSION"
if ! command -v fastfetch &>/dev/null && [[ ! -f /usr/local/bin/fastfetch ]]; then
msg_warn "$(translate "Fastfetch is not installed.")"
return 0
fi
msg_info2 "$(translate "Uninstalling Fastfetch...")"
rm -f /usr/local/bin/fastfetch /usr/bin/fastfetch
pmx_remove_file /usr/local/bin/fastfetch
pmx_remove_file /usr/bin/fastfetch
pmx_record_execution "Remove Fastfetch configuration directory" "rm -rf $HOME/.config/fastfetch"
rm -rf "$HOME/.config/fastfetch"
pmx_record_execution "Remove shared Fastfetch files" "rm -rf /usr/local/share/fastfetch"
rm -rf /usr/local/share/fastfetch
sed -i '/fastfetch/d' "$HOME/.bashrc" "$HOME/.profile" /etc/profile 2>/dev/null
sed -i '/# BEGIN FASTFETCH/,/# END FASTFETCH/d' "$HOME/.bashrc"
rm -f /etc/profile.d/fastfetch.sh /etc/update-motd.d/99-fastfetch
local profile_file
for profile_file in "$HOME/.bashrc" "$HOME/.profile" /etc/profile; do
pmx_edit_file "$profile_file" '/fastfetch/d' 2>/dev/null || true
done
pmx_edit_file "$HOME/.bashrc" '/# BEGIN FASTFETCH/,/# END FASTFETCH/d'
pmx_remove_file /etc/profile.d/fastfetch.sh
pmx_remove_file /etc/update-motd.d/99-fastfetch
pmx_record_execution "Remove Fastfetch package" "dpkg -r fastfetch"
dpkg -r fastfetch &>/dev/null
msg_ok "$(translate "Fastfetch removed from system")"
@@ -74,18 +87,23 @@ uninstall_fastfetch() {
################################################################
uninstall_figurine() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_figurine" "$FUNC_VERSION"
if ! command -v figurine &>/dev/null; then
msg_warn "$(translate "Figurine is not installed.")"
return 0
fi
msg_info2 "$(translate "Uninstalling Figurine...")"
rm -f /usr/local/bin/figurine
rm -f /etc/profile.d/figurine.sh
pmx_remove_file /usr/local/bin/figurine
pmx_remove_file /etc/profile.d/figurine.sh
sed -i '/lxcclean/d;/lxcupdate/d;/kernelclean/d;/cpugov/d;/updatecerts/d;/seqwrite/d;/seqread/d;/ranwrite/d;/ranread/d' "$HOME/.bashrc" "$HOME/.profile" 2>/dev/null
sed -i '/# ProxMenux Figurine aliases and tools/,+20d' "$HOME/.bashrc" "$HOME/.profile" 2>/dev/null
sed -i '/# BEGIN PROXMENUX ALIASES/,/# END PROXMENUX ALIASES/d' "$HOME/.bashrc" "$HOME/.profile" 2>/dev/null
local profile_file
for profile_file in "$HOME/.bashrc" "$HOME/.profile"; do
pmx_edit_file "$profile_file" '/lxcclean/d;/lxcupdate/d;/kernelclean/d;/cpugov/d;/updatecerts/d;/seqwrite/d;/seqread/d;/ranwrite/d;/ranread/d' 2>/dev/null || true
pmx_edit_file "$profile_file" '/# ProxMenux Figurine aliases and tools/,+20d' 2>/dev/null || true
pmx_edit_file "$profile_file" '/# BEGIN PROXMENUX ALIASES/,/# END PROXMENUX ALIASES/d' 2>/dev/null || true
done
msg_ok "$(translate "Figurine removed from system")"
register_tool "figurine" false
@@ -95,15 +113,18 @@ uninstall_figurine() {
################################################################
uninstall_kexec() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_kexec" "$FUNC_VERSION"
if ! dpkg -s kexec-tools >/dev/null 2>&1 && [ ! -f /etc/systemd/system/kexec-pve.service ]; then
msg_warn "$(translate "kexec-tools is not installed or already removed.")"
return 0
fi
msg_info2 "$(translate "Uninstalling kexec-tools and removing custom service...")"
systemctl disable --now kexec-pve.service &>/dev/null
rm -f /etc/systemd/system/kexec-pve.service
sed -i "/alias reboot-quick='systemctl kexec'/d" /root/.bash_profile
pmx_disable_service kexec-pve.service
pmx_remove_file /etc/systemd/system/kexec-pve.service
pmx_edit_file /root/.bash_profile "/alias reboot-quick='systemctl kexec'/d"
pmx_record_execution "Purge kexec-tools package" "apt-get purge -y kexec-tools"
apt-get purge -y kexec-tools >/dev/null 2>&1
msg_ok "$(translate "kexec-tools and related settings removed")"
@@ -269,6 +290,8 @@ uninstall_rpc() {
################################################################
uninstall_motd() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_motd" "$FUNC_VERSION"
local state_file="$BASE_DIR/motd.state"
local original_file="$BASE_DIR/motd.original"
local motd_file="${PROXMENUX_MOTD_FILE:-/etc/motd}"
@@ -287,15 +310,15 @@ uninstall_motd() {
msg_error "$(translate "The original MOTD backup is unavailable; no changes were made")"
return 1
fi
cp -a "$original_file" "$motd_file"
pmx_write_file "$motd_file" < "$original_file"
;;
absent)
rm -f "$motd_file"
pmx_remove_file "$motd_file"
;;
legacy-marker)
if [[ -f "$motd_file" ]]; then
sed -i "\|^${custom_message}$|d" "$motd_file"
sed -i '/./,$!d' "$motd_file"
pmx_edit_file "$motd_file" "\|^${custom_message}$|d"
pmx_edit_file "$motd_file" '/./,$!d'
fi
;;
*)
@@ -304,7 +327,8 @@ uninstall_motd() {
;;
esac
rm -f "$state_file" "$original_file"
pmx_remove_file "$state_file"
pmx_remove_file "$original_file"
register_tool "motd" false
msg_ok "$(translate "The original MOTD configuration has been restored")"
}
@@ -380,10 +404,12 @@ uninstall_apt_languages() {
################################################################
uninstall_journald() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_journald" "$FUNC_VERSION"
msg_info "$(translate "Restoring default journald configuration...")"
# Restore default journald configuration
cat > /etc/systemd/journald.conf << 'EOF'
pmx_write_file /etc/systemd/journald.conf << 'EOF'
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
@@ -425,6 +451,7 @@ uninstall_journald() {
#MaxLevelWall=emerg
EOF
pmx_record_execution "Restart systemd-journald" "systemctl restart systemd-journald.service"
systemctl restart systemd-journald.service >/dev/null 2>&1
msg_ok "$(translate "Default journald configuration restored")"
@@ -452,37 +479,40 @@ uninstall_logrotate() {
################################################################
uninstall_system_limits() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_system_limits" "$FUNC_VERSION"
msg_info "$(translate "Removing system limits optimizations...")"
# Remove ProxMenux sysctl configurations
rm -f /etc/sysctl.d/99-maxwatches.conf
rm -f /etc/sysctl.d/99-maxkeys.conf
rm -f /etc/sysctl.d/99-swap.conf
rm -f /etc/sysctl.d/99-fs.conf
pmx_remove_file /etc/sysctl.d/99-maxwatches.conf
pmx_remove_file /etc/sysctl.d/99-maxkeys.conf
pmx_remove_file /etc/sysctl.d/99-swap.conf
pmx_remove_file /etc/sysctl.d/99-fs.conf
# Remove ProxMenux limits configuration
rm -f /etc/security/limits.d/99-limits.conf
pmx_remove_file /etc/security/limits.d/99-limits.conf
# Remove systemd limits (restore defaults)
for file in /etc/systemd/system.conf /etc/systemd/user.conf; do
if [ -f "$file" ]; then
sed -i '/^DefaultLimitNOFILE=256000/d' "$file"
pmx_edit_file "$file" '/^DefaultLimitNOFILE=256000/d'
fi
done
# Remove PAM limits
for file in /etc/pam.d/common-session /etc/pam.d/runuser-l; do
if [ -f "$file" ]; then
sed -i '/^session required pam_limits.so/d' "$file"
pmx_edit_file "$file" '/^session required pam_limits.so/d'
fi
done
# Remove ulimit from profile
if [ -f /root/.profile ]; then
sed -i '/ulimit -n 256000/d' /root/.profile
pmx_edit_file /root/.profile '/ulimit -n 256000/d'
fi
# Reload sysctl
pmx_record_execution "Apply sysctl configuration" "sysctl --system"
sysctl --system >/dev/null 2>&1
msg_ok "$(translate "System limits optimizations removed")"
@@ -553,26 +583,31 @@ uninstall_apt_ipv4() {
################################################################
uninstall_network_optimization() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_network_optimization" "$FUNC_VERSION"
msg_info "$(translate "Removing network optimizations...")"
rm -f /etc/sysctl.d/99-network.conf
pmx_remove_file /etc/sysctl.d/99-network.conf
local interfaces_file="/etc/network/interfaces"
if [ -f "$interfaces_file" ]; then
sed -i '/^source \/etc\/network\/interfaces\.d\/\*/d' "$interfaces_file"
pmx_edit_file "$interfaces_file" '/^source \/etc\/network\/interfaces\.d\/\*/d'
fi
rm -f /etc/sysctl.d/97-proxmenux-fwbr.conf \
/etc/sysctl.d/98-proxmenux-rpf.conf
pmx_remove_file /etc/sysctl.d/97-proxmenux-fwbr.conf
pmx_remove_file /etc/sysctl.d/98-proxmenux-rpf.conf
systemctl disable --now proxmenux-fwbr-tune.service >/dev/null 2>&1 || true
rm -f /etc/systemd/system/proxmenux-fwbr-tune.service
rm -f /usr/local/sbin/proxmenux-fwbr-tune
rm -f /etc/udev/rules.d/99-proxmenux-fwbr-tune.rules \
/etc/udev/rules.d/99-zz-proxmenux-fwbr-tune.rules
pmx_disable_service proxmenux-fwbr-tune.service || true
pmx_remove_file /etc/systemd/system/proxmenux-fwbr-tune.service
pmx_remove_file /usr/local/sbin/proxmenux-fwbr-tune
pmx_remove_file /etc/udev/rules.d/99-proxmenux-fwbr-tune.rules
pmx_remove_file /etc/udev/rules.d/99-zz-proxmenux-fwbr-tune.rules
pmx_record_execution "Reload udev rules" "udevadm control --reload-rules"
udevadm control --reload-rules >/dev/null 2>&1 || true
pmx_record_execution "Reload systemd configuration" "systemctl daemon-reload"
systemctl daemon-reload >/dev/null 2>&1 || true
pmx_record_execution "Apply sysctl configuration" "sysctl --system"
sysctl --system >/dev/null 2>&1 || true
@@ -585,24 +620,27 @@ uninstall_network_optimization() {
################################################################
uninstall_bashrc_custom() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_bashrc_custom" "$FUNC_VERSION"
msg_info "$(translate "Restoring original bashrc...")"
# Restore original bashrc from backup
if [ -f /root/.bashrc.bak ]; then
mv /root/.bashrc.bak /root/.bashrc
pmx_write_file /root/.bashrc < /root/.bashrc.bak
pmx_remove_file /root/.bashrc.bak
msg_ok "$(translate "Original bashrc restored")"
else
# Remove ProxMenux customizations manually
if [ -f /root/.bashrc ]; then
# Remove the customization block using the markers written by customize_bashrc
sed -i '/# BEGIN PMX_CORE_BASHRC/,/# END PMX_CORE_BASHRC/d' /root/.bashrc
pmx_edit_file /root/.bashrc '/# BEGIN PMX_CORE_BASHRC/,/# END PMX_CORE_BASHRC/d'
fi
msg_ok "$(translate "ProxMenux customizations removed from bashrc")"
fi
# Remove bash_profile source line if we added it
if [ -f /root/.bash_profile ]; then
sed -i '/source \/root\/\.bashrc/d' /root/.bash_profile
pmx_edit_file /root/.bash_profile '/source \/root\/\.bashrc/d'
fi
register_tool "bashrc_custom" false
@@ -718,21 +756,23 @@ uninstall_persistent_network() {
uninstall_vfio_iommu() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_vfio_iommu" "$FUNC_VERSION"
msg_info2 "$(translate "Reverting IOMMU/VFIO configuration...")"
NECESSARY_REBOOT=1
# Remove VFIO modules from /etc/modules
local modules_file="/etc/modules"
if [ -f "$modules_file" ]; then
sed -i '/^vfio$/d;/^vfio_iommu_type1$/d;/^vfio_pci$/d;/^vfio_virqfd$/d' "$modules_file"
pmx_edit_file "$modules_file" '/^vfio$/d;/^vfio_iommu_type1$/d;/^vfio_pci$/d;/^vfio_virqfd$/d'
msg_ok "$(translate "VFIO modules removed from /etc/modules")"
fi
# Remove driver blacklists added by ProxMenux
local blacklist_file="/etc/modprobe.d/blacklist.conf"
if [ -f "$blacklist_file" ]; then
sed -i '/^blacklist nouveau$/d;/^blacklist lbm-nouveau$/d;/^blacklist radeon$/d;/^blacklist nvidia$/d;/^blacklist nvidiafb$/d;/^options nouveau modeset=0$/d' "$blacklist_file"
[ ! -s "$blacklist_file" ] && rm -f "$blacklist_file"
pmx_edit_file "$blacklist_file" '/^blacklist nouveau$/d;/^blacklist lbm-nouveau$/d;/^blacklist radeon$/d;/^blacklist nvidia$/d;/^blacklist nvidiafb$/d;/^options nouveau modeset=0$/d'
[ ! -s "$blacklist_file" ] && pmx_remove_file "$blacklist_file"
msg_ok "$(translate "Driver blacklist entries removed")"
fi
@@ -742,9 +782,12 @@ uninstall_vfio_iommu() {
# systemd-boot / ZFS
if grep -qE 'intel_iommu=on|amd_iommu=on|iommu=pt|pcie_acs_override=' "$cmdline_file"; then
cp "$cmdline_file" "${cmdline_file}.bak.$(date +%Y%m%d_%H%M%S)"
sed -i -E 's/\b(intel_iommu=on|amd_iommu=on|iommu=pt|pcie_acs_override=[^ ]*)\b//g' "$cmdline_file"
sed -i -E 's/[[:space:]]+/ /g; s/^ //; s/ $//' "$cmdline_file"
command -v proxmox-boot-tool >/dev/null 2>&1 && proxmox-boot-tool refresh >/dev/null 2>&1 || true
pmx_edit_file "$cmdline_file" -E 's/\b(intel_iommu=on|amd_iommu=on|iommu=pt|pcie_acs_override=[^ ]*)\b//g'
pmx_edit_file "$cmdline_file" -E 's/[[:space:]]+/ /g; s/^ //; s/ $//'
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 || true
fi
msg_ok "$(translate "IOMMU parameters removed from /etc/kernel/cmdline")"
fi
else
@@ -752,9 +795,10 @@ uninstall_vfio_iommu() {
local grub_file="/etc/default/grub"
if [[ -f "$grub_file" ]] && grep -qE 'intel_iommu=on|amd_iommu=on|iommu=pt|pcie_acs_override=' "$grub_file"; then
cp "$grub_file" "${grub_file}.bak.$(date +%Y%m%d_%H%M%S)"
sed -i -E 's/\b(intel_iommu=on|amd_iommu=on|iommu=pt|pcie_acs_override=[^ "]*)\b//g' "$grub_file"
pmx_edit_file "$grub_file" -E 's/\b(intel_iommu=on|amd_iommu=on|iommu=pt|pcie_acs_override=[^ "]*)\b//g'
awk -F\" 'BEGIN{OFS="\""} /GRUB_CMDLINE_LINUX_DEFAULT=/{gsub(/[[:space:]]+/," ",$2);sub(/^ /,"",$2);sub(/ $/,"",$2)}1' \
"$grub_file" > "${grub_file}.tmp" && mv "${grub_file}.tmp" "$grub_file"
"$grub_file" | pmx_write_file "$grub_file"
pmx_record_execution "Regenerate GRUB configuration" "update-grub"
update-grub >/dev/null 2>&1 || true
msg_ok "$(translate "IOMMU parameters removed from GRUB")"
fi
@@ -762,7 +806,9 @@ uninstall_vfio_iommu() {
msg_info "$(translate 'Updating initramfs (this may take a minute)...')"
pmx_record_execution "Regenerate initramfs" "update-initramfs -u -k all"
update-initramfs -u -k all >/dev/null 2>&1 || true
pmx_record_execution "Refresh Proxmox boot configuration" "proxmox-boot-tool refresh"
proxmox-boot-tool refresh >/dev/null 2>&1 || true
msg_ok "$(translate "IOMMU/VFIO configuration reverted")"
@@ -772,6 +818,8 @@ uninstall_vfio_iommu() {
################################################################
uninstall_amd_fixes() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_amd_fixes" "$FUNC_VERSION"
msg_info2 "$(translate "Reverting AMD (Ryzen/EPYC) fixes...")"
NECESSARY_REBOOT=1
@@ -785,9 +833,10 @@ uninstall_amd_fixes() {
return 1
}
sed -i 's/\bidle=nomwait\b//g; s/[[:space:]]\+/ /g; s/^ //; s/ $//' "$cmdline_file"
pmx_edit_file "$cmdline_file" 's/\bidle=nomwait\b//g; s/[[:space:]]\+/ /g; s/^ //; s/ $//'
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_error "$(translate "Failed to refresh boot configuration")"
return 1
@@ -805,14 +854,15 @@ uninstall_amd_fixes() {
return 1
}
sed -i -E 's/(GRUB_CMDLINE_LINUX_DEFAULT=")/\1/; s/\bidle=nomwait\b//g' "$grub_file"
pmx_edit_file "$grub_file" -E 's/(GRUB_CMDLINE_LINUX_DEFAULT=")/\1/; s/\bidle=nomwait\b//g'
awk -F\" '
$1=="GRUB_CMDLINE_LINUX_DEFAULT=" {
gsub(/[[:space:]]+/," ",$2); sub(/^ /,"",$2); sub(/ $/,"",$2)
}1
' OFS="\"" "$grub_file" > "${grub_file}.tmp" && mv "${grub_file}.tmp" "$grub_file"
' OFS="\"" "$grub_file" | pmx_write_file "$grub_file"
pmx_record_execution "Regenerate GRUB configuration" "update-grub"
update-grub >/dev/null 2>&1 || {
msg_error "$(translate "Failed to update GRUB configuration")"
return 1
@@ -830,17 +880,19 @@ uninstall_amd_fixes() {
msg_error "$(translate "Failed to backup $kvm_conf")"
return 1
}
sed -i -E '/ignore_msrs|report_ignored_msrs/d' "$kvm_conf"
pmx_edit_file "$kvm_conf" -E '/ignore_msrs|report_ignored_msrs/d'
if [[ ! -s "$kvm_conf" ]]; then
rm -f "$kvm_conf"
pmx_remove_file "$kvm_conf"
msg_ok "$(translate "Removed empty KVM configuration file")"
else
msg_ok "$(translate "Removed KVM MSR options from configuration")"
fi
pmx_record_execution "Regenerate initramfs" "update-initramfs -u -k all"
update-initramfs -u -k all >/dev/null 2>&1 || true
proxmox-boot-tool refresh >/dev/null 2>&1 || true
pmx_record_execution "Refresh Proxmox boot configuration" "proxmox-boot-tool refresh"
proxmox-boot-tool refresh >/dev/null 2>&1 || true
else
msg_ok "$(translate "KVM MSR options not present, nothing to revert")"
fi
@@ -957,8 +1009,12 @@ uninstall_ceph() {
}
uninstall_ha() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_ha" "$FUNC_VERSION"
msg_info2 "$(translate 'Disabling High Availability services...')"
systemctl disable --now pve-ha-lrm pve-ha-crm corosync >/dev/null 2>&1 || true
pmx_disable_service pve-ha-lrm || true
pmx_disable_service pve-ha-crm || true
pmx_disable_service corosync || true
msg_ok "$(translate 'HA services disabled (configs preserved)')"
register_tool "ha" false
}
@@ -1009,13 +1065,17 @@ uninstall_ovh_rtm() {
}
uninstall_pigz() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_pigz" "$FUNC_VERSION"
msg_info2 "$(translate 'Reverting pigz wrapper...')"
if [[ -f /bin/gzip.original ]]; then
mv -f /bin/gzip.original /bin/gzip
pmx_write_file /bin/gzip < /bin/gzip.original
pmx_remove_file /bin/gzip.original
msg_ok "$(translate 'Restored original /bin/gzip')"
fi
rm -f /bin/pigzwrapper
sed -i 's/^pigz: 1/#pigz: 1/' /etc/vzdump.conf 2>/dev/null || true
pmx_remove_file /bin/pigzwrapper
pmx_edit_file /etc/vzdump.conf 's/^pigz: 1/#pigz: 1/' 2>/dev/null || true
pmx_record_execution "Purge pigz package" "apt-get purge -y pigz"
apt-get purge -y pigz >/dev/null 2>&1 || true
msg_ok "$(translate 'pigz removed')"
register_tool "pigz" false
@@ -1124,12 +1184,15 @@ uninstall_zfs_autotrim() {
}
uninstall_vzdump_speed() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_vzdump_speed" "$FUNC_VERSION"
msg_info2 "$(translate 'Reverting vzdump speed tuning...')"
if [[ -f /etc/vzdump.conf.bak ]]; then
mv -f /etc/vzdump.conf.bak /etc/vzdump.conf
pmx_write_file /etc/vzdump.conf < /etc/vzdump.conf.bak
pmx_remove_file /etc/vzdump.conf.bak
msg_ok "$(translate 'Restored original /etc/vzdump.conf from .bak')"
else
sed -i '/^bwlimit: 0$/d;/^ionice: 5$/d' /etc/vzdump.conf 2>/dev/null
pmx_edit_file /etc/vzdump.conf '/^bwlimit: 0$/d;/^ionice: 5$/d' 2>/dev/null
msg_ok "$(translate 'Removed bwlimit/ionice tuning (no .bak found)')"
fi
register_tool "vzdump_speed" false
+49 -33
View File
@@ -48,6 +48,10 @@ if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
if [[ ! -f "$COMPONENTS_STATUS_FILE" ]]; then
echo "{}" > "$COMPONENTS_STATUS_FILE"
fi
@@ -79,6 +83,9 @@ detect_fail2ban() {
# Installation
# ==========================================================
install_fail2ban() {
local FUNC_VERSION="1.0"
pmx_journal_context "install_fail2ban" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "$SCRIPT_TITLE")"
msg_info2 "$(translate "Installing and configuring Fail2Ban to protect Proxmox web interface and SSH...")"
@@ -90,7 +97,7 @@ install_fail2ban() {
if ! grep -RqsE "debian.*(bookworm|trixie)" /etc/apt/sources.list /etc/apt/sources.list.d 2>/dev/null; then
msg_warn "$(translate "Debian repositories missing; creating default source file")"
local src="/etc/apt/sources.list.d/debian.sources"
cat > "$src" <<EOF
pmx_write_file "$src" <<EOF
Types: deb
URIs: http://deb.debian.org/debian
Suites: ${deb_codename} ${deb_codename}-updates
@@ -107,7 +114,7 @@ EOF
# Install Fail2Ban
msg_info "$(translate "Installing Fail2Ban...")"
if ! DEBIAN_FRONTEND=noninteractive apt-get update -y >/dev/null 2>&1 || \
! DEBIAN_FRONTEND=noninteractive apt-get install -y fail2ban >/dev/null 2>&1; then
! pmx_install_pkg fail2ban; then
msg_error "$(translate "Failed to install Fail2Ban")"
return 1
fi
@@ -132,7 +139,7 @@ EOF
# Create a drop-in so we don't break other Proxmox settings
mkdir -p /etc/systemd/journald.conf.d
cat > /etc/systemd/journald.conf.d/proxmenux-loglevel.conf <<'JEOF'
pmx_write_file /etc/systemd/journald.conf.d/proxmenux-loglevel.conf <<'JEOF'
# ProxMenux: Allow auth/info messages so Fail2Ban can detect SSH failures
# Proxmox default MaxLevelStore=warning drops PAM/SSH auth events
[Journal]
@@ -148,6 +155,7 @@ JEOF
esac
if $journald_changed; then
pmx_record_execution "restart systemd-journald" "systemctl restart systemd-journald"
systemctl restart systemd-journald
sleep 1
msg_ok "$(translate "journald restarted - auth messages will now be stored")"
@@ -163,7 +171,7 @@ JEOF
# -- Proxmox UI auth logger (pvedaemon) --
msg_info "$(translate "Creating Proxmox auth logger service...")"
cat > /etc/systemd/system/proxmox-auth-logger.service <<'EOF'
pmx_write_file /etc/systemd/system/proxmox-auth-logger.service <<'EOF'
[Unit]
Description=Proxmox Auth Logger for Fail2Ban
Documentation=https://github.com/MacRimi/ProxMenux
@@ -185,12 +193,12 @@ EOF
chown root:adm /var/log/proxmox-auth.log 2>/dev/null || true
systemctl daemon-reload
systemctl enable --now proxmox-auth-logger.service >/dev/null 2>&1
pmx_enable_service proxmox-auth-logger.service
msg_ok "$(translate "Proxmox auth logger service created and started")"
# -- SSH auth logger --
msg_info "$(translate "Creating SSH auth logger service...")"
cat > /etc/systemd/system/ssh-auth-logger.service <<'EOF'
pmx_write_file /etc/systemd/system/ssh-auth-logger.service <<'EOF'
[Unit]
Description=SSH Auth Logger for Fail2Ban
Documentation=https://github.com/MacRimi/ProxMenux
@@ -212,13 +220,13 @@ EOF
chown root:adm /var/log/ssh-auth.log 2>/dev/null || true
systemctl daemon-reload
systemctl enable --now ssh-auth-logger.service >/dev/null 2>&1
pmx_enable_service ssh-auth-logger.service
msg_ok "$(translate "SSH auth logger service created and started")"
# Configure Proxmox filter
mkdir -p /etc/fail2ban/filter.d /etc/fail2ban/jail.d
msg_info "$(translate "Configuring Proxmox filter...")"
cat > /etc/fail2ban/filter.d/proxmox.conf <<'EOF'
pmx_write_file /etc/fail2ban/filter.d/proxmox.conf <<'EOF'
[Definition]
# The proxmox-auth-logger service writes journal lines to /var/log/proxmox-auth.log
# in short-iso format: 2026-02-10T19:36:08+01:00 host pvedaemon[PID]: message
@@ -231,7 +239,7 @@ EOF
# Configure Proxmox jail (file-based backend)
msg_info "$(translate "Configuring Proxmox jail...")"
cat > /etc/fail2ban/jail.d/proxmox.conf <<'EOF'
pmx_write_file /etc/fail2ban/jail.d/proxmox.conf <<'EOF'
[proxmox]
enabled = true
port = 8006
@@ -248,7 +256,7 @@ EOF
# This reads from a file written directly by the Flask app (not syslog/journal),
# so it uses a datepattern that matches Python's logging format.
msg_info "$(translate "Configuring ProxMenux Monitor filter...")"
cat > /etc/fail2ban/filter.d/proxmenux.conf <<'EOF'
pmx_write_file /etc/fail2ban/filter.d/proxmenux.conf <<'EOF'
[Definition]
failregex = ^.*proxmenux-auth: authentication failure; rhost=<HOST> user=.*$
ignoreregex =
@@ -259,7 +267,7 @@ EOF
# Configure ProxMenux Monitor jail (port 8008 + http/https for reverse proxy)
# Uses backend=auto with logpath because the Flask app writes directly to this file.
msg_info "$(translate "Configuring ProxMenux Monitor jail...")"
cat > /etc/fail2ban/jail.d/proxmenux.conf <<'EOF'
pmx_write_file /etc/fail2ban/jail.d/proxmenux.conf <<'EOF'
[proxmenux]
enabled = true
port = 8008,http,https
@@ -289,7 +297,7 @@ EOF
# Configure global settings and SSH jail
msg_info "$(translate "Configuring global Fail2Ban settings and SSH jail...")"
cat > /etc/fail2ban/jail.local <<EOF
pmx_write_file /etc/fail2ban/jail.local <<EOF
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1
ignoreself = true
@@ -325,18 +333,19 @@ EOF
fi
# Store original value in our config directory
echo "$original_max_auth" > "${BASE_DIR}/sshd_maxauthtries_backup"
printf '%s\n' "$original_max_auth" | pmx_write_file "${BASE_DIR}/sshd_maxauthtries_backup"
msg_info "$(translate "Hardening SSH: setting MaxAuthTries to 3...")"
if grep -qi '^MaxAuthTries' "$sshd_config"; then
sed -i 's/^MaxAuthTries.*/MaxAuthTries 3/' "$sshd_config"
pmx_edit_file "$sshd_config" 's/^MaxAuthTries.*/MaxAuthTries 3/'
elif grep -qi '^#MaxAuthTries' "$sshd_config"; then
sed -i 's/^#MaxAuthTries.*/MaxAuthTries 3/' "$sshd_config"
pmx_edit_file "$sshd_config" 's/^#MaxAuthTries.*/MaxAuthTries 3/'
else
echo "MaxAuthTries 3" >> "$sshd_config"
echo "MaxAuthTries 3" | pmx_append_file "$sshd_config"
fi
# Reload SSH to apply the change (reload, not restart, to keep existing sessions)
pmx_record_execution "reload SSH service" "systemctl reload sshd or ssh"
systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || true
msg_ok "$(translate "SSH MaxAuthTries set to 3 (original: ${original_max_auth})")"
fi
@@ -344,7 +353,9 @@ EOF
# Enable and restart the service (restart ensures new jails are loaded
# even if fail2ban was already running from a previous install)
systemctl daemon-reload
systemctl enable fail2ban >/dev/null 2>&1
pmx_apply_setting "fail2ban enabled state" "systemctl is-enabled fail2ban 2>/dev/null || true" \
systemctl enable fail2ban
pmx_record_execution "restart fail2ban" "systemctl restart fail2ban"
systemctl restart fail2ban >/dev/null 2>&1
sleep 3
@@ -372,29 +383,32 @@ EOF
# Uninstall
# ==========================================================
uninstall_fail2ban() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_fail2ban" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "$SCRIPT_TITLE")"
msg_info2 "$(translate "Removing Fail2Ban...")"
systemctl stop fail2ban 2>/dev/null || true
systemctl disable fail2ban 2>/dev/null || true
pmx_disable_service fail2ban 2>/dev/null || true
# Stop and remove the auth logger services
systemctl stop proxmox-auth-logger.service 2>/dev/null || true
systemctl disable proxmox-auth-logger.service 2>/dev/null || true
rm -f /etc/systemd/system/proxmox-auth-logger.service
systemctl stop ssh-auth-logger.service 2>/dev/null || true
systemctl disable ssh-auth-logger.service 2>/dev/null || true
rm -f /etc/systemd/system/ssh-auth-logger.service
pmx_disable_service proxmox-auth-logger.service 2>/dev/null || true
pmx_remove_file /etc/systemd/system/proxmox-auth-logger.service
pmx_disable_service ssh-auth-logger.service 2>/dev/null || true
pmx_remove_file /etc/systemd/system/ssh-auth-logger.service
systemctl daemon-reload 2>/dev/null || true
pmx_record_execution "remove Fail2Ban auth logger files" \
"rm -f /var/log/proxmox-auth.log /var/log/ssh-auth.log"
rm -f /var/log/proxmox-auth.log /var/log/ssh-auth.log
pmx_record_execution "purge fail2ban package" "apt-get purge -y fail2ban"
DEBIAN_FRONTEND=noninteractive apt-get purge -y fail2ban >/dev/null 2>&1
rm -f /etc/fail2ban/jail.d/proxmox.conf
rm -f /etc/fail2ban/jail.d/proxmenux.conf
rm -f /etc/fail2ban/filter.d/proxmox.conf
rm -f /etc/fail2ban/filter.d/proxmenux.conf
rm -f /etc/fail2ban/jail.local
pmx_remove_file /etc/fail2ban/jail.d/proxmox.conf
pmx_remove_file /etc/fail2ban/jail.d/proxmenux.conf
pmx_remove_file /etc/fail2ban/filter.d/proxmox.conf
pmx_remove_file /etc/fail2ban/filter.d/proxmenux.conf
pmx_remove_file /etc/fail2ban/jail.local
# ── Restore SSH MaxAuthTries to original value ──
local sshd_config="/etc/ssh/sshd_config"
@@ -405,17 +419,19 @@ uninstall_fail2ban() {
if [[ -n "$original_val" ]]; then
msg_info "$(translate "Restoring SSH MaxAuthTries to ${original_val}...")"
if grep -qi '^MaxAuthTries' "$sshd_config"; then
sed -i "s/^MaxAuthTries.*/MaxAuthTries ${original_val}/" "$sshd_config"
pmx_edit_file "$sshd_config" "s/^MaxAuthTries.*/MaxAuthTries ${original_val}/"
fi
pmx_record_execution "reload SSH service" "systemctl reload sshd or ssh"
systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || true
msg_ok "$(translate "SSH MaxAuthTries restored to ${original_val}")"
fi
rm -f "$backup_file"
pmx_remove_file "$backup_file"
fi
# Remove journald drop-in and restore original log level
if [[ -f /etc/systemd/journald.conf.d/proxmenux-loglevel.conf ]]; then
rm -f /etc/systemd/journald.conf.d/proxmenux-loglevel.conf
pmx_remove_file /etc/systemd/journald.conf.d/proxmenux-loglevel.conf
pmx_record_execution "restart systemd-journald" "systemctl restart systemd-journald"
systemctl restart systemd-journald 2>/dev/null || true
msg_ok "$(translate "journald log level restored")"
fi
+24 -3
View File
@@ -46,6 +46,10 @@ if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
if [[ ! -f "$COMPONENTS_STATUS_FILE" ]]; then
echo "{}" > "$COMPONENTS_STATUS_FILE"
fi
@@ -80,6 +84,9 @@ detect_lynis() {
# Installation
# ==========================================================
install_lynis() {
local FUNC_VERSION="1.0"
pmx_journal_context "install_lynis" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "$SCRIPT_TITLE")"
msg_info2 "$(translate "Installing latest Lynis security scan tool...")"
@@ -91,7 +98,7 @@ install_lynis() {
if ! command -v git >/dev/null 2>&1; then
msg_info "$(translate "Installing Git as a prerequisite...")"
apt-get update -qq >/dev/null 2>&1
if apt-get install -y git >/dev/null 2>&1 && command -v git >/dev/null 2>&1; then
if pmx_install_pkg git && command -v git >/dev/null 2>&1; then
msg_ok "$(translate "Git installed")"
else
msg_error "$(translate "Could not install Git — Lynis cannot be cloned. Run 'apt-get install git' manually.")"
@@ -102,15 +109,17 @@ install_lynis() {
# Remove old installation if present
if [[ -d /opt/lynis ]]; then
msg_info "$(translate "Removing previous Lynis installation...")"
pmx_record_execution "remove previous Lynis installation from /opt/lynis" "rm -rf /opt/lynis"
rm -rf /opt/lynis >/dev/null 2>&1
msg_ok "$(translate "Previous installation removed")"
fi
# Clone from GitHub
msg_info "$(translate "Cloning Lynis from GitHub...")"
pmx_record_execution "install Lynis in /opt/lynis" "git clone https://github.com/CISOfy/lynis.git /opt/lynis"
if git clone --quiet https://github.com/CISOfy/lynis.git /opt/lynis >/dev/null 2>&1; then
# Create wrapper script
cat << 'EOF' > /usr/local/bin/lynis
pmx_write_file /usr/local/bin/lynis << 'EOF'
#!/bin/bash
cd /opt/lynis && ./lynis "$@"
EOF
@@ -144,6 +153,9 @@ EOF
# Update
# ==========================================================
update_lynis() {
local FUNC_VERSION="1.0"
pmx_journal_context "update_lynis" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "$SCRIPT_TITLE")"
msg_info2 "$(translate "Updating Lynis to the latest version...")"
@@ -151,6 +163,7 @@ update_lynis() {
if [[ -d /opt/lynis/.git ]]; then
cd /opt/lynis
msg_info "$(translate "Pulling latest changes from GitHub...")"
pmx_record_execution "update Lynis installation in /opt/lynis" "git pull --quiet"
if git pull --quiet >/dev/null 2>&1; then
local version
version=$(/usr/local/bin/lynis show version 2>/dev/null)
@@ -174,6 +187,9 @@ update_lynis() {
# Run Audit
# ==========================================================
run_audit() {
local FUNC_VERSION="1.0"
pmx_journal_context "run_audit" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "$SCRIPT_TITLE")"
msg_info2 "$(translate "Running Lynis security audit...")"
@@ -185,6 +201,7 @@ run_audit() {
fi
# Run the audit
pmx_record_execution "run Lynis system audit" "$LYNIS_CMD audit system --no-colors"
"$LYNIS_CMD" audit system --no-colors 2>&1
echo ""
@@ -197,12 +214,16 @@ run_audit() {
# Uninstall
# ==========================================================
uninstall_lynis() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_lynis" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "$SCRIPT_TITLE")"
msg_info2 "$(translate "Removing Lynis...")"
pmx_record_execution "remove Lynis installation from /opt/lynis" "rm -rf /opt/lynis"
rm -rf /opt/lynis 2>/dev/null
rm -f /usr/local/bin/lynis 2>/dev/null
pmx_remove_file /usr/local/bin/lynis 2>/dev/null
update_component_status "lynis" "removed" "" "security" '{}'
+52 -11
View File
@@ -54,6 +54,10 @@ elif [[ -f "$LOCAL_SCRIPTS_DEFAULT/global/disk_ops_helpers.sh" ]]; then
source "$LOCAL_SCRIPTS_DEFAULT/global/disk_ops_helpers.sh"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
load_language
initialize_cache
@@ -471,6 +475,8 @@ format_and_mount_disk() {
local disk="$1"
local mount_path="$2"
local filesystem="$3"
local FUNC_VERSION="1.0"
pmx_journal_context "format_and_mount_disk" "$FUNC_VERSION"
# Final confirmation before any destructive operation
local disk_size
@@ -480,6 +486,8 @@ format_and_mount_disk() {
14 80; then
return 1
fi
pmx_record_execution "format disk ${disk} as ${filesystem} for ${mount_path}" \
"wipe disk, create partition and format as ${filesystem}"
show_proxmenux_logo
if [[ "$MODE_PVESM" -eq 1 && "$MODE_FSTAB" -eq 1 ]]; then
msg_title "$(translate "Add Local Disk (Proxmox storage + host mount)")"
@@ -544,6 +552,8 @@ mount_disk_permanently() {
local partition="$1"
local mount_path="$2"
local filesystem="$3"
local FUNC_VERSION="1.0"
pmx_journal_context "mount_disk_permanently" "$FUNC_VERSION"
if [[ "$filesystem" == "zfs" ]]; then
if ! zpool list "$STORAGE_ID" >/dev/null 2>&1; then
@@ -562,6 +572,8 @@ mount_disk_permanently() {
msg_ok "$(translate "Mount point created")"
msg_info "$(translate "Mounting disk...")"
pmx_record_execution "mount ${partition} at ${mount_path}" \
"mount -t ${filesystem} ${partition} ${mount_path}"
if ! mount -t "$filesystem" "$partition" "$mount_path" 2>/dev/null; then
msg_error "$(translate "Failed to mount disk")"
return 1
@@ -574,13 +586,13 @@ mount_disk_permanently() {
if [[ -n "$disk_uuid" ]]; then
# Remove any existing fstab entry for this UUID or mount point
sed -i "\|UUID=$disk_uuid|d" /etc/fstab
sed -i "\|[[:space:]]${mount_path}[[:space:]]|d" /etc/fstab
echo "UUID=$disk_uuid $mount_path $filesystem defaults,nofail 0 2" >> /etc/fstab
pmx_edit_file /etc/fstab "\|UUID=$disk_uuid|d"
pmx_edit_file /etc/fstab "\|[[:space:]]${mount_path}[[:space:]]|d"
echo "UUID=$disk_uuid $mount_path $filesystem defaults,nofail 0 2" | pmx_append_file /etc/fstab
msg_ok "$(translate "Added to /etc/fstab using UUID")"
else
sed -i "\|[[:space:]]${mount_path}[[:space:]]|d" /etc/fstab
echo "$partition $mount_path $filesystem defaults,nofail 0 2" >> /etc/fstab
pmx_edit_file /etc/fstab "\|[[:space:]]${mount_path}[[:space:]]|d"
echo "$partition $mount_path $filesystem defaults,nofail 0 2" | pmx_append_file /etc/fstab
msg_ok "$(translate "Added to /etc/fstab using device path")"
fi
@@ -604,10 +616,14 @@ mount_disk_permanently() {
# but the change is harmless: existing owners keep their access.
_apply_lxc_bind_mount_perms() {
local mount_path="$1"
local FUNC_VERSION="1.0"
pmx_journal_context "_apply_lxc_bind_mount_perms" "$FUNC_VERSION"
[[ "${MODE_FSTAB:-0}" -eq 1 ]] || return 0
[[ -d "$mount_path" ]] || return 0
msg_info "$(translate "Applying host permissions for unprivileged LXC bind-mounts...")"
pmx_record_execution "apply LXC bind-mount permissions to ${mount_path}" \
"chmod o+rwx and setfacl on ${mount_path}"
chmod o+rwx "$mount_path" 2>/dev/null || true
if command -v setfacl >/dev/null 2>&1; then
setfacl -m o::rwx "$mount_path" 2>/dev/null || true
@@ -619,6 +635,8 @@ _apply_lxc_bind_mount_perms() {
mount_existing_disk() {
local disk="$1"
local mount_path="$2"
local FUNC_VERSION="1.0"
pmx_journal_context "mount_existing_disk" "$FUNC_VERSION"
local existing_fs
existing_fs=$(blkid -s TYPE -o value "$disk" 2>/dev/null || true)
@@ -635,6 +653,7 @@ mount_existing_disk() {
msg_ok "$(translate "Mount point created")"
msg_info "$(translate "Mounting existing") $existing_fs $(translate "filesystem...")"
pmx_record_execution "mount existing disk ${disk} at ${mount_path}" "mount ${disk} ${mount_path}"
if ! mount "$disk" "$mount_path" 2>/dev/null; then
msg_error "$(translate "Failed to mount disk")"
return 1
@@ -645,9 +664,9 @@ mount_existing_disk() {
local disk_uuid
disk_uuid=$(blkid -s UUID -o value "$disk" 2>/dev/null)
if [[ -n "$disk_uuid" ]]; then
sed -i "\|UUID=$disk_uuid|d" /etc/fstab
sed -i "\|[[:space:]]${mount_path}[[:space:]]|d" /etc/fstab
echo "UUID=$disk_uuid $mount_path $existing_fs defaults,nofail 0 2" >> /etc/fstab
pmx_edit_file /etc/fstab "\|UUID=$disk_uuid|d"
pmx_edit_file /etc/fstab "\|[[:space:]]${mount_path}[[:space:]]|d"
echo "UUID=$disk_uuid $mount_path $existing_fs defaults,nofail 0 2" | pmx_append_file /etc/fstab
msg_ok "$(translate "Added to /etc/fstab")"
fi
@@ -664,6 +683,8 @@ add_proxmox_dir_storage() {
local content="$3"
local storage_kind="dir"
local pool_name="$storage_id"
local FUNC_VERSION="1.0"
pmx_journal_context "add_proxmox_dir_storage" "$FUNC_VERSION"
if [[ "${FILESYSTEM:-}" == "zfs" ]]; then
storage_kind="zfspool"
@@ -681,6 +702,7 @@ add_proxmox_dir_storage() {
8 60; then
return 0
fi
pmx_record_execution "remove existing Proxmox storage ${storage_id}" "pvesm remove ${storage_id}"
pvesm remove "$storage_id" 2>/dev/null || true
fi
@@ -688,12 +710,16 @@ add_proxmox_dir_storage() {
local pvesm_output
local add_ok=false
if [[ "$storage_kind" == "zfspool" ]]; then
pmx_record_execution "add ZFS pool ${pool_name} as Proxmox storage ${storage_id}" \
"pvesm add zfspool ${storage_id} --pool ${pool_name} --content ${content}"
if pvesm_output=$(pvesm add zfspool "$storage_id" \
--pool "$pool_name" \
--content "$content" 2>&1); then
add_ok=true
fi
else
pmx_record_execution "add directory ${path} as Proxmox storage ${storage_id}" \
"pvesm add dir ${storage_id} --path ${path} --content ${content}"
if pvesm_output=$(pvesm add dir "$storage_id" \
--path "$path" \
--content "$content" 2>&1); then
@@ -742,6 +768,9 @@ add_proxmox_dir_storage() {
# ==========================================================
add_disk_to_proxmox() {
local FUNC_VERSION="1.0"
pmx_journal_context "add_disk_to_proxmox" "$FUNC_VERSION"
# Check required tools
for tool in parted mkfs.ext4 mkfs.xfs blkid lsblk sgdisk; do
if ! command -v "$tool" >/dev/null 2>&1; then
@@ -749,7 +778,7 @@ add_disk_to_proxmox() {
msg_title "$(translate "Add Local Disk as Proxmox Storage")"
msg_info "$(translate "Installing required tools...")"
apt-get update &>/dev/null
apt-get install -y parted e2fsprogs util-linux xfsprogs gdisk btrfs-progs &>/dev/null
pmx_install_pkg parted e2fsprogs util-linux xfsprogs gdisk btrfs-progs
stop_spinner
break
fi
@@ -990,6 +1019,8 @@ view_disk_storages() {
_remove_pvesm_storage() {
local storage_id="$1"
local FUNC_VERSION="1.0"
pmx_journal_context "_remove_pvesm_storage" "$FUNC_VERSION"
local path pool content stype
path=$(get_storage_config "$storage_id" | awk '$1 == "path" {print $2}')
pool=$(get_storage_config "$storage_id" | awk '$1 == "pool" {print $2}')
@@ -1017,6 +1048,7 @@ _remove_pvesm_storage() {
# Step 1: Remove from Proxmox
msg_info "$(translate "Removing storage from Proxmox...")"
pmx_record_execution "remove Proxmox storage ${storage_id}" "pvesm remove ${storage_id}"
if ! pvesm remove "$storage_id" 2>/dev/null; then
msg_error "$(translate "Failed to remove storage from Proxmox.")"
echo ""
@@ -1029,6 +1061,7 @@ _remove_pvesm_storage() {
# Step 2: Unmount if mounted (dir-backed storages only)
if [[ -n "$path" ]] && mountpoint -q "$path" 2>/dev/null; then
msg_info "$(translate "Unmounting disk...")"
pmx_record_execution "unmount disk from ${path}" "umount ${path}"
if umount "$path" 2>/dev/null; then
msg_ok "$(translate "Disk unmounted from") $path"
else
@@ -1045,7 +1078,9 @@ _remove_pvesm_storage() {
msg_info "$(translate "Removing from /etc/fstab...")"
local tmp
tmp=$(mktemp)
awk -v mp="$path" '$2 != mp' /etc/fstab > "$tmp" && mv "$tmp" /etc/fstab
if awk -v mp="$path" '$2 != mp' /etc/fstab > "$tmp"; then
pmx_write_file /etc/fstab < "$tmp" && rm -f "$tmp"
fi
systemctl daemon-reload 2>/dev/null || true
msg_ok "$(translate "Removed from /etc/fstab")"
fi
@@ -1053,6 +1088,7 @@ _remove_pvesm_storage() {
# Step 3b: Export ZFS pool if applicable
if [[ -n "$pool" ]] && zpool list "$pool" >/dev/null 2>&1; then
msg_info "$(translate "Exporting ZFS pool...") $pool"
pmx_record_execution "export ZFS pool ${pool}" "zpool export ${pool}"
if zpool export "$pool" 2>/dev/null; then
msg_ok "$(translate "ZFS pool exported:") $pool"
else
@@ -1069,6 +1105,7 @@ _remove_pvesm_storage() {
read -r
echo ""
msg_warn "$(translate "Rebooting the system...")"
pmx_record_execution "reboot host after removing storage ${storage_id}" "reboot"
reboot
else
echo ""
@@ -1082,6 +1119,8 @@ _remove_pvesm_storage() {
_remove_fstab_entry() {
local mount_point="$1"
local FUNC_VERSION="1.0"
pmx_journal_context "_remove_fstab_entry" "$FUNC_VERSION"
local fs fstype
while IFS= read -r line; do
@@ -1122,6 +1161,7 @@ _remove_fstab_entry() {
if $mounted; then
msg_info "$(translate "Unmounting") $mount_point..."
pmx_record_execution "unmount disk from ${mount_point}" "umount ${mount_point}"
if umount "$mount_point" 2>/dev/null; then
msg_ok "$(translate "Unmounted successfully")"
else
@@ -1133,7 +1173,8 @@ _remove_fstab_entry() {
local tmp
tmp=$(mktemp)
awk -v mp="$mount_point" '$2 != mp' /etc/fstab > "$tmp"
mv "$tmp" /etc/fstab
pmx_write_file /etc/fstab < "$tmp"
rm -f "$tmp"
systemctl daemon-reload 2>/dev/null || true
msg_ok "$(translate "Removed from /etc/fstab")"
+22 -3
View File
@@ -30,6 +30,10 @@ if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
load_language
initialize_cache
@@ -56,16 +60,20 @@ get_storage_config() {
# ==========================================================
ensure_iscsi_tools() {
local FUNC_VERSION="1.0"
pmx_journal_context "ensure_iscsi_tools" "$FUNC_VERSION"
if ! command -v iscsiadm >/dev/null 2>&1; then
msg_info "$(translate "Installing iSCSI initiator tools...")"
apt-get update &>/dev/null
apt-get install -y open-iscsi &>/dev/null
systemctl enable --now iscsid 2>/dev/null || true
pmx_install_pkg open-iscsi
pmx_enable_service iscsid 2>/dev/null || true
msg_ok "$(translate "iSCSI tools installed")"
fi
if ! systemctl is-active --quiet iscsid 2>/dev/null; then
systemctl start iscsid 2>/dev/null || true
pmx_apply_setting "iscsid active state" "systemctl is-active iscsid 2>/dev/null || true" \
systemctl start iscsid || true
fi
}
@@ -217,6 +225,9 @@ configure_iscsi_storage() {
# ==========================================================
add_proxmox_iscsi_storage() {
local FUNC_VERSION="1.0"
pmx_journal_context "add_proxmox_iscsi_storage" "$FUNC_VERSION"
local storage_id="$1"
local portal="$2"
local target="$3"
@@ -233,6 +244,8 @@ add_proxmox_iscsi_storage() {
8 60 --title "$(translate "Storage Exists")"; then
return 0
fi
pmx_record_execution "remove existing Proxmox iSCSI storage ${storage_id}" \
"pvesm remove ${storage_id}"
pvesm remove "$storage_id" 2>/dev/null || true
fi
@@ -240,6 +253,8 @@ add_proxmox_iscsi_storage() {
msg_info "$(translate "Adding iSCSI storage to Proxmox...")"
local pvesm_output pvesm_result
pmx_record_execution "add iSCSI target ${target} as Proxmox storage ${storage_id}" \
"pvesm add iscsi ${storage_id} --portal ${portal} --target ${target} --content ${content}"
pvesm_output=$(pvesm add iscsi "$storage_id" \
--portal "$portal" \
--target "$target" \
@@ -359,6 +374,9 @@ view_iscsi_storages() {
}
remove_iscsi_storage() {
local FUNC_VERSION="1.0"
pmx_journal_context "remove_iscsi_storage" "$FUNC_VERSION"
if ! command -v pvesm >/dev/null 2>&1; then
dialog --backtitle "ProxMenux" --title "$(translate "Error")" \
--msgbox "\n$(translate "pvesm not found.")" 8 60
@@ -400,6 +418,7 @@ remove_iscsi_storage() {
show_proxmenux_logo
msg_title "$(translate "Remove iSCSI Storage")"
pmx_record_execution "remove Proxmox iSCSI storage ${SELECTED}" "pvesm remove ${SELECTED}"
if pvesm remove "$SELECTED" 2>/dev/null; then
msg_ok "$(translate "Storage") $SELECTED $(translate "removed successfully from Proxmox.")"
else
+13
View File
@@ -42,6 +42,10 @@ if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
SHARE_COMMON_FILE="$LOCAL_SCRIPTS/global/share-common.func"
if ! source "$SHARE_COMMON_FILE" 2>/dev/null; then
msg_error "$(translate "Could not load shared functions. Script cannot continue.")"
@@ -64,9 +68,14 @@ fi
lsm_apply_multi_unpriv_permissions() {
local dir="$1"
local FUNC_VERSION="1.0"
pmx_journal_context "lsm_apply_multi_unpriv_permissions" "$FUNC_VERSION"
[[ -z "$dir" || ! -d "$dir" ]] && return 1
pmx_record_execution "apply shared LXC permission profile to ${dir}" \
"chown root:root; chmod 1777; chmod -R a+rwX; apply default ACLs when available"
# root:root ownership — no new group needed.
chown root:root "$dir" 2>/dev/null || true
@@ -224,6 +233,9 @@ lsm_select_host_mount_point_dialog() {
}
create_shared_directory() {
local FUNC_VERSION="1.0"
pmx_journal_context "create_shared_directory" "$FUNC_VERSION"
lsm_select_host_mount_point_dialog "$(translate "Select Shared Directory Location")" "shared"
[[ -z "$LSM_SELECTED_MOUNT_POINT" ]] && return
SHARED_DIR="$LSM_SELECTED_MOUNT_POINT"
@@ -231,6 +243,7 @@ create_shared_directory() {
show_proxmenux_logo
msg_title "$(translate "Create Shared Directory")"
pmx_record_execution "create shared directory ${SHARED_DIR}" "mkdir -p ${SHARED_DIR}"
if ! mkdir -p "$SHARED_DIR" 2>/dev/null; then
msg_error "$(translate "Failed to create directory:") $SHARED_DIR"
echo ""
+40 -1
View File
@@ -30,6 +30,10 @@
BASE_DIR="/usr/local/share/proxmenux"
source "$BASE_DIR/utils.sh"
if [[ -f "/usr/local/share/proxmenux/scripts/global/pmx_journal.sh" ]]; then
source "/usr/local/share/proxmenux/scripts/global/pmx_journal.sh"
fi
load_language
initialize_cache
@@ -289,6 +293,8 @@ select_lxc_container() {
select_container_mount_point() {
local ctid="$1"
local host_dir="$2"
local FUNC_VERSION="1.0"
pmx_journal_context "select_container_mount_point" "$FUNC_VERSION"
local base_name
base_name=$(basename "$host_dir")
@@ -333,6 +339,8 @@ select_container_mount_point() {
local ct_status
ct_status=$(pct status "$ctid" 2>/dev/null | awk '{print $2}')
if [[ "$ct_status" == "running" ]]; then
pmx_record_execution "create mount directory ${mount_point} in CT ${ctid}" \
"pct exec ${ctid} -- mkdir -p ${mount_point}"
pct exec "$ctid" -- mkdir -p "$mount_point" 2>/dev/null
fi
@@ -367,6 +375,8 @@ add_bind_mount() {
local ctid="$1"
local host_path="$2"
local ct_path="$3"
local FUNC_VERSION="1.0"
pmx_journal_context "add_bind_mount" "$FUNC_VERSION"
if [[ ! "$ctid" =~ ^[0-9]+$ || -z "$host_path" || -z "$ct_path" ]]; then
msg_error "$(translate "Invalid parameters for bind mount")"
@@ -383,6 +393,8 @@ add_bind_mount() {
mpidx=$(get_next_mp_index "$ctid")
local result
pmx_record_execution "add bind mount ${host_path} to CT ${ctid} at ${ct_path}" \
"pct set ${ctid} -mp${mpidx} ${host_path},mp=${ct_path},shared=1,backup=0"
result=$(pct set "$ctid" -mp${mpidx} "$host_path,mp=$ct_path,shared=1,backup=0" 2>&1)
if [[ $? -eq 0 ]]; then
@@ -451,6 +463,9 @@ view_mount_points() {
}
remove_mount_point() {
local FUNC_VERSION="1.0"
pmx_journal_context "remove_mount_point" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "Remove LXC Mount Point")"
@@ -532,6 +547,8 @@ $(translate "Proceed with removal")?"
msg_title "$(translate "Remove LXC Mount Point")"
msg_info "$(translate "Removing mount point") $selected_mp $(translate "from container") $container_id..."
pmx_record_execution "remove mount point ${selected_mp} from CT ${container_id}" \
"pct set ${container_id} --delete ${selected_mp}"
if pct set "$container_id" --delete "$selected_mp" 2>/dev/null; then
msg_ok "$(translate "Mount point removed successfully")"
@@ -541,6 +558,8 @@ $(translate "Proceed with removal")?"
echo ""
if whiptail --yesno "$(translate "Container is running. Restart to apply changes?")" 8 60; then
msg_info "$(translate "Restarting container...")"
pmx_record_execution "restart CT ${container_id} after removing ${selected_mp}" \
"pct reboot ${container_id}"
if pct reboot "$container_id"; then
sleep 3
msg_ok "$(translate "Container restarted successfully")"
@@ -573,6 +592,8 @@ $(translate "Proceed with removal")?"
lmm_fix_cifs_access() {
local host_dir="$1"
local is_unprivileged="$2"
local FUNC_VERSION="1.0"
pmx_journal_context "lmm_fix_cifs_access" "$FUNC_VERSION"
# CIFS mounted by Proxmox GUI uses uid=0/gid=0 by default (root only).
# The fix: remount with uid/gid that the LXC can access.
@@ -620,13 +641,16 @@ $(translate "Apply fix now? (The share will be briefly remounted)")" \
18 84 3>&1 1>&2 2>&3; then
msg_info "$(translate "Remounting CIFS share with open permissions...")"
pmx_record_execution "remount CIFS share ${mount_src} at ${host_dir}" \
"umount ${host_dir}; mount -t cifs ${mount_src} ${host_dir} -o ${new_opts}"
if umount "$host_dir" 2>/dev/null && \
mount -t cifs "$mount_src" "$host_dir" -o "$new_opts" 2>/dev/null; then
msg_ok "$(translate "CIFS share remounted — LXC containers can now read and write")"
# Update fstab if the mount is there
if grep -qF "$host_dir" /etc/fstab 2>/dev/null; then
sed -i "s|^\(${mount_src}[[:space:]].*${host_dir}.*cifs[[:space:]]\).*|\1${new_opts} 0 0|" /etc/fstab 2>/dev/null || true
pmx_edit_file /etc/fstab \
"s|^\(${mount_src}[[:space:]].*${host_dir}.*cifs[[:space:]]\).*|\1${new_opts} 0 0|" 2>/dev/null || true
msg_ok "$(translate "/etc/fstab updated — permissions will persist after reboot")"
fi
else
@@ -639,6 +663,8 @@ lmm_fix_nfs_access() {
local host_dir="$1"
local is_unprivileged="$2"
local uid_shift="${3:-100000}"
local FUNC_VERSION="1.0"
pmx_journal_context "lmm_fix_nfs_access" "$FUNC_VERSION"
# NFS: the host cannot override server-side permissions.
# BUT: if the server exports with root_squash (default), we can check
@@ -678,6 +704,8 @@ $(translate "If it still fails, the NFS server export options must be changed on
$(translate "Apply fix now?")" \
18 84 3>&1 1>&2 2>&3; then
pmx_record_execution "apply LXC access permissions to NFS directory ${host_dir}" \
"chmod 1777 and setfacl on ${host_dir}"
if chmod 1777 "$host_dir" 2>/dev/null; then
msg_ok "$(translate "NFS directory permissions set — containers should now be able to write")"
else
@@ -716,6 +744,8 @@ $(translate "You can still mount this share for READ-ONLY access.")" \
lmm_offer_host_permissions() {
local host_dir="$1"
local is_unprivileged="$2"
local FUNC_VERSION="1.0"
pmx_journal_context "lmm_offer_host_permissions" "$FUNC_VERSION"
# Privileged containers: UID 0 inside = UID 0 on host — always accessible
[[ "$is_unprivileged" != "1" ]] && return 0
@@ -749,6 +779,8 @@ $(translate "Apply read+write access for 'others' on the host directory?")\n\n\
$(translate "(Only the host directory is modified. Nothing inside the container is changed.")" \
16 80 3>&1 1>&2 2>&3; then
pmx_record_execution "grant mapped LXC users access to host directory ${host_dir}" \
"chmod o+rwx and setfacl on ${host_dir}"
chmod o+rwx "$host_dir" 2>/dev/null || true
if command -v setfacl >/dev/null 2>&1; then
setfacl -m o::rwx "$host_dir" 2>/dev/null || true
@@ -798,6 +830,8 @@ _lmm_verify_writable() {
# ==========================================================
mount_host_directory_minimal() {
local FUNC_VERSION="1.0"
# Step 1: Select container
local container_id
container_id=$(select_lxc_container)
@@ -900,10 +934,13 @@ $(translate "Proceed")?"
# bind-mount is supposed to spare them.
local ct_status
ct_status=$(pct status "$container_id" 2>/dev/null | awk '{print $2}')
pmx_journal_context "mount_host_directory_minimal" "$FUNC_VERSION"
echo ""
if [[ "$ct_status" == "running" ]]; then
if whiptail --yesno "$(translate "Restart container to activate mount?")" 8 60; then
msg_info "$(translate "Restarting container...")"
pmx_record_execution "restart CT ${container_id} to activate bind mount" \
"pct reboot ${container_id}"
if pct reboot "$container_id"; then
sleep 5
msg_ok "$(translate "Container restarted successfully")"
@@ -918,6 +955,8 @@ $(translate "Proceed")?"
# declines, fall back to the informational line.
if whiptail --yesno "$(translate "Container is stopped. Start it now to verify the mount works?")" 8 70; then
msg_info "$(translate "Starting container...")"
pmx_record_execution "start CT ${container_id} to activate and verify bind mount" \
"pct start ${container_id}"
if pct start "$container_id"; then
sleep 5
msg_ok "$(translate "Container started successfully")"
+29 -5
View File
@@ -29,6 +29,10 @@ if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
# Load shared functions
SHARE_COMMON_FILE="$LOCAL_SCRIPTS/global/share-common.func"
if ! source "$SHARE_COMMON_FILE" 2>/dev/null; then
@@ -44,6 +48,8 @@ select_privileged_lxc
install_nfs_client() {
local FUNC_VERSION="1.0"
pmx_journal_context "install_nfs_client" "$FUNC_VERSION"
if pct exec "$CTID" -- dpkg -s nfs-common &>/dev/null; then
return 0
@@ -65,6 +71,8 @@ install_nfs_client() {
fi
msg_info "$(translate "Installing NFS client packages...")"
pmx_record_execution "install NFS client packages in CT ${CTID}" \
"pct exec ${CTID} -- apt-get update and apt-get install -y nfs-common"
if ! pct exec "$CTID" -- apt-get update >/dev/null 2>&1; then
msg_error "$(translate "Failed to update package list.")"
msg_success "$(translate "Press Enter to return to menu...")"
@@ -99,6 +107,9 @@ install_nfs_client() {
discover_nfs_servers() {
local FUNC_VERSION="1.0"
pmx_journal_context "discover_nfs_servers" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "Mount NFS Client in LXC")"
msg_info "$(translate "Scanning network for NFS servers...")"
@@ -110,7 +121,7 @@ discover_nfs_servers() {
if ! which nmap >/dev/null 2>&1; then
apt-get install -y nmap &>/dev/null
pmx_install_pkg nmap
fi
@@ -367,6 +378,7 @@ validate_export_exists() {
mount_nfs_share() {
local FUNC_VERSION="1.0"
# Step 0: Install NFS client first
install_nfs_client || return
@@ -395,7 +407,9 @@ mount_nfs_share() {
# Step 4: Configure mount options
configure_mount_options || return
pmx_journal_context "mount_nfs_share" "$FUNC_VERSION"
pmx_record_execution "mount NFS export ${NFS_SERVER}:${NFS_EXPORT} in CT ${CTID} at ${MOUNT_POINT}" \
"pct exec ${CTID} -- mount NFS; persistent=${PERMANENT_MOUNT}"
if ! pct exec "$CTID" -- test -d "$MOUNT_POINT"; then
@@ -432,9 +446,9 @@ mount_nfs_share() {
# Add to fstab if permanent
if [[ "$PERMANENT_MOUNT" == "true" ]]; then
pct exec "$CTID" -- sed -i "\|$MOUNT_POINT|d" /etc/fstab
pct exec "$CTID" -- sed --in-place "\|$MOUNT_POINT|d" /etc/fstab
FSTAB_ENTRY="$NFS_PATH $MOUNT_POINT nfs ${MOUNT_OPTIONS},_netdev,x-systemd.automount,noauto 0 0"
pct exec "$CTID" -- bash -c "echo '$FSTAB_ENTRY' >> /etc/fstab"
pct exec "$CTID" -- bash -c "printf '%s\\n' '$FSTAB_ENTRY' | tee -a /etc/fstab >/dev/null"
msg_ok "$(translate "Added to /etc/fstab for permanent mounting.")"
fi
@@ -543,6 +557,9 @@ view_nfs_mounts() {
unmount_nfs_share() {
local FUNC_VERSION="1.0"
pmx_journal_context "unmount_nfs_share" "$FUNC_VERSION"
# Get current NFS mounts
MOUNTS=$(pct exec "$CTID" -- mount | grep -E "type nfs|:.*on.*nfs" | awk '{print $3}' | sort -u || true)
FSTAB_MOUNTS=$(pct exec "$CTID" -- grep -E "nfs" /etc/fstab 2>/dev/null | grep -v "^#" | awk '{print $2}' | sort -u || true)
@@ -568,7 +585,9 @@ unmount_nfs_share() {
msg_title "$(translate "Unmount NFS Share")"
# Remove from fstab
pct exec "$CTID" -- sed -i "\|[[:space:]]$SELECTED_MOUNT[[:space:]]|d" /etc/fstab
pmx_record_execution "remove NFS mount ${SELECTED_MOUNT} from CT ${CTID}" \
"remove CT fstab entry and unmount ${SELECTED_MOUNT}"
pct exec "$CTID" -- sed --in-place "\|[[:space:]]$SELECTED_MOUNT[[:space:]]|d" /etc/fstab
msg_ok "$(translate "Removed from /etc/fstab.")"
# Actually unmount it now (the previous version only edited fstab,
@@ -598,6 +617,9 @@ unmount_nfs_share() {
test_nfs_connectivity() {
local FUNC_VERSION="1.0"
pmx_journal_context "test_nfs_connectivity" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "Test NFS Connectivity")"
@@ -621,6 +643,8 @@ test_nfs_connectivity() {
else
echo "$(translate "RPC Bind Service: STOPPED")"
msg_warn "$(translate "Starting rpcbind service...")"
pmx_record_execution "start rpcbind in CT ${CTID}" \
"pct exec ${CTID} -- systemctl start rpcbind"
pct exec "$CTID" -- systemctl start rpcbind 2>/dev/null || true
fi
+38 -6
View File
@@ -38,6 +38,10 @@ if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
load_language
initialize_cache
@@ -64,6 +68,9 @@ get_storage_config() {
# ==========================================================
discover_nfs_servers() {
local FUNC_VERSION="1.0"
pmx_journal_context "discover_nfs_servers" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "Add NFS Share as Proxmox Storage")"
msg_info "$(translate "Scanning network for NFS servers...")"
@@ -72,7 +79,7 @@ discover_nfs_servers() {
NETWORK=$(echo "$HOST_IP" | cut -d. -f1-3).0/24
if ! which nmap >/dev/null 2>&1; then
apt-get install -y nmap &>/dev/null
pmx_install_pkg nmap
fi
SERVERS=$(nmap -p 2049 --open "$NETWORK" 2>/dev/null | grep -B 4 "2049/tcp open" | grep "Nmap scan report" | awk '{print $5}' | sort -u || true)
@@ -253,6 +260,8 @@ add_proxmox_nfs_storage() {
local server="$2"
local export="$3"
local content="${4:-import}"
local FUNC_VERSION="1.0"
pmx_journal_context "add_proxmox_nfs_storage" "$FUNC_VERSION"
msg_info "$(translate "Starting Proxmox storage integration...")"
@@ -267,11 +276,15 @@ add_proxmox_nfs_storage() {
8 60 --title "$(translate "Storage Exists")"; then
return 0
fi
pmx_record_execution "remove existing Proxmox NFS storage ${storage_id}" \
"pvesm remove ${storage_id}"
pvesm remove "$storage_id" 2>/dev/null || true
fi
msg_ok "$(translate "Storage ID is available")"
msg_info "$(translate "NFS storage adding in progress...")"
pmx_record_execution "add NFS export ${server}:${export} as Proxmox storage ${storage_id}" \
"pvesm add nfs ${storage_id} --server ${server} --export ${export} --content ${content}"
if pvesm_output=$(pvesm add nfs "$storage_id" \
--server "$server" \
--export "$export" \
@@ -384,6 +397,8 @@ mount_nfs_via_fstab() {
local mount_path="$3"
local mount_opts="$4"
local replace="$5"
local FUNC_VERSION="1.0"
pmx_journal_context "mount_nfs_via_fstab" "$FUNC_VERSION"
msg_info "$(translate "Preparing host mount...")"
@@ -396,6 +411,8 @@ mount_nfs_via_fstab() {
msg_ok "$(translate "Mount point ready:") $mount_path"
msg_info "$(translate "Mounting NFS share...")"
pmx_record_execution "mount NFS export ${server}:${export_path} at ${mount_path}" \
"mount -t nfs -o ${mount_opts} ${server}:${export_path} ${mount_path}"
if ! mount -t nfs -o "$mount_opts" "${server}:${export_path}" "$mount_path" >/dev/null 2>&1; then
msg_error "$(translate "Failed to mount NFS share on host.")"
return 1
@@ -418,11 +435,12 @@ mount_nfs_via_fstab() {
# Persist in /etc/fstab.
if [[ "$replace" == "1" ]]; then
sed -i "\|[[:space:]]${mount_path}[[:space:]]|d" /etc/fstab
pmx_edit_file /etc/fstab "\|[[:space:]]${mount_path}[[:space:]]|d"
fi
echo "${server}:${export_path} $mount_path nfs $mount_opts 0 0" >> /etc/fstab
echo "${server}:${export_path} $mount_path nfs $mount_opts 0 0" | pmx_append_file /etc/fstab
msg_ok "$(translate "Added to /etc/fstab.")"
pmx_record_execution "reload systemd units after NFS fstab update" "systemctl daemon-reload"
systemctl daemon-reload 2>/dev/null || true
echo -e ""
@@ -480,10 +498,13 @@ select_mount_methods() {
# ==========================================================
mount_nfs_share() {
local FUNC_VERSION="1.0"
pmx_journal_context "mount_nfs_share" "$FUNC_VERSION"
if ! which showmount >/dev/null 2>&1; then
msg_info "$(translate "Installing NFS client tools...")"
apt-get update &>/dev/null
apt-get install -y nfs-common &>/dev/null
pmx_install_pkg nfs-common
msg_ok "$(translate "NFS client tools installed")"
fi
@@ -654,6 +675,9 @@ view_nfs_storages() {
}
remove_nfs_storage() {
local FUNC_VERSION="1.0"
pmx_journal_context "remove_nfs_storage" "$FUNC_VERSION"
# Collect every removable NFS entry: pvesm storages and fstab-only mounts.
local OPTIONS=()
local has_pvesm=0
@@ -718,6 +742,7 @@ remove_nfs_storage() {
show_proxmenux_logo
msg_title "$(translate "Remove NFS Storage")"
pmx_record_execution "remove Proxmox NFS storage ${target}" "pvesm remove ${target}"
if pvesm remove "$target" 2>/dev/null; then
msg_ok "$(translate "Storage") $target $(translate "removed successfully from Proxmox.")"
else
@@ -742,6 +767,7 @@ remove_nfs_storage() {
# Try umount only if currently mounted; never force.
if mount | grep -q " on ${mount_path} type "; then
pmx_record_execution "unmount NFS path ${mount_path}" "umount ${mount_path}"
if umount "$mount_path" 2>/dev/null; then
msg_ok "$(translate "Unmounted:") $mount_path"
else
@@ -756,12 +782,14 @@ remove_nfs_storage() {
if awk -v mp="$mount_path" '
$2 == mp && ($3 == "nfs" || $3 == "nfs4") { next }
{ print }
' /etc/fstab > /etc/fstab.tmp && mv /etc/fstab.tmp /etc/fstab; then
' /etc/fstab > /etc/fstab.tmp && pmx_write_file /etc/fstab < /etc/fstab.tmp; then
rm -f /etc/fstab.tmp
msg_ok "$(translate "Removed entry from /etc/fstab") ($(translate "backup at /etc/fstab.proxmenux.bak"))"
else
msg_error "$(translate "Failed to edit /etc/fstab — remove the line manually.")"
fi
pmx_record_execution "reload systemd units after NFS fstab removal" "systemctl daemon-reload"
systemctl daemon-reload 2>/dev/null || true
# Try to remove the directory if empty; keep it otherwise.
@@ -778,6 +806,9 @@ remove_nfs_storage() {
}
test_nfs_connectivity() {
local FUNC_VERSION="1.0"
pmx_journal_context "test_nfs_connectivity" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "Test NFS Connectivity")"
@@ -791,7 +822,8 @@ test_nfs_connectivity() {
msg_ok "$(translate "RPC Bind Service: RUNNING")"
else
msg_warn "$(translate "RPC Bind Service: STOPPED - starting...")"
systemctl start rpcbind 2>/dev/null || true
pmx_apply_setting "rpcbind active state" "systemctl is-active rpcbind 2>/dev/null || true" \
systemctl start rpcbind || true
fi
else
msg_warn "$(translate "NFS Client Tools: NOT AVAILABLE")"
+33 -5
View File
@@ -31,6 +31,10 @@ if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
# Load shared functions
SHARE_COMMON_FILE="$LOCAL_SCRIPTS/global/share-common.func"
if ! source "$SHARE_COMMON_FILE" 2>/dev/null; then
@@ -49,6 +53,10 @@ select_privileged_lxc
setup_universal_sharedfiles_group() {
local ctid="$1"
local FUNC_VERSION="1.0"
pmx_journal_context "setup_universal_sharedfiles_group" "$FUNC_VERSION"
pmx_record_execution "configure sharedfiles group and UID mappings in CT ${ctid}" \
"pct exec ${ctid} -- manage sharedfiles group, memberships and remapped users"
msg_info "$(translate "Setting sharedfiles group with UID remapping...")"
@@ -135,6 +143,9 @@ setup_universal_sharedfiles_group() {
select_mount_point() {
local FUNC_VERSION="1.0"
pmx_journal_context "select_mount_point" "$FUNC_VERSION"
while true; do
METHOD=$(whiptail --backtitle "ProxMenux" --title "$(translate "Select Folder")" \
--menu "$(translate "How do you want to select the folder to export?")" 15 60 5 \
@@ -181,6 +192,8 @@ select_mount_point() {
--msgbox "$(translate "No mount point was specified.")" 8 50
continue
fi
pmx_record_execution "create NFS export directory ${MOUNT_POINT} in CT ${CTID}" \
"pct exec ${CTID} -- mkdir -p ${MOUNT_POINT}"
pct exec "$CTID" -- mkdir -p "$MOUNT_POINT" 2>/dev/null
return 0
;;
@@ -252,6 +265,7 @@ select_export_options() {
create_nfs_export() {
local FUNC_VERSION="1.0"
show_proxmenux_logo
msg_title "$(translate "Create LXC server NFS")"
@@ -262,6 +276,10 @@ create_nfs_export() {
get_network_config || return
select_export_options || return
pmx_journal_context "create_nfs_export" "$FUNC_VERSION"
pmx_record_execution "configure NFS export ${MOUNT_POINT} in CT ${CTID}" \
"install and enable NFS services, update /etc/exports and reload exports"
msg_ok "$(translate "Directory successfully.")"
@@ -269,7 +287,7 @@ create_nfs_export() {
if ! pct exec "$CTID" -- dpkg -s nfs-kernel-server &>/dev/null; then
msg_info "$(translate "Installing NFS server packages inside the CT...")"
pct exec "$CTID" -- bash -c "apt-get update && apt-get install -y nfs-kernel-server nfs-common rpcbind"
pct exec "$CTID" -- systemctl enable --now rpcbind nfs-kernel-server
pct exec "$CTID" -- systemctl --now enable rpcbind nfs-kernel-server
msg_ok "$(translate "NFS server installed successfully.")"
else
msg_ok "$(translate "NFS server is already installed.")"
@@ -296,8 +314,8 @@ create_nfs_export() {
if pct exec "$CTID" -- grep -q "^$MOUNT_POINT " /etc/exports; then
if dialog --yesno "$(translate "Do you want to update the existing export?")" \
10 60 --title "$(translate "Update Export")"; then
pct exec "$CTID" -- sed -i "\|^$MOUNT_POINT |d" /etc/exports
pct exec "$CTID" -- bash -c "echo '$EXPORT_LINE' >> /etc/exports"
pct exec "$CTID" -- sed --in-place "\|^$MOUNT_POINT |d" /etc/exports
pct exec "$CTID" -- bash -c "printf '%s\\n' '$EXPORT_LINE' | tee -a /etc/exports >/dev/null"
show_proxmenux_logo
msg_title "$(translate "Create LXC server NFS")"
msg_ok "$(translate "Directory successfully.")"
@@ -307,7 +325,7 @@ create_nfs_export() {
fi
else
pct exec "$CTID" -- bash -c "echo '$EXPORT_LINE' >> /etc/exports"
pct exec "$CTID" -- bash -c "printf '%s\\n' '$EXPORT_LINE' | tee -a /etc/exports >/dev/null"
msg_ok "$(translate "Export added successfully.")"
fi
@@ -405,6 +423,9 @@ view_exports() {
}
delete_export() {
local FUNC_VERSION="1.0"
pmx_journal_context "delete_export" "$FUNC_VERSION"
if ! pct exec "$CTID" -- test -f /etc/exports; then
dialog --title "$(translate "Error")" --msgbox "\n$(translate "No exports file found.")" 8 50
return
@@ -435,7 +456,9 @@ delete_export() {
if whiptail --yesno "$(translate "Are you sure you want to delete this export?")\n\n$EXPORT_LINE" 10 70 --title "$(translate "Confirm Deletion")"; then
show_proxmenux_logo
msg_title "$(translate "Delete Export")"
pct exec "$CTID" -- sed -i "${SELECTED_NUM}d" /etc/exports
pmx_record_execution "remove NFS export line ${SELECTED_NUM} from CT ${CTID}" \
"edit /etc/exports and restart nfs-kernel-server"
pct exec "$CTID" -- sed --in-place "${SELECTED_NUM}d" /etc/exports
pct exec "$CTID" -- exportfs -ra
pct exec "$CTID" -- systemctl restart nfs-kernel-server
msg_ok "$(translate "Export deleted and NFS service restarted.")"
@@ -506,6 +529,9 @@ check_nfs_status() {
}
uninstall_nfs() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_nfs" "$FUNC_VERSION"
if ! pct exec "$CTID" -- dpkg -s nfs-kernel-server &>/dev/null; then
dialog --title "$(translate "NFS Not Installed")" --msgbox "\n$(translate "NFS server is not installed in this CT.")" 8 60
return
@@ -519,6 +545,8 @@ uninstall_nfs() {
show_proxmenux_logo
msg_title "$(translate "Uninstall NFS Server")"
pmx_record_execution "uninstall NFS server from CT ${CTID}" \
"stop and disable NFS services, clear exports, remove users, groups and packages"
msg_info "$(translate "Stopping NFS services...")"
pct exec "$CTID" -- systemctl stop nfs-kernel-server 2>/dev/null || true
+30 -5
View File
@@ -33,6 +33,10 @@ if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
SHARE_COMMON_FILE="$LOCAL_SCRIPTS/global/share-common.func"
if ! source "$SHARE_COMMON_FILE" 2>/dev/null; then
@@ -49,6 +53,10 @@ select_privileged_lxc
install_samba_client() {
local FUNC_VERSION="1.0"
pmx_journal_context "install_samba_client" "$FUNC_VERSION"
pmx_record_execution "install and prepare Samba client in CT ${CTID}" \
"pct exec ${CTID} -- install cifs-utils and smbclient; create ${CREDENTIALS_DIR}"
if pct exec "$CTID" -- dpkg -s cifs-utils &>/dev/null && pct exec "$CTID" -- dpkg -s smbclient &>/dev/null; then
pct exec "$CTID" -- mkdir -p "$CREDENTIALS_DIR"
@@ -94,6 +102,9 @@ install_samba_client() {
discover_samba_servers() {
local FUNC_VERSION="1.0"
pmx_journal_context "discover_samba_servers" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "Samba LXC Manager")"
msg_info "$(translate "Scanning network for Samba servers...")"
@@ -105,7 +116,7 @@ discover_samba_servers() {
for pkg in nmap samba-common-bin; do
if ! which ${pkg%%-*} >/dev/null 2>&1; then
apt-get install -y "$pkg" &>/dev/null
pmx_install_pkg "$pkg"
fi
done
@@ -678,13 +689,18 @@ configure_mount_options() {
}
create_credentials_file() {
local FUNC_VERSION="1.0"
pmx_journal_context "create_credentials_file" "$FUNC_VERSION"
if [[ "$USE_GUEST" == "true" ]]; then
return 0
fi
CRED_FILE="$CREDENTIALS_DIR/${SAMBA_SERVER}_${SAMBA_SHARE}.cred"
pmx_record_execution "create Samba credentials file ${CRED_FILE} in CT ${CTID}" \
"pct exec ${CTID} -- write credentials file and chmod 600"
pct exec "$CTID" -- bash -c "cat > '$CRED_FILE' << EOF
username=$USERNAME
@@ -729,6 +745,7 @@ EOF"
}
mount_samba_share() {
local FUNC_VERSION="1.0"
# Step 0:
install_samba_client || return
@@ -754,6 +771,10 @@ mount_samba_share() {
# Step 5:
configure_mount_options || return
pmx_journal_context "mount_samba_share" "$FUNC_VERSION"
pmx_record_execution "mount Samba share //${SAMBA_SERVER}/${SAMBA_SHARE} in CT ${CTID} at ${MOUNT_POINT}" \
"pct exec ${CTID} -- mount CIFS share; persistent=${PERMANENT_MOUNT}"
show_proxmenux_logo
msg_title "$(translate "Installing Samba Client in LXC")"
@@ -803,11 +824,11 @@ mount_samba_share() {
if [[ "$PERMANENT_MOUNT" == "true" ]]; then
pct exec "$CTID" -- sed -i "\|$MOUNT_POINT|d" /etc/fstab
pct exec "$CTID" -- sed --in-place "\|$MOUNT_POINT|d" /etc/fstab
FSTAB_ENTRY="$UNC_PATH $MOUNT_POINT cifs ${FULL_OPTIONS},_netdev,x-systemd.automount,noauto 0 0"
pct exec "$CTID" -- bash -c "echo '$FSTAB_ENTRY' >> /etc/fstab"
pct exec "$CTID" -- bash -c "printf '%s\\n' '$FSTAB_ENTRY' | tee -a /etc/fstab >/dev/null"
msg_ok "$(translate "Added to /etc/fstab for permanent mounting.")"
fi
@@ -927,6 +948,8 @@ view_samba_mounts() {
unmount_samba_share() {
local FUNC_VERSION="1.0"
pmx_journal_context "unmount_samba_share" "$FUNC_VERSION"
MOUNTS=$(pct exec "$CTID" -- mount -t cifs 2>/dev/null | awk '{print $3}' | sort -u || true)
@@ -955,7 +978,9 @@ unmount_samba_share() {
msg_title "$(translate "Unmount Samba Share")"
CRED_FILE=$(pct exec "$CTID" -- grep -E "\s+$SELECTED_MOUNT\s+" /etc/fstab 2>/dev/null | grep -o "credentials=[^, ]*" | cut -d= -f2 || true)
pct exec "$CTID" -- sed -i "\|[[:space:]]$SELECTED_MOUNT[[:space:]]|d" /etc/fstab
pmx_record_execution "remove Samba mount ${SELECTED_MOUNT} from CT ${CTID}" \
"remove CT fstab entry and credentials file when present"
pct exec "$CTID" -- sed --in-place "\|[[:space:]]$SELECTED_MOUNT[[:space:]]|d" /etc/fstab
msg_ok "$(translate "Removed from /etc/fstab.")"
if [[ -n "$CRED_FILE" && "$CRED_FILE" != "guest" ]]; then
+47 -8
View File
@@ -44,6 +44,10 @@ if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
load_language
initialize_cache
@@ -70,6 +74,9 @@ get_storage_config() {
# ==========================================================
discover_samba_servers() {
local FUNC_VERSION="1.0"
pmx_journal_context "discover_samba_servers" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "Add Samba Share as Proxmox Storage")"
msg_info "$(translate "Scanning network for Samba servers...")"
@@ -79,7 +86,7 @@ discover_samba_servers() {
for pkg in nmap samba-common-bin; do
if ! which "${pkg%%-*}" >/dev/null 2>&1; then
apt-get install -y "$pkg" &>/dev/null
pmx_install_pkg "$pkg" &>/dev/null
fi
done
@@ -274,6 +281,8 @@ add_proxmox_cifs_storage() {
local server="$2"
local share="$3"
local content="${4:-import}"
local FUNC_VERSION="1.0"
pmx_journal_context "add_proxmox_cifs_storage" "$FUNC_VERSION"
if ! command -v pvesm >/dev/null 2>&1; then
msg_error "$(translate "pvesm command not found. This should not happen on Proxmox.")"
@@ -288,6 +297,8 @@ add_proxmox_cifs_storage() {
8 60 --title "$(translate "Storage Exists")"; then
return 0
fi
pmx_record_execution "remove Proxmox CIFS storage ${storage_id}" \
"pvesm remove ${storage_id}"
pvesm remove "$storage_id" 2>/dev/null || true
fi
@@ -295,6 +306,8 @@ add_proxmox_cifs_storage() {
msg_info "$(translate "Adding CIFS storage to Proxmox...")"
local pvesm_result pvesm_output
pmx_record_execution "add Proxmox CIFS storage ${storage_id}" \
"pvesm add cifs ${storage_id} --server ${server} --share ${share} --content ${content}"
if [[ "$USE_GUEST" == "true" ]]; then
pvesm_output=$(pvesm add cifs "$storage_id" \
--server "$server" \
@@ -414,15 +427,20 @@ select_cifs_mount_options() {
# Write a root-only credentials file for the fstab mount.
# Sets HOST_CRED_FILE on success, or empty string for guest mode.
write_host_credentials_file() {
local FUNC_VERSION="1.0"
pmx_journal_context "write_host_credentials_file" "$FUNC_VERSION"
if [[ "$USE_GUEST" == "true" ]]; then
HOST_CRED_FILE=""
return 0
fi
local creds_dir="/etc/samba/credentials"
pmx_record_execution "create Samba credentials directory ${creds_dir}" \
"mkdir -p ${creds_dir}; chmod 0700 ${creds_dir}"
mkdir -p "$creds_dir"
chmod 0700 "$creds_dir"
HOST_CRED_FILE="${creds_dir}/$(echo "${SAMBA_SERVER}_${SAMBA_SHARE}" | tr -c 'A-Za-z0-9._-' '_').cred"
cat > "$HOST_CRED_FILE" <<EOF
pmx_write_file "$HOST_CRED_FILE" <<EOF
username=${USERNAME}
password=${PASSWORD}
EOF
@@ -440,10 +458,14 @@ mount_cifs_via_fstab() {
local replace="$5"
local cred_file="$6"
local use_guest="$7"
local FUNC_VERSION="1.0"
pmx_journal_context "mount_cifs_via_fstab" "$FUNC_VERSION"
msg_info "$(translate "Preparing host mount...")"
if [[ ! -d "$mount_path" ]]; then
pmx_record_execution "create CIFS mount point ${mount_path}" \
"mkdir -p ${mount_path}"
if ! mkdir -p "$mount_path" 2>/dev/null; then
msg_error "$(translate "Failed to create mount point:") $mount_path"
return 1
@@ -459,6 +481,8 @@ mount_cifs_via_fstab() {
fi
msg_info "$(translate "Mounting CIFS share...")"
pmx_record_execution "mount CIFS share //${server}/${share} at ${mount_path}" \
"mount -t cifs //${server}/${share} ${mount_path}"
if ! mount -t cifs -o "$mount_opts" "//${server}/${share}" "$mount_path" >/dev/null 2>&1; then
msg_error "$(translate "Failed to mount CIFS share on host.")"
return 1
@@ -474,11 +498,12 @@ mount_cifs_via_fstab() {
# Persist in /etc/fstab.
if [[ "$replace" == "1" ]]; then
sed -i "\|[[:space:]]${mount_path}[[:space:]]|d" /etc/fstab
pmx_edit_file /etc/fstab "\|[[:space:]]${mount_path}[[:space:]]|d"
fi
echo "//${server}/${share} $mount_path cifs $mount_opts 0 0" >> /etc/fstab
echo "//${server}/${share} $mount_path cifs $mount_opts 0 0" | pmx_append_file /etc/fstab
msg_ok "$(translate "Added to /etc/fstab.")"
pmx_record_execution "reload systemd after CIFS fstab update" "systemctl daemon-reload"
systemctl daemon-reload 2>/dev/null || true
echo -e ""
@@ -535,10 +560,13 @@ select_cifs_mount_methods() {
# ==========================================================
mount_cifs_share() {
local FUNC_VERSION="1.0"
pmx_journal_context "mount_cifs_share" "$FUNC_VERSION"
if ! which smbclient >/dev/null 2>&1; then
msg_info "$(translate "Installing Samba client tools...")"
apt-get update &>/dev/null
apt-get install -y cifs-utils smbclient &>/dev/null
pmx_install_pkg cifs-utils smbclient &>/dev/null
msg_ok "$(translate "Samba client tools installed")"
fi
@@ -721,6 +749,9 @@ view_cifs_storages() {
}
remove_cifs_storage() {
local FUNC_VERSION="1.0"
pmx_journal_context "remove_cifs_storage" "$FUNC_VERSION"
local OPTIONS=()
local has_pvesm=0
local has_fstab=0
@@ -784,6 +815,8 @@ remove_cifs_storage() {
show_proxmenux_logo
msg_title "$(translate "Remove CIFS Storage")"
pmx_record_execution "remove Proxmox CIFS storage ${target}" \
"pvesm remove ${target}"
if pvesm remove "$target" 2>/dev/null; then
msg_ok "$(translate "Storage") $target $(translate "removed successfully from Proxmox.")"
else
@@ -817,6 +850,8 @@ remove_cifs_storage() {
msg_title "$(translate "Remove CIFS fstab Mount")"
if mount | grep -q " on ${mount_path} type "; then
pmx_record_execution "unmount CIFS path ${mount_path}" \
"umount ${mount_path}"
if umount "$mount_path" 2>/dev/null; then
msg_ok "$(translate "Unmounted:") $mount_path"
else
@@ -831,17 +866,18 @@ remove_cifs_storage() {
if awk -v mp="$mount_path" '
$2 == mp && $3 == "cifs" { next }
{ print }
' /etc/fstab > /etc/fstab.tmp && mv /etc/fstab.tmp /etc/fstab; then
' /etc/fstab > /etc/fstab.tmp && pmx_write_file /etc/fstab < /etc/fstab.tmp; then
msg_ok "$(translate "Removed entry from /etc/fstab") ($(translate "backup at /etc/fstab.proxmenux.bak"))"
else
msg_error "$(translate "Failed to edit /etc/fstab — remove the line manually.")"
fi
pmx_record_execution "reload systemd after CIFS fstab removal" "systemctl daemon-reload"
systemctl daemon-reload 2>/dev/null || true
# Remove credentials file if it's under the standard ProxMenux dir
if [[ -n "$cred_file" && -f "$cred_file" && "$cred_file" == /etc/samba/credentials/* ]]; then
rm -f "$cred_file"
pmx_remove_file "$cred_file"
msg_ok "$(translate "Removed credentials file:") $cred_file"
fi
@@ -858,6 +894,9 @@ remove_cifs_storage() {
}
test_samba_connectivity() {
local FUNC_VERSION="1.0"
pmx_journal_context "test_samba_connectivity" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "Test Samba Connectivity")"
@@ -869,7 +908,7 @@ test_samba_connectivity() {
else
msg_warn "$(translate "CIFS Client Tools: NOT AVAILABLE - installing...")"
apt-get update &>/dev/null
apt-get install -y cifs-utils smbclient &>/dev/null
pmx_install_pkg cifs-utils smbclient &>/dev/null
msg_ok "$(translate "CIFS client tools installed.")"
fi
+22 -2
View File
@@ -32,6 +32,10 @@ if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
SHARE_COMMON_FILE="$LOCAL_SCRIPTS/global/share-common.func"
if ! source "$SHARE_COMMON_FILE" 2>/dev/null; then
@@ -48,6 +52,9 @@ select_privileged_lxc
select_mount_point() {
local FUNC_VERSION="1.0"
pmx_journal_context "select_mount_point" "$FUNC_VERSION"
while true; do
METHOD=$(whiptail --backtitle "ProxMenux" --title "$(translate "Select Folder")" \
--menu "$(translate "How do you want to select the folder to share?")" 15 60 5 \
@@ -104,12 +111,16 @@ select_mount_point() {
create_share() {
local FUNC_VERSION="1.0"
show_proxmenux_logo
msg_title "$(translate "Create Samba server service")"
sleep 2
select_mount_point || return
pmx_journal_context "create_share" "$FUNC_VERSION"
pmx_record_execution "configure Samba share ${MOUNT_POINT} in CT ${CTID}" \
"pct exec ${CTID} -- install and configure Samba share ${MOUNT_POINT}"
if ! pct exec "$CTID" -- test -d "$MOUNT_POINT"; then
@@ -311,7 +322,7 @@ EOF
msg_warn "$(translate "The share already exists in smb.conf:") [$SHARE_NAME]"
if whiptail --yesno "$(translate "Do you want to update the existing share?")" 10 60 --title "$(translate "Update Share")"; then
pct exec "$CTID" -- sed -i "/^\[$SHARE_NAME\]/,/^$/d" /etc/samba/smb.conf
pct exec "$CTID" -- sed --in-place "/^\[$SHARE_NAME\]/,/^$/d" /etc/samba/smb.conf
pct exec "$CTID" -- bash -c "echo '$CONFIG' >> /etc/samba/smb.conf"
msg_ok "$(translate "Share updated successfully.")"
else
@@ -406,6 +417,9 @@ view_shares() {
delete_share() {
local FUNC_VERSION="1.0"
pmx_journal_context "delete_share" "$FUNC_VERSION"
if ! pct exec "$CTID" -- test -f /etc/samba/smb.conf; then
dialog --backtitle "ProxMenux" --title "$(translate "Error")" --msgbox "\n$(translate "No smb.conf file found.")" 8 50
return
@@ -438,7 +452,9 @@ delete_share() {
msg_title "$(translate "Delete Share")"
pct exec "$CTID" -- sed -i "/^\[$SELECTED_SHARE\]/,/^$/d" /etc/samba/smb.conf
pmx_record_execution "remove Samba share ${SELECTED_SHARE} from CT ${CTID}" \
"pct exec ${CTID} -- remove share ${SELECTED_SHARE} from /etc/samba/smb.conf and restart smbd"
pct exec "$CTID" -- sed --in-place "/^\[$SELECTED_SHARE\]/,/^$/d" /etc/samba/smb.conf
pct exec "$CTID" -- systemctl restart smbd.service
msg_ok "$(translate "Share deleted and Samba service restarted.")"
fi
@@ -495,6 +511,7 @@ check_samba_status() {
uninstall_samba() {
local FUNC_VERSION="1.0"
if ! pct exec "$CTID" -- dpkg -s samba &>/dev/null; then
dialog --backtitle "ProxMenux" --title "$(translate "Samba Not Installed")" --msgbox "\n$(translate "Samba server is not installed in this CT.")" 8 60
@@ -510,6 +527,9 @@ uninstall_samba() {
show_proxmenux_logo
msg_title "$(translate "Uninstall Samba Server")"
pmx_journal_context "uninstall_samba" "$FUNC_VERSION"
pmx_record_execution "uninstall Samba server from CT ${CTID}" \
"pct exec ${CTID} -- stop services, preserve smb.conf backup, remove Samba users and packages"
msg_info "$(translate "Stopping Samba services...")"
+36 -5
View File
@@ -39,6 +39,9 @@ if [[ -f "$LOCAL_SCRIPTS_LOCAL/global/pci_passthrough_helpers.sh" ]]; then
elif [[ -f "$LOCAL_SCRIPTS_DEFAULT/global/pci_passthrough_helpers.sh" ]]; then
source "$LOCAL_SCRIPTS_DEFAULT/global/pci_passthrough_helpers.sh"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
load_language
initialize_cache
@@ -74,6 +77,9 @@ register_vfio_iommu_tool() {
}
enable_iommu_cmdline() {
local FUNC_VERSION="1.0"
pmx_journal_context "enable_iommu_cmdline" "$FUNC_VERSION"
local silent="${1:-}"
local cpu_vendor iommu_param
cpu_vendor=$(grep -m1 "vendor_id" /proc/cpuinfo 2>/dev/null | awk '{print $3}')
@@ -95,7 +101,8 @@ enable_iommu_cmdline() {
if [[ -f "$cmdline_file" ]] && grep -qE 'root=ZFS=|root=ZFS/' "$cmdline_file" 2>/dev/null; then
if ! grep -q "$iommu_param" "$cmdline_file" || ! grep -q "iommu=pt" "$cmdline_file"; then
cp "$cmdline_file" "${cmdline_file}.bak.$(date +%Y%m%d_%H%M%S)"
sed -i "s|\\s*$| ${iommu_param} iommu=pt|" "$cmdline_file"
pmx_edit_file "$cmdline_file" "s|\\s*$| ${iommu_param} iommu=pt|"
pmx_record_execution "refresh Proxmox boot entries" "proxmox-boot-tool refresh"
proxmox-boot-tool refresh >/dev/null 2>&1 || true
[[ "$silent" != "silent" ]] && msg_ok "$(translate "IOMMU parameters added to /etc/kernel/cmdline")"
else
@@ -104,7 +111,8 @@ enable_iommu_cmdline() {
elif [[ -f "$grub_file" ]]; then
if ! grep -q "$iommu_param" "$grub_file" || ! grep -q "iommu=pt" "$grub_file"; then
cp "$grub_file" "${grub_file}.bak.$(date +%Y%m%d_%H%M%S)"
sed -i "/GRUB_CMDLINE_LINUX_DEFAULT=/ s|\"$| ${iommu_param} iommu=pt\"|" "$grub_file"
pmx_edit_file "$grub_file" "/GRUB_CMDLINE_LINUX_DEFAULT=/ s|\"$| ${iommu_param} iommu=pt\"|"
pmx_record_execution "regenerate GRUB configuration" "update-grub"
update-grub >/dev/null 2>&1 || true
[[ "$silent" != "silent" ]] && msg_ok "$(translate "IOMMU parameters added to GRUB")"
else
@@ -521,6 +529,9 @@ prompt_controller_conflict_policy() {
# ── DIALOG PHASE: resolve all conflicts before terminal ───────────────────────
resolve_disk_conflicts() {
local FUNC_VERSION="1.0"
pmx_journal_context "resolve_disk_conflicts" "$FUNC_VERSION"
local -a new_pci_list=()
local pci vmid action slot_base scope_key has_running
@@ -559,13 +570,18 @@ resolve_disk_conflicts() {
case "$action" in
keep_disable_onboot)
for vmid in "${source_vms[@]}"; do
_vm_onboot_is_enabled "$vmid" && qm set "$vmid" -onboot 0 >/dev/null 2>&1
if _vm_onboot_is_enabled "$vmid"; then
pmx_record_execution "disable autostart for source VM ${vmid}" "qm set ${vmid} -onboot 0"
qm set "$vmid" -onboot 0 >/dev/null 2>&1
fi
done
new_pci_list+=("$pci")
;;
move_remove_source)
slot_base=$(_pci_slot_base "$pci")
for vmid in "${source_vms[@]}"; do
pmx_record_execution "remove PCI slot ${slot_base} from source VM ${vmid}" \
"_remove_pci_slot_from_vm_config ${vmid} ${slot_base}"
_remove_pci_slot_from_vm_config "$vmid" "$slot_base"
done
new_pci_list+=("$pci")
@@ -616,10 +632,15 @@ resolve_disk_conflicts() {
for gid in "${guest_ids[@]}"; do
gtype="${gid%%:*}"; gid_num="${gid##*:}"
if [[ "$gtype" == "VM" ]]; then
_vm_onboot_is_enabled "$gid_num" && qm set "$gid_num" -onboot 0 >/dev/null 2>&1
if _vm_onboot_is_enabled "$gid_num"; then
pmx_record_execution "disable autostart for VM ${gid_num}" "qm set ${gid_num} -onboot 0"
qm set "$gid_num" -onboot 0 >/dev/null 2>&1
fi
else
grep -qE '^onboot:\s*1' "/etc/pve/lxc/$gid_num.conf" 2>/dev/null && \
if grep -qE '^onboot:\s*1' "/etc/pve/lxc/$gid_num.conf" 2>/dev/null; then
pmx_record_execution "disable autostart for CT ${gid_num}" "pct set ${gid_num} -onboot 0"
pct set "$gid_num" -onboot 0 >/dev/null 2>&1
fi
fi
done
;;
@@ -629,11 +650,15 @@ resolve_disk_conflicts() {
if [[ "$gtype" == "VM" ]]; then
while IFS= read -r slot; do
[[ -z "$slot" ]] && continue
pmx_record_execution "remove disk slot ${slot} from VM ${gid_num}" \
"qm set ${gid_num} -delete ${slot}"
qm set "$gid_num" -delete "$slot" >/dev/null 2>&1
done < <(_find_disk_slots_in_vm "$gid_num" "$disk")
else
while IFS= read -r slot; do
[[ -z "$slot" ]] && continue
pmx_record_execution "remove disk slot ${slot} from CT ${gid_num}" \
"pct set ${gid_num} -delete ${slot}"
pct set "$gid_num" -delete "$slot" >/dev/null 2>&1
done < <(_find_disk_slots_in_ct "$gid_num" "$disk")
fi
@@ -647,6 +672,9 @@ resolve_disk_conflicts() {
}
apply_assignment() {
local FUNC_VERSION="1.0"
pmx_journal_context "apply_assignment" "$FUNC_VERSION"
: >"$LOG_FILE"
set_title
@@ -681,6 +709,8 @@ apply_assignment() {
local display_name
display_name=$(_pci_storage_display_name "$pci")
msg_info "$(translate "Adding") ${display_name} (${pci}) → hostpci${hostpci_idx}..."
pmx_record_execution "assign PCI device ${pci} to VM ${SELECTED_VMID} as hostpci${hostpci_idx}" \
"qm set ${SELECTED_VMID} --hostpci${hostpci_idx} ${pci},pcie=1"
if qm set "$SELECTED_VMID" "--hostpci${hostpci_idx}" "${pci},pcie=1" >>"$LOG_FILE" 2>&1; then
msg_ok "$(translate "Controller/NVMe assigned") (hostpci${hostpci_idx}${pci})"
assigned_count=$((assigned_count + 1))
@@ -709,6 +739,7 @@ apply_assignment() {
msg_success "$(translate "Press Enter to continue...")"
read -r
msg_warn "$(translate "Rebooting the system...")"
pmx_record_execution "reboot host after enabling IOMMU" "reboot"
reboot
else
msg_info2 "$(translate "To use the VM without issues, the host must be restarted before starting it.")"
+32 -2
View File
@@ -48,6 +48,12 @@ elif [[ -f "$LOCAL_SCRIPTS_DEFAULT/global/vm_storage_helpers.sh" ]]; then
source "$LOCAL_SCRIPTS_DEFAULT/global/vm_storage_helpers.sh"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
FUNC_VERSION="1.3"
BACKTITLE="ProxMenux"
UI_MENU_H=20
UI_MENU_W=84
@@ -120,12 +126,20 @@ get_preferred_disk_path() {
install_fs_tools_in_ct() {
local ctid="$1"
local pkg="$2"
local FUNC_VERSION="1.3"
pmx_journal_context "install_fs_tools_in_ct" "$FUNC_VERSION"
if pct exec "$ctid" -- sh -c "[ -f /etc/alpine-release ]"; then
pmx_record_execution "install ${pkg} in CT ${ctid}" \
"pct exec ${ctid} -- apk update and apk add ${pkg}"
pct exec "$ctid" -- sh -c "apk update >/dev/null 2>&1 && apk add --no-progress $pkg >/dev/null 2>&1"
elif pct exec "$ctid" -- sh -c "grep -qi 'arch' /etc/os-release 2>/dev/null"; then
pmx_record_execution "install ${pkg} in CT ${ctid}" \
"pct exec ${ctid} -- pacman -Sy --noconfirm ${pkg}"
pct exec "$ctid" -- sh -c "pacman -Sy --noconfirm $pkg >/dev/null 2>&1"
elif pct exec "$ctid" -- sh -c "grep -qiE 'debian|ubuntu' /etc/os-release 2>/dev/null"; then
pmx_record_execution "install ${pkg} in CT ${ctid}" \
"pct exec ${ctid} -- apt-get update and apt-get install ${pkg}"
pct exec "$ctid" -- sh -c "apt-get update -qq >/dev/null 2>&1 && apt-get install -y -qq $pkg >/dev/null 2>&1"
else
return 1
@@ -247,12 +261,15 @@ msg_ok "$(translate "CT $CTID selected successfully.")"
if [ "$CONVERT_PRIVILEGED" = true ]; then
pmx_journal_context "disk_passthrough_ct" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "Import Disk to LXC")"
CURRENT_CT_STATUS=$(pct status "$CTID" | awk '{print $2}')
if [ "$CURRENT_CT_STATUS" == "running" ]; then
msg_info "$(translate "Stopping container") $CTID..."
pmx_record_execution "stop CT ${CTID} for privileged conversion" "pct shutdown ${CTID}"
pct shutdown "$CTID" &>/dev/null
for i in {1..10}; do
sleep 1
@@ -266,12 +283,13 @@ if [ "$CONVERT_PRIVILEGED" = true ]; then
fi
cp "$CONF_FILE" "$CONF_FILE.bak"
sed -i '/^unprivileged: 1/d' "$CONF_FILE"
echo "unprivileged: 0" >> "$CONF_FILE"
pmx_edit_file "$CONF_FILE" '/^unprivileged: 1/d'
echo "unprivileged: 0" | pmx_append_file "$CONF_FILE"
msg_ok "$(translate "Container successfully converted to privileged.")"
if [ "$CT_RUNNING" = true ]; then
msg_info "$(translate "Starting container") $CTID..."
pmx_record_execution "start CT ${CTID} after privileged conversion" "pct start ${CTID}"
pct start "$CTID" &>/dev/null
sleep 2
if [ "$(pct status "$CTID" | awk '{print $2}')" != "running" ]; then
@@ -567,6 +585,8 @@ msg_title "$(translate "Import Disk to LXC")"
msg_ok "$(translate "CT $CTID selected successfully.")"
msg_ok "$(translate "Disks to process:") ${#DISK_LIST[@]}"
for i in "${!DISK_LIST[@]}"; do
pmx_journal_context "disk_passthrough_ct" "$FUNC_VERSION"
IFS=$'\t' read -r _desc_model _desc_size <<< "${DISK_DESCRIPTIONS[$i]}"
echo -e "${TAB}${BL}${DISK_LIST[$i]} $_desc_model $_desc_size${CL}"
done
@@ -590,6 +610,8 @@ for i in "${!DISK_LIST[@]}"; do
if [ "$NEEDS_PARTITION" = true ]; then
msg_info "$(translate "Creating partition table and partition...")"
pmx_record_execution "create GPT partition on ${DISK} for CT ${CTID}" \
"parted -s ${DISK} mklabel gpt mkpart primary 0% 100%"
if ! parted -s "$DISK" mklabel gpt mkpart primary 0% 100% >/dev/null 2>&1; then
msg_error "$(translate "Failed to create partition table on disk") $DISK_INFO."
continue
@@ -616,6 +638,8 @@ for i in "${!DISK_LIST[@]}"; do
if [ "$SKIP_FORMAT" != true ]; then
msg_info "$(translate "Formatting partition") $PARTITION $(translate "with") $FORMAT_TYPE..."
pmx_record_execution "format ${PARTITION} as ${FORMAT_TYPE} for CT ${CTID}" \
"mkfs ${FORMAT_TYPE} ${PARTITION}"
if ! case "$FORMAT_TYPE" in
"ext4") mkfs.ext4 -F "$PARTITION" >/dev/null 2>&1 ;;
"xfs") mkfs.xfs -f "$PARTITION" >/dev/null 2>&1 ;;
@@ -658,6 +682,7 @@ for i in "${!DISK_LIST[@]}"; do
--yesno "$(translate "The filesystem") $FORMAT_TYPE $(translate "requires the package") $FS_PKG $(translate "installed inside CT") $CTID.\n\n$(translate "The container is currently stopped. Do you want to start it now to install the package?")\n\n$(translate "If you choose No, install") $FS_PKG $(translate "manually inside the container before starting it.")" \
$UI_YESNO_H $UI_YESNO_W; then
msg_info "$(translate "Starting CT") $CTID..."
pmx_record_execution "start CT ${CTID} to install filesystem tools" "pct start ${CTID}"
pct start "$CTID" &>/dev/null
sleep 2
if [ "$(pct status "$CTID" | awk '{print $2}')" != "running" ]; then
@@ -685,9 +710,14 @@ for i in "${!DISK_LIST[@]}"; do
PERSISTENT_PARTITION=$(get_preferred_disk_path "$PARTITION")
msg_info "$(translate "Applying passthrough to CT") $CTID..."
pmx_journal_context "disk_passthrough_ct" "$FUNC_VERSION"
if [ "$FORMAT_TYPE" == "xfs" ]; then
pmx_record_execution "assign ${PERSISTENT_PARTITION} to CT ${CTID} at ${MOUNT_POINT}" \
"pct set ${CTID} -mp${INDEX} ${PERSISTENT_PARTITION},mp=${MOUNT_POINT},backup=0,ro=0"
RESULT=$(pct set "$CTID" -mp${INDEX} "$PERSISTENT_PARTITION,mp=$MOUNT_POINT,backup=0,ro=0" 2>&1)
else
pmx_record_execution "assign ${PERSISTENT_PARTITION} to CT ${CTID} at ${MOUNT_POINT}" \
"pct set ${CTID} -mp${INDEX} ${PERSISTENT_PARTITION},mp=${MOUNT_POINT},backup=0,ro=0,acl=1"
RESULT=$(pct set "$CTID" -mp${INDEX} "$PERSISTENT_PARTITION,mp=$MOUNT_POINT,backup=0,ro=0,acl=1" 2>&1)
fi
SET_STATUS=$?
+15 -1
View File
@@ -64,6 +64,10 @@ elif [[ -f "$LOCAL_SCRIPTS_DEFAULT/global/utils-install-functions.sh" ]]; then
source "$LOCAL_SCRIPTS_DEFAULT/global/utils-install-functions.sh"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
BACKTITLE="ProxMenux"
UI_MENU_H=20
UI_MENU_W=84
@@ -607,13 +611,16 @@ prompt_zfs_pool_name() {
# ──────────────────────────────────────────────────────────────────────────────
ensure_fs_tool() {
local FUNC_VERSION="2.0"
pmx_journal_context "ensure_fs_tool" "$FUNC_VERSION"
case "$FORMAT_TYPE" in
exfat)
command -v mkfs.exfat >/dev/null 2>&1 && return 0
if declare -F ensure_repositories >/dev/null 2>&1; then
ensure_repositories || true
fi
if DEBIAN_FRONTEND=noninteractive apt-get install -y exfatprogs >/dev/null 2>&1; then
if pmx_install_pkg exfatprogs; then
command -v mkfs.exfat >/dev/null 2>&1 && {
msg_ok "$(translate "exFAT tools installed successfully.")"
return 0
@@ -657,6 +664,9 @@ wait_for_enter_to_main() {
# ──────────────────────────────────────────────────────────────────────────────
main() {
local FUNC_VERSION="2.0"
pmx_journal_context "main" "$FUNC_VERSION"
select_target_disk || exit 0
select_operation_mode || exit 0
confirm_format_action || exit 0
@@ -701,6 +711,10 @@ main() {
export DOH_SHOW_PROGRESS=0
export DOH_ENABLE_STACK_RELEASE=0
pmx_record_execution \
"disk operation ${OPERATION_MODE} on ${SELECTED_DISK}" \
"format-disk operation=${OPERATION_MODE} disk=${SELECTED_DISK} filesystem=${FORMAT_TYPE:-none} zfs_pool=${ZFS_POOL_NAME:-none}"
if [[ "$OPERATION_MODE" == "wipe_all" ]]; then
msg_info "$(translate "Wiping partitions and metadata...")"
doh_wipe_disk "$SELECTED_DISK"
+14
View File
@@ -41,6 +41,10 @@ if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
load_language
initialize_cache
@@ -134,6 +138,9 @@ select_vm() {
}
ensure_vm_stopped() {
local FUNC_VERSION="1.0"
pmx_journal_context "ensure_vm_stopped" "$FUNC_VERSION"
local status
status=$(qm status "$VMID" 2>/dev/null | awk '{print $2}')
@@ -146,6 +153,7 @@ ensure_vm_stopped() {
return 1
fi
pmx_record_execution "shut down VM ${VMID} for export" "qm shutdown ${VMID} --timeout 120"
qm shutdown "$VMID" --timeout 120 >/dev/null 2>&1 || true
local i
@@ -157,6 +165,7 @@ ensure_vm_stopped() {
if dialog --backtitle "ProxMenux" --title "$(translate "Shutdown timeout")" --yesno \
"$(translate "Graceful shutdown timed out.")\n\n$(translate "Force stop VM now?")" 10 60; then
pmx_record_execution "force stop VM ${VMID} for export" "qm stop ${VMID}"
qm stop "$VMID" >/dev/null 2>&1 || true
sleep 2
status=$(qm status "$VMID" 2>/dev/null | awk '{print $2}')
@@ -516,12 +525,17 @@ print_export_result() {
}
run_export() {
local FUNC_VERSION="1.0"
pmx_journal_context "run_export" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "Export VM to OVA or OVF")"
msg_ok "$(translate "VM selected:") $VMID ($VM_NAME)"
msg_ok "$(translate "Export mode:") ${EXPORT_MODE^^}"
msg_ok "$(translate "Destination:") $DEST_DIR"
pmx_record_execution "export VM ${VMID} as ${EXPORT_MODE^^} to ${DEST_DIR}" \
"convert ${DISK_COUNT} VM disk(s), generate OVF metadata and package ${EXPORT_MODE^^}"
local ts vm_safe base_name
ts=$(date +%Y%m%d_%H%M%S)
+16 -1
View File
@@ -49,6 +49,9 @@ INSTALL_HELPERS="$LOCAL_SCRIPTS/global/utils-install-functions.sh"
[[ -f "$UTILS_FILE" ]] && source "$UTILS_FILE"
[[ -f "$INSTALL_HELPERS" ]] && source "$INSTALL_HELPERS"
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
load_language
initialize_cache
@@ -87,6 +90,9 @@ BRIDGE="vmbr0"
# with "syntax error at or near ,". Returns 0 on success, 1 if install
# fails (caller is expected to abort with a clear error).
ensure_gawk() {
local FUNC_VERSION="1.0"
pmx_journal_context "ensure_gawk" "$FUNC_VERSION"
if command -v gawk >/dev/null 2>&1; then
return 0
fi
@@ -111,7 +117,7 @@ ensure_gawk() {
# Fallback when utils-install-functions.sh was not sourced.
# Here we own the spinner: msg_info opens it, msg_ok / msg_error closes it.
msg_info "$(translate "Installing gawk (required for OVF parsing)...")"
if apt-get update -qq >/dev/null 2>&1 && apt-get install -y gawk >/dev/null 2>&1; then
if apt-get update -qq >/dev/null 2>&1 && pmx_install_pkg gawk; then
msg_ok "$(translate "gawk installed")"
return 0
fi
@@ -478,6 +484,9 @@ confirm_import() {
# -------------------------------------------------------
run_import() {
local FUNC_VERSION="1.0"
pmx_journal_context "run_import" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "Import VM from OVA or OVF")"
@@ -488,6 +497,8 @@ run_import() {
# 1. Create VM shell
msg_info "$(translate "Creating VM...")"
pmx_record_execution "import ${SOURCE_FILE} as VM ${NEW_VMID} on storage ${STORAGE}" \
"qm create ${NEW_VMID}; qm importdisk for ${#OVF_DISK_FILES[@]} disk(s); attach disks and configure boot"
if ! qm create "$NEW_VMID" \
--name "$NEW_VM_NAME" \
--memory "$OVF_MEMORY_MB" \
@@ -624,6 +635,7 @@ print_import_result() {
# -------------------------------------------------------
main() {
local FUNC_VERSION="1.0"
if ! command -v pveversion >/dev/null 2>&1; then
dialog --backtitle "$BACKTITLE" --title "$(translate "Error")" \
--msgbox "$(translate "This script must be run on a Proxmox host.")" 8 60
@@ -694,6 +706,9 @@ main() {
--yesno "$(translate "Remove the partial VM ($NEW_VMID) and its imported disks?")" 8 60; then
clear
msg_info "$(translate "Removing partial VM") $NEW_VMID..."
pmx_journal_context "main" "$FUNC_VERSION"
pmx_record_execution "remove partial imported VM ${NEW_VMID}" \
"qm destroy ${NEW_VMID} --destroy-unreferenced-disks 1"
if qm destroy "$NEW_VMID" --destroy-unreferenced-disks 1 &>/dev/null; then
msg_ok "$(translate "Partial VM removed")"
else
+24 -13
View File
@@ -62,6 +62,9 @@ fi
if [[ -f "$LOCAL_SCRIPTS/global/utils-install-functions.sh" ]]; then
source "$LOCAL_SCRIPTS/global/utils-install-functions.sh"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
# ==========================================================
@@ -376,31 +379,37 @@ EOF
}
disable_enterprise_repo_if_present() {
local FUNC_VERSION="1.0"
pmx_journal_context "disable_enterprise_repo_if_present" "$FUNC_VERSION"
local s="/etc/apt/sources.list.d/pve-enterprise.sources"
local l="/etc/apt/sources.list.d/pve-enterprise.list"
if [[ -f "$s" ]]; then
if grep -qi '^Enabled:' "$s"; then
sed -i 's/^Enabled:.*/Enabled: false/i' "$s"
pmx_edit_file "$s" 's/^Enabled:.*/Enabled: false/i'
else
echo "Enabled: false" >> "$s"
echo "Enabled: false" | pmx_append_file "$s"
fi
fi
if [[ -f "$l" ]]; then
sed -i 's/^[[:space:]]*deb/# deb/' "$l"
pmx_edit_file "$l" 's/^[[:space:]]*deb/# deb/'
fi
}
comment_legacy_pve8_lists() {
local FUNC_VERSION="1.0"
pmx_journal_context "comment_legacy_pve8_lists" "$FUNC_VERSION"
for f in /etc/apt/sources.list.d/pve-public-repo.list /etc/apt/sources.list.d/pve-install-repo.list; do
[[ -f "$f" ]] || continue
sed -i 's/^[[:space:]]*deb/# deb/' "$f" || true
pmx_edit_file "$f" 's/^[[:space:]]*deb/# deb/' || true
done
}
comment_legacy_ceph_list() {
local FUNC_VERSION="1.0"
pmx_journal_context "comment_legacy_ceph_list" "$FUNC_VERSION"
local f="/etc/apt/sources.list.d/ceph.list"
[[ -f "$f" ]] || return 0
sed -i 's/^[[:space:]]*deb/# deb/' "$f" || true
pmx_edit_file "$f" 's/^[[:space:]]*deb/# deb/' || true
}
apt_update_with_repo_fallback() {
@@ -811,11 +820,11 @@ else
fi
FUNC_VERSION="1.0"
pmx_journal_context "upgrade_pve8_to_pve9" "$FUNC_VERSION"
if [[ "$DISABLE_AUDIT" == "1" ]]; then
append_step \
"" \
"Audit socket disabled or not required" \
"systemctl disable --now systemd-journald-audit.socket >/dev/null 2>&1 || true"
pmx_disable_service systemd-journald-audit.socket >> "$LOG" 2>&1 || true
echo -e "${BFR}${TAB}${CM}${GN}$(translate "Audit socket disabled or not required")${CL}"
fi
@@ -856,10 +865,12 @@ fi
# Step 4
# ---------------------------
FUNC_VERSION="1.0"
pmx_journal_context "upgrade_pve8_to_pve9" "$FUNC_VERSION"
OS_FILE="/etc/apt/sources.list"
if [[ -f "$OS_FILE" ]]; then
msg_info "$(translate "Updating Debian Bookworm → Trixie in sources.list...")"
if sed -i 's/bookworm/trixie/g' "$OS_FILE"; then
if pmx_edit_file "$OS_FILE" 's/bookworm/trixie/g'; then
msg_ok "$(translate "sources.list updated to Trixie")"
else
msg_ok "$(translate "sources.list update skipped (no change)")"
@@ -871,7 +882,7 @@ fi
PVE_ENT_LIST="/etc/apt/sources.list.d/pve-enterprise.list"
msg_info "$(translate "Updating pve-enterprise.list (if present) to Trixie...")"
if [[ -f "$PVE_ENT_LIST" ]]; then
if sed -i 's/bookworm/trixie/g' "$PVE_ENT_LIST"; then
if pmx_edit_file "$PVE_ENT_LIST" 's/bookworm/trixie/g'; then
msg_ok "$(translate "pve-enterprise.list updated to Trixie")"
else
msg_ok "$(translate "pve-enterprise.list update skipped (no change)")"
@@ -884,9 +895,9 @@ fi
msg_info "$(translate "Commenting any residual Bookworm lines in *.list...")"
for f in /etc/apt/sources.list.d/*.list; do
[[ -f "$f" ]] || continue
sed -i '/bookworm/s/^/# /' "$f" || true
pmx_edit_file "$f" '/bookworm/s/^/# /' || true
done
sed -i '/bookworm/s/^/# /' "$OS_FILE" 2>/dev/null || true
pmx_edit_file "$OS_FILE" '/bookworm/s/^/# /' 2>/dev/null || true
msg_ok "$(translate "Residual Bookworm entries commented where applicable")"