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
+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