From 7145fb3fa48534acdd71731e8373895e3a3bdc43 Mon Sep 17 00:00:00 2001 From: kronecker <2026692687@qq.com> Date: Mon, 12 Oct 2026 20:50:11 +0800 Subject: [PATCH] fix(delivery): derive installation reserve from verified inventory --- deploy/release/README.md | 4 ++++ scripts/release/__main__.py | 4 ++-- scripts/release/install.py | 2 +- scripts/release/preflight.py | 23 +++++++++++++++++++---- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/deploy/release/README.md b/deploy/release/README.md index efc8e07..8586f01 100644 --- a/deploy/release/README.md +++ b/deploy/release/README.md @@ -10,6 +10,10 @@ The target has at least 4 GiB RAM and 12 GiB installation/data capacity. Judging and problem-import concurrency remain one each. Configured service and execution budgets, effective kernel limits and free capacity must also pass preflight; total RAM alone does not prove enough capacity or verified execution isolation. +Preflight requires at least 12 GiB of filesystem capacity and derives required +free bytes from the verified application, protected runtime-image copies and +separate GNU legacy copy, plus 4 GiB of mutable-data/operation reserve. It does +not assume that a nominal 12-GiB filesystem exposes 12 GiB free after metadata. Release payloads are directory bundles with a bounded manifest: at most 50,000 single-link regular files, 256 MiB per file, 4 GiB total and 8 MiB metadata. The diff --git a/scripts/release/__main__.py b/scripts/release/__main__.py index 43546c0..5a3010d 100644 --- a/scripts/release/__main__.py +++ b/scripts/release/__main__.py @@ -61,8 +61,8 @@ def main() -> None: elif args.command == "build": result = build.build(args.repo, args.inputs, args.output, args.work) elif args.command == "preflight": - manifest.verify(args.bundle, args.manifest_sha256) - result = preflight.observe(args.bundle / "payload") + data = manifest.verify(args.bundle, args.manifest_sha256) + result = preflight.observe(args.bundle / "payload", bundle_data=data) print(json.dumps(result, sort_keys=True)) if not result["eligible"]: parser.exit(1) diff --git a/scripts/release/install.py b/scripts/release/install.py index 5df18fd..7b60a21 100644 --- a/scripts/release/install.py +++ b/scripts/release/install.py @@ -182,7 +182,7 @@ def files(bundle: Path, expected: str) -> dict: raise DeliveryError("installer requires root") preflight.protected(bundle, directory=True) data = manifest.verify(bundle, expected) - observation = preflight.observe(bundle / "payload") + observation = preflight.observe(bundle / "payload", bundle_data=data) if not observation["eligible"]: raise DeliveryError("installation preflight refused: " + "; ".join(observation["errors"])) # Per-entry ownership matters even when the inventory is identical. diff --git a/scripts/release/preflight.py b/scripts/release/preflight.py index caddc74..6c6900e 100644 --- a/scripts/release/preflight.py +++ b/scripts/release/preflight.py @@ -120,7 +120,7 @@ def identity_conflicts() -> list[str]: return errors -def observe(payload: Path, *, require_root: bool = True) -> dict: +def observe(payload: Path, *, bundle_data: dict, require_root: bool = True) -> dict: errors = [] observations = {} if platform.system() != "Linux" or platform.machine() != "x86_64": @@ -183,9 +183,24 @@ def observe(payload: Path, *, require_root: bool = True) -> dict: # A 4-GiB physical guest exposes slightly less MemTotal after kernel reserve. if memory["MemTotal"] < 15 * GIB // 4 or memory["MemAvailable"] < 5 * GIB // 2: errors.append("physical/available memory below installation capacity") - observations["disk_free_bytes"] = shutil.disk_usage("/var/lib").free - if observations["disk_free_bytes"] < 12 * GIB: - errors.append("installation/data disk reserve below 12 GiB") + disk = shutil.disk_usage("/var/lib") + protected_images = sum( + entry["bytes"] for entry in bundle_data["files"] if entry["path"].startswith("images/") + ) + legacy_image = sum( + entry["bytes"] for entry in bundle_data["files"] if entry["path"].startswith("images/gnu/") + ) + # Count a full new application + protected image copies + legacy image and + # four GiB of mutable data/operation reserve. Filesystem metadata makes a + # 12-GiB device expose less than 12 GiB free even when it is empty. + required = bundle_data["payload_bytes"] + protected_images + legacy_image + 4 * GIB + observations["disk"] = { + "total_bytes": disk.total, + "free_bytes": disk.free, + "required_free_bytes": required, + } + if disk.total < 12 * GIB or disk.free < required: + errors.append("installation/data capacity or inventory-derived free reserve insufficient") for name in ("libc.so.6", "ld-linux-x86-64.so.2"): system = Path("/usr/lib") / name try: