sand-boxer/scripts/build-rein-runtime.py
tegwick d69827aaa2 feat: pin bwrap rein runtimes and isolate private state
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0726e-5232-73f2-aaca-2c05ceb62efb
2026-09-05 20:36:11 +02:00

67 lines
2.8 KiB
Python

"""Build a standalone rein-aharness runtime for the fixed bwrap mount path.
Only package installation occurs here. No credentials or model calls are used.
Run with the sand-boxer Python environment; uv must be available on PATH.
"""
from __future__ import annotations
import argparse
import json
import subprocess
from pathlib import Path
from sandboxer.extensions.runtime import RUNTIME_MOUNT, runtime_digest
def checked(command: list[str]) -> str:
result = subprocess.run(command, capture_output=True, text=True, timeout=300)
if result.returncode:
raise RuntimeError(f"runtime build command failed: {Path(command[0]).name}")
return result.stdout.strip()
def build(output: Path, rein_source: Path, llm_source: Path) -> dict:
revisions = {}
for name, source in (("rein-aharness", rein_source), ("llm-connect", llm_source)):
if checked(["git", "-C", str(source), "status", "--porcelain"]):
raise ValueError(f"{name} source must be committed before building")
revisions[name] = checked(["git", "-C", str(source), "rev-parse", "HEAD"])
output.mkdir(parents=True, exist_ok=False)
checked(["/usr/bin/python3", "-m", "venv", "--copies", "--without-pip", str(output)])
checked(["uv", "pip", "install", "--python", str(output / "bin/python3"),
str(rein_source), str(llm_source)])
# Console entrypoints must reference the in-sandbox mount, not the build host.
for path in (output / "bin").iterdir():
if not path.is_file() or path.is_symlink():
continue
with path.open("rb") as stream:
first_line = stream.readline(4096)
if first_line.startswith(f"#!{output}/bin/python".encode()):
body = path.read_bytes().partition(b"\n")[2]
path.write_bytes(f"#!{RUNTIME_MOUNT}/bin/python3\n".encode() + body)
metadata = json.loads(checked([
str(output / "bin/python3"), "-c",
"import importlib.metadata,json,platform; "
"print(json.dumps({'python':platform.python_version(),'packages':"
"{d.metadata['Name']:d.version for d in importlib.metadata.distributions()}}))",
]))
metadata["source_revisions"] = revisions
(output / "build-info.json").write_text(json.dumps(metadata, indent=2) + "\n")
return {"runtime": {"path": str(output), "sha256": runtime_digest(output)},
"build": metadata}
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--rein-source", type=Path, required=True)
parser.add_argument("--llm-source", type=Path, required=True)
args = parser.parse_args()
result = build(args.output.resolve(), args.rein_source.resolve(), args.llm_source.resolve())
print(json.dumps(result, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())