bootstrapper/_run.sh
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env sh
#
# Shared dispatcher used by start.sh, stop.sh, and
# bootstrapper/generate_supabase_keys.sh.
#
# Picks `uv` when available (fast venv resolution, locked deps), falls
# back to system `python3` otherwise. Usage:
#
# sh bootstrapper/_run.sh <script.py> [args...]
#
# The <script.py> path is interpreted relative to this file's directory
# (i.e. bootstrapper/), so callers can pass plain `start.py`, `stop.py`,
# or `generate_supabase_keys.py`.
set -e
if [ $# -lt 1 ]; then
echo "Usage: $0 <python-script-relative-to-bootstrapper> [args...]" >&2
exit 64
fi
SCRIPT_REL="$1"
shift
# Resolve the bootstrapper directory (this script's parent).
# CDPATH= keeps cd from echoing a path into the captured value.
SELF_DIR="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)"
if command -v uv >/dev/null 2>&1; then
# Banner to stderr: stdout must stay clean so `<script> --format json`
# (e.g. `doctor --format json`) is directly parseable without a shim (#650).
echo "📦 Using uv for dependency management..." >&2
# --no-dev: the launcher needs only the runtime dependencies, the same set
# the system-Python branch installs. The dev group (test and docs
# toolchain) stays opt-in: `uv sync --group dev`, as CI does.
exec uv run --no-dev --directory "$SELF_DIR" "$SCRIPT_REL" "$@"
elif command -v python3 >/dev/null 2>&1; then
echo "📦 Using system Python (install uv for better dependency management)..." >&2
BOOTSTRAPPER_VENV="${ATLAS_BOOTSTRAPPER_VENV:-$SELF_DIR/.venv}"
VENV_PYTHON="$BOOTSTRAPPER_VENV/bin/python"
BOOTSTRAPPER_LOCK="$SELF_DIR/requirements-locked.txt"
PYTHON_SUPPORTED='import sys; raise SystemExit(sys.version_info < (3, 11))'
REQUIRED_IMPORTS="import click, jsonschema, PIL, requests, rich, textual, textual_image, urllib3, yaml"
# Fingerprint the DEPENDENCY DECLARATION, not the module list. The guard
# used to be presence-only: if those modules imported at ANY version, the
# install was skipped forever. So the security floors declared in
# pyproject.toml (click, urllib3, pillow) were never applied to an
# existing venv, and the two dispatch branches produced materially
# different dependency states from identical inputs (the uv branch
# re-resolves against uv.lock every run). A new dependency added to
# pyproject.toml but not to REQUIRED_IMPORTS was likewise never installed.
DEPS_STAMP="$BOOTSTRAPPER_VENV/.atlas-deps-stamp"
if command -v shasum >/dev/null 2>&1; then
WANT_STAMP="$(shasum -a 256 "$SELF_DIR/pyproject.toml" "$BOOTSTRAPPER_LOCK" | shasum -a 256 | cut -d' ' -f1)"
elif command -v sha256sum >/dev/null 2>&1; then
WANT_STAMP="$(sha256sum "$SELF_DIR/pyproject.toml" "$BOOTSTRAPPER_LOCK" | sha256sum | cut -d' ' -f1)"
elif command -v cksum >/dev/null 2>&1; then
# POSIX fallback: unlike the old size-only stamp, CRC+length changes
# for same-size edits to either locked dependency input.
WANT_STAMP="$(cksum "$SELF_DIR/pyproject.toml" "$BOOTSTRAPPER_LOCK" | cksum | awk '{print $1 "-" $2}')"
else
echo "❌ No checksum tool is available to validate the locked dependency graph." >&2
exit 1
fi
HAVE_STAMP=""
if [ -f "$DEPS_STAMP" ]; then
HAVE_STAMP="$(cat "$DEPS_STAMP" 2>/dev/null || true)"
fi
# Does the venv meet the Python floor and satisfy every import? The stamp
# also decides whether a locked refresh is required; failures always close.
VENV_USABLE=0
if [ -x "$VENV_PYTHON" ] \
&& "$VENV_PYTHON" -c "$PYTHON_SUPPORTED" >/dev/null 2>&1 \
&& "$VENV_PYTHON" -c "$REQUIRED_IMPORTS" >/dev/null 2>&1; then
VENV_USABLE=1
fi
if [ "$VENV_USABLE" = "0" ] || [ "$HAVE_STAMP" != "$WANT_STAMP" ]; then
# Create or REPAIR whenever the venv cannot satisfy the imports —
# absent, or present but broken. `python3 -m venv` on an existing
# directory re-runs ensurepip, which is what restores a missing pip in
# a `uv venv`-created environment; gating this on absence alone turned
# "pip-less venv plus a newly added dependency" from a self-repair into
# a hard launch failure.
#
# A pure stamp refresh (venv usable, declaration changed) skips this
# entirely and so does NOT re-run the interpreter gate: that venv
# already has a supported Python, and gating on the SYSTEM python3
# would refuse to apply a security floor on a host whose system
# interpreter has since aged out.
if [ "$VENV_USABLE" = "0" ]; then
if [ -x "$VENV_PYTHON" ]; then
echo "Repairing Atlas bootstrapper environment at $BOOTSTRAPPER_VENV..." >&2
else
echo "Creating Atlas bootstrapper environment at $BOOTSTRAPPER_VENV..." >&2
fi
if ! python3 -c "$PYTHON_SUPPORTED"; then
echo "❌ Atlas requires Python 3.11 or newer." >&2
exit 1
fi
if ! python3 -m venv "$BOOTSTRAPPER_VENV"; then
echo "❌ Could not create the Atlas bootstrapper virtual environment." >&2
echo " Install the Python venv module or install uv, then retry." >&2
exit 1
fi
# venv creation can retain an older bin/python symlink. Never
# install into or launch an interpreter below the supported floor.
if ! "$VENV_PYTHON" -c "$PYTHON_SUPPORTED"; then
echo "❌ Atlas requires Python 3.11 or newer." >&2
echo " The existing environment still has an unsupported interpreter." >&2
echo " Delete $BOOTSTRAPPER_VENV and rerun this command to recreate it." >&2
echo " Or create a fresh environment and select it with ATLAS_BOOTSTRAPPER_VENV," >&2
echo " or install uv, then retry." >&2
exit 1
fi
else
echo "Refreshing Atlas bootstrapper dependencies (declaration changed)..." >&2
fi
# The scripts run from this directory, so install only the locked
# runtime closure, not the bootstrapper itself (no build-backend fetch).
if "$VENV_PYTHON" -m pip install --disable-pip-version-check --upgrade --constraint "$BOOTSTRAPPER_LOCK" --requirement "$BOOTSTRAPPER_LOCK"; then
# Stamp only AFTER a successful install, so a failed run retries.
printf '%s\n' "$WANT_STAMP" > "$DEPS_STAMP" 2>/dev/null || true
else
echo "❌ Could not install Atlas bootstrapper dependencies from the locked graph." >&2
echo " Install uv, or run: $VENV_PYTHON -m ensurepip --upgrade" >&2
exit 1
fi
fi
exec "$VENV_PYTHON" "$SELF_DIR/$SCRIPT_REL" "$@"
else
echo "❌ Neither 'uv' nor 'python3' was found on PATH." >&2
echo "" >&2
echo " Install one of:" >&2
echo " • uv (recommended): https://github.com/astral-sh/uv" >&2
echo " • Python 3.11+: https://www.python.org/downloads/" >&2
echo "" >&2
echo " Then re-run the script that invoked this dispatcher." >&2
exit 127
fi