diff --git a/.github/scripts/tests/test_optimization_description_wording.py b/.github/scripts/tests/test_optimization_description_wording.py new file mode 100644 index 00000000..894404aa --- /dev/null +++ b/.github/scripts/tests/test_optimization_description_wording.py @@ -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() diff --git a/AppImage/messages/en/common.json b/AppImage/messages/en/common.json index 65a42efe..acba994a 100644 --- a/AppImage/messages/en/common.json +++ b/AppImage/messages/en/common.json @@ -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": { diff --git a/AppImage/messages/it/common.json b/AppImage/messages/it/common.json index 1ce25c40..378ca10b 100644 --- a/AppImage/messages/it/common.json +++ b/AppImage/messages/it/common.json @@ -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": { diff --git a/scripts/post_install/auto_post_install.sh b/scripts/post_install/auto_post_install.sh index 1602ad39..40d0dd96 100644 --- a/scripts/post_install/auto_post_install.sh +++ b/scripts/post_install/auto_post_install.sh @@ -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" \ diff --git a/scripts/post_install/customizable_post_install.sh b/scripts/post_install/customizable_post_install.sh index 2ce24dc3..0d22f884 100644 --- a/scripts/post_install/customizable_post_install.sh +++ b/scripts/post_install/customizable_post_install.sh @@ -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" \