mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-09-14 18:56:52 +00:00
fix: harden post-install migrations and bundle multi-ABI Python runtimes
- safely migrate historical Bashrc and Log2RAM installations - align post-install function versions across both flows - bundle gevent runtimes for CPython 3.11 and 3.13 - select the correct Python ABI at runtime - validate both supported Proxmox VE Python versions in AppImage workflows - fix #327, #328 and #331
This commit is contained in:
@@ -42,6 +42,26 @@ jobs:
|
|||||||
- name: Build AppImage
|
- name: Build AppImage
|
||||||
working-directory: AppImage
|
working-directory: AppImage
|
||||||
run: ./scripts/build_appimage.sh
|
run: ./scripts/build_appimage.sh
|
||||||
|
|
||||||
|
- name: Set up Python 3.11 validation runtime
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Verify bundled Python 3.11 runtime
|
||||||
|
env:
|
||||||
|
PYTHONPATH: /tmp/proxmenux_build/ProxMenux.AppDir/usr/lib/python3/vendor/cp311:/tmp/proxmenux_build/ProxMenux.AppDir/usr/lib/python3/dist-packages
|
||||||
|
run: python -c 'import gevent, greenlet, geventwebsocket, zope.interface; from gevent import pywsgi; print(gevent.__version__)'
|
||||||
|
|
||||||
|
- name: Set up Python 3.13 validation runtime
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.13'
|
||||||
|
|
||||||
|
- name: Verify bundled Python 3.13 runtime
|
||||||
|
env:
|
||||||
|
PYTHONPATH: /tmp/proxmenux_build/ProxMenux.AppDir/usr/lib/python3/vendor/cp313:/tmp/proxmenux_build/ProxMenux.AppDir/usr/lib/python3/dist-packages
|
||||||
|
run: python -c 'import gevent, greenlet, geventwebsocket, zope.interface; from gevent import pywsgi; print(gevent.__version__)'
|
||||||
|
|
||||||
- name: Get version from package.json
|
- name: Get version from package.json
|
||||||
id: version
|
id: version
|
||||||
|
|||||||
@@ -42,6 +42,26 @@ jobs:
|
|||||||
- name: Build AppImage
|
- name: Build AppImage
|
||||||
working-directory: AppImage
|
working-directory: AppImage
|
||||||
run: ./scripts/build_appimage.sh
|
run: ./scripts/build_appimage.sh
|
||||||
|
|
||||||
|
- name: Set up Python 3.11 validation runtime
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Verify bundled Python 3.11 runtime
|
||||||
|
env:
|
||||||
|
PYTHONPATH: /tmp/proxmenux_build/ProxMenux.AppDir/usr/lib/python3/vendor/cp311:/tmp/proxmenux_build/ProxMenux.AppDir/usr/lib/python3/dist-packages
|
||||||
|
run: python -c 'import gevent, greenlet, geventwebsocket, zope.interface; from gevent import pywsgi; print(gevent.__version__)'
|
||||||
|
|
||||||
|
- name: Set up Python 3.13 validation runtime
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.13'
|
||||||
|
|
||||||
|
- name: Verify bundled Python 3.13 runtime
|
||||||
|
env:
|
||||||
|
PYTHONPATH: /tmp/proxmenux_build/ProxMenux.AppDir/usr/lib/python3/vendor/cp313:/tmp/proxmenux_build/ProxMenux.AppDir/usr/lib/python3/dist-packages
|
||||||
|
run: python -c 'import gevent, greenlet, geventwebsocket, zope.interface; from gevent import pywsgi; print(gevent.__version__)'
|
||||||
|
|
||||||
- name: Get version from package.json
|
- name: Get version from package.json
|
||||||
id: version
|
id: version
|
||||||
|
|||||||
@@ -40,6 +40,26 @@ jobs:
|
|||||||
- name: Build AppImage
|
- name: Build AppImage
|
||||||
working-directory: AppImage
|
working-directory: AppImage
|
||||||
run: ./scripts/build_appimage.sh
|
run: ./scripts/build_appimage.sh
|
||||||
|
|
||||||
|
- name: Set up Python 3.11 validation runtime
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Verify bundled Python 3.11 runtime
|
||||||
|
env:
|
||||||
|
PYTHONPATH: /tmp/proxmenux_build/ProxMenux.AppDir/usr/lib/python3/vendor/cp311:/tmp/proxmenux_build/ProxMenux.AppDir/usr/lib/python3/dist-packages
|
||||||
|
run: python -c 'import gevent, greenlet, geventwebsocket, zope.interface; from gevent import pywsgi; print(gevent.__version__)'
|
||||||
|
|
||||||
|
- name: Set up Python 3.13 validation runtime
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.13'
|
||||||
|
|
||||||
|
- name: Verify bundled Python 3.13 runtime
|
||||||
|
env:
|
||||||
|
PYTHONPATH: /tmp/proxmenux_build/ProxMenux.AppDir/usr/lib/python3/vendor/cp313:/tmp/proxmenux_build/ProxMenux.AppDir/usr/lib/python3/dist-packages
|
||||||
|
run: python -c 'import gevent, greenlet, geventwebsocket, zope.interface; from gevent import pywsgi; print(gevent.__version__)'
|
||||||
|
|
||||||
- name: Get version from package.json
|
- name: Get version from package.json
|
||||||
id: version
|
id: version
|
||||||
|
|||||||
@@ -42,6 +42,26 @@ jobs:
|
|||||||
- name: Build AppImage
|
- name: Build AppImage
|
||||||
working-directory: AppImage
|
working-directory: AppImage
|
||||||
run: ./scripts/build_appimage.sh
|
run: ./scripts/build_appimage.sh
|
||||||
|
|
||||||
|
- name: Set up Python 3.11 validation runtime
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Verify bundled Python 3.11 runtime
|
||||||
|
env:
|
||||||
|
PYTHONPATH: /tmp/proxmenux_build/ProxMenux.AppDir/usr/lib/python3/vendor/cp311:/tmp/proxmenux_build/ProxMenux.AppDir/usr/lib/python3/dist-packages
|
||||||
|
run: python -c 'import gevent, greenlet, geventwebsocket, zope.interface; from gevent import pywsgi; print(gevent.__version__)'
|
||||||
|
|
||||||
|
- name: Set up Python 3.13 validation runtime
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.13'
|
||||||
|
|
||||||
|
- name: Verify bundled Python 3.13 runtime
|
||||||
|
env:
|
||||||
|
PYTHONPATH: /tmp/proxmenux_build/ProxMenux.AppDir/usr/lib/python3/vendor/cp313:/tmp/proxmenux_build/ProxMenux.AppDir/usr/lib/python3/dist-packages
|
||||||
|
run: python -c 'import gevent, greenlet, geventwebsocket, zope.interface; from gevent import pywsgi; print(gevent.__version__)'
|
||||||
|
|
||||||
- name: Get version from package.json
|
- name: Get version from package.json
|
||||||
id: version
|
id: version
|
||||||
|
|||||||
+23
-1
@@ -8,7 +8,29 @@ APPDIR="$(dirname "$(readlink -f "${0}")")"
|
|||||||
|
|
||||||
export PATH="${APPDIR}/usr/bin:${PATH}"
|
export PATH="${APPDIR}/usr/bin:${PATH}"
|
||||||
export LD_LIBRARY_PATH="${APPDIR}/usr/lib/x86_64-linux-gnu:${APPDIR}/usr/lib:${APPDIR}/lib/x86_64-linux-gnu:${APPDIR}/lib:${LD_LIBRARY_PATH}"
|
export LD_LIBRARY_PATH="${APPDIR}/usr/lib/x86_64-linux-gnu:${APPDIR}/usr/lib:${APPDIR}/lib/x86_64-linux-gnu:${APPDIR}/lib:${LD_LIBRARY_PATH}"
|
||||||
export PYTHONPATH="${APPDIR}/usr/lib/python3/dist-packages:${APPDIR}/usr/lib/python3/site-packages:${PYTHONPATH}"
|
|
||||||
|
# Select the native dependency tree matching the host interpreter. PVE 8
|
||||||
|
# uses CPython 3.11 and PVE 9 uses CPython 3.13; both runtimes are bundled in
|
||||||
|
# the same AppImage so its gevent HTTPS/WSS server is independent of the
|
||||||
|
# Python version used by the GitHub Actions build runner.
|
||||||
|
PYTHON_ABI="$(python3 -c 'import sys; print(f"cp{sys.version_info.major}{sys.version_info.minor}")' 2>/dev/null || true)"
|
||||||
|
PYTHON_ABI_DIR="${APPDIR}/usr/lib/python3/vendor/${PYTHON_ABI}"
|
||||||
|
PYTHON_COMMON_DIRS="${APPDIR}/usr/lib/python3/dist-packages:${APPDIR}/usr/lib/python3/site-packages"
|
||||||
|
|
||||||
|
case "$PYTHON_ABI" in
|
||||||
|
cp311|cp313)
|
||||||
|
if [ -d "$PYTHON_ABI_DIR" ]; then
|
||||||
|
export PYTHONPATH="${PYTHON_ABI_DIR}:${PYTHON_COMMON_DIRS}${PYTHONPATH:+:${PYTHONPATH}}"
|
||||||
|
else
|
||||||
|
echo "⚠️ Bundled Python runtime ${PYTHON_ABI} is missing; HTTPS/WSS may be unavailable" >&2
|
||||||
|
export PYTHONPATH="${PYTHON_COMMON_DIRS}${PYTHONPATH:+:${PYTHONPATH}}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "⚠️ Unsupported host Python ABI '${PYTHON_ABI:-unknown}'; expected cp311 (PVE 8) or cp313 (PVE 9)" >&2
|
||||||
|
export PYTHONPATH="${PYTHON_COMMON_DIRS}${PYTHONPATH:+:${PYTHONPATH}}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
# Change to the AppImage directory
|
# Change to the AppImage directory
|
||||||
cd "${APPDIR}"
|
cd "${APPDIR}"
|
||||||
|
|||||||
@@ -11,6 +11,18 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
DIST_DIR="$SCRIPT_DIR/../dist"
|
DIST_DIR="$SCRIPT_DIR/../dist"
|
||||||
APPIMAGE_ROOT="$SCRIPT_DIR/.."
|
APPIMAGE_ROOT="$SCRIPT_DIR/.."
|
||||||
|
|
||||||
|
# Native Python dependencies are selected explicitly for the two Python ABIs
|
||||||
|
# used by supported Proxmox VE hosts. GitHub Actions currently builds on
|
||||||
|
# Ubuntu 22.04 (CPython 3.10), while PVE 8 and PVE 9 execute the AppImage with
|
||||||
|
# the host's CPython 3.11 and 3.13 respectively. Installing gevent with the
|
||||||
|
# builder's interpreter therefore creates an AppImage that cannot import its
|
||||||
|
# C extensions on either supported host (issue #331).
|
||||||
|
GEVENT_VERSION="26.8.0"
|
||||||
|
GREENLET_VERSION="3.5.5"
|
||||||
|
ZOPE_INTERFACE_VERSION="8.6"
|
||||||
|
ZOPE_EVENT_VERSION="6.2"
|
||||||
|
GEVENT_WEBSOCKET_VERSION="0.10.1"
|
||||||
|
|
||||||
VERSION=$(node -p "require('$APPIMAGE_ROOT/package.json').version")
|
VERSION=$(node -p "require('$APPIMAGE_ROOT/package.json').version")
|
||||||
APPIMAGE_NAME="ProxMenux-${VERSION}.AppImage"
|
APPIMAGE_NAME="ProxMenux-${VERSION}.AppImage"
|
||||||
|
|
||||||
@@ -58,6 +70,7 @@ fi
|
|||||||
# Create directory structure
|
# Create directory structure
|
||||||
mkdir -p "$APP_DIR/usr/bin"
|
mkdir -p "$APP_DIR/usr/bin"
|
||||||
mkdir -p "$APP_DIR/usr/lib/python3/dist-packages"
|
mkdir -p "$APP_DIR/usr/lib/python3/dist-packages"
|
||||||
|
mkdir -p "$APP_DIR/usr/lib/python3/vendor"
|
||||||
mkdir -p "$APP_DIR/usr/share/applications"
|
mkdir -p "$APP_DIR/usr/share/applications"
|
||||||
mkdir -p "$APP_DIR/usr/share/icons/hicolor/256x256/apps"
|
mkdir -p "$APP_DIR/usr/share/icons/hicolor/256x256/apps"
|
||||||
mkdir -p "$APP_DIR/web"
|
mkdir -p "$APP_DIR/web"
|
||||||
@@ -319,11 +332,59 @@ pip3 install --target "$APP_DIR/usr/lib/python3/dist-packages" --upgrade \
|
|||||||
simple-websocket>=0.10.0 \
|
simple-websocket>=0.10.0 \
|
||||||
flask-sock>=0.6.0
|
flask-sock>=0.6.0
|
||||||
|
|
||||||
# Phase 3b: Install gevent for SSL+WebSocket support (WSS)
|
# Phase 3b: Install the complete gevent/WSS runtime separately for every
|
||||||
pip3 install --target "$APP_DIR/usr/lib/python3/dist-packages" --upgrade \
|
# supported host ABI. Keeping each dependency tree isolated avoids mixing
|
||||||
gevent>=24.2.1 \
|
# CPython-specific modules from the build runner with those loaded by PVE.
|
||||||
gevent-websocket>=0.10.1 \
|
# The two --platform values allow gevent's manylinux_2_28 wheels and the
|
||||||
greenlet>=3.0.0
|
# backwards-compatible manylinux2014 zope.interface wheels to be resolved in
|
||||||
|
# one deterministic installation.
|
||||||
|
install_gevent_runtime() {
|
||||||
|
local python_version="$1"
|
||||||
|
local abi="$2"
|
||||||
|
local target="$APP_DIR/usr/lib/python3/vendor/$abi"
|
||||||
|
|
||||||
|
echo "📦 Installing gevent runtime for ${abi} (Python ${python_version})..."
|
||||||
|
mkdir -p "$target"
|
||||||
|
python3 -m pip install \
|
||||||
|
--disable-pip-version-check \
|
||||||
|
--target "$target" \
|
||||||
|
--upgrade \
|
||||||
|
--only-binary=:all: \
|
||||||
|
--platform manylinux_2_28_x86_64 \
|
||||||
|
--platform manylinux2014_x86_64 \
|
||||||
|
--implementation cp \
|
||||||
|
--python-version "$python_version" \
|
||||||
|
--abi "$abi" \
|
||||||
|
--no-deps \
|
||||||
|
"gevent==${GEVENT_VERSION}" \
|
||||||
|
"greenlet==${GREENLET_VERSION}" \
|
||||||
|
"zope.interface==${ZOPE_INTERFACE_VERSION}" \
|
||||||
|
"zope.event==${ZOPE_EVENT_VERSION}" \
|
||||||
|
"gevent-websocket==${GEVENT_WEBSOCKET_VERSION}"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_gevent_runtime "3.11" "cp311"
|
||||||
|
install_gevent_runtime "3.13" "cp313"
|
||||||
|
|
||||||
|
echo "🔍 Verifying bundled Python ABI extensions..."
|
||||||
|
for abi in cp311 cp313; do
|
||||||
|
abi_digits="${abi#cp}"
|
||||||
|
abi_dir="$APP_DIR/usr/lib/python3/vendor/$abi"
|
||||||
|
for extension in \
|
||||||
|
"gevent/_gevent_c_hub_local.cpython-${abi_digits}-*.so" \
|
||||||
|
"greenlet/_greenlet.cpython-${abi_digits}-*.so" \
|
||||||
|
"zope/interface/_zope_interface_coptimizations.cpython-${abi_digits}-*.so"; do
|
||||||
|
if ! compgen -G "$abi_dir/$extension" >/dev/null; then
|
||||||
|
echo "❌ Missing ${abi} extension: ${extension}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ ! -f "$abi_dir/geventwebsocket/__init__.py" ]; then
|
||||||
|
echo "❌ Missing gevent-websocket runtime for ${abi}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " ✅ ${abi} runtime complete"
|
||||||
|
done
|
||||||
|
|
||||||
# Phase 3c: Apprise notification hub (issue #207). One library handles
|
# Phase 3c: Apprise notification hub (issue #207). One library handles
|
||||||
# ~80 notification services behind a single URL scheme (`tgram://`,
|
# ~80 notification services behind a single URL scheme (`tgram://`,
|
||||||
|
|||||||
@@ -634,19 +634,150 @@ EOF
|
|||||||
|
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
_migrate_proxmenux_bashrc() {
|
||||||
|
local bashrc="$1"
|
||||||
|
local mode="${2:-migrate}"
|
||||||
|
|
||||||
|
# Releases up to v1.1.3 appended an unmarked block. A later release
|
||||||
|
# accidentally wrote the marker variable names literally because its
|
||||||
|
# heredoc was quoted. Remove only those ProxMenux-owned shapes, together
|
||||||
|
# with the current marked block, before writing one canonical block.
|
||||||
|
python3 - "$bashrc" "$mode" <<'PY'
|
||||||
|
import os
|
||||||
|
import stat
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
path = Path(sys.argv[1])
|
||||||
|
mode = sys.argv[2] if len(sys.argv) > 2 else "migrate"
|
||||||
|
if mode not in {"inspect", "migrate"}:
|
||||||
|
raise SystemExit(f"invalid Bashrc migration mode: {mode}")
|
||||||
|
lines = path.read_text(encoding="utf-8", errors="surrogateescape").splitlines(keepends=True)
|
||||||
|
|
||||||
|
|
||||||
|
def content(line: str) -> str:
|
||||||
|
return line.rstrip("\r\n")
|
||||||
|
|
||||||
|
|
||||||
|
def prompt_style(block: list[str]) -> str:
|
||||||
|
for line in block:
|
||||||
|
if content(line).startswith("export PS1=") and r"\w" in content(line):
|
||||||
|
return "full"
|
||||||
|
return "short"
|
||||||
|
|
||||||
|
|
||||||
|
legacy_tail = [
|
||||||
|
"alias l='ls -CF'",
|
||||||
|
"alias la='ls -A'",
|
||||||
|
"alias ll='ls -alF'",
|
||||||
|
"alias ls='ls --color=auto'",
|
||||||
|
"alias grep='grep --color=auto'",
|
||||||
|
"alias fgrep='fgrep --color=auto'",
|
||||||
|
"alias egrep='egrep --color=auto'",
|
||||||
|
"source /etc/profile.d/bash_completion.sh",
|
||||||
|
]
|
||||||
|
marker_pairs = (
|
||||||
|
("# BEGIN PMX_CORE_BASHRC", "# END PMX_CORE_BASHRC"),
|
||||||
|
("${marker_begin}", "${marker_end}"),
|
||||||
|
)
|
||||||
|
|
||||||
|
result: list[str] = []
|
||||||
|
detected_style = "short"
|
||||||
|
changed = False
|
||||||
|
i = 0
|
||||||
|
while i < len(lines):
|
||||||
|
current = content(lines[i])
|
||||||
|
|
||||||
|
removed = False
|
||||||
|
for marker_begin, marker_end in marker_pairs:
|
||||||
|
if current != marker_begin:
|
||||||
|
continue
|
||||||
|
end_index = next(
|
||||||
|
(j for j in range(i + 1, len(lines)) if content(lines[j]) == marker_end),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
# Preserve incomplete ranges rather than risk consuming user content.
|
||||||
|
if end_index is None or end_index - i > 32:
|
||||||
|
continue
|
||||||
|
block = lines[i:end_index + 1]
|
||||||
|
if prompt_style(block) == "full":
|
||||||
|
detected_style = "full"
|
||||||
|
i = end_index + 1
|
||||||
|
changed = True
|
||||||
|
removed = True
|
||||||
|
break
|
||||||
|
if removed:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Exact legacy signature: ProxMenux header, history format, coloured
|
||||||
|
# prompt, seven stock aliases and bash-completion. Unrelated user blocks
|
||||||
|
# cannot match this complete sequence.
|
||||||
|
if current == "# ProxMenux customizations" and i + 10 < len(lines):
|
||||||
|
candidate = lines[i:i + 11]
|
||||||
|
candidate_text = [content(line) for line in candidate]
|
||||||
|
ps1 = candidate_text[2]
|
||||||
|
if (
|
||||||
|
candidate_text[1] == 'export HISTTIMEFORMAT="%d/%m/%y %T "'
|
||||||
|
and ps1.startswith('export PS1="')
|
||||||
|
and r"\e[38;5;172m" in ps1
|
||||||
|
and r"\e[38;5;153m" in ps1
|
||||||
|
and r"\e[38;5;214m" in ps1
|
||||||
|
and candidate_text[3:] == legacy_tail
|
||||||
|
):
|
||||||
|
if prompt_style(candidate) == "full":
|
||||||
|
detected_style = "full"
|
||||||
|
i += len(candidate)
|
||||||
|
changed = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
result.append(lines[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if changed and mode == "migrate":
|
||||||
|
original_stat = path.stat()
|
||||||
|
fd, temporary = tempfile.mkstemp(prefix=".bashrc.proxmenux.", dir=str(path.parent))
|
||||||
|
try:
|
||||||
|
with os.fdopen(fd, "w", encoding="utf-8", errors="surrogateescape", newline="") as handle:
|
||||||
|
handle.write("".join(result))
|
||||||
|
handle.flush()
|
||||||
|
os.fsync(handle.fileno())
|
||||||
|
os.chmod(temporary, stat.S_IMODE(original_stat.st_mode))
|
||||||
|
try:
|
||||||
|
os.chown(temporary, original_stat.st_uid, original_stat.st_gid)
|
||||||
|
except PermissionError:
|
||||||
|
pass
|
||||||
|
os.replace(temporary, path)
|
||||||
|
finally:
|
||||||
|
if os.path.exists(temporary):
|
||||||
|
os.unlink(temporary)
|
||||||
|
|
||||||
|
print(detected_style)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
customize_bashrc() {
|
customize_bashrc() {
|
||||||
local FUNC_VERSION="1.1"
|
local FUNC_VERSION="1.2"
|
||||||
# description: Install the managed ProxMenux Bash prompt and aliases while preserving or selecting the short/full working-directory style.
|
# 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...")"
|
msg_info "$(translate "Customizing bashrc for root user...")"
|
||||||
local bashrc="/root/.bashrc"
|
local bashrc="/root/.bashrc"
|
||||||
local bash_profile="/root/.bash_profile"
|
local bash_profile="/root/.bash_profile"
|
||||||
local marker_begin="# BEGIN PMX_CORE_BASHRC"
|
local marker_begin="# BEGIN PMX_CORE_BASHRC"
|
||||||
local marker_end="# END PMX_CORE_BASHRC"
|
local marker_end="# END PMX_CORE_BASHRC"
|
||||||
local prompt_path_escape='\W'
|
local prompt_path_escape='\W'
|
||||||
|
local detected_path_style="short"
|
||||||
|
|
||||||
|
[[ -f "$bashrc" ]] || touch "$bashrc"
|
||||||
|
if ! detected_path_style="$(_migrate_proxmenux_bashrc "$bashrc" inspect)"; then
|
||||||
|
msg_error "$(translate "Failed to inspect the existing ProxMenux Bash configuration.")"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Automated installs remain non-interactive. Preserve a previous
|
# Automated installs remain non-interactive. Preserve a previous
|
||||||
# ProxMenux choice on re-run and use the compact \W style on first use.
|
# ProxMenux choice on re-run, including legacy blocks, and use the compact
|
||||||
if sed -n "/^${marker_begin}$/,/^${marker_end}$/p" "$bashrc" 2>/dev/null | grep -Fq '\w'; then
|
# \W style on first use.
|
||||||
|
if [[ "$detected_path_style" == "full" ]]; then
|
||||||
prompt_path_escape='\w'
|
prompt_path_escape='\w'
|
||||||
fi
|
fi
|
||||||
case "${PMX_BASHRC_PATH_STYLE:-}" in
|
case "${PMX_BASHRC_PATH_STYLE:-}" in
|
||||||
@@ -658,13 +789,11 @@ customize_bashrc() {
|
|||||||
return 1
|
return 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|
||||||
[ -f "${bashrc}.bak" ] || cp "$bashrc" "${bashrc}.bak" > /dev/null 2>&1
|
|
||||||
|
|
||||||
|
|
||||||
if grep -q "^${marker_begin}$" "$bashrc" 2>/dev/null; then
|
[ -f "${bashrc}.bak" ] || cp "$bashrc" "${bashrc}.bak" > /dev/null 2>&1
|
||||||
sed -i "/^${marker_begin}$/,/^${marker_end}$/d" "$bashrc"
|
if ! _migrate_proxmenux_bashrc "$bashrc" migrate >/dev/null; then
|
||||||
|
msg_error "$(translate "Failed to migrate the existing ProxMenux Bash configuration.")"
|
||||||
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
@@ -706,13 +835,152 @@ EOF
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_update_existing_log2ram_auto() {
|
||||||
|
local func_version="$1"
|
||||||
|
local log2ram_bin=""
|
||||||
|
local candidate resolved tmp_file
|
||||||
|
|
||||||
|
msg_ok "$(translate "Log2RAM already registered — updating to latest configuration")"
|
||||||
|
|
||||||
|
for candidate in \
|
||||||
|
"$(command -v log2ram 2>/dev/null)" \
|
||||||
|
/usr/local/bin/log2ram \
|
||||||
|
/usr/sbin/log2ram \
|
||||||
|
/usr/bin/log2ram
|
||||||
|
do
|
||||||
|
[[ -n "$candidate" && -f "$candidate" ]] || continue
|
||||||
|
resolved="$(readlink -f "$candidate" 2>/dev/null || printf '%s' "$candidate")"
|
||||||
|
[[ -f "$resolved" ]] || continue
|
||||||
|
log2ram_bin="$resolved"
|
||||||
|
break
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$log2ram_bin" || ! -f /etc/log2ram.conf ]]; then
|
||||||
|
msg_error "$(translate "Log2RAM installation verification failed. Check /tmp/log2ram_install.log")"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q 'rsync -aAXv ' "$log2ram_bin" 2>/dev/null; then
|
||||||
|
[[ -e "${log2ram_bin}.proxmenux.bak" ]] || cp -a "$log2ram_bin" "${log2ram_bin}.proxmenux.bak"
|
||||||
|
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"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if dpkg-query -W -f='${Status}' proxmox-backup-server 2>/dev/null \
|
||||||
|
| grep -q 'install ok installed'; then
|
||||||
|
tmp_file="$(mktemp /etc/logrotate.d/.proxmox-backup-api.XXXXXX)" || return 1
|
||||||
|
cat > "$tmp_file" <<'EOF'
|
||||||
|
/var/log/proxmox-backup/api/access.log /var/log/proxmox-backup/api/auth.log {
|
||||||
|
size 20M
|
||||||
|
rotate 3
|
||||||
|
compress
|
||||||
|
delaycompress
|
||||||
|
missingok
|
||||||
|
notifempty
|
||||||
|
copytruncate
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
chmod 0644 "$tmp_file"
|
||||||
|
chown root:root "$tmp_file"
|
||||||
|
mv -f "$tmp_file" /etc/logrotate.d/proxmox-backup-api
|
||||||
|
|
||||||
|
tmp_file="$(mktemp /etc/cron.hourly/.proxmox-backup-logrotate.XXXXXX)" || return 1
|
||||||
|
cat > "$tmp_file" <<'EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
/usr/sbin/logrotate /etc/logrotate.d/proxmox-backup-api >/dev/null 2>&1
|
||||||
|
EOF
|
||||||
|
chmod 0755 "$tmp_file"
|
||||||
|
chown root:root "$tmp_file"
|
||||||
|
mv -f "$tmp_file" /etc/cron.hourly/proxmox-backup-logrotate
|
||||||
|
msg_ok "$(translate "PBS API log rotation configured (hourly, size-based)")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp_file="$(mktemp /usr/local/bin/.log2ram-check.XXXXXX)" || return 1
|
||||||
|
cat > "$tmp_file" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Watch /var/log usage on Log2RAM's tmpfs and act at two thresholds:
|
||||||
|
# > 80% → vacuum journald down to ~30% of SIZE, then log2ram write
|
||||||
|
# > 92% → aggressive: journal to ~5%, rotate PBS API logs if present,
|
||||||
|
# truncate existing pveproxy/pveam logs, then log2ram write
|
||||||
|
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
|
||||||
|
CONF_FILE="/etc/log2ram.conf"
|
||||||
|
L2R_BIN="$(command -v log2ram || true)"
|
||||||
|
[[ -z "$L2R_BIN" && -x /usr/sbin/log2ram ]] && L2R_BIN="/usr/sbin/log2ram"
|
||||||
|
[[ -z "$L2R_BIN" ]] && exit 0
|
||||||
|
|
||||||
|
SIZE_MiB="$(grep -E '^SIZE=' "$CONF_FILE" 2>/dev/null | cut -d'=' -f2 | tr -dc '0-9')"
|
||||||
|
[[ -z "$SIZE_MiB" ]] && SIZE_MiB=128
|
||||||
|
LIMIT_BYTES=$(( SIZE_MiB * 1024 * 1024 ))
|
||||||
|
WARN_BYTES=$(( LIMIT_BYTES * 80 / 100 ))
|
||||||
|
EMERGENCY_BYTES=$(( LIMIT_BYTES * 92 / 100 ))
|
||||||
|
|
||||||
|
USED_BYTES="$(df -B1 --output=used /var/log 2>/dev/null | tail -1 | tr -dc '0-9')"
|
||||||
|
[[ -z "$USED_BYTES" ]] && exit 0
|
||||||
|
|
||||||
|
LOCK="/run/log2ram-check.lock"
|
||||||
|
exec 9>"$LOCK" 2>/dev/null || exit 0
|
||||||
|
flock -n 9 || exit 0
|
||||||
|
|
||||||
|
if (( USED_BYTES > EMERGENCY_BYTES )); then
|
||||||
|
SAFE_JOURNAL_MB=$(( SIZE_MiB * 5 / 100 ))
|
||||||
|
[[ "$SAFE_JOURNAL_MB" -lt 16 ]] && SAFE_JOURNAL_MB=16
|
||||||
|
journalctl --vacuum-size="${SAFE_JOURNAL_MB}M" >/dev/null 2>&1 || true
|
||||||
|
if [[ -x /usr/sbin/logrotate && -f /etc/logrotate.d/proxmox-backup-api ]]; then
|
||||||
|
/usr/sbin/logrotate -f /etc/logrotate.d/proxmox-backup-api >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
[ -e /var/log/pveproxy/access.log ] && : > /var/log/pveproxy/access.log 2>/dev/null || true
|
||||||
|
[ -e /var/log/pveproxy/error.log ] && : > /var/log/pveproxy/error.log 2>/dev/null || true
|
||||||
|
[ -e /var/log/pveam.log ] && : > /var/log/pveam.log 2>/dev/null || true
|
||||||
|
"$L2R_BIN" write 2>/dev/null || true
|
||||||
|
elif (( USED_BYTES > WARN_BYTES )); then
|
||||||
|
SOFT_JOURNAL_MB=$(( SIZE_MiB * 30 / 100 ))
|
||||||
|
[[ "$SOFT_JOURNAL_MB" -lt 32 ]] && SOFT_JOURNAL_MB=32
|
||||||
|
journalctl --vacuum-size="${SOFT_JOURNAL_MB}M" >/dev/null 2>&1 || true
|
||||||
|
"$L2R_BIN" write 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
chmod 0755 "$tmp_file"
|
||||||
|
chown root:root "$tmp_file"
|
||||||
|
bash -n "$tmp_file" || return 1
|
||||||
|
mv -f "$tmp_file" /usr/local/bin/log2ram-check.sh
|
||||||
|
|
||||||
|
tmp_file="$(mktemp /etc/cron.d/.log2ram-auto-sync.XXXXXX)" || return 1
|
||||||
|
cat > "$tmp_file" <<'EOF'
|
||||||
|
# Log2RAM auto-sync based on /var/log usage - Created by ProxMenux
|
||||||
|
SHELL=/bin/bash
|
||||||
|
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
MAILTO=""
|
||||||
|
# nice/ionice keep the check off the priority queue for scheduled tasks.
|
||||||
|
3-59/10 * * * * root nice -n 19 ionice -c 3 /usr/local/bin/log2ram-check.sh >/dev/null 2>&1
|
||||||
|
EOF
|
||||||
|
chmod 0644 "$tmp_file"
|
||||||
|
chown root:root "$tmp_file"
|
||||||
|
mv -f "$tmp_file" /etc/cron.d/log2ram-auto-sync
|
||||||
|
|
||||||
|
register_tool "log2ram" true "$func_version"
|
||||||
|
msg_success "$(translate "Log2RAM installation and configuration completed successfully.")"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
install_log2ram_auto() {
|
install_log2ram_auto() {
|
||||||
local FUNC_VERSION="1.4"
|
local FUNC_VERSION="1.5"
|
||||||
|
local existing_log2ram_bin=""
|
||||||
|
|
||||||
# description: Install Log2RAM with size auto-tuned to host RAM (128M/256M/512M); SSD/M.2 detection skips on rotational disks.
|
# description: Install Log2RAM with size auto-tuned to host RAM (128M/256M/512M); SSD/M.2 detection skips on rotational disks.
|
||||||
|
|
||||||
|
existing_log2ram_bin="$(command -v log2ram 2>/dev/null || true)"
|
||||||
|
if [[ -f /etc/log2ram.conf || -n "$existing_log2ram_bin" \
|
||||||
|
|| -e /etc/systemd/system/log2ram.service \
|
||||||
|
|| -d /var/hdd.log || -d /var/log.hdd ]]; then
|
||||||
|
_update_existing_log2ram_auto "$FUNC_VERSION"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -f "$TOOLS_JSON" ]] && jq -e '.log2ram == true or .log2ram.installed == true' "$TOOLS_JSON" >/dev/null 2>&1; then
|
if [[ -f "$TOOLS_JSON" ]] && jq -e '.log2ram == true or .log2ram.installed == true' "$TOOLS_JSON" >/dev/null 2>&1; then
|
||||||
msg_ok "$(translate "Log2RAM already registered — updating to latest configuration")"
|
msg_info2 "$(translate "Preparing Log2RAM configuration")"
|
||||||
else
|
else
|
||||||
# ── First-time install: detect SSD/M.2 ─────────────────────────────────
|
# ── First-time install: detect SSD/M.2 ─────────────────────────────────
|
||||||
msg_info "$(translate "Checking if system disk is SSD or M.2...")"
|
msg_info "$(translate "Checking if system disk is SSD or M.2...")"
|
||||||
@@ -784,7 +1052,7 @@ install_log2ram_auto() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
rm -rf /tmp/log2ram 2>/dev/null || true
|
rm -rf /tmp/log2ram 2>/dev/null || true
|
||||||
if ! git clone https://github.com/azlux/log2ram.git /tmp/log2ram >/dev/null 2>>/tmp/log2ram_install.log; then
|
if ! git clone --depth 1 https://github.com/azlux/log2ram.git /tmp/log2ram >/dev/null 2>>/tmp/log2ram_install.log; then
|
||||||
msg_error "$(translate "Failed to clone log2ram repository. Check /tmp/log2ram_install.log")"
|
msg_error "$(translate "Failed to clone log2ram repository. Check /tmp/log2ram_install.log")"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@@ -842,7 +1110,6 @@ EOF
|
|||||||
msg_ok "$(translate "PBS API log rotation configured (hourly, size-based)")"
|
msg_ok "$(translate "PBS API log rotation configured (hourly, size-based)")"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
systemctl enable --now log2ram >/dev/null 2>&1 || true
|
|
||||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||||
|
|
||||||
if [[ -f /etc/log2ram.conf ]] && command -v log2ram >/dev/null 2>&1; then
|
if [[ -f /etc/log2ram.conf ]] && command -v log2ram >/dev/null 2>&1; then
|
||||||
@@ -997,15 +1264,15 @@ EOF
|
|||||||
chown -R www-data:www-data /var/log.hdd/pveproxy
|
chown -R www-data:www-data /var/log.hdd/pveproxy
|
||||||
chmod 0750 /var/log.hdd/pveproxy
|
chmod 0750 /var/log.hdd/pveproxy
|
||||||
|
|
||||||
systemctl restart systemd-journald >/dev/null 2>&1 || true
|
|
||||||
#msg_ok "$(translate "Backup created:") /etc/systemd/journald.conf.bak.$(date +%Y%m%d-%H%M%S)"
|
#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})"
|
msg_ok "$(translate "Journald configuration adjusted to") ${USE_MB}M (Log2RAM ${LOG2RAM_SIZE})"
|
||||||
|
|
||||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||||
systemctl restart log2ram >/dev/null 2>&1 || true
|
if ! systemctl enable log2ram >/dev/null 2>&1; then
|
||||||
log2ram clean >/dev/null 2>&1 || true
|
msg_error "$(translate "Log2RAM installation verification failed. Check /tmp/log2ram_install.log")"
|
||||||
log2ram write >/dev/null 2>&1 || true
|
return 1
|
||||||
systemctl restart rsyslog >/dev/null 2>&1 || true
|
fi
|
||||||
|
NECESSARY_REBOOT=1
|
||||||
|
|
||||||
register_tool "log2ram" true "$FUNC_VERSION"
|
register_tool "log2ram" true "$FUNC_VERSION"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1939,16 +1939,132 @@ enable_vfio_iommu() {
|
|||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
|
|
||||||
|
_migrate_proxmenux_bashrc() {
|
||||||
|
local bashrc="$1"
|
||||||
|
local mode="${2:-migrate}"
|
||||||
|
|
||||||
|
# Releases up to v1.1.3 appended an unmarked block. A later release
|
||||||
|
# accidentally wrote the marker variable names literally because its
|
||||||
|
# heredoc was quoted. Remove only those ProxMenux-owned shapes, together
|
||||||
|
# with the current marked block, before writing one canonical block.
|
||||||
|
python3 - "$bashrc" "$mode" <<'PY'
|
||||||
|
import os
|
||||||
|
import stat
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
path = Path(sys.argv[1])
|
||||||
|
mode = sys.argv[2] if len(sys.argv) > 2 else "migrate"
|
||||||
|
if mode not in {"inspect", "migrate"}:
|
||||||
|
raise SystemExit(f"invalid Bashrc migration mode: {mode}")
|
||||||
|
lines = path.read_text(encoding="utf-8", errors="surrogateescape").splitlines(keepends=True)
|
||||||
|
|
||||||
|
|
||||||
|
def content(line: str) -> str:
|
||||||
|
return line.rstrip("\r\n")
|
||||||
|
|
||||||
|
|
||||||
|
def prompt_style(block: list[str]) -> str:
|
||||||
|
for line in block:
|
||||||
|
if content(line).startswith("export PS1=") and r"\w" in content(line):
|
||||||
|
return "full"
|
||||||
|
return "short"
|
||||||
|
|
||||||
|
|
||||||
|
legacy_tail = [
|
||||||
|
"alias l='ls -CF'",
|
||||||
|
"alias la='ls -A'",
|
||||||
|
"alias ll='ls -alF'",
|
||||||
|
"alias ls='ls --color=auto'",
|
||||||
|
"alias grep='grep --color=auto'",
|
||||||
|
"alias fgrep='fgrep --color=auto'",
|
||||||
|
"alias egrep='egrep --color=auto'",
|
||||||
|
"source /etc/profile.d/bash_completion.sh",
|
||||||
|
]
|
||||||
|
marker_pairs = (
|
||||||
|
("# BEGIN PMX_CORE_BASHRC", "# END PMX_CORE_BASHRC"),
|
||||||
|
("${marker_begin}", "${marker_end}"),
|
||||||
|
)
|
||||||
|
|
||||||
|
result: list[str] = []
|
||||||
|
detected_style = "short"
|
||||||
|
changed = False
|
||||||
|
i = 0
|
||||||
|
while i < len(lines):
|
||||||
|
current = content(lines[i])
|
||||||
|
|
||||||
|
removed = False
|
||||||
|
for marker_begin, marker_end in marker_pairs:
|
||||||
|
if current != marker_begin:
|
||||||
|
continue
|
||||||
|
end_index = next(
|
||||||
|
(j for j in range(i + 1, len(lines)) if content(lines[j]) == marker_end),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
# Preserve incomplete ranges rather than risk consuming user content.
|
||||||
|
if end_index is None or end_index - i > 32:
|
||||||
|
continue
|
||||||
|
block = lines[i:end_index + 1]
|
||||||
|
if prompt_style(block) == "full":
|
||||||
|
detected_style = "full"
|
||||||
|
i = end_index + 1
|
||||||
|
changed = True
|
||||||
|
removed = True
|
||||||
|
break
|
||||||
|
if removed:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Exact legacy signature: ProxMenux header, history format, coloured
|
||||||
|
# prompt, seven stock aliases and bash-completion. Unrelated user blocks
|
||||||
|
# cannot match this complete sequence.
|
||||||
|
if current == "# ProxMenux customizations" and i + 10 < len(lines):
|
||||||
|
candidate = lines[i:i + 11]
|
||||||
|
candidate_text = [content(line) for line in candidate]
|
||||||
|
ps1 = candidate_text[2]
|
||||||
|
if (
|
||||||
|
candidate_text[1] == 'export HISTTIMEFORMAT="%d/%m/%y %T "'
|
||||||
|
and ps1.startswith('export PS1="')
|
||||||
|
and r"\e[38;5;172m" in ps1
|
||||||
|
and r"\e[38;5;153m" in ps1
|
||||||
|
and r"\e[38;5;214m" in ps1
|
||||||
|
and candidate_text[3:] == legacy_tail
|
||||||
|
):
|
||||||
|
if prompt_style(candidate) == "full":
|
||||||
|
detected_style = "full"
|
||||||
|
i += len(candidate)
|
||||||
|
changed = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
result.append(lines[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if changed and mode == "migrate":
|
||||||
|
original_stat = path.stat()
|
||||||
|
fd, temporary = tempfile.mkstemp(prefix=".bashrc.proxmenux.", dir=str(path.parent))
|
||||||
|
try:
|
||||||
|
with os.fdopen(fd, "w", encoding="utf-8", errors="surrogateescape", newline="") as handle:
|
||||||
|
handle.write("".join(result))
|
||||||
|
handle.flush()
|
||||||
|
os.fsync(handle.fileno())
|
||||||
|
os.chmod(temporary, stat.S_IMODE(original_stat.st_mode))
|
||||||
|
try:
|
||||||
|
os.chown(temporary, original_stat.st_uid, original_stat.st_gid)
|
||||||
|
except PermissionError:
|
||||||
|
pass
|
||||||
|
os.replace(temporary, path)
|
||||||
|
finally:
|
||||||
|
if os.path.exists(temporary):
|
||||||
|
os.unlink(temporary)
|
||||||
|
|
||||||
|
print(detected_style)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
customize_bashrc() {
|
customize_bashrc() {
|
||||||
local FUNC_VERSION="1.1"
|
local FUNC_VERSION="1.2"
|
||||||
# description: Install the managed ProxMenux Bash prompt and aliases while preserving or selecting the short/full working-directory style.
|
# description: Install and safely migrate the managed ProxMenux Bash prompt and aliases while preserving or selecting the short/full working-directory style.
|
||||||
msg_info2 "$(translate "Customizing bashrc for root user...")"
|
msg_info2 "$(translate "Customizing bashrc for root user...")"
|
||||||
|
|
||||||
msg_info "$(translate "Customizing bashrc for root user...")"
|
msg_info "$(translate "Customizing bashrc for root user...")"
|
||||||
@@ -1961,10 +2077,18 @@ customize_bashrc() {
|
|||||||
local short_state="on"
|
local short_state="on"
|
||||||
local full_state="off"
|
local full_state="off"
|
||||||
local choice=""
|
local choice=""
|
||||||
|
local detected_path_style="short"
|
||||||
|
|
||||||
|
[[ -f "$bashrc" ]] || touch "$bashrc"
|
||||||
|
if ! detected_path_style="$(_migrate_proxmenux_bashrc "$bashrc" inspect)"; then
|
||||||
|
msg_error "$(translate "Failed to inspect the existing ProxMenux Bash configuration.")"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Preserve an existing ProxMenux-managed choice when the function is
|
# Preserve an existing ProxMenux-managed choice when the function is
|
||||||
# re-run. \W shows only the current directory; \w shows the full path.
|
# re-run, including legacy blocks. \W shows only the current directory;
|
||||||
if sed -n "/^${marker_begin}$/,/^${marker_end}$/p" "$bashrc" 2>/dev/null | grep -Fq '\w'; then
|
# \w shows the full path.
|
||||||
|
if [[ "$detected_path_style" == "full" ]]; then
|
||||||
prompt_path_escape='\w'
|
prompt_path_escape='\w'
|
||||||
short_state="off"
|
short_state="off"
|
||||||
full_state="on"
|
full_state="on"
|
||||||
@@ -1997,13 +2121,11 @@ customize_bashrc() {
|
|||||||
return 1
|
return 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|
||||||
[ -f "${bashrc}.bak" ] || cp "$bashrc" "${bashrc}.bak" > /dev/null 2>&1
|
|
||||||
|
|
||||||
|
|
||||||
if grep -q "^${marker_begin}$" "$bashrc" 2>/dev/null; then
|
[ -f "${bashrc}.bak" ] || cp "$bashrc" "${bashrc}.bak" > /dev/null 2>&1
|
||||||
sed -i "/^${marker_begin}$/,/^${marker_end}$/d" "$bashrc"
|
if ! _migrate_proxmenux_bashrc "$bashrc" migrate >/dev/null; then
|
||||||
|
msg_error "$(translate "Failed to migrate the existing ProxMenux Bash configuration.")"
|
||||||
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
@@ -2728,9 +2850,137 @@ update_pve_appliance_manager() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_update_existing_log2ram_custom() {
|
||||||
|
local func_version="$1"
|
||||||
|
local log2ram_bin=""
|
||||||
|
local candidate resolved tmp_file
|
||||||
|
|
||||||
|
msg_ok "$(translate "Log2RAM already registered — updating to latest configuration")"
|
||||||
|
|
||||||
|
for candidate in \
|
||||||
|
"$(command -v log2ram 2>/dev/null)" \
|
||||||
|
/usr/local/bin/log2ram \
|
||||||
|
/usr/sbin/log2ram \
|
||||||
|
/usr/bin/log2ram
|
||||||
|
do
|
||||||
|
[[ -n "$candidate" && -f "$candidate" ]] || continue
|
||||||
|
resolved="$(readlink -f "$candidate" 2>/dev/null || printf '%s' "$candidate")"
|
||||||
|
[[ -f "$resolved" ]] || continue
|
||||||
|
log2ram_bin="$resolved"
|
||||||
|
break
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$log2ram_bin" || ! -f /etc/log2ram.conf ]]; then
|
||||||
|
msg_error "$(translate "Log2RAM installation verification failed. Check /tmp/log2ram_install.log")"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q 'rsync -aAXv ' "$log2ram_bin" 2>/dev/null; then
|
||||||
|
[[ -e "${log2ram_bin}.proxmenux.bak" ]] || cp -a "$log2ram_bin" "${log2ram_bin}.proxmenux.bak"
|
||||||
|
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"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if dpkg-query -W -f='${Status}' proxmox-backup-server 2>/dev/null \
|
||||||
|
| grep -q 'install ok installed'; then
|
||||||
|
tmp_file="$(mktemp /etc/logrotate.d/.proxmox-backup-api.XXXXXX)" || return 1
|
||||||
|
cat > "$tmp_file" <<'EOF'
|
||||||
|
/var/log/proxmox-backup/api/access.log /var/log/proxmox-backup/api/auth.log {
|
||||||
|
size 20M
|
||||||
|
rotate 3
|
||||||
|
compress
|
||||||
|
delaycompress
|
||||||
|
missingok
|
||||||
|
notifempty
|
||||||
|
copytruncate
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
chmod 0644 "$tmp_file"
|
||||||
|
chown root:root "$tmp_file"
|
||||||
|
mv -f "$tmp_file" /etc/logrotate.d/proxmox-backup-api
|
||||||
|
|
||||||
|
tmp_file="$(mktemp /etc/cron.hourly/.proxmox-backup-logrotate.XXXXXX)" || return 1
|
||||||
|
cat > "$tmp_file" <<'EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
/usr/sbin/logrotate /etc/logrotate.d/proxmox-backup-api >/dev/null 2>&1
|
||||||
|
EOF
|
||||||
|
chmod 0755 "$tmp_file"
|
||||||
|
chown root:root "$tmp_file"
|
||||||
|
mv -f "$tmp_file" /etc/cron.hourly/proxmox-backup-logrotate
|
||||||
|
msg_ok "$(translate "PBS API log rotation configured (hourly, size-based)")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f /usr/local/bin/log2ram-check.sh ]]; then
|
||||||
|
tmp_file="$(mktemp /usr/local/bin/.log2ram-check.XXXXXX)" || return 1
|
||||||
|
cat > "$tmp_file" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Watch /var/log usage on Log2RAM's tmpfs and act at two thresholds:
|
||||||
|
# > 80% → vacuum journald down to ~30% of SIZE, then log2ram write
|
||||||
|
# > 92% → aggressive: journal to ~5%, rotate PBS API logs if present,
|
||||||
|
# truncate existing pveproxy/pveam logs, then log2ram write
|
||||||
|
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
CONF_FILE="/etc/log2ram.conf"
|
||||||
|
L2R_BIN="$(command -v log2ram || true)"
|
||||||
|
[[ -z "$L2R_BIN" && -x /usr/sbin/log2ram ]] && L2R_BIN="/usr/sbin/log2ram"
|
||||||
|
[[ -z "$L2R_BIN" ]] && exit 0
|
||||||
|
|
||||||
|
SIZE_MiB="$(grep -E '^SIZE=' "$CONF_FILE" 2>/dev/null | cut -d'=' -f2 | tr -dc '0-9')"
|
||||||
|
[[ -z "$SIZE_MiB" ]] && SIZE_MiB=128
|
||||||
|
LIMIT_BYTES=$(( SIZE_MiB * 1024 * 1024 ))
|
||||||
|
WARN_BYTES=$(( LIMIT_BYTES * 80 / 100 ))
|
||||||
|
EMERGENCY_BYTES=$(( LIMIT_BYTES * 92 / 100 ))
|
||||||
|
|
||||||
|
USED_BYTES="$(df -B1 --output=used /var/log 2>/dev/null | tail -1 | tr -dc '0-9')"
|
||||||
|
[[ -z "$USED_BYTES" ]] && exit 0
|
||||||
|
|
||||||
|
LOCK="/run/log2ram-check.lock"
|
||||||
|
exec 9>"$LOCK" 2>/dev/null || exit 0
|
||||||
|
flock -n 9 || exit 0
|
||||||
|
|
||||||
|
if (( USED_BYTES > EMERGENCY_BYTES )); then
|
||||||
|
SAFE_JOURNAL_MB=$(( SIZE_MiB * 5 / 100 ))
|
||||||
|
[[ "$SAFE_JOURNAL_MB" -lt 16 ]] && SAFE_JOURNAL_MB=16
|
||||||
|
journalctl --vacuum-size="${SAFE_JOURNAL_MB}M" >/dev/null 2>&1 || true
|
||||||
|
if [[ -x /usr/sbin/logrotate && -f /etc/logrotate.d/proxmox-backup-api ]]; then
|
||||||
|
/usr/sbin/logrotate -f /etc/logrotate.d/proxmox-backup-api >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
[ -e /var/log/pveproxy/access.log ] && : > /var/log/pveproxy/access.log 2>/dev/null || true
|
||||||
|
[ -e /var/log/pveproxy/error.log ] && : > /var/log/pveproxy/error.log 2>/dev/null || true
|
||||||
|
[ -e /var/log/pveam.log ] && : > /var/log/pveam.log 2>/dev/null || true
|
||||||
|
"$L2R_BIN" write 2>/dev/null || true
|
||||||
|
elif (( USED_BYTES > WARN_BYTES )); then
|
||||||
|
SOFT_JOURNAL_MB=$(( SIZE_MiB * 30 / 100 ))
|
||||||
|
[[ "$SOFT_JOURNAL_MB" -lt 32 ]] && SOFT_JOURNAL_MB=32
|
||||||
|
journalctl --vacuum-size="${SOFT_JOURNAL_MB}M" >/dev/null 2>&1 || true
|
||||||
|
"$L2R_BIN" write 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
chmod 0755 "$tmp_file"
|
||||||
|
chown root:root "$tmp_file"
|
||||||
|
bash -n "$tmp_file" || return 1
|
||||||
|
mv -f "$tmp_file" /usr/local/bin/log2ram-check.sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
register_tool "log2ram" true "$func_version"
|
||||||
|
msg_success "$(translate "Log2RAM installation and configuration completed successfully.")"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
configure_log2ram() {
|
configure_log2ram() {
|
||||||
local FUNC_VERSION="1.3"
|
local FUNC_VERSION="1.5"
|
||||||
|
local existing_log2ram_bin=""
|
||||||
# description: Install Log2RAM with user-chosen RAM size; prompts for size and SSD/M.2 awareness before applying.
|
# description: Install Log2RAM with user-chosen RAM size; prompts for size and SSD/M.2 awareness before applying.
|
||||||
|
|
||||||
|
existing_log2ram_bin="$(command -v log2ram 2>/dev/null || true)"
|
||||||
|
if [[ -f /etc/log2ram.conf || -n "$existing_log2ram_bin" \
|
||||||
|
|| -e /etc/systemd/system/log2ram.service \
|
||||||
|
|| -d /var/hdd.log || -d /var/log.hdd ]]; then
|
||||||
|
_update_existing_log2ram_custom "$FUNC_VERSION"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
msg_info2 "$(translate "Preparing Log2RAM configuration")"
|
msg_info2 "$(translate "Preparing Log2RAM configuration")"
|
||||||
sleep 1
|
sleep 1
|
||||||
|
|
||||||
@@ -2809,13 +3059,7 @@ configure_log2ram() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
rm -rf /tmp/log2ram 2>/dev/null || true
|
rm -rf /tmp/log2ram 2>/dev/null || true
|
||||||
# Pin to a tested release — `master` could ship breaking changes
|
if ! git clone --depth 1 https://github.com/azlux/log2ram.git /tmp/log2ram \
|
||||||
# without notice. `LOG2RAM_TAG` env var lets the caller upgrade.
|
|
||||||
local LOG2RAM_TAG="${LOG2RAM_TAG:-1.7.0}"
|
|
||||||
if ! git clone --depth 1 --branch "$LOG2RAM_TAG" \
|
|
||||||
https://github.com/azlux/log2ram.git /tmp/log2ram \
|
|
||||||
>/dev/null 2>>/tmp/log2ram_install.log \
|
|
||||||
&& ! git clone --depth 1 https://github.com/azlux/log2ram.git /tmp/log2ram \
|
|
||||||
>/dev/null 2>>/tmp/log2ram_install.log; then
|
>/dev/null 2>>/tmp/log2ram_install.log; then
|
||||||
msg_error "$(translate "Failed to clone log2ram repository. Check /tmp/log2ram_install.log")"
|
msg_error "$(translate "Failed to clone log2ram repository. Check /tmp/log2ram_install.log")"
|
||||||
return 1
|
return 1
|
||||||
@@ -2874,7 +3118,6 @@ EOF
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||||
systemctl enable --now log2ram >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
if [[ -f /etc/log2ram.conf ]] && command -v log2ram >/dev/null 2>&1; then
|
if [[ -f /etc/log2ram.conf ]] && command -v log2ram >/dev/null 2>&1; then
|
||||||
msg_ok "$(translate "Log2RAM installed successfully")"
|
msg_ok "$(translate "Log2RAM installed successfully")"
|
||||||
@@ -2905,7 +3148,7 @@ EOF
|
|||||||
# Watch /var/log usage on Log2RAM's tmpfs and act at two thresholds:
|
# Watch /var/log usage on Log2RAM's tmpfs and act at two thresholds:
|
||||||
# > 80% → vacuum journald down to ~30% of SIZE, then log2ram write
|
# > 80% → vacuum journald down to ~30% of SIZE, then log2ram write
|
||||||
# > 92% → aggressive: journal to ~5%, rotate PBS API logs if present,
|
# > 92% → aggressive: journal to ~5%, rotate PBS API logs if present,
|
||||||
# truncate pveproxy/pveam, then log2ram write
|
# truncate existing pveproxy/pveam logs, then log2ram write
|
||||||
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
CONF_FILE="/etc/log2ram.conf"
|
CONF_FILE="/etc/log2ram.conf"
|
||||||
L2R_BIN="$(command -v log2ram || true)"
|
L2R_BIN="$(command -v log2ram || true)"
|
||||||
@@ -2932,9 +3175,9 @@ if (( USED_BYTES > EMERGENCY_BYTES )); then
|
|||||||
if [[ -x /usr/sbin/logrotate && -f /etc/logrotate.d/proxmox-backup-api ]]; then
|
if [[ -x /usr/sbin/logrotate && -f /etc/logrotate.d/proxmox-backup-api ]]; then
|
||||||
/usr/sbin/logrotate -f /etc/logrotate.d/proxmox-backup-api >/dev/null 2>&1 || true
|
/usr/sbin/logrotate -f /etc/logrotate.d/proxmox-backup-api >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
: > /var/log/pveproxy/access.log 2>/dev/null || true
|
[ -e /var/log/pveproxy/access.log ] && : > /var/log/pveproxy/access.log 2>/dev/null || true
|
||||||
: > /var/log/pveproxy/error.log 2>/dev/null || true
|
[ -e /var/log/pveproxy/error.log ] && : > /var/log/pveproxy/error.log 2>/dev/null || true
|
||||||
: > /var/log/pveam.log 2>/dev/null || true
|
[ -e /var/log/pveam.log ] && : > /var/log/pveam.log 2>/dev/null || true
|
||||||
"$L2R_BIN" write 2>/dev/null || true
|
"$L2R_BIN" write 2>/dev/null || true
|
||||||
elif (( USED_BYTES > WARN_BYTES )); then
|
elif (( USED_BYTES > WARN_BYTES )); then
|
||||||
SOFT_JOURNAL_MB=$(( SIZE_MiB * 30 / 100 ))
|
SOFT_JOURNAL_MB=$(( SIZE_MiB * 30 / 100 ))
|
||||||
@@ -3001,7 +3244,6 @@ MaxLevelConsole=notice
|
|||||||
MaxLevelWall=crit
|
MaxLevelWall=crit
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
systemctl restart systemd-journald >/dev/null 2>&1 || true
|
|
||||||
[[ "$BAK_OK" = "0" ]] && msg_ok "$(translate "Backup created:") /etc/systemd/journald.conf.bak.$(date +%Y%m%d-%H%M%S)"
|
[[ "$BAK_OK" = "0" ]] && msg_ok "$(translate "Backup created:") /etc/systemd/journald.conf.bak.$(date +%Y%m%d-%H%M%S)"
|
||||||
msg_ok "$(translate "Journald configuration adjusted to") ${USE_MB}M (Log2RAM ${LOG2RAM_SIZE})"
|
msg_ok "$(translate "Journald configuration adjusted to") ${USE_MB}M (Log2RAM ${LOG2RAM_SIZE})"
|
||||||
|
|
||||||
@@ -3014,12 +3256,11 @@ EOF
|
|||||||
chmod 0750 /var/log.hdd/pveproxy
|
chmod 0750 /var/log.hdd/pveproxy
|
||||||
|
|
||||||
systemctl restart cron >/dev/null 2>&1 || true
|
systemctl restart cron >/dev/null 2>&1 || true
|
||||||
systemctl restart log2ram >/dev/null 2>&1 || true
|
if ! systemctl enable log2ram >/dev/null 2>&1; then
|
||||||
|
msg_error "$(translate "Log2RAM installation verification failed. Check /tmp/log2ram_install.log")"
|
||||||
|
return 1
|
||||||
log2ram write >/dev/null 2>&1 || true
|
fi
|
||||||
log2ram clean >/dev/null 2>&1 || true
|
NECESSARY_REBOOT=1
|
||||||
systemctl restart rsyslog >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
msg_success "$(translate "Log2RAM installation and configuration completed successfully.")"
|
msg_success "$(translate "Log2RAM installation and configuration completed successfully.")"
|
||||||
register_tool "log2ram" true "$FUNC_VERSION"
|
register_tool "log2ram" true "$FUNC_VERSION"
|
||||||
|
|||||||
@@ -106,10 +106,10 @@ ensure_flow_loaded() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Suppress the Monitor's `service_fail` notifications while this
|
# Suppress the Monitor's `service_fail` notifications while this
|
||||||
# wrapper runs. Tools like log2ram or chrony stop and reinstall their
|
# wrapper runs. Some post-install functions legitimately restart their
|
||||||
# systemd units, and systemd flags the transient "failed" state
|
# services, and systemd can expose a transient "failed" state before the
|
||||||
# before the fresh unit starts — same class of noise as PVE services
|
# fresh unit starts — same class of noise as PVE services restarting
|
||||||
# restarting during an apt full-upgrade. Shared markers with
|
# during an apt full-upgrade. Shared markers with
|
||||||
# scripts/utilities/proxmox_update.sh; consumed by
|
# scripts/utilities/proxmox_update.sh; consumed by
|
||||||
# AppImage/scripts/notification_events.py::is_apt_active_on_host.
|
# AppImage/scripts/notification_events.py::is_apt_active_on_host.
|
||||||
_PROXMENUX_UPDATE_MARKER="/var/run/proxmenux-update-in-progress"
|
_PROXMENUX_UPDATE_MARKER="/var/run/proxmenux-update-in-progress"
|
||||||
|
|||||||
Reference in New Issue
Block a user