#!/usr/bin/env bash # Pre-bake the Python packages the agent Bash sandbox needs. # # These are installed into the SYSTEM python's site-packages, because that is # what bwrap read-only bind-mounts (/usr) into every Bash sandbox. A `--user` # install (~/.local) is NOT visible inside the sandbox (/home is not mounted), # so it must be a system install. # # Idempotent and safe to re-run. Non-fatal by default (prints a warning and # exits 0 so it does not break a build); pass --strict to fail on error. # # See docs/operations/bash-sandbox-provisioning.md. set -uo pipefail PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)" REQ="$PROJECT_DIR/runtime/python-requirements.txt" STRICT=0 for arg in "$@"; do case "$arg" in --strict) STRICT=1 ;; -h|--help) echo "Usage: $0 [--strict]"; exit 0 ;; *) echo "Unknown option: $arg" >&2; exit 2 ;; esac done soft_fail() { echo "[prebake-python] $1" >&2 if [ "$STRICT" -eq 1 ]; then exit 1; fi echo "[prebake-python] continuing (non-strict); Bash-tool python packages may be unavailable." >&2 exit 0 } [ -f "$REQ" ] || soft_fail "requirements file not found: $REQ" if command -v pip3 >/dev/null 2>&1; then PIP="pip3" elif command -v pip >/dev/null 2>&1; then PIP="pip" elif command -v python3 >/dev/null 2>&1; then PIP="python3 -m pip" else soft_fail "no pip / python3 found — skipping python pre-bake"; fi echo "[prebake-python] installing $REQ into the system python via: $PIP" # PEP668 distros (Debian/Ubuntu/alpine) mark the system python "externally # managed"; --break-system-packages is required there. Older pip rejects that # flag, so retry without it. if $PIP install --break-system-packages -r "$REQ"; then : elif $PIP install -r "$REQ"; then : else soft_fail "pip install failed. If this is a permission error, retry as: sudo $PIP install --break-system-packages -r runtime/python-requirements.txt" fi echo "[prebake-python] done. Verifying imports..." if command -v python3 >/dev/null 2>&1; then python3 - <<'PYEOF' || echo "[prebake-python] WARNING: some packages failed to import (see above)." >&2 import importlib, sys # import-name : pip-name (for the warning message) mods = { "pypdf": "pypdf", "fitz": "pymupdf", "pdfplumber": "pdfplumber", "docx": "python-docx", "pptx": "python-pptx", "openpyxl": "openpyxl", "xlsxwriter": "xlsxwriter", "xlrd": "xlrd", "odf": "odfpy", "striprtf": "striprtf", "bs4": "beautifulsoup4", "lxml": "lxml", "markdownify": "markdownify", "markdown": "markdown", "numpy": "numpy", "pandas": "pandas", "tabulate": "tabulate", "dateutil": "python-dateutil", "matplotlib": "matplotlib", "PIL": "Pillow", "charset_normalizer": "charset-normalizer", "yaml": "PyYAML", } missing = [] for mod, pkg in mods.items(): try: importlib.import_module(mod) except Exception: missing.append(f"{pkg} (import {mod})") if missing: print("[prebake-python] MISSING: " + ", ".join(missing), file=sys.stderr) sys.exit(1) print(f"[prebake-python] all {len(mods)} packages import OK.") PYEOF fi