diff --git a/.github/workflows/gate.yml b/.github/workflows/gate.yml index 560b6a7f..a9e8ad5a 100644 --- a/.github/workflows/gate.yml +++ b/.github/workflows/gate.yml @@ -221,19 +221,29 @@ jobs: timeout-minutes: 10 runs-on: ubuntu-22.04 + # Toolchain via three-way-parity install.sh (GOVERNANCE §24): same + # semgrep version that dev laptops + devcontainers get, pinned in + # `.mise.toml` as `pipx:semgrep`. No `actions/setup-python` here + # — the host-portability invariant is to minimise GitHub-specific + # surface so switching CI hosts is cheap, and to keep dev-machine + # and build-machine setup as close as possible. Resolves Scorecard + # PinnedDependenciesID #17 + #18 (pip install --upgrade pip + pip + # install semgrep) by removing the unpinned pip-bootstrap surface + # entirely. steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Setup Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: '3.14' - - - name: Install Semgrep - run: | - python -m pip install --upgrade pip - pip install semgrep + - name: Install toolchain via three-way-parity script (GOVERNANCE §24) + # Single source of truth for toolchain state. mise installs + # python + semgrep (the latter via the `pipx:semgrep` pin in + # `.mise.toml`, which mise auto-routes through `uv tool install` + # since uv is in the toolchain — see ADR + # docs/DECISIONS/2026-04-27-uv-canonical-python-tool-manager.md). + # shellenv.sh wires BASH_ENV so subsequent run: steps auto- + # source the managed shellenv. Same pattern as build-and-test + # job above. + run: ./tools/setup/install.sh - name: Run Semgrep # --error exits non-zero if any rule matches. The 14 rules in diff --git a/.mise.toml b/.mise.toml index 25769cfd..62c3fab0 100644 --- a/.mise.toml +++ b/.mise.toml @@ -34,8 +34,11 @@ bun = "1.3" # uv pinned (round-34 pull-in from ../scratch). Manages Python # CLI tooling (ruff etc.) via `uv tool install` with # reproducible versions across laptops + CI. See BACKLOG -# "Python tool management via uv tool (from ../scratch)". -uv = "0.9" +# "Python tool management via uv tool (from ../scratch)". Also +# powers mise's `pipx:` backend below (mise auto-routes `pipx:` +# tools through `uv tool install` when uv is available — no +# pipx package needed, faster + already in our toolchain). +uv = "0.11.8" # actionlint: GitHub Actions workflow linter. Declarative install # (GOVERNANCE §24 three-way-parity) so dev laptops + CI runners # (including ubuntu-slim which doesn't ship with it) have identical @@ -62,6 +65,22 @@ node = "22" # script in package.json) so dev laptops can also `bun run # lint:markdown`. Both pins point at the same version. "npm:markdownlint-cli2" = "0.22.1" +# semgrep: SAST linter (the 14 rules in `.semgrep.yml`). Pinned +# here per the three-way-parity invariant (GOVERNANCE §24) — dev +# laptops + CI runners + devcontainers should all install +# semgrep through the same `tools/setup/install.sh` rather than +# CI fetching its own copy via `actions/setup-python` + pip. +# The `pipx:` backend prefix is mise's naming; the actual +# installer mise uses is `uv tool install` (since `uv` above is +# in this toolchain — mise auto-routes pipx: through uv when +# uv is available; faster than real pipx, no separate package +# needed). See docs/DECISIONS/2026-04-27-uv-canonical-python- +# tool-manager.md for the uv-as-canonical decision and lineage +# from ../scratch. Resolves Scorecard PinnedDependenciesID #17 +# + #18 (pip install --upgrade pip + pip install semgrep — the +# unpinned pip-bootstrap surface goes away entirely once mise +# owns this tool). +"pipx:semgrep" = "1.161.0" [settings] # `python-build-standalone` (upstream for mise's python plugin) diff --git a/docs/DECISIONS/2026-04-27-uv-canonical-python-tool-manager.md b/docs/DECISIONS/2026-04-27-uv-canonical-python-tool-manager.md new file mode 100644 index 00000000..a38b5d05 --- /dev/null +++ b/docs/DECISIONS/2026-04-27-uv-canonical-python-tool-manager.md @@ -0,0 +1,71 @@ +# ADR: uv is the canonical Python tool manager for Zeta — pipx-named mise tools route through uv + +**Date:** 2026-04-27 +**Status:** *Decision: `uv` (Astral) is the canonical Python tool installer / runtime / venv manager for the Zeta factory. Tools that the mise registry exposes via the `pipx:` backend (e.g. `pipx:semgrep`) are pinned in `.mise.toml` with the `pipx:` prefix because that is mise's registry naming; the actual installer mise invokes is `uv tool install` (mise auto-routes `pipx:` through uv when uv is in the toolchain — no separate `pipx` package is required). No `pip install` / `actions/setup-python` paths in CI; no parallel pipx-vs-uv camps in the install scripts.* +**Owner:** architect (synthesis); human maintainer (shaping-decision owner). +**Decision confidence:** *high* — choice is dictated by composing two prior commitments (three-way-parity per GOVERNANCE §24 + ../scratch first-class adoption of uv) plus a verified mise behaviour; no novel tradeoff to weigh. + +## Context + +1. **../scratch made uv first-class first.** `../scratch` is the maintainer's adjacent project where Python tooling decisions get burned in before this factory absorbs them. uv was made first-class there before any Zeta-side commitment, and the round-34 .mise.toml pull-in here cited *"`../scratch`"* explicitly as the source. The lineage matters: this ADR is documenting an *already-made* decision that was sitting implicit in `.mise.toml` comments rather than a fresh choice. +2. **Three-way parity per GOVERNANCE §24.** Dev laptops + CI runners + devcontainers all bootstrap toolchain via `tools/setup/install.sh` → mise → declarative `.mise.toml`. Any second Python tool installer (pipx, bare pip, conda) creates a fork in the install graph and breaks the parity invariant. +3. **mise routes pipx: through uv automatically.** Per upstream mise docs (`mise.jdx.dev/dev-tools/backends/pipx.html`): *"If you have uv installed, mise will use uv tool install under the hood and you don't need to install pipx to run the commands containing 'pipx:'."* Verified locally on 2026-04-27 with `uv = "0.11.8"` + `pipx:semgrep = "1.161.0"` (no separate pipx in the toolchain) — mise installs semgrep via `uv tool install` and `semgrep --version` reports 1.161.0. +4. **Aaron 2026-04-27 explicit course-correction.** During the Scorecard PinnedDependenciesID fix work, Aaron pushed back on an initial draft that added `pipx = "1.11.1"` alongside `uv`: + > "we have uv do we need pipx, isn't there a uvx this should be much faster" + + Plus the prior framing: + > "the fact that uv is our desired python setup should be documented somewehre this project ../scratch made it first class too" + + This ADR is the documenting move. + +## Decision + +1. **`uv` is canonical.** Pinned in `.mise.toml` `[tools]` section. All Python-language CLI tooling routes through it. +2. **`pipx:` prefix in mise registry calls is fine.** It is just mise's naming for the namespace; the actual installer is uv. Do NOT add `pipx = "X.Y.Z"` to `.mise.toml` to "satisfy" the prefix — that would install a redundant package that mise will not use. +3. **No `actions/setup-python` in CI.** CI gets Python from the same `mise install` pass that dev laptops run. +4. **No `pip install ...` lines in CI workflows.** Anything that wants a Python CLI tool gets it via `.mise.toml` pin (`pipx:foo = "X.Y.Z"`) or via `uv tool install` invoked from a script that ran through `install.sh`. + +## Consequences + +**Positive:** + +- **Dev/CI parity.** Same Python toolchain, same versions, same install path, same provenance. +- **Speed.** uv's resolver and installer are materially faster than pip+pipx. +- **GitHub artifact attestation verification** for `uv` itself (mise's aqua: backend handles it). One fewer tool in the toolchain (no pipx) means one fewer attestation surface. +- **Resolves Scorecard PinnedDependenciesID** for any future `pip install foo` patterns by removing the pattern entirely from CI. +- **Single-bump surface.** Bumping a Python CLI tool is a single `.mise.toml` edit. CI inherits. + +**Negative:** + +- **Naming surprise.** `pipx:semgrep` reads as if pipx is involved; the actual installer is uv. The `.mise.toml` comment block on `uv` and on each `pipx:*` entry must call this out (they do, post this ADR). Mitigation: this ADR is linked from those comments. +- **Tied to mise's auto-route behaviour.** If a future mise release changes the auto-route preference (e.g. requires `uvx:` prefix instead), all `pipx:*` entries need a rename. Mitigation: pinned mise version in `.mise.toml`; bump deliberately, with a smoke test. + +**Neutral (deferred):** + +- A potential future `uvx:` first-class backend in mise would let us drop the `pipx:` prefix entirely. Not blocking; cosmetic. + +## What this ADR does NOT decide + +- Does NOT decide whether Zeta itself ships Python code (it does not; F# / C# / TS). +- Does NOT decide build-system choice for any future Python *project* (we do not have one). This ADR is about *tooling* — installing Python-language CLI tools (semgrep, ruff, etc.) — not about authoring Python. +- Does NOT decide between `uv tool install` and `uv venv` — both are valid for their respective use cases (CLI tools vs project venvs); they both flow through uv. + +## Lineage + +- `../scratch` `.mise.toml` (Aaron's adjacent project) — uv made first-class here first +- Round-34 pull-in — Zeta `.mise.toml` cited `../scratch` as the source of the uv pin +- 2026-04-27 — Aaron's explicit ask to document the decision; this ADR + +## Composes with + +- **GOVERNANCE.md §24** (three-way parity) +- **`docs/DECISIONS/2026-04-20-tools-scripting-language.md`** (no Python authored in Zeta tooling — this ADR is the orthogonal "but Python *tools* are fine, installed via uv" companion) +- **`memory/feedback_three_way_parity_install_scripts_dev_ci_devcontainer_minimize_github_specific_surface_aaron_2026_04_27.md`** (the substrate memory naming Aaron's host-portability invariant) +- **#653** — the PR landing the first concrete `pipx:semgrep` use, removing pip install + actions/setup-python from gate.yml lint-semgrep + +## Forward-action + +- Land this ADR alongside the `.mise.toml` change in #653 +- Update `.mise.toml` comments on `uv` and on each `pipx:*` entry to point at this ADR +- Future `pipx:*` additions cite this ADR in their comment, not the verbose rationale +- If `actions/setup-python` is found anywhere in `.github/workflows/`, file a follow-up issue to migrate to `install.sh + .mise.toml` diff --git a/memory/MEMORY.md b/memory/MEMORY.md index 81f20e28..afe71e19 100644 --- a/memory/MEMORY.md +++ b/memory/MEMORY.md @@ -2,6 +2,7 @@ **📌 Fast path: read `CURRENT-aaron.md` and `CURRENT-amara.md` first.** These per-maintainer distillations show what's currently in force. Raw memories below are the history; CURRENT files are the projection. (`CURRENT-aaron.md` refreshed 2026-04-25 with the Otto-281..285 substrate cluster + factory-as-superfluid framing — sections 18-22; prior refresh 2026-04-24 covered sections 13-17.) +- [**Three-way-parity invariant — dev/CI/devcontainer share install scripts; minimize GitHub-specific surface so switching CI hosts is cheap (Aaron 2026-04-27)**](feedback_three_way_parity_install_scripts_dev_ci_devcontainer_minimize_github_specific_surface_aaron_2026_04_27.md) — When fixing CI, default-check `.mise.toml` first; reach for GitHub-specific shapes (custom action / container: block / setup-X) only when no parity-preserving option exists. uv-canonical decision documented in `docs/DECISIONS/2026-04-27-uv-canonical-python-tool-manager.md`. - [**LFG=master, AceHack=0-divergence fork, double-hop AceHack→LFG (Aaron 2026-04-27 strategic reframe)**](feedback_lfg_master_acehack_zero_divergence_fork_double_hop_aaron_2026_04_27.md) — Aaron 2026-04-27: bidirectional content-sync too hard; LFG is master, AceHack is pure fork (0 ahead AND 0 behind), force-push AceHack to LFG state after every paired sync. Replaces Option C parallel-SHA-history. Done criterion: `git diff acehack/main..origin/main` empty AND `git rev-list --count` returns 0 both directions. - [**0-diff is "start" line — until then we're hobbling (Aaron 2026-04-27)**](feedback_zero_diff_is_start_line_until_then_hobbling_aaron_2026_04_27.md) — Aaron 2026-04-27 reframe: AceHack-LFG content-divergence (53 files / 6065 lines) isn't polish, it's the gate to factory operational status. #43's diff-minimization invariant DEFINES "started." Reverse-sync work moves to high priority. Distinguish commit-count (76/492, NEVER zero, structural) from content-diff (53 files / 6065 lines, CAN reach 0, the actual metric). Forward-action: Batch 1 workflow drift first (~80 lines, 1-2h) as concrete progress on the gate. - [Laptop-only-source integration HIGH PRIORITY — `../scratch` = future ACE PACKAGE MANAGER seed (22 files); `../SQLSharp` = pre-DBSP event-stream-processing with LINQ/SQL (14 files, predates Aaron's DBSP discovery, Zeta-progenitor); goal = either ship feature OR write detailed-enough design that we no longer need the reference; Aaron 2026-04-27 clarification: NOT literal copy-paste, self-contained-understanding floor; refined triage per directory identity — `../scratch` references absorb into canonical location or design-doc the Ace-package-manager intent; `../SQLSharp` references map to DBSP-rigorous Zeta equivalents or design-doc the gap; sequenced AFTER PR #26 sync](project_laptop_only_source_integration_scratch_sqlsharp_features_or_designs_high_priority_2026_04_27.md) — 2026-04-27 P1 backlog row; per-reference triage with three outcomes (ship / design-doc / delete-decorative); composes Otto-275 (log-but-don't-implement default to design when uncertain) + Otto-323/346 (NOT external deps, in-repo or eliminate) + Otto-340 (substrate IS identity); done = `git grep ../scratch` and `git grep ../SQLSharp` return zero matches; effort L (3+ days); closes with Aaron's "good job today!!" second positive validation; Aaron's third 2026-04-27 clarification reveals `../SQLSharp` features potentially subsumed by Zeta's DBSP-rigorous form (linq-expert + sql-expert + sql-engine-expert skills already track this class). diff --git a/memory/feedback_three_way_parity_install_scripts_dev_ci_devcontainer_minimize_github_specific_surface_aaron_2026_04_27.md b/memory/feedback_three_way_parity_install_scripts_dev_ci_devcontainer_minimize_github_specific_surface_aaron_2026_04_27.md new file mode 100644 index 00000000..be16c970 --- /dev/null +++ b/memory/feedback_three_way_parity_install_scripts_dev_ci_devcontainer_minimize_github_specific_surface_aaron_2026_04_27.md @@ -0,0 +1,77 @@ +--- +name: Three-way-parity invariant — dev/CI/devcontainer share install scripts; minimize GitHub-specific surface so switching CI hosts is cheap (Aaron 2026-04-27) +description: Aaron 2026-04-27 explicit course-correction during Scorecard PinnedDependencies fix. Otto's first attempt switched gate.yml lint-semgrep job to a SHA-pinned semgrep/semgrep Docker image. Aaron pushed back: this still uses GitHub-specific stuff (Docker container action) AND breaks the dev/CI parity invariant (dev laptops don't run semgrep via Docker). Right answer: install semgrep through the same `tools/setup/install.sh` that dev laptops + devcontainers use — i.e. add `pipx:semgrep` to `.mise.toml`. Composes GOVERNANCE §24 (already-codified three-way parity) + Otto's authority to push toolchain through install.sh + Aaron's host-portability invariant ("easy to switch hosts"). Triggered by Scorecard PinnedDependenciesID #17/#18 work where the convenience-fix (Docker container) would have introduced a real divergence. +type: feedback +--- + +# Three-way-parity invariant — dev/CI/devcontainer share install scripts + +## Verbatim quote (Aaron 2026-04-27) + +> "actions/setup-python we should be using our base python that our install scripts install we are trying to not use github stuff unless we have to so it's easy to switch hosts and our dev macchine and build machine setup is the same, that's one of the invariants we want to try to keep as close as possible dev machine / build machines are same/very similar for setup/share the setup/install scripts and post install scripts. this makes CI more deterministic too. if i'm off base here just let me know. but SHA pinning github actions is a supply change risk mitigation good idea." + +## The invariant (codified) + +GOVERNANCE.md §24 already names this as the "three-way parity" rule: + +- **Dev laptop** runs `tools/setup/install.sh` to bootstrap toolchain +- **CI runner** runs `tools/setup/install.sh` to bootstrap toolchain +- **Devcontainer image** runs `tools/setup/install.sh` to bootstrap toolchain + +Same script. Same `.mise.toml`. Same versions. No GitHub-specific actions stand in for what install.sh already does (no `actions/setup-python`, no `actions/setup-node`, no `setup-dotnet`). + +## What this BUYS + +- **Host portability.** If a future CI host swap happens (GitHub Actions → GitLab CI → self-hosted Forgejo runners → BuildKite → ...), the install path is the same script. Only the YAML wrapper differs. +- **Dev/CI parity for debugging.** A failure on CI reproduces locally because the toolchain is bit-identical (same mise pins, same install.sh path). +- **Determinism.** mise resolves declarative pins atomically; pip's dependency-resolver wandering or `actions/setup-python` cache poisoning don't enter the picture. +- **One bump surface.** Bumping a tool version is a single `.mise.toml` edit; CI inherits. + +## What was almost violated (today's failure mode) + +Otto's first attempt to fix Scorecard PinnedDependenciesID #17/#18 (pip install in lint-semgrep) was to switch the job to a SHA-pinned `semgrep/semgrep:1.161.0@sha256:...` Docker image at the `container:` job level. + +That fix: + +- **Did** resolve the Scorecard alerts (image bytes are content-addressed) +- **Did NOT** install semgrep on dev laptops via the install path +- **Did** introduce a GitHub-Actions-specific shape (`container:` block) that isn't portable across CI hosts +- **Did** break the dev/CI parity invariant — a dev laptop running semgrep manually wouldn't be running the same version that CI runs + +Aaron caught this immediately. The invariant is more important than the immediate fix. + +## The right fix (post-correction) + +1. Add `pipx = "1.11.1"` to `.mise.toml` (aqua-backed; same SHA-pinned release path as `actionlint` / `shellcheck` / `uv`) +2. Add `"pipx:semgrep" = "1.161.0"` to `.mise.toml` (mise's pipx: backend installs the named PyPI package at the pinned version) +3. Drop `actions/setup-python` + the two `pip install` steps from gate.yml +4. Replace with `./tools/setup/install.sh` (same step build-and-test already uses) + +Now: dev laptops + CI + devcontainers all install semgrep 1.161.0 the same way, through the same tooling, pinned in the same `.mise.toml`. + +## Operational rule + +When fixing a CI-side problem, before reaching for a GitHub-specific solution (custom action, container: block, runner-side feature), check: + +- **Does this tool exist in `.mise.toml` already?** If so, use it via install.sh. +- **Could this tool live in `.mise.toml`?** If yes (mise registry has it via aqua / pipx / npm / cargo / etc.), add it there and route CI through install.sh. +- **Is this genuinely GitHub-specific?** (e.g. workflow triggering, GITHUB_TOKEN scope, code-scanning upload) — only then is a GitHub-specific shape correct. + +## What this rule does NOT mean + +- Does NOT mean "never use GitHub Actions" — `actions/checkout`, `actions/cache`, `github/codeql-action/*`, `actions/upload-artifact` are all genuinely GitHub-specific and stay +- Does NOT mean "never SHA-pin GitHub actions" — Aaron explicitly affirmed: *"SHA pinning github actions is a supply change risk mitigation good idea"*. SHA-pinning the actions we DO use is correct +- Does NOT mean "rewrite all of CI today" — apply the rule going forward; existing breaches are tech-debt to fix opportunistically when touching the surrounding code + +## Composes with + +- **GOVERNANCE.md §24** — three-way parity rule (this memory is the lived rationale, not just a re-citation) +- **Otto-247 (version-currency)** — version pins in `.mise.toml` get the same WebSearch-first discipline +- **#71 (Otto owns settings)** — install-path decisions are within Otto's authority; the binding decision is "share via install.sh", not "let CI drift" +- **#652 (block-only-on-Aaron-must-do)** — Aaron's earlier framing today: drive forward with best long-term judgment. The course-correction today refines what "best long-term" looks like for CI design + +## Forward-action + +- File this memory + MEMORY.md row +- Apply the rule to the in-flight fix: replace Docker-container approach with `pipx:semgrep` in `.mise.toml` + install.sh in gate.yml +- Future-self check: when adding a CI step, default-check `.mise.toml` first; reach for a GitHub-specific shape only when no parity-preserving option exists diff --git a/tools/setup/common/elan.sh b/tools/setup/common/elan.sh index 4a20c43e..05859127 100755 --- a/tools/setup/common/elan.sh +++ b/tools/setup/common/elan.sh @@ -12,8 +12,31 @@ set -euo pipefail if ! command -v elan >/dev/null 2>&1; then echo "↓ installing elan (Lean 4 toolchain manager)..." - curl -fsSL https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh \ - | sh -s -- -y --default-toolchain none + # Pinned to v4.2.1 commit SHA + verified SHA256 of elan-init.sh. + # Bumping procedure: bump both ELAN_INIT_COMMIT (gh api + # /repos/leanprover/elan/releases/latest) and ELAN_INIT_SHA256 + # (curl | sha256sum) together — they form a content-pin pair. + # Resolves Scorecard PinnedDependenciesID #15 (downloadThenRun + # not pinned by hash). + ELAN_INIT_COMMIT="58e8d545e33641f66dbcbd22c4283109e71757be" # v4.2.1 + ELAN_INIT_SHA256="4bacca9502cb89736fe63d2685abc2947cfbf34dc87673504f1bb4c43eda9264" + ELAN_INIT_URL="https://raw.githubusercontent.com/leanprover/elan/${ELAN_INIT_COMMIT}/elan-init.sh" + ELAN_INIT_TMP="$(mktemp)" + # Always clean up the tmp file, even on failure (download error, SHA + # mismatch, installer non-zero exit). `set -euo pipefail` would + # otherwise leak the file on any failure path. + trap 'rm -f "${ELAN_INIT_TMP}"' EXIT + curl -fsSL "${ELAN_INIT_URL}" -o "${ELAN_INIT_TMP}" + # Portable SHA256 verification: sha256sum (Linux/git-bash/WSL) or + # shasum -a 256 (macOS default). Per the 4-shell portability target + # (macOS bash 3.2 / Ubuntu / git-bash / WSL). + if command -v sha256sum >/dev/null 2>&1; then + echo "${ELAN_INIT_SHA256} ${ELAN_INIT_TMP}" | sha256sum -c - + else + echo "${ELAN_INIT_SHA256} ${ELAN_INIT_TMP}" | shasum -a 256 -c - + fi + sh "${ELAN_INIT_TMP}" -y --default-toolchain none + # Tmp file cleanup happens via the EXIT trap above. fi # Source the elan env file for the remainder of this script run; also diff --git a/tools/setup/linux.sh b/tools/setup/linux.sh index e0d73187..3206c9d3 100755 --- a/tools/setup/linux.sh +++ b/tools/setup/linux.sh @@ -53,12 +53,53 @@ fi echo "✓ apt packages up to date" # ── 2. mise ───────────────────────────────────────────────────────── +# Pinned to a specific mise release tarball + verified SHA256 (per +# arch). Resolves Scorecard PinnedDependenciesID #16 (downloadThenRun +# not pinned by hash). The official `curl mise.run | sh` installer +# auto-detects the latest release at runtime, which is what Scorecard +# flags. Bumping: pull /repos/jdx/mise/releases/latest, update +# MISE_VERSION + both MISE_SHA256_* values together — they form a +# content-pin set. if ! command -v mise >/dev/null 2>&1; then - echo "↓ installing mise via the official installer..." - curl -fsSL https://mise.run | sh + echo "↓ installing mise from pinned release tarball..." + MISE_VERSION="v2026.4.24" + MISE_SHA256_X64="de2f924940c29b8983035833e2fb3a50092c5794562ca0dcd0cf87b40cae2c58" + MISE_SHA256_ARM64="cf5f4899c3f1b56239d2eedf173c68c47b7db95400c4fa1b61e943dee4965727" + MISE_SHA256_ARMV7="2e122fd8bec64f86449872c633e47023b56416f887e4646307ad176baae3bfa9" + # The previous `curl mise.run | sh` shape supported armv7 implicitly + # (the installer auto-detects). Preserve that here — no Zeta CI leg + # uses armv7 today, but dev laptops on a Raspberry Pi 4 in 32-bit + # mode or older single-board computers do, and the cost of carrying + # the case is tiny (one extra SHA256 to bump per release). + case "$(uname -m)" in + x86_64|amd64) MISE_ARCH=x64; MISE_SHA256="${MISE_SHA256_X64}" ;; + aarch64|arm64) MISE_ARCH=arm64; MISE_SHA256="${MISE_SHA256_ARM64}" ;; + armv7l|armv7) MISE_ARCH=armv7; MISE_SHA256="${MISE_SHA256_ARMV7}" ;; + *) echo "error: unsupported arch $(uname -m) for mise install" >&2; exit 1 ;; + esac + MISE_TARBALL="mise-${MISE_VERSION}-linux-${MISE_ARCH}.tar.gz" + MISE_URL="https://github.com/jdx/mise/releases/download/${MISE_VERSION}/${MISE_TARBALL}" + MISE_TMP="$(mktemp -d)" + # Always clean up the tmp dir, even on failure (download error, SHA + # mismatch, tar extract failure). `set -euo pipefail` would otherwise + # leak the directory on any failure path. + trap 'rm -rf "${MISE_TMP}"' EXIT + curl -fsSL "${MISE_URL}" -o "${MISE_TMP}/${MISE_TARBALL}" + # Portable SHA256 verification: sha256sum (Linux) or shasum (macOS, + # though linux.sh runs on Linux only). Per the 4-shell portability + # target (macOS bash 3.2 / Ubuntu / git-bash / WSL). + if command -v sha256sum >/dev/null 2>&1; then + echo "${MISE_SHA256} ${MISE_TMP}/${MISE_TARBALL}" | sha256sum -c - + else + echo "${MISE_SHA256} ${MISE_TMP}/${MISE_TARBALL}" | shasum -a 256 -c - + fi + tar -C "${MISE_TMP}" -xzf "${MISE_TMP}/${MISE_TARBALL}" + mkdir -p "${HOME}/.local/bin" + mv "${MISE_TMP}/mise/bin/mise" "${HOME}/.local/bin/mise" + # Tmp dir cleanup happens via the EXIT trap above. # The installer puts mise at $HOME/.local/bin/mise; ensure we can # invoke it for the remainder of this script run. - export PATH="$HOME/.local/bin:$PATH" + export PATH="${HOME}/.local/bin:${PATH}" fi echo "✓ mise: $(mise --version)"