Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions runs/atreboot.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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!"
Expand Down
133 changes: 133 additions & 0 deletions runs/install_packages.py
Original file line number Diff line number Diff line change
@@ -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")
Comment thread
LKuemmel marked this conversation as resolved.
Outdated
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()
Comment thread
LKuemmel marked this conversation as resolved.
Outdated
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())