#!/usr/bin/env bash # Install the pinned Lean 4 + Mathlib + REPL toolchain under /apps (Track L / P1). # # WHY this exists as a script and not as Python: the toolchain is an EXTERNAL BINARY # (see docs/DEPENDENCY_POLICY.md, "Lean toolchain"). It is installed once, outside the # Python environment, and `align/verify/lean` reaches it only through subprocess. # # Idempotent: every step is guarded by a marker/exists check, so re-running after a # partial failure resumes instead of redoing a 5 GB download. Every network or build # step runs under `timeout`, so a stalled mirror cannot hang the shell. # # Host rules (/apps/CLAUDE.md, binding): nothing is ever written to `/` or `/tmp`. # elan, the Mathlib cache, git temp files and XDG caches are all redirected below. # The preflight refuses on <45GB MemAvailable, load >= 2x cores, disk queue >= 64, or # less than 40GB free on /apps. A Mathlib cache decompress is I/O heavy; it is not a # GPU job so it does not take the GPU heavy-job lock. # # THE PIN (see docs/LEAN_VERIFIER.md section "Pin"): # Goedel-Prover-V2 follows DeepSeek-Prover-V1.5: its `mathlib4` git submodule is # xinhjBrant/mathlib4 @ 2f65ba7f1a9144b20c8e7358513548e317d26de1, a single commit on # top of leanprover-community/mathlib4 @ d1d1e4b72c6e009edd97cabe1d661b021fe8eeae # (2024-06-12) that ONLY edits lakefile.lean/lake-manifest.json to add DeepSeek's REPL # fork as a dependency. Its lean-toolchain is `leanprover/lean4:v4.9.0-rc1` (nominally # "Lean 4.9"; the binary reports 4.9.0-rc1). We pin the UPSTREAM parent: the Mathlib # .olean cache is keyed on file hashes that include lakefile.lean, so the DeepSeek # edit would orphan the cache, while the parent's .oleans are byte-for-byte the # Mathlib sources Goedel proofs were checked against. The REPL is upstream # leanprover-community/repl at the tag whose toolchain equals ours. # # Usage: bash scripts/lean_setup.sh # full install # LEAN_ROOT=... bash scripts/lean_setup.sh # bash scripts/lean_setup.sh --check # only verify an existing install set -euo pipefail LEAN_ROOT="${LEAN_ROOT:-/apps/development/rlhf/lean}" LEAN_TOOLCHAIN="${LEAN_TOOLCHAIN:-leanprover/lean4:v4.9.0-rc1}" MATHLIB_REV="${MATHLIB_REV:-d1d1e4b72c6e009edd97cabe1d661b021fe8eeae}" REPL_TAG="${REPL_TAG:-v4.9.0-rc1}" REPL_URL="${REPL_URL:-https://github.com/leanprover-community/repl}" MATHLIB_URL="https://github.com/leanprover-community/mathlib4" case "$LEAN_ROOT" in /apps/*) ;; *) echo "refusing: LEAN_ROOT=$LEAN_ROOT is not under /apps (host rule: never write to / or /tmp)" >&2; exit 2 ;; esac export ELAN_HOME="$LEAN_ROOT/elan" export MATHLIB_CACHE_DIR="$LEAN_ROOT/mathlib-cache" export XDG_CACHE_HOME="$LEAN_ROOT/xdg-cache" export XDG_CONFIG_HOME="$LEAN_ROOT/xdg-config" export TMPDIR="$LEAN_ROOT/tmp" export GIT_TERMINAL_PROMPT=0 export PATH="$ELAN_HOME/bin:$PATH" WS="$LEAN_ROOT/workspace" REPL_DIR="$LEAN_ROOT/repl" LOGS="$LEAN_ROOT/logs" mkdir -p "$LEAN_ROOT" "$MATHLIB_CACHE_DIR" "$XDG_CACHE_HOME" "$XDG_CONFIG_HOME" "$TMPDIR" "$LOGS" log() { printf '[lean_setup %s] %s\n' "$(date +%H:%M:%S)" "$*"; } preflight() { local avail_kb load cores q free_gb avail_kb=$(awk '/^MemAvailable:/{print $2}' /proc/meminfo) load=$(awk '{print $1}' /proc/loadavg) cores=$(nproc) q=$(awk '$3=="nvme2n1"{print $12}' /proc/diskstats) q=${q:-0} free_gb=$(df -BG --output=avail /apps | tail -1 | tr -dc 0-9) log "preflight: MemAvailable=$((avail_kb/1024/1024))GB load1=$load cores=$cores diskq=$q /apps_free=${free_gb}GB" [ "$avail_kb" -ge $((45*1024*1024)) ] || { log "REFUSED: MemAvailable < 45GB"; exit 83; } awk -v l="$load" -v c="$cores" 'BEGIN{exit !(l < 2*c)}' || { log "REFUSED: load >= 2x cores"; exit 83; } [ "$q" -lt 64 ] || { log "REFUSED: disk queue >= 64"; exit 83; } [ "$free_gb" -ge 40 ] || { log "REFUSED: < 40GB free on /apps"; exit 83; } } check_install() { [ -x "$ELAN_HOME/bin/lake" ] || { log "missing elan/lake"; return 1; } [ -x "$REPL_DIR/.lake/build/bin/repl" ] || { log "missing repl binary"; return 1; } [ -f "$WS/.lake/packages/mathlib/.lake/build/lib/Mathlib.olean" ] || { log "missing Mathlib.olean (cache not fetched)"; return 1; } log "install present" } if [ "${1:-}" = "--check" ]; then check_install; exit $?; fi preflight # ---- 1. elan ----------------------------------------------------------------------- if [ ! -x "$ELAN_HOME/bin/elan" ]; then log "installing elan into $ELAN_HOME" timeout 120 curl -sSfL https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -o "$LEAN_ROOT/elan-init.sh" # --no-modify-path: never touch ~/.profile / ~/.bashrc. --default-toolchain none: the # toolchain is installed explicitly below so the pin is visible in this file. timeout 600 sh "$LEAN_ROOT/elan-init.sh" -y --no-modify-path --default-toolchain none fi elan --version # ---- 2. toolchain ------------------------------------------------------------------ if ! elan toolchain list | grep -qF "${LEAN_TOOLCHAIN##*:}"; then log "installing $LEAN_TOOLCHAIN" timeout 900 elan toolchain install "$LEAN_TOOLCHAIN" fi # ---- 3. workspace (Lake project requiring Mathlib at the pinned rev) --------------- mkdir -p "$WS" if [ ! -f "$WS/lakefile.lean" ]; then log "writing workspace lakefile" printf '%s\n' "$LEAN_TOOLCHAIN" > "$WS/lean-toolchain" cat > "$WS/lakefile.lean" <&1 | tee "$LOGS/lake_update.log" | tail -5 fi # Guard against silent drift: the manifest must name exactly the pinned rev. grep -q "$MATHLIB_REV" "$WS/lake-manifest.json" || { log "FATAL: manifest does not pin $MATHLIB_REV"; exit 3; } # ---- 4. prebuilt Mathlib cache ----------------------------------------------------- if [ ! -f "$WS/.lake/packages/mathlib/.lake/build/lib/Mathlib.olean" ]; then log "lake exe cache get (downloads + unpacks the prebuilt oleans)" (cd "$WS" && timeout 3000 lake exe cache get) 2>&1 | tee "$LOGS/cache_get.log" | tail -8 fi # Deps (batteries, Qq, aesop, proofwidgets) are built from source by lake when the cache # does not cover them; this is a no-op when it does. if ! (cd "$WS" && timeout 120 lake build --no-build Mathlib >/dev/null 2>&1); then log "lake build Mathlib (deps not fully cached; building the remainder)" (cd "$WS" && timeout 5400 lake build Mathlib) 2>&1 | tee "$LOGS/lake_build.log" | tail -5 fi # ---- 5. REPL at the matching tag --------------------------------------------------- if [ ! -d "$REPL_DIR/.git" ]; then log "cloning repl $REPL_TAG" timeout 300 git clone --branch "$REPL_TAG" --depth 1 "$REPL_URL" "$REPL_DIR" fi REPL_TC=$(cat "$REPL_DIR/lean-toolchain") [ "$REPL_TC" = "$LEAN_TOOLCHAIN" ] || { log "FATAL: repl toolchain $REPL_TC != $LEAN_TOOLCHAIN"; exit 3; } if [ ! -x "$REPL_DIR/.lake/build/bin/repl" ]; then log "building repl" (cd "$REPL_DIR" && timeout 1200 lake build) 2>&1 | tee "$LOGS/repl_build.log" | tail -5 fi # ---- 6. smoke test: import Mathlib through the built REPL -------------------------- log "smoke test" SMOKE=$(cd "$WS" && printf '%s\n\n%s\n\n' \ '{"cmd": "import Mathlib\ntheorem t (a b : Nat) : a + b = b + a := by omega"}' \ '{"cmd": "#print axioms t", "env": 0}' \ | timeout 300 lake env "$REPL_DIR/.lake/build/bin/repl") echo "$SMOKE" | head -20 echo "$SMOKE" | grep -q '"env"' || { log "FATAL: REPL smoke failed"; exit 4; } cat > "$LEAN_ROOT/PINS.json" < "$LEAN_ROOT/env.sh" </dev/null | cut -f1) total under $LEAN_ROOT" log "done"