#!/bin/sh # Compute CLI installer — https://compute.cx/install.sh # Usage: curl -fsSL https://compute.cx/install.sh | sh # # Installs the `compute` CLI from a prebuilt wheel (PyPI comes in v0.2). # Requires Python ≥3.10 on macOS or Linux. # # Optional integrity: # COMPUTE_WHEEL_SHA256= require exact digest of the downloaded wheel # COMPUTE_MANIFEST_URL= JSON with {"version","sha256","wheel_url"} set -eu COMPUTE_VERSION="${COMPUTE_VERSION:-0.1.5}" COMPUTE_BASE_URL="${COMPUTE_BASE_URL:-https://compute.cx}" WHEEL_URL="${COMPUTE_WHEEL_URL:-${COMPUTE_BASE_URL}/releases/compute-${COMPUTE_VERSION}-py3-none-any.whl}" MANIFEST_URL="${COMPUTE_MANIFEST_URL:-${COMPUTE_BASE_URL}/releases/compute-${COMPUTE_VERSION}.json}" PREFIX="${COMPUTE_PREFIX:-${HOME}/.local}" BIN_DIR="${PREFIX}/bin" VENV_DIR="${PREFIX}/share/compute/venv" # Prefer mktemp so the staging dir is unpredictable (not TMPDIR/compute-install-$$). if command -v mktemp >/dev/null 2>&1; then TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/compute-install.XXXXXX")" else TMP_DIR="${TMPDIR:-/tmp}/compute-install-$$" mkdir -m 700 -p "${TMP_DIR}" fi cleanup() { rm -rf "${TMP_DIR}" } trap cleanup EXIT INT HUP say() { printf '%s\n' "$*"; } err() { printf 'error: %s\n' "$*" >&2; exit 1; } need_cmd() { command -v "$1" >/dev/null 2>&1 || err "missing required command: $1" } python_ok() { "$1" - <<'PY' 2>/dev/null import sys raise SystemExit(0 if sys.version_info >= (3, 10) else 1) PY } resolve_python() { for candidate in python3.14 python3.13 python3.12 python3.11 python3.10 python3; do if command -v "${candidate}" >/dev/null 2>&1 && python_ok "${candidate}"; then command -v "${candidate}" return 0 fi done return 1 } sha256_file() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}' elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | awk '{print $1}' else err "need sha256sum or shasum to verify the wheel digest" fi } need_cmd curl need_cmd mkdir need_cmd chmod PYTHON="$(resolve_python)" || err "Python ≥3.10 is required (macOS or Linux)" EXPECTED_SHA256="${COMPUTE_WHEEL_SHA256:-}" # Best-effort: load digest from release manifest when present. if [ -z "${EXPECTED_SHA256}" ]; then MANIFEST_PATH="${TMP_DIR}/manifest.json" if curl -fsSL "${MANIFEST_URL}" -o "${MANIFEST_PATH}" 2>/dev/null; then EXPECTED_SHA256="$( "${PYTHON}" - "${MANIFEST_PATH}" <<'PY' import json, sys doc = json.load(open(sys.argv[1], encoding="utf-8")) print(doc.get("sha256") or "") PY )" || EXPECTED_SHA256="" MANIFEST_WHEEL="$("${PYTHON}" - "${MANIFEST_PATH}" <<'PY' import json, sys doc = json.load(open(sys.argv[1], encoding="utf-8")) print(doc.get("wheel_url") or "") PY )" || MANIFEST_WHEEL="" if [ -n "${MANIFEST_WHEEL}" ]; then WHEEL_URL="${MANIFEST_WHEEL}" fi fi fi say "Installing Compute CLI ${COMPUTE_VERSION}" say " python: ${PYTHON}" say " wheel: ${WHEEL_URL}" say " bin: ${BIN_DIR}/compute" mkdir -p "${BIN_DIR}" "$(dirname "${VENV_DIR}")" if [ ! -x "${VENV_DIR}/bin/python" ]; then "${PYTHON}" -m venv "${VENV_DIR}" fi # Ensure pip exists; do not upgrade pip globally — use ensurepip only. "${VENV_DIR}/bin/python" -m ensurepip --upgrade >/dev/null 2>&1 || true WHEEL_PATH="${TMP_DIR}/compute-${COMPUTE_VERSION}-py3-none-any.whl" curl -fsSL "${WHEEL_URL}" -o "${WHEEL_PATH}" if [ -n "${EXPECTED_SHA256}" ]; then ACTUAL_SHA256="$(sha256_file "${WHEEL_PATH}")" if [ "${ACTUAL_SHA256}" != "${EXPECTED_SHA256}" ]; then err "wheel sha256 mismatch (expected ${EXPECTED_SHA256}, got ${ACTUAL_SHA256})" fi say " sha256: ${ACTUAL_SHA256} (verified)" else say " sha256: (unchecked — set COMPUTE_WHEEL_SHA256 or publish a release manifest)" fi "${VENV_DIR}/bin/python" -m pip install --upgrade --no-cache-dir "${WHEEL_PATH}" ln -sfn "${VENV_DIR}/bin/compute" "${BIN_DIR}/compute" chmod 755 "${BIN_DIR}/compute" case ":${PATH}:" in *":${BIN_DIR}:"*) ;; *) say "" say "Add ${BIN_DIR} to your PATH, for example:" say " export PATH=\"${BIN_DIR}:\$PATH\"" ;; esac say "" "${BIN_DIR}/compute" --version say "Next:" say " compute setup" say " compute credits add 10" say " compute run path/to/file.py::fn --gpu H100"