From bb66f8762e8dcfc997d9c8ef80afdbe3b92a53b9 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 28 Aug 2026 09:54:55 +0200 Subject: [PATCH 1/5] repair corrupt packages --- runs/atreboot.sh | 4 +- runs/install_packages.py | 133 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 runs/install_packages.py diff --git a/runs/atreboot.sh b/runs/atreboot.sh index 8166249e82..b01a7a2194 100755 --- a/runs/atreboot.sh +++ b/runs/atreboot.sh @@ -316,8 +316,8 @@ chmod 666 "$LOGFILE" # check for python dependencies if ((hasInet == 1)); then - echo "install required python packages with 'pip3'..." - if pip3 install --only-binary :all: -r "${OPENWBBASEDIR}/requirements.txt"; then + echo "install required python packages with 'install_packages.py'..." + if python3 "${OPENWBBASEDIR}/runs/install_packages.py"; then echo "done" else echo "failed!" diff --git a/runs/install_packages.py b/runs/install_packages.py new file mode 100644 index 0000000000..0dd40fb14d --- /dev/null +++ b/runs/install_packages.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 + +import base64 +import csv +import hashlib +import importlib.metadata +import subprocess +import sys +from pathlib import Path + + +REQUIREMENTS = Path(__file__).resolve().parents[1] / "requirements.txt" +MAIN_LOG = Path(__file__).resolve().parents[1] / "ramdisk" / "main.log" + + +def write_log(message: str = "") -> None: + MAIN_LOG.parent.mkdir(parents=True, exist_ok=True) + with MAIN_LOG.open("a", encoding="utf-8") as log_file: + log_file.write(f"{message}\n") + + +def check_packages() -> dict[str, list[str]]: + """Check installed package files against their RECORD hashes.""" + broken = {} + + for dist in importlib.metadata.distributions(): + package = dist._path.name.removesuffix(".dist-info") + record = dist.read_text("RECORD") + + if not record: + continue + + for row in csv.reader(record.splitlines()): + if len(row) < 3 or not row[1] or not row[2]: + continue + + path = Path(dist.locate_file(row[0])) + + if not path.is_file(): + broken.setdefault(package, []).append(str(path)) + continue + + algorithm, expected_hash = row[1].split("=", 1) + + if algorithm != "sha256": + continue + + digest = hashlib.sha256(path.read_bytes()).digest() + actual_hash = ( + base64.urlsafe_b64encode(digest) + .rstrip(b"=") + .decode() + ) + + if actual_hash != expected_hash: + broken.setdefault(package, []).append(str(path)) + + return broken + + +def install_requirements() -> None: + subprocess.run( + [ + sys.executable, + "-m", + "pip", + "install", + "--only-binary", + ":all:", + "-r", + str(REQUIREMENTS), + ], + check=True, + ) + + +def reinstall(packages: set[str]) -> None: + if not packages: + return + + subprocess.run( + [ + sys.executable, + "-m", + "pip", + "install", + "--only-binary", + ":all:", + "--force-reinstall", + *sorted(packages), + ], + check=True, + ) + + +def print_broken(broken: dict[str, list[str]]) -> None: + for package, files in sorted(broken.items()): + write_log(f"CORRUPT: {package}") + for path in files: + write_log(f" {path}") + + +def main() -> int: + write_log("Installing Python packages...") + install_requirements() + + write_log("Checking package integrity...") + broken = check_packages() + + if not broken: + write_log("All packages are intact.") + return 0 + + write_log("\nCorrupted packages:") + print_broken(broken) + + write_log("\nReinstalling corrupted packages...") + reinstall(set(broken)) + + write_log("\nChecking package integrity after reinstall...") + broken = check_packages() + + if broken: + write_log("\nERROR: The following packages are still corrupted:") + print_broken(broken) + return 1 + + write_log("All packages are intact after reinstall.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 8bf3ccf44a710135d2bcf3faca0044616db97fcf Mon Sep 17 00:00:00 2001 From: LKuemmel <76958050+LKuemmel@users.noreply.github.com> Date: Fri, 28 Aug 2026 11:36:08 +0200 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- runs/install_packages.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/runs/install_packages.py b/runs/install_packages.py index 0dd40fb14d..ad34c5c525 100644 --- a/runs/install_packages.py +++ b/runs/install_packages.py @@ -24,7 +24,7 @@ def check_packages() -> dict[str, list[str]]: broken = {} for dist in importlib.metadata.distributions(): - package = dist._path.name.removesuffix(".dist-info") + package = f"{dist.metadata['Name']}=={dist.version}" record = dist.read_text("RECORD") if not record: @@ -40,12 +40,12 @@ def check_packages() -> dict[str, list[str]]: broken.setdefault(package, []).append(str(path)) continue - algorithm, expected_hash = row[1].split("=", 1) - - if algorithm != "sha256": + try: + algorithm, expected_hash = row[1].split("=", 1) + digest = hashlib.new(algorithm, path.read_bytes()).digest() + except ValueError: + broken.setdefault(package, []).append(str(path)) continue - - digest = hashlib.sha256(path.read_bytes()).digest() actual_hash = ( base64.urlsafe_b64encode(digest) .rstrip(b"=") From f3c827c19b7755e6da495cf7a0aa24d57d10c613 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 28 Aug 2026 12:48:54 +0200 Subject: [PATCH 3/5] use pip3 --- runs/install_packages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runs/install_packages.py b/runs/install_packages.py index ad34c5c525..770e7034e2 100644 --- a/runs/install_packages.py +++ b/runs/install_packages.py @@ -63,7 +63,7 @@ def install_requirements() -> None: [ sys.executable, "-m", - "pip", + "pip3", "install", "--only-binary", ":all:", @@ -82,7 +82,7 @@ def reinstall(packages: set[str]) -> None: [ sys.executable, "-m", - "pip", + "pip3", "install", "--only-binary", ":all:", From 8f6f4f4214610b787c228b7286cb7cd7e5f8f2ae Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 28 Aug 2026 12:50:23 +0200 Subject: [PATCH 4/5] use print --- runs/install_packages.py | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/runs/install_packages.py b/runs/install_packages.py index 770e7034e2..82688af272 100644 --- a/runs/install_packages.py +++ b/runs/install_packages.py @@ -10,13 +10,6 @@ REQUIREMENTS = Path(__file__).resolve().parents[1] / "requirements.txt" -MAIN_LOG = Path(__file__).resolve().parents[1] / "ramdisk" / "main.log" - - -def write_log(message: str = "") -> None: - MAIN_LOG.parent.mkdir(parents=True, exist_ok=True) - with MAIN_LOG.open("a", encoding="utf-8") as log_file: - log_file.write(f"{message}\n") def check_packages() -> dict[str, list[str]]: @@ -95,37 +88,37 @@ def reinstall(packages: set[str]) -> None: def print_broken(broken: dict[str, list[str]]) -> None: for package, files in sorted(broken.items()): - write_log(f"CORRUPT: {package}") + print(f"CORRUPT: {package}") for path in files: - write_log(f" {path}") + print(f" {path}") def main() -> int: - write_log("Installing Python packages...") + print("Installing Python packages...") install_requirements() - write_log("Checking package integrity...") + print("Checking package integrity...") broken = check_packages() if not broken: - write_log("All packages are intact.") + print("All packages are intact.") return 0 - write_log("\nCorrupted packages:") + print("\nCorrupted packages:") print_broken(broken) - write_log("\nReinstalling corrupted packages...") + print("\nReinstalling corrupted packages...") reinstall(set(broken)) - write_log("\nChecking package integrity after reinstall...") + print("\nChecking package integrity after reinstall...") broken = check_packages() if broken: - write_log("\nERROR: The following packages are still corrupted:") + print("\nERROR: The following packages are still corrupted:") print_broken(broken) return 1 - write_log("All packages are intact after reinstall.") + print("All packages are intact after reinstall.") return 0 From abe39788a46c2c11a46c661608619961140f19bb Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Fri, 28 Aug 2026 12:54:16 +0200 Subject: [PATCH 5/5] use regular pip3 --- runs/install_packages.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/runs/install_packages.py b/runs/install_packages.py index 82688af272..6879dd7877 100644 --- a/runs/install_packages.py +++ b/runs/install_packages.py @@ -54,8 +54,6 @@ def check_packages() -> dict[str, list[str]]: def install_requirements() -> None: subprocess.run( [ - sys.executable, - "-m", "pip3", "install", "--only-binary", @@ -73,8 +71,6 @@ def reinstall(packages: set[str]) -> None: subprocess.run( [ - sys.executable, - "-m", "pip3", "install", "--only-binary",