#!/bin/sh # LlamaForge one-line installer for Linux and macOS. # # curl -fsSL https://raw.githubusercontent.com/dadwritestech/LlamaForge/master/install.sh | sh # # No git, no sudo, no build tools. It: # 1. finds Python 3.10+ (the backend is pure stdlib - nothing to pip install) # 2. downloads the latest LlamaForge release and lays it over the install # dir (backend/appinstall.py: your config, models and engines are kept) # 3. adds a `llamaforge` command, plus an app-menu entry (Linux) or # ~/Applications/LlamaForge.app (macOS) # 4. starts LlamaForge; the panel then offers the official llama.cpp build # for your machine in one click # Re-run it (or `llamaforge update`) any time to update. # # Options (environment variables): # LLAMAFORGE_HOME install dir (default ~/.local/share/llamaforge) # LLAMAFORGE_REF release tag or branch (default: latest release) # LLAMAFORGE_ARCHIVE install from a local .tar.gz/.zip instead of downloading # LLAMAFORGE_NO_LAUNCH don't start LlamaForge at the end # LLAMAFORGE_NO_SHORTCUTS skip the command, menu entry and app bundle # LLAMAFORGE_NO_STOP don't stop a running copy before updating it set -eu REPO="dadwritestech/LlamaForge" DEST="${LLAMAFORGE_HOME:-${XDG_DATA_HOME:-$HOME/.local/share}/llamaforge}" BIN_DIR="$HOME/.local/bin" say() { printf ' %s\n' "$*"; } die() { printf ' error: %s\n' "$*" >&2; exit 1; } fetch() { # fetch URL FILE if command -v curl >/dev/null 2>&1; then curl -fsSL "$1" -o "$2" elif command -v wget >/dev/null 2>&1; then wget -qO "$2" "$1" else die "need curl or wget"; fi } latest_tag() { # the tag github.com//releases/latest redirects to if command -v curl >/dev/null 2>&1; then url="$(curl -fsSIL -o /dev/null -w '%{url_effective}' "https://github.com/$REPO/releases/latest" 2>/dev/null)" else url="$(wget -S --spider "https://github.com/$REPO/releases/latest" 2>&1 | sed -n 's/^ *[Ll]ocation: *\([^ ]*\).*/\1/p' | tail -n 1)" fi url="$(printf '%s' "$url" | tr -d '\r')" case "$url" in */releases/tag/?*) printf '%s\n' "${url##*/}" ;; esac } printf '\n \033[33mLlamaForge installer\033[0m\n' say "-> $DEST" echo mkdir -p "$DEST" TMP="$(mktemp -d 2>/dev/null || mktemp -d -t lfinstall)" trap 'rm -rf "$TMP"' EXIT INT TERM # ---- 1. Python ---------------------------------------------------------- PY="" for cand in python3.13 python3.12 python3.11 python3.10 python3 python; do p="$(command -v "$cand" 2>/dev/null || true)" [ -n "$p" ] || continue if "$p" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 10) else 1)' 2>/dev/null; then PY="$p"; break fi done if [ -z "$PY" ]; then if [ "$(uname -s)" = "Darwin" ]; then die "Python 3.10+ not found. Install it with 'brew install python@3.12' (or from python.org), then re-run." else die "Python 3.10+ not found. Install it with your package manager (e.g. 'sudo apt install python3'), then re-run." fi fi say "[1/4] Python: $PY" # ---- 2. LlamaForge ------------------------------------------------------- if [ -n "${LLAMAFORGE_ARCHIVE:-}" ]; then ARCHIVE="$LLAMAFORGE_ARCHIVE" VERSION="${LLAMAFORGE_REF:-local}" say "[2/4] LlamaForge: from $ARCHIVE" else REF="${LLAMAFORGE_REF:-}" # The latest release's tag, from the redirect github.com//releases/latest # answers with - the API is rate-limited (60/h per IP, shared behind NAT/VPN), # so it is only the second try. Never fall back to master: a copy installed # from a branch records no version and is never offered an update again. [ -n "$REF" ] || REF="$(latest_tag)" if [ -z "$REF" ]; then fetch "https://api.github.com/repos/$REPO/releases/latest" "$TMP/latest.json" 2>/dev/null || true REF="$("$PY" -c 'import json,sys try: print(json.load(open(sys.argv[1]))["tag_name"]) except Exception: pass' "$TMP/latest.json" 2>/dev/null)" fi [ -n "$REF" ] || die "could not find the latest LlamaForge release (GitHub unreachable or rate-limited). Retry in a few minutes, or pin one: LLAMAFORGE_REF=v0.16.4" case "$REF" in v[0-9]*) KIND=tags ;; *) KIND=heads ;; esac VERSION="$REF" ARCHIVE="$TMP/llamaforge.tar.gz" say "[2/4] LlamaForge: downloading $REF" fetch "https://github.com/$REPO/archive/refs/$KIND/$REF.tar.gz" "$ARCHIVE" fi mkdir -p "$TMP/src" case "$ARCHIVE" in *.zip) "$PY" -m zipfile -e "$ARCHIVE" "$TMP/src" ;; *) "$PY" -m tarfile -e "$ARCHIVE" "$TMP/src" ;; esac if [ -f "$DEST/stop.sh" ] && [ -f "$DEST/config.json" ] && [ -z "${LLAMAFORGE_NO_STOP:-}" ]; then say " stopping the running copy to update it" bash "$DEST/stop.sh" >/dev/null 2>&1 || true fi INSTALLER="$(find "$TMP/src" -path '*/backend/appinstall.py' | head -n 1)" [ -n "$INSTALLER" ] || die "that archive is not a LlamaForge release" "$PY" "$INSTALLER" --from "$TMP/src" --to "$DEST" --version "$VERSION" chmod +x "$DEST"/*.sh 2>/dev/null || true printf '%s\n' "$PY" > "$DEST/.lf-python" # run.sh/stop.sh use this Python # ---- 3. launchers -------------------------------------------------------- if [ -z "${LLAMAFORGE_NO_SHORTCUTS:-}" ]; then say "[3/4] llamaforge command + app launcher" mkdir -p "$BIN_DIR" cat > "$BIN_DIR/llamaforge" < "$APP/MacOS/LlamaForge" chmod +x "$APP/MacOS/LlamaForge" cat > "$APP/Info.plist" < CFBundleNameLlamaForge CFBundleIdentifierio.github.dadwritestech.llamaforge CFBundleExecutableLlamaForge CFBundleIconFilellamaforge CFBundlePackageTypeAPPL CFBundleShortVersionString${VERSION#v} LSUIElement EOF else APPS="${XDG_DATA_HOME:-$HOME/.local/share}/applications" mkdir -p "$APPS" cat > "$APPS/llamaforge.desktop" <