mirror of
https://github.com/Mesh-LLM/mesh-llm.git
synced 2026-08-08 22:23:19 -04:00
- build one backend-neutral host per platform and package native runtimes separately - compose immutable product-v2 bundles from verified host and runtime artifacts - enforce host import policy and runtime provenance across release and SDK lanes - align debug, release, Windows, Kotlin, and Swift validation with the composed product model - update release documentation, CI topology, and agent guidance for the unified path - require no-device client readiness and bounded clean shutdown for packaged runtimes
831 lines
26 KiB
Bash
831 lines
26 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
REPO="${MESH_LLM_INSTALL_REPO:-Mesh-LLM/mesh-llm}"
|
|
INSTALL_DIR="${MESH_LLM_INSTALL_DIR:-$HOME/.local/bin}"
|
|
INSTALL_FLAVOR="${MESH_LLM_INSTALL_FLAVOR:-}"
|
|
INSTALL_PRERELEASE="${MESH_LLM_INSTALL_PRERELEASE:-0}"
|
|
INSTALL_SERVICE="${MESH_LLM_INSTALL_SERVICE:-0}"
|
|
INSTALL_SERVICE_ARGS="${MESH_LLM_INSTALL_SERVICE_ARGS:-}"
|
|
INSTALL_SERVICE_START="${MESH_LLM_INSTALL_SERVICE_START:-1}"
|
|
RELEASE_URL_BASE="${MESH_LLM_INSTALL_URL_BASE:-}"
|
|
REQUIRE_CHECKSUM="${MESH_LLM_REQUIRE_CHECKSUM:-0}"
|
|
INSTALL_VERBOSE="${MESH_LLM_INSTALL_VERBOSE:-0}"
|
|
# v0.75.0 is the first release whose public archives are required to contain a
|
|
# contract-v2 host/runtime product. Keep the legacy path for older pinned tags
|
|
# only; remove it after their documented support window ends.
|
|
COMPOSED_PRODUCT_MIN_VERSION="0.75.0"
|
|
AUTO_SETUP=1
|
|
DOWNLOADED_ARCHIVE=""
|
|
DOWNLOADED_ASSET=""
|
|
SETUP_ARGS=()
|
|
|
|
need_cmd() {
|
|
if ! command -v "$1" >/dev/null 2>&1; then
|
|
echo "error: required command not found: $1" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
warn() {
|
|
echo "warning: $*" >&2
|
|
}
|
|
|
|
info() {
|
|
if bool_is_true "$INSTALL_VERBOSE"; then
|
|
echo "$@"
|
|
fi
|
|
}
|
|
|
|
style_ok() {
|
|
local text="$1"
|
|
if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then
|
|
printf '\033[32m%s\033[0m' "$text"
|
|
else
|
|
printf '%s' "$text"
|
|
fi
|
|
}
|
|
|
|
bool_is_true() {
|
|
local value="${1:-}"
|
|
value="$(printf '%s' "$value" | tr '[:upper:]' '[:lower:]')"
|
|
case "$value" in
|
|
1|true|yes|on) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
path_contains_install_dir() {
|
|
case ":$PATH:" in
|
|
*":$INSTALL_DIR:"*) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: install.sh [--pre-release] [--install-dir DIR] [--no-setup] [--verbose]
|
|
|
|
Options:
|
|
--pre-release Install the latest published GitHub prerelease instead of the latest stable release.
|
|
--install-dir DIR Install the binary into DIR.
|
|
--no-setup Do not run \
|
|
\
|
|
mesh-llm setup automatically after install.
|
|
--service Legacy compatibility flag. Passes --service through to \
|
|
\
|
|
mesh-llm setup instead of installing services in shell.
|
|
--no-start-service Legacy compatibility flag. Ignored with a warning.
|
|
--service-args VALUE Legacy compatibility flag. Ignored with a warning.
|
|
--verbose Print detailed download, checksum, and setup command output.
|
|
-h, --help Show this help text.
|
|
|
|
Environment overrides:
|
|
MESH_LLM_INSTALL_DIR
|
|
MESH_LLM_INSTALL_URL_BASE Override release asset base URL for testing.
|
|
MESH_LLM_INSTALL_PRERELEASE=1
|
|
MESH_LLM_INSTALL_FLAVOR Legacy compatibility variable. Ignored with a warning.
|
|
MESH_LLM_INSTALL_SERVICE=1 Legacy compatibility variable. Passed through as --service.
|
|
MESH_LLM_INSTALL_SERVICE_START=0 Legacy compatibility variable. Ignored with a warning.
|
|
MESH_LLM_REQUIRE_CHECKSUM=1
|
|
MESH_LLM_INSTALL_VERBOSE=1
|
|
EOF
|
|
}
|
|
|
|
add_setup_arg() {
|
|
local arg="$1"
|
|
local existing
|
|
for existing in "${SETUP_ARGS[@]:-}"; do
|
|
if [[ "$existing" == "$arg" ]]; then
|
|
return 0
|
|
fi
|
|
done
|
|
SETUP_ARGS+=("$arg")
|
|
}
|
|
|
|
parse_args() {
|
|
while (($# > 0)); do
|
|
case "$1" in
|
|
--pre-release)
|
|
INSTALL_PRERELEASE=1
|
|
;;
|
|
--install-dir)
|
|
shift
|
|
if (($# == 0)); then
|
|
echo "error: --install-dir requires a directory" >&2
|
|
exit 1
|
|
fi
|
|
INSTALL_DIR="$1"
|
|
;;
|
|
--no-setup)
|
|
AUTO_SETUP=0
|
|
;;
|
|
--verbose)
|
|
INSTALL_VERBOSE=1
|
|
add_setup_arg "--verbose"
|
|
;;
|
|
--service)
|
|
warn "--service is deprecated in install.sh; forwarding it to \`mesh-llm setup --service\`."
|
|
add_setup_arg "--service"
|
|
;;
|
|
--no-start-service)
|
|
warn "--no-start-service is deprecated in install.sh; service start policy now belongs to \`mesh-llm setup\`."
|
|
;;
|
|
--service-args)
|
|
shift
|
|
if (($# == 0)); then
|
|
echo "error: --service-args requires a value" >&2
|
|
exit 1
|
|
fi
|
|
warn "--service-args is deprecated in install.sh and is ignored; configure startup behavior after \`mesh-llm setup\`."
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "error: unknown argument: $1" >&2
|
|
echo >&2
|
|
usage >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
}
|
|
|
|
apply_legacy_env_compat() {
|
|
if bool_is_true "$INSTALL_VERBOSE"; then
|
|
add_setup_arg "--verbose"
|
|
fi
|
|
if [[ -n "$INSTALL_FLAVOR" ]]; then
|
|
warn "MESH_LLM_INSTALL_FLAVOR is deprecated in install.sh and is ignored; \`mesh-llm setup\` now owns runtime selection."
|
|
fi
|
|
if bool_is_true "$INSTALL_SERVICE"; then
|
|
warn "MESH_LLM_INSTALL_SERVICE is deprecated in install.sh; forwarding it to \`mesh-llm setup --service\`."
|
|
add_setup_arg "--service"
|
|
fi
|
|
if [[ -n "$INSTALL_SERVICE_ARGS" ]]; then
|
|
warn "MESH_LLM_INSTALL_SERVICE_ARGS is deprecated in install.sh and is ignored; configure startup behavior after \`mesh-llm setup\`."
|
|
fi
|
|
if ! bool_is_true "$INSTALL_SERVICE_START"; then
|
|
warn "MESH_LLM_INSTALL_SERVICE_START is deprecated in install.sh and is ignored; service start policy now belongs to \`mesh-llm setup\`."
|
|
fi
|
|
}
|
|
|
|
platform_os() {
|
|
if [[ -n "${MESH_LLM_TEST_UNAME_S:-}" ]]; then
|
|
printf '%s\n' "$MESH_LLM_TEST_UNAME_S"
|
|
return 0
|
|
fi
|
|
uname -s
|
|
}
|
|
|
|
platform_arch() {
|
|
local os
|
|
local arch
|
|
os="$(platform_os)"
|
|
if [[ -n "${MESH_LLM_TEST_UNAME_M:-}" ]]; then
|
|
arch="$MESH_LLM_TEST_UNAME_M"
|
|
else
|
|
arch="$(uname -m)"
|
|
fi
|
|
case "$os/$arch" in
|
|
Linux/amd64) printf 'x86_64\n' ;;
|
|
Linux/arm64|Linux/aarch64) printf 'aarch64\n' ;;
|
|
Linux/arm|Linux/armv6l|Linux/armv6hf|Linux/armv7l|Linux/armv7hf) printf 'arm\n' ;;
|
|
*) printf '%s\n' "$arch" ;;
|
|
esac
|
|
}
|
|
|
|
platform_id() {
|
|
printf '%s/%s\n' "$(platform_os)" "$(platform_arch)"
|
|
}
|
|
|
|
platform_support_status() {
|
|
case "$(platform_id)" in
|
|
Darwin/arm64|Linux/aarch64|Linux/x86_64) printf 'supported\n' ;;
|
|
Linux/arm) printf 'recognized-unsupported\n' ;;
|
|
*) printf 'unsupported\n' ;;
|
|
esac
|
|
}
|
|
|
|
platform_error_message() {
|
|
case "$(platform_support_status)" in
|
|
recognized-unsupported)
|
|
printf 'error: recognized but unsupported platform: %s (32-bit ARM release bundles are not published)\n' "$(platform_id)"
|
|
;;
|
|
*)
|
|
printf 'error: unsupported platform: %s\n' "$(platform_id)"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
tegra_model_text() {
|
|
if [[ -n "${MESH_LLM_TEST_TEGRA_MODEL:-}" ]]; then
|
|
printf '%s\n' "$MESH_LLM_TEST_TEGRA_MODEL"
|
|
return 0
|
|
fi
|
|
|
|
local path
|
|
for path in \
|
|
/proc/device-tree/model \
|
|
/proc/device-tree/compatible \
|
|
/sys/firmware/devicetree/base/model \
|
|
/sys/firmware/devicetree/base/compatible; do
|
|
[[ -r "$path" ]] || continue
|
|
tr '\0' '\n' <"$path" 2>/dev/null || true
|
|
printf '\n'
|
|
done
|
|
}
|
|
|
|
probe_tegra_nvidia() {
|
|
local model
|
|
model="$(tegra_model_text | tr '[:lower:]' '[:upper:]')"
|
|
case "$model" in
|
|
*JETSON*|*TEGRA*|*ORIN*|*NVGPU*|*THOR*) return 0 ;;
|
|
esac
|
|
|
|
[[ -e /dev/nvhost-gpu ]] || [[ -e /dev/nvhost-ctrl-gpu ]]
|
|
}
|
|
|
|
probe_nvidia() {
|
|
command -v nvidia-smi >/dev/null 2>&1 ||
|
|
command -v nvcc >/dev/null 2>&1 ||
|
|
[[ -e /dev/nvidiactl ]] ||
|
|
[[ -d /proc/driver/nvidia/gpus ]] ||
|
|
probe_tegra_nvidia
|
|
}
|
|
|
|
probe_rocm() {
|
|
command -v rocm-smi >/dev/null 2>&1 ||
|
|
command -v rocminfo >/dev/null 2>&1 ||
|
|
command -v hipcc >/dev/null 2>&1 ||
|
|
[[ -x /opt/rocm/bin/hipcc ]]
|
|
}
|
|
|
|
probe_vulkan() {
|
|
if command -v vulkaninfo >/dev/null 2>&1 && vulkaninfo --summary >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
if command -v glslc >/dev/null 2>&1; then
|
|
if command -v pkg-config >/dev/null 2>&1 && pkg-config --exists vulkan 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
if [[ -f /usr/include/vulkan/vulkan.h || -f /usr/local/include/vulkan/vulkan.h ]]; then
|
|
return 0
|
|
fi
|
|
if [[ -n "${VULKAN_SDK:-}" ]]; then
|
|
return 0
|
|
fi
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
supported_flavors() {
|
|
case "$(platform_support_status)" in
|
|
supported)
|
|
case "$(platform_id)" in
|
|
Darwin/arm64) printf 'metal\n' ;;
|
|
Linux/aarch64) printf 'cuda cpu\n' ;;
|
|
Linux/x86_64) printf 'cuda rocm vulkan cpu\n' ;;
|
|
*) platform_error_message >&2; return 1 ;;
|
|
esac
|
|
;;
|
|
*)
|
|
platform_error_message >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
recommended_flavor() {
|
|
case "$(platform_support_status)" in
|
|
supported)
|
|
case "$(platform_id)" in
|
|
Darwin/arm64) printf 'metal\n' ;;
|
|
Linux/aarch64)
|
|
if probe_nvidia; then
|
|
printf 'cuda\n'
|
|
else
|
|
printf 'cpu\n'
|
|
fi
|
|
;;
|
|
Linux/x86_64)
|
|
if probe_nvidia; then
|
|
printf 'cuda\n'
|
|
elif probe_rocm; then
|
|
printf 'rocm\n'
|
|
elif probe_vulkan; then
|
|
printf 'vulkan\n'
|
|
else
|
|
printf 'cpu\n'
|
|
fi
|
|
;;
|
|
*) platform_error_message >&2; return 1 ;;
|
|
esac
|
|
;;
|
|
*)
|
|
platform_error_message >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
detect_cuda_major() {
|
|
local ver=""
|
|
if command -v nvcc >/dev/null 2>&1; then
|
|
ver="$(nvcc --version 2>/dev/null | grep -oE 'release [0-9]+' | awk '{print $2}' | head -n 1)"
|
|
fi
|
|
if [[ -z "$ver" ]]; then
|
|
local lib
|
|
for lib in /usr/local/cuda*/targets/*/lib/libcudart.so.* /usr/local/cuda*/targets/*/lib/stubs/libcudart.so.*; do
|
|
if [[ -f "$lib" ]]; then
|
|
ver="$(basename "$lib" | grep -oE 'libcudart\.so\.[0-9]+' | awk -F. '{print $3}' | head -n 1)"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
if [[ -z "$ver" ]]; then
|
|
ver="$(ldconfig -p 2>/dev/null | grep -oE 'libcudart\.so\.[0-9]+' | awk -F. '{print $3}' | sort -rn | head -n 1)"
|
|
fi
|
|
case "$ver" in
|
|
12|13) printf '%s\n' "$ver" ;;
|
|
*) printf '\n' ;;
|
|
esac
|
|
}
|
|
|
|
asset_name() {
|
|
local flavor="$1"
|
|
case "$(platform_support_status)" in
|
|
supported)
|
|
case "$(platform_id)" in
|
|
Darwin/arm64) printf 'mesh-llm-aarch64-apple-darwin.tar.gz\n' ;;
|
|
Linux/aarch64)
|
|
case "$flavor" in
|
|
cpu) printf 'mesh-llm-aarch64-unknown-linux-gnu.tar.gz\n' ;;
|
|
cuda)
|
|
local cuda_major
|
|
cuda_major="$(detect_cuda_major)"
|
|
if [[ -n "$cuda_major" ]]; then
|
|
printf 'mesh-llm-aarch64-unknown-linux-gnu-cuda-%s.tar.gz\n' "$cuda_major"
|
|
else
|
|
printf 'mesh-llm-aarch64-unknown-linux-gnu-cuda.tar.gz\n'
|
|
fi
|
|
;;
|
|
*) echo "error: unsupported aarch64 flavor '$flavor'" >&2; return 1 ;;
|
|
esac
|
|
;;
|
|
Linux/x86_64)
|
|
case "$flavor" in
|
|
cpu) printf 'mesh-llm-x86_64-unknown-linux-gnu.tar.gz\n' ;;
|
|
cuda)
|
|
local cuda_major
|
|
cuda_major="$(detect_cuda_major)"
|
|
if [[ -n "$cuda_major" ]]; then
|
|
printf 'mesh-llm-x86_64-unknown-linux-gnu-cuda-%s.tar.gz\n' "$cuda_major"
|
|
else
|
|
printf 'mesh-llm-x86_64-unknown-linux-gnu-cuda.tar.gz\n'
|
|
fi
|
|
;;
|
|
rocm) printf 'mesh-llm-x86_64-unknown-linux-gnu-rocm.tar.gz\n' ;;
|
|
vulkan) printf 'mesh-llm-x86_64-unknown-linux-gnu-vulkan.tar.gz\n' ;;
|
|
*) echo "error: unsupported Linux flavor '$flavor'" >&2; return 1 ;;
|
|
esac
|
|
;;
|
|
*) platform_error_message >&2; return 1 ;;
|
|
esac
|
|
;;
|
|
*)
|
|
platform_error_message >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
latest_prerelease_tag() {
|
|
local api_url="https://api.github.com/repos/${REPO}/releases?per_page=20"
|
|
local response
|
|
local -a curl_args=(
|
|
-fsSL
|
|
-H 'Accept: application/vnd.github+json'
|
|
-H 'X-GitHub-Api-Version: 2022-11-28'
|
|
)
|
|
|
|
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
|
curl_args+=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
|
|
elif [[ -n "${GH_TOKEN:-}" ]]; then
|
|
curl_args+=(-H "Authorization: Bearer ${GH_TOKEN}")
|
|
fi
|
|
|
|
response="$(curl "${curl_args[@]}" "$api_url")"
|
|
printf '%s' "$response" |
|
|
tr -d '\n\r\t ' |
|
|
sed 's/},{/}\
|
|
{/g' |
|
|
awk '
|
|
/"prerelease":true/ && !/"draft":true/ {
|
|
if (match($0, /"tag_name":"[^"]+"/)) {
|
|
value = substr($0, RSTART, RLENGTH)
|
|
sub(/^"tag_name":"/, "", value)
|
|
sub(/"$/, "", value)
|
|
print value
|
|
exit
|
|
}
|
|
}
|
|
'
|
|
}
|
|
|
|
release_url() {
|
|
local asset="$1"
|
|
if [[ -n "$RELEASE_URL_BASE" ]]; then
|
|
printf '%s/%s\n' "${RELEASE_URL_BASE%/}" "$asset"
|
|
return 0
|
|
fi
|
|
if bool_is_true "$INSTALL_PRERELEASE"; then
|
|
local tag
|
|
tag="$(latest_prerelease_tag)"
|
|
printf 'https://github.com/%s/releases/download/%s/%s\n' "$REPO" "$tag" "$asset"
|
|
return 0
|
|
fi
|
|
printf 'https://github.com/%s/releases/latest/download/%s\n' "$REPO" "$asset"
|
|
}
|
|
|
|
checksum_url() {
|
|
printf '%s.sha256\n' "$1"
|
|
}
|
|
|
|
sha256_file() {
|
|
local file="$1"
|
|
if command -v shasum >/dev/null 2>&1; then
|
|
shasum -a 256 "$file" | awk '{print tolower($1)}'
|
|
return 0
|
|
fi
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
sha256sum "$file" | awk '{print tolower($1)}'
|
|
return 0
|
|
fi
|
|
echo "error: shasum or sha256sum is required to verify release artifacts" >&2
|
|
return 1
|
|
}
|
|
|
|
checksum_from_sidecar() {
|
|
local sidecar="$1"
|
|
local expected
|
|
expected="$(LC_ALL=C grep -Eio '[[:xdigit:]]{64}' "$sidecar" | head -n 1 | tr '[:upper:]' '[:lower:]' || true)"
|
|
if [[ -z "$expected" ]]; then
|
|
echo "error: checksum sidecar did not contain a SHA-256 digest: $sidecar" >&2
|
|
return 1
|
|
fi
|
|
printf '%s\n' "$expected"
|
|
}
|
|
|
|
download_checksum_sidecar() {
|
|
local url="$1"
|
|
local sidecar="$2"
|
|
local status
|
|
status="$(curl -sSL -w '%{http_code}' -o "$sidecar" "$url")" || {
|
|
rm -f -- "$sidecar"
|
|
echo "error: could not download checksum sidecar: $url" >&2
|
|
return 2
|
|
}
|
|
|
|
case "$status" in
|
|
2*) return 0 ;;
|
|
000)
|
|
if [[ -f "$sidecar" ]]; then
|
|
return 0
|
|
fi
|
|
rm -f -- "$sidecar"
|
|
return 1
|
|
;;
|
|
404|410)
|
|
rm -f -- "$sidecar"
|
|
return 1
|
|
;;
|
|
*)
|
|
rm -f -- "$sidecar"
|
|
echo "error: checksum sidecar download returned HTTP $status: $url" >&2
|
|
return 2
|
|
;;
|
|
esac
|
|
}
|
|
|
|
verify_downloaded_file() {
|
|
local file="$1"
|
|
local url="$2"
|
|
local require_checksum="${3:-$REQUIRE_CHECKSUM}"
|
|
local sidecar="$file.sha256"
|
|
local sidecar_url
|
|
local sidecar_status=0
|
|
local expected
|
|
local actual
|
|
|
|
sidecar_url="$(checksum_url "$url")"
|
|
download_checksum_sidecar "$sidecar_url" "$sidecar" || sidecar_status=$?
|
|
if [[ "$sidecar_status" -ne 0 ]]; then
|
|
case "$sidecar_status" in
|
|
1)
|
|
if bool_is_true "$require_checksum"; then
|
|
echo "error: checksum sidecar is required but missing: $sidecar_url" >&2
|
|
return 1
|
|
fi
|
|
echo "warning: checksum sidecar not found; continuing without archive verification: $sidecar_url" >&2
|
|
return 0
|
|
;;
|
|
*) return 1 ;;
|
|
esac
|
|
fi
|
|
|
|
expected="$(checksum_from_sidecar "$sidecar")"
|
|
actual="$(sha256_file "$file")"
|
|
if [[ "$actual" != "$expected" ]]; then
|
|
echo "error: checksum mismatch for $(basename "$file")" >&2
|
|
echo "expected: $expected" >&2
|
|
echo "actual: $actual" >&2
|
|
return 1
|
|
fi
|
|
info "Verified checksum: $(basename "$file")"
|
|
}
|
|
|
|
download_release_archive() {
|
|
local tmp_dir="$1"
|
|
local preferred_asset="$2"
|
|
local fallback_asset="mesh-bundle.tar.gz"
|
|
local asset
|
|
local url
|
|
local archive
|
|
|
|
for asset in "$preferred_asset" "$fallback_asset"; do
|
|
url="$(release_url "$asset")"
|
|
archive="$tmp_dir/$asset"
|
|
if ! curl -fsSL "$url" -o "$archive"; then
|
|
rm -f -- "$archive"
|
|
continue
|
|
fi
|
|
verify_downloaded_file "$archive" "$url"
|
|
DOWNLOADED_ASSET="$asset"
|
|
DOWNLOADED_ARCHIVE="$archive"
|
|
if [[ "$asset" == "$fallback_asset" ]]; then
|
|
info "Using runtime-enabled mesh bundle fallback: $fallback_asset"
|
|
fi
|
|
return 0
|
|
done
|
|
|
|
echo "error: could not download release archive for $preferred_asset or $fallback_asset" >&2
|
|
return 1
|
|
}
|
|
|
|
stale_binary_names() {
|
|
cat <<'EOF'
|
|
rpc-server
|
|
llama-server
|
|
llama-moe-split
|
|
rpc-server-cpu
|
|
llama-server-cpu
|
|
rpc-server-cuda
|
|
llama-server-cuda
|
|
rpc-server-rocm
|
|
llama-server-rocm
|
|
rpc-server-vulkan
|
|
llama-server-vulkan
|
|
rpc-server-metal
|
|
llama-server-metal
|
|
EOF
|
|
}
|
|
|
|
remove_stale_binaries() {
|
|
mkdir -p "$INSTALL_DIR"
|
|
local name
|
|
while IFS= read -r name; do
|
|
[[ -n "$name" ]] || continue
|
|
rm -f "$INSTALL_DIR/$name"
|
|
done < <(stale_binary_names)
|
|
}
|
|
|
|
restore_bundle_install() {
|
|
local backup_dir="$1"
|
|
shift
|
|
local installed_name
|
|
for installed_name in "$@"; do
|
|
rm -rf -- "${INSTALL_DIR:?}/$installed_name"
|
|
done
|
|
|
|
local backup_item
|
|
while IFS= read -r -d '' backup_item; do
|
|
mv "$backup_item" "$INSTALL_DIR/$(basename "$backup_item")"
|
|
done < <(find "$backup_dir" -mindepth 1 -maxdepth 1 -print0)
|
|
}
|
|
|
|
validate_bundle() {
|
|
local bundle_dir="$1"
|
|
local binary="$bundle_dir/mesh-llm"
|
|
if [[ ! -f "$binary" ]]; then
|
|
echo "error: release archive did not contain an installable mesh-llm binary" >&2
|
|
return 1
|
|
fi
|
|
if [[ ! -x "$binary" ]]; then
|
|
echo "error: mesh-llm binary in release archive is not executable" >&2
|
|
return 1
|
|
fi
|
|
local has_product_manifest=0
|
|
local has_native_runtimes=0
|
|
[[ -f "$bundle_dir/product-manifest.json" ]] && has_product_manifest=1
|
|
[[ -d "$bundle_dir/native-runtimes" ]] && has_native_runtimes=1
|
|
if [[ "$has_product_manifest" != "$has_native_runtimes" ]]; then
|
|
echo "error: release archive has an incomplete composed native runtime bundle" >&2
|
|
return 1
|
|
fi
|
|
if [[ "$has_product_manifest" == 0 ]]; then
|
|
# Pinned/pre-contract release assets remain installable. Their hosts
|
|
# either carry the legacy static runtime or use their own historical
|
|
# runtime-install flow; requiring a v2 product manifest here would make
|
|
# a previously published, checksum-valid release unrecoverable.
|
|
local version_output legacy_version
|
|
if ! version_output="$("$binary" --version 2>&1)"; then
|
|
echo "error: cannot verify legacy release version before install: $version_output" >&2
|
|
return 1
|
|
fi
|
|
legacy_version="$(printf '%s\n' "$version_output" | sed -nE 's/^mesh-llm[[:space:]]+([0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?).*$/\1/p' | head -n 1)"
|
|
if [[ -z "$legacy_version" ]]; then
|
|
echo "error: release archive omitted the composed runtime bundle and its host version is not parseable" >&2
|
|
return 1
|
|
fi
|
|
if version_at_least "$legacy_version" "$COMPOSED_PRODUCT_MIN_VERSION"; then
|
|
echo "error: MeshLLM $legacy_version requires product-manifest.json and native-runtimes (contract floor: v$COMPOSED_PRODUCT_MIN_VERSION)" >&2
|
|
return 1
|
|
fi
|
|
warn "installing supported legacy MeshLLM $legacy_version archive without a composed native runtime bundle"
|
|
fi
|
|
}
|
|
|
|
version_at_least() {
|
|
local candidate="${1%%-*}"
|
|
local floor="${2%%-*}"
|
|
local c_major c_minor c_patch f_major f_minor f_patch
|
|
IFS=. read -r c_major c_minor c_patch <<< "$candidate"
|
|
IFS=. read -r f_major f_minor f_patch <<< "$floor"
|
|
((10#$c_major > 10#$f_major)) && return 0
|
|
((10#$c_major < 10#$f_major)) && return 1
|
|
((10#$c_minor > 10#$f_minor)) && return 0
|
|
((10#$c_minor < 10#$f_minor)) && return 1
|
|
((10#$c_patch >= 10#$f_patch))
|
|
}
|
|
|
|
install_bundle() {
|
|
local bundle_dir="$1"
|
|
validate_bundle "$bundle_dir"
|
|
|
|
mkdir -p "$INSTALL_DIR"
|
|
local staging_dir
|
|
local backup_dir
|
|
if ! staging_dir="$(mktemp -d "$INSTALL_DIR/.mesh-llm-stage.XXXXXX")"; then
|
|
return 1
|
|
fi
|
|
if ! backup_dir="$(mktemp -d "$INSTALL_DIR/.mesh-llm-backup.XXXXXX")"; then
|
|
rm -rf -- "$staging_dir"
|
|
return 1
|
|
fi
|
|
if ! cp -R "$bundle_dir/." "$staging_dir/"; then
|
|
rm -rf -- "$staging_dir" "$backup_dir"
|
|
return 1
|
|
fi
|
|
if ! validate_bundle "$staging_dir"; then
|
|
rm -rf -- "$staging_dir" "$backup_dir"
|
|
return 1
|
|
fi
|
|
|
|
local -a installed_names=()
|
|
trap 'restore_bundle_install "$backup_dir" ${installed_names[@]+"${installed_names[@]}"}; rm -rf -- "$staging_dir" "$backup_dir"; trap - INT TERM; exit 130' INT TERM
|
|
local staged_item
|
|
local item_name
|
|
local destination
|
|
while IFS= read -r -d '' staged_item; do
|
|
item_name="$(basename "$staged_item")"
|
|
destination="$INSTALL_DIR/$item_name"
|
|
if [[ -e "$destination" || -L "$destination" ]]; then
|
|
if ! mv "$destination" "$backup_dir/$item_name"; then
|
|
restore_bundle_install \
|
|
"$backup_dir" \
|
|
${installed_names[@]+"${installed_names[@]}"}
|
|
rm -rf -- "$staging_dir" "$backup_dir"
|
|
trap - INT TERM
|
|
return 1
|
|
fi
|
|
fi
|
|
if ! mv "$staged_item" "$destination"; then
|
|
restore_bundle_install \
|
|
"$backup_dir" \
|
|
${installed_names[@]+"${installed_names[@]}"}
|
|
rm -rf -- "$staging_dir" "$backup_dir"
|
|
trap - INT TERM
|
|
return 1
|
|
fi
|
|
installed_names+=("$item_name")
|
|
done < <(find "$staging_dir" -mindepth 1 -maxdepth 1 -print0)
|
|
|
|
trap - INT TERM
|
|
rm -rf -- "$staging_dir" "$backup_dir"
|
|
remove_stale_binaries
|
|
}
|
|
|
|
shell_is_interactive() {
|
|
if [[ -n "${MESH_LLM_TEST_INTERACTIVE:-}" ]]; then
|
|
bool_is_true "$MESH_LLM_TEST_INTERACTIVE"
|
|
return
|
|
fi
|
|
[[ -t 0 && -t 1 ]]
|
|
}
|
|
|
|
setup_command_string() {
|
|
local -a command=("$INSTALL_DIR/mesh-llm" setup)
|
|
local token
|
|
local rendered=""
|
|
if ((${#SETUP_ARGS[@]} > 0)); then
|
|
command+=("${SETUP_ARGS[@]}")
|
|
fi
|
|
for token in "${command[@]}"; do
|
|
printf -v rendered '%s%q ' "$rendered" "$token"
|
|
done
|
|
printf '%s\n' "${rendered% }"
|
|
}
|
|
|
|
run_or_print_setup() {
|
|
local binary="$INSTALL_DIR/mesh-llm"
|
|
local command
|
|
command="$(setup_command_string)"
|
|
|
|
if (( AUTO_SETUP == 1 )) && shell_is_interactive; then
|
|
if bool_is_true "$INSTALL_VERBOSE"; then
|
|
echo
|
|
echo "Running post-install setup: $command"
|
|
else
|
|
echo
|
|
fi
|
|
if ((${#SETUP_ARGS[@]} > 0)); then
|
|
"$binary" setup "${SETUP_ARGS[@]}"
|
|
else
|
|
"$binary" setup
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
echo "Run this next: $command"
|
|
}
|
|
|
|
main() {
|
|
parse_args "$@"
|
|
apply_legacy_env_compat
|
|
need_cmd curl
|
|
need_cmd tar
|
|
need_cmd mktemp
|
|
|
|
local asset
|
|
asset="$(asset_name "$(recommended_flavor)")"
|
|
|
|
local tmp_dir
|
|
tmp_dir="$(mktemp -d)"
|
|
local tmp_dir_escaped
|
|
printf -v tmp_dir_escaped '%q' "$tmp_dir"
|
|
trap "rm -rf -- $tmp_dir_escaped" EXIT
|
|
|
|
info "Release channel: $(bool_is_true "$INSTALL_PRERELEASE" && echo prerelease || echo stable)"
|
|
if ! bool_is_true "$INSTALL_VERBOSE"; then
|
|
echo "↓ Fetching mesh-llm release..."
|
|
fi
|
|
download_release_archive "$tmp_dir" "$asset"
|
|
|
|
tar -xzf "$DOWNLOADED_ARCHIVE" -C "$tmp_dir"
|
|
if [[ ! -d "$tmp_dir/mesh-bundle" ]]; then
|
|
echo "error: release archive did not contain mesh-bundle/" >&2
|
|
exit 1
|
|
fi
|
|
|
|
install_bundle "$tmp_dir/mesh-bundle"
|
|
if bool_is_true "$INSTALL_VERBOSE"; then
|
|
echo "Installed $DOWNLOADED_ASSET to $INSTALL_DIR"
|
|
else
|
|
printf '%s Installed mesh-llm to %s\n' "$(style_ok "✓")" "$INSTALL_DIR"
|
|
fi
|
|
|
|
if ! path_contains_install_dir; then
|
|
echo
|
|
echo "$INSTALL_DIR is not on your PATH."
|
|
echo "Add it with one of these commands:"
|
|
echo
|
|
echo "bash:"
|
|
echo " echo 'export PATH=\"$INSTALL_DIR:\$PATH\"' >> ~/.bashrc"
|
|
echo " source ~/.bashrc"
|
|
echo
|
|
echo "zsh:"
|
|
echo " echo 'export PATH=\"$INSTALL_DIR:\$PATH\"' >> ~/.zshrc"
|
|
echo " source ~/.zshrc"
|
|
fi
|
|
|
|
run_or_print_setup
|
|
}
|
|
|
|
if [[ "${BASH_SOURCE[0]-}" == "$0" || ( -z "${BASH_SOURCE[0]-}" && "$0" == "bash" ) ]]; then
|
|
main "$@"
|
|
fi
|