fix(post-install): align optimization descriptions with existing behavior

This commit is contained in:
martino
2026-10-22 23:46:56 +02:00
parent 6422d6ecd5
commit e032d869ff
5 changed files with 153 additions and 18 deletions
@@ -0,0 +1,135 @@
"""Wording contracts through the real metadata parser and exact-match formatter.
No post-install function or scanner is imported or executed. Node 22.6+ is
required, as in test_command_descriptions.py; no npm dependencies are needed.
"""
import ast
import json
import os
from pathlib import Path
import re
import shutil
import subprocess
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[3]
class OptimizationDescriptionWordingTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
# Admit only the pure parser and its actual dependencies, not the scanner.
path = ROOT / "AppImage/scripts/post_install_versions.py"
functions = {"_read_text", "_version_tuple", "parse_post_install_script"}
constants = {"_FN_DEF_RE", "_VERSION_RE", "_DESC_RE", "_REGISTER_RE", "_HEREDOC_RE"}
nodes: list[ast.stmt] = [node for node in ast.parse(path.read_text(encoding="utf-8")).body
if (isinstance(node, ast.FunctionDef) and node.name in functions)
or (isinstance(node, ast.Assign) and any(
isinstance(target, ast.Name) and target.id in constants
for target in node.targets))]
if len(nodes) != len(functions) + len(constants):
raise AssertionError("Metadata parser seam changed: review its dependencies")
scope = {"re": re, "Path": Path}
exec(compile(ast.Module(body=nodes, type_ignores=[]), str(path), "exec"), scope)
cls.metadata = {
source: scope["parse_post_install_script"](ROOT / "scripts/post_install" / filename)
for source, filename in (("auto", "auto_post_install.sh"),
("custom", "customizable_post_install.sh"))
}
cls.catalogs = {
locale: json.loads((ROOT / f"AppImage/messages/{locale}/common.json").read_text(encoding="utf-8"))
for locale in ("en", "it")
}
node = os.environ.get("NODE_BINARY") or shutil.which("node")
if not node:
raise AssertionError("Install Node.js 22.6+ or set NODE_BINARY")
cls.node = node
version = subprocess.run([node, "--version"], capture_output=True, text=True, check=True).stdout
match = re.fullmatch(r"v(\d+)\.(\d+)\.(\d+)\s*", version)
if not match or tuple(map(int, match.groups())) < (22, 6, 0):
raise AssertionError(f"Node.js 22.6+ required; detected {version.strip()}")
# Replace only the JSON import; the formatter body executes unchanged.
formatter = (ROOT / "AppImage/lib/optimization-descriptions.ts").read_text(encoding="utf-8")
json_import = 'import enMessages from "../messages/en/common.json"'
if formatter.count(json_import) != 1:
raise AssertionError("Formatter import seam changed")
fixture = formatter.replace(json_import, "const enMessages = " + json.dumps(cls.catalogs["en"]))
fixture += '''
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
const [key, description, italian, catalogs] = JSON.parse(readFileSync(0, "utf8"));
for (const locale of ["en", "it"]) {
const calls: string[] = [];
const t = (path: string) => {
calls.push(path);
return path.split(".").reduce((value, part) => value[part], catalogs[locale]);
};
assert.equal(optimizationDescription(description, t), locale === "en" ? description : italian);
assert.deepEqual(calls, [`settings.optimizations.toolDescriptions.${key}`]);
calls.length = 0;
const unknown = description + " [new server wording]";
assert.equal(optimizationDescription(unknown, t), unknown);
assert.deepEqual(calls, []);
}
console.log("PASS actual exact lookup: EN/IT and unknown-description fallback");
'''
cls.directory = tempfile.TemporaryDirectory(prefix="optimization-wording-")
cls.addClassCleanup(cls.directory.cleanup)
cls.fixture = Path(cls.directory.name) / "formatter.mts"
cls.fixture.write_text(fixture, encoding="utf-8")
def assert_description(self, key, source, tool, function, english, italian):
for locale, expected in (("en", english), ("it", italian)):
with self.subTest(key=key, locale=locale):
self.assertEqual(self.catalogs[locale]["settings"]["optimizations"]["toolDescriptions"][key], expected)
metadata = self.metadata[source][tool]
self.assertEqual(metadata["function"], function)
self.assertEqual(metadata["description"], english)
result = subprocess.run(
[self.node, "--experimental-strip-types", str(self.fixture)],
input=json.dumps([key, metadata["description"], italian, self.catalogs]),
capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn("PASS actual exact lookup", result.stdout)
def test_log2ram_auto_fresh_install_detection_and_update_size(self):
self.assert_description(
"log2ramAuto", "auto", "log2ram", "install_log2ram_auto",
"Fresh installs size Log2RAM from host RAM and ask for confirmation if storage is not detected as SSD. Updates preserve the configured size.",
"Alla prima installazione, dimensiona Log2RAM in base alla RAM dell'host e chiede conferma se il dispositivo di archiviazione non viene rilevato come SSD. Gli aggiornamenti mantengono la dimensione configurata.")
def test_kexec_prepares_unit_and_alias_not_hotkey(self):
self.assert_description(
"kexec", "custom", "kexec", "enable_kexec",
"Install kexec-tools and prepare a systemd unit plus the reboot-quick alias for fast reboots that skip BIOS/POST.",
"Installa kexec-tools e prepara un'unità systemd e l'alias reboot-quick per riavvii rapidi che saltano BIOS/POST.")
def test_amd_only_claims_idle_parameter_and_kvm_module_options(self):
self.assert_description(
"amd", "custom", "amd_fixes", "apply_amd_fixes",
"Apply idle=nomwait and KVM module options on AMD EPYC/Ryzen hosts.",
"Applica idle=nomwait e opzioni del modulo KVM sugli host AMD EPYC/Ryzen.")
def test_guest_agent_only_lists_supported_environments_and_skip(self):
self.assert_description(
"guestAgent", "custom", "guest_agent", "install_guest_agent",
"Detect QEMU/KVM, VMware or VirtualBox and install the matching guest agent; skip other environments.",
"Rileva QEMU/KVM, VMware o VirtualBox e installa il guest agent corrispondente; salta gli altri ambienti.")
def test_backup_describes_global_defaults_without_speed_guarantee(self):
self.assert_description(
"backupLimits", "custom", "vzdump_speed", "optimize_vzdump",
"Set global vzdump defaults to bwlimit: 0 and ionice: 5. This does not guarantee faster backups.",
"Imposta i valori predefiniti globali di vzdump su bwlimit: 0 e ionice: 5. Questo non garantisce backup più veloci.")
def test_log2ram_custom_choices_and_update_preservation(self):
self.assert_description(
"log2ramCustom", "custom", "log2ram", "configure_log2ram",
"Install Log2RAM with a chosen size, sync interval and optional autosync. Updates preserve the size and autosync choice.",
"Installa Log2RAM con dimensione e intervallo di sincronizzazione a scelta e autosync facoltativo. Gli aggiornamenti mantengono la dimensione e la scelta sull'autosync.")
if __name__ == "__main__":
unittest.main()
+6 -6
View File
@@ -2600,11 +2600,11 @@
"aptIpv4": "Force APT to use IPv4 to avoid stalls on hosts with broken IPv6 connectivity.",
"network": "Tune TCP buffers, somaxconn, IPv4 hardening and disable rp_filter on fw bridges (PVE 9 compatible).",
"bashrc": "Install and safely migrate the managed ProxMenux Bash prompt and aliases while preserving or selecting the short/full working-directory style.",
"log2ramAuto": "Install Log2RAM with size auto-tuned to host RAM (128M/256M/512M); SSD/M.2 detection skips on rotational disks.",
"log2ramAuto": "Fresh installs size Log2RAM from host RAM and ask for confirmation if storage is not detected as SSD. Updates preserve the configured size.",
"zfsAutotrim": "Enable ZFS autotrim on detected pools and record only pools changed by ProxMenux.",
"persistentNetwork": "Pin NIC names to MAC addresses via systemd .link files so kernel updates don't shuffle interface names.",
"kexec": "Install kexec-tools and add a Ctrl+Alt+K hotkey + systemd unit for fast reboots that skip BIOS/POST.",
"amd": "Detect AMD EPYC/Ryzen CPUs and apply microcode + IOMMU + KVM-specific kernel boot params.",
"kexec": "Install kexec-tools and prepare a systemd unit plus the reboot-quick alias for fast reboots that skip BIOS/POST.",
"amd": "Apply idle=nomwait and KVM module options on AMD EPYC/Ryzen hosts.",
"openVSwitch": "Install OpenVSwitch for software-defined networking inside VMs and containers.",
"tcpBbr": "Enable TCP Fast Open (clients + server) and BBR congestion control for better latency under load.",
"ceph": "Install Ceph (client + server packages) for distributed RBD/CephFS storage; PVE 8/9 aware repo selection.",
@@ -2612,17 +2612,17 @@
"zfsSnapshots": "Install zfs-auto-snapshot with cron schedules for hourly/daily/weekly/monthly snapshots.",
"rpc": "Disable rpcbind service/socket while preserving their exact previous systemd state for rollback.",
"pigz": "Replace gzip with pigz (parallel implementation) for faster vzdump backup compression.",
"guestAgent": "Detect the host's hypervisor (qemu/vmware/hyperv/virtualbox) and install the matching guest agent.",
"guestAgent": "Detect QEMU/KVM, VMware or VirtualBox and install the matching guest agent; skip other environments.",
"vfio": "Enable IOMMU and load VFIO modules to allow GPU/PCI passthrough into VMs.",
"motd": "Add the ProxMenux MOTD banner while preserving the original file contents or absence for rollback.",
"subscriptionCustom": "Patch the Proxmox web UI to suppress the \"no valid subscription\" dialog (PVE 8 + 9 variants supported).",
"zram": "Enable an opt-in ZRAM swap profile sized to one tenth of RAM.",
"backupLimits": "Lift vzdump bandwidth/IO limits so backups run at the storage's real throughput.",
"backupLimits": "Set global vzdump defaults to bwlimit: 0 and ionice: 5. This does not guarantee faster backups.",
"ovh": "Detect OVH-rented hardware via whois lookup and install OVH Real-Time Monitoring (no-op on non-OVH).",
"ha": "Enable the Proxmox HA stack (pve-ha-lrm, pve-ha-crm, corosync) for cluster failover.",
"fastfetch": "Install Fastfetch system summary tool with the ProxMenux logo + status block as the SSH login banner.",
"figurine": "Install Figurine (ASCII-art hostname banner) and wire it into the SSH login flow.",
"log2ramCustom": "Install Log2RAM with user-chosen RAM size; prompts for size and SSD/M.2 awareness before applying.",
"log2ramCustom": "Install Log2RAM with a chosen size, sync interval and optional autosync. Updates preserve the size and autosync choice.",
"systemUtilities": "Install selected system utilities and track only packages that were newly added by ProxMenux."
},
"toolNames": {
+6 -6
View File
@@ -2473,11 +2473,11 @@
"aptIpv4": "Forza APT a usare IPv4 per evitare blocchi sugli host con connettività IPv6 non funzionante.",
"network": "Regola i buffer TCP, somaxconn, l'hardening IPv4 e disabilita rp_filter sui bridge fw (compatibile con PVE 9).",
"bashrc": "Installa e migra in modo sicuro il prompt Bash e gli alias gestiti da ProxMenux, mantenendo o selezionando lo stile breve/completo della directory di lavoro.",
"log2ramAuto": "Installa Log2RAM con dimensione regolata automaticamente in base alla RAM dell'host (128M/256M/512M); il rilevamento SSD/M.2 lo salta sui dischi rotazionali.",
"log2ramAuto": "Alla prima installazione, dimensiona Log2RAM in base alla RAM dell'host e chiede conferma se il dispositivo di archiviazione non viene rilevato come SSD. Gli aggiornamenti mantengono la dimensione configurata.",
"zfsAutotrim": "Abilita l'autotrim ZFS sui pool rilevati e registra solo i pool modificati da ProxMenux.",
"persistentNetwork": "Associa i nomi delle NIC agli indirizzi MAC tramite file .link di systemd, così gli aggiornamenti del kernel non cambiano i nomi delle interfacce.",
"kexec": "Installa kexec-tools e aggiunge una scorciatoia Ctrl+Alt+K + un'unità systemd per riavvii rapidi che saltano BIOS/POST.",
"amd": "Rileva le CPU AMD EPYC/Ryzen e applica microcode + IOMMU + parametri di avvio del kernel specifici per KVM.",
"kexec": "Installa kexec-tools e prepara un'unità systemd e l'alias reboot-quick per riavvii rapidi che saltano BIOS/POST.",
"amd": "Applica idle=nomwait e opzioni del modulo KVM sugli host AMD EPYC/Ryzen.",
"openVSwitch": "Installa OpenVSwitch per il networking software-defined all'interno di VM e container.",
"tcpBbr": "Abilita TCP Fast Open (client + server) e il controllo della congestione BBR per una latenza migliore sotto carico.",
"ceph": "Installa Ceph (pacchetti client + server) per lo storage distribuito RBD/CephFS; selezione del repository in base a PVE 8/9.",
@@ -2485,17 +2485,17 @@
"zfsSnapshots": "Installa zfs-auto-snapshot con pianificazioni cron per snapshot orari/giornalieri/settimanali/mensili.",
"rpc": "Disabilita il servizio/socket rpcbind conservandone l'esatto stato systemd precedente per il rollback.",
"pigz": "Sostituisce gzip con pigz (implementazione parallela) per una compressione più rapida dei backup vzdump.",
"guestAgent": "Rileva l'hypervisor dell'host (qemu/vmware/hyperv/virtualbox) e installa il guest agent corrispondente.",
"guestAgent": "Rileva QEMU/KVM, VMware o VirtualBox e installa il guest agent corrispondente; salta gli altri ambienti.",
"vfio": "Abilita IOMMU e carica i moduli VFIO per consentire il passthrough di GPU/PCI nelle VM.",
"motd": "Aggiunge il banner MOTD di ProxMenux conservando il contenuto originale del file, o la sua assenza, per il rollback.",
"subscriptionCustom": "Applica una patch all'interfaccia web di Proxmox per eliminare la finestra \"no valid subscription\" (supporta le varianti PVE 8 + 9).",
"zram": "Abilita un profilo di scambio ZRAM facoltativo dimensionato a un decimo della RAM.",
"backupLimits": "Rimuove i limiti di banda/IO di vzdump affinché i backup vengano eseguiti alla velocità reale dello storage.",
"backupLimits": "Imposta i valori predefiniti globali di vzdump su bwlimit: 0 e ionice: 5. Questo non garantisce backup più veloci.",
"ovh": "Rileva l'hardware noleggiato da OVH tramite whois e installa OVH Real-Time Monitoring (nessuna azione su sistemi non OVH).",
"ha": "Abilita lo stack HA di Proxmox (pve-ha-lrm, pve-ha-crm, corosync) per il failover del cluster.",
"fastfetch": "Installa lo strumento di riepilogo del sistema Fastfetch con il logo ProxMenux + un blocco di stato come banner di accesso SSH.",
"figurine": "Installa Figurine (banner del nome host in ASCII art) e lo integra nel flusso di accesso SSH.",
"log2ramCustom": "Installa Log2RAM con dimensione della RAM scelta dall'utente; chiede la dimensione e il rilevamento SSD/M.2 prima di applicarlo.",
"log2ramCustom": "Installa Log2RAM con dimensione e intervallo di sincronizzazione a scelta e autosync facoltativo. Gli aggiornamenti mantengono la dimensione e la scelta sull'autosync.",
"systemUtilities": "Installa le utilità di sistema selezionate e tiene traccia solo dei pacchetti aggiunti da ProxMenux."
},
"toolNames": {
+1 -1
View File
@@ -1043,7 +1043,7 @@ install_log2ram_auto() {
local existing_log2ram_bin=""
pmx_journal_context "install_log2ram_auto" "$FUNC_VERSION"
# description: Install Log2RAM with size auto-tuned to host RAM (128M/256M/512M); SSD/M.2 detection skips on rotational disks.
# description: Fresh installs size Log2RAM from host RAM and ask for confirmation if storage is not detected as SSD. Updates preserve the configured size.
existing_log2ram_bin="$(command -v log2ram 2>/dev/null || true)"
if [[ -f /etc/log2ram.conf || -n "$existing_log2ram_bin" \
@@ -187,7 +187,7 @@ $(translate "Do you want to continue anyway?")" 13 70
enable_kexec() {
local FUNC_VERSION="1.1"
pmx_journal_context "enable_kexec" "$FUNC_VERSION"
# description: Install kexec-tools and add a Ctrl+Alt+K hotkey + systemd unit for fast reboots that skip BIOS/POST.
# description: Install kexec-tools and prepare a systemd unit plus the reboot-quick alias for fast reboots that skip BIOS/POST.
msg_info2 "$(translate "Configuring kexec for quick reboots...")"
NECESSARY_REBOOT=1
@@ -695,7 +695,7 @@ configure_time_sync() {
apply_amd_fixes() {
local FUNC_VERSION="1.0"
pmx_journal_context "apply_amd_fixes" "$FUNC_VERSION"
# description: Detect AMD EPYC/Ryzen CPUs and apply microcode + IOMMU + KVM-specific kernel boot params.
# description: Apply idle=nomwait and KVM module options on AMD EPYC/Ryzen hosts.
msg_info2 "$(translate "Detecting AMD CPU and applying fixes if necessary...")"
NECESSARY_REBOOT=1
@@ -1812,7 +1812,7 @@ EOF
install_guest_agent() {
local FUNC_VERSION="1.0"
# description: Detect the host's hypervisor (qemu/vmware/hyperv/virtualbox) and install the matching guest agent.
# description: Detect QEMU/KVM, VMware or VirtualBox and install the matching guest agent; skip other environments.
msg_info2 "$(translate "Detecting virtualization and installing guest agent...")"
NECESSARY_REBOOT=1
@@ -2669,7 +2669,7 @@ configure_zram_swap() {
optimize_vzdump() {
local FUNC_VERSION="1.0"
pmx_journal_context "optimize_vzdump" "$FUNC_VERSION"
# description: Lift vzdump bandwidth/IO limits so backups run at the storage's real throughput.
# description: Set global vzdump defaults to bwlimit: 0 and ionice: 5. This does not guarantee faster backups.
msg_info2 "$(translate "Optimizing vzdump backup speed...")"
local vzdump_conf="/etc/vzdump.conf"
@@ -3350,7 +3350,7 @@ configure_log2ram() {
local FUNC_VERSION="1.8"
local existing_log2ram_bin=""
pmx_journal_context "configure_log2ram" "$FUNC_VERSION"
# description: Install Log2RAM with user-chosen RAM size; prompts for size and SSD/M.2 awareness before applying.
# description: Install Log2RAM with a chosen size, sync interval and optional autosync. Updates preserve the size and autosync choice.
existing_log2ram_bin="$(command -v log2ram 2>/dev/null || true)"
if [[ -f /etc/log2ram.conf || -n "$existing_log2ram_bin" \