#!/usr/bin/env bash # # OpenEng AI Engine - one-line installer (Linux & macOS; Windows via WSL / Git Bash). # # Downloads a prebuilt `openeng` engine binary from openeng.ai and installs it. # The engine runs open-weight models on your machine and dials out to reach the # web app (app.openeng.ai) - it opens no inbound port and is not a server. One # engine per machine. No source code, no Rust toolchain, no build step - just the binary. # # curl -fsSL https://openeng.ai/engine/install.sh | bash # # Tunables (environment): # OPENENG_ENGINE_URL base URL for binaries (default: https://openeng.ai/engine/bin) # OPENENG_BIN_DIR install directory (default: ~/.local/bin) # OPENENG_NO_MODELS=1 skip the default model pull (serve downloads what a config needs) # # NOTE: keep this script pure ASCII (macOS bash 3.2 + set -u, see the cli installer). # set -eu if [ -t 1 ]; then esc=$(printf '\033') BLUE="${esc}[36m"; GRN="${esc}[32m"; RED="${esc}[31m"; DIM="${esc}[2m"; NC="${esc}[0m" else BLUE=''; GRN=''; RED=''; DIM=''; NC='' fi say() { printf "${BLUE}==>${NC} %s\n" "$1"; } ok() { printf "${GRN}ok:${NC} %s\n" "$1"; } warn() { printf "${RED}!${NC} %s\n" "$1" >&2; } die() { printf "${RED}error: %s${NC}\n" "$1" >&2; exit 1; } have() { command -v "$1" >/dev/null 2>&1; } OPENENG_ENGINE_URL="${OPENENG_ENGINE_URL:-https://openeng.ai/engine/bin}" OPENENG_BIN_DIR="${OPENENG_BIN_DIR:-$HOME/.local/bin}" printf "${BLUE}+--- OpenEng AI Engine installer --------------+${NC}\n" # -- detect platform --------------------------------------------------------- os="$(uname -s 2>/dev/null || echo unknown)" arch="$(uname -m 2>/dev/null || echo unknown)" case "$os" in Linux) OS=linux ;; Darwin) OS=darwin ;; MINGW*|MSYS*|CYGWIN*) die "Windows shell detected. Use PowerShell: irm https://openeng.ai/engine/install.ps1 | iex" ;; *) die "unsupported OS '${os}'." ;; esac case "$arch" in x86_64|amd64) ARCH=x86_64 ;; arm64|aarch64) ARCH=aarch64 ;; *) die "unsupported architecture '${arch}'." ;; esac ASSET="openeng-${OS}-${ARCH}" URL="${OPENENG_ENGINE_URL}/${ASSET}" # -- prerequisites ----------------------------------------------------------- have curl || die "curl is required." # -- download ---------------------------------------------------------------- say "Downloading openeng (${OS}/${ARCH}) ..." TMP="$(mktemp 2>/dev/null || echo "${TMPDIR:-/tmp}/openeng.$$")" curl -fSL --proto '=https' --tlsv1.2 "$URL" -o "$TMP" \ || die "could not download ${URL} -- no engine binary published for ${OS}/${ARCH} yet." # A static host may answer a missing file with its SPA/HTML page (HTTP 200). Real # binaries never start with '<', so reject an HTML page up front. if [ "$(head -c 1 "$TMP" 2>/dev/null)" = "<" ]; then rm -f "$TMP" die "no engine binary published for ${OS}/${ARCH} yet (the URL returned a web page)." fi # -- verify checksum (enforced only if a real .sha256 is published) ---------- if curl -fsSL "${URL}.sha256" -o "${TMP}.sha256" 2>/dev/null; then WANT="$(cut -d' ' -f1 < "${TMP}.sha256" 2>/dev/null || echo)" rm -f "${TMP}.sha256" if printf '%s' "$WANT" | grep -Eq '^[0-9a-fA-F]{64}$'; then if have sha256sum; then GOT="$(sha256sum "$TMP" | cut -d' ' -f1)" elif have shasum; then GOT="$(shasum -a 256 "$TMP" | cut -d' ' -f1)" else GOT=""; fi if [ -n "$GOT" ] && [ "$GOT" != "$WANT" ]; then rm -f "$TMP"; die "checksum mismatch for ${ASSET} (got ${GOT}, want ${WANT})." fi [ -n "$GOT" ] && ok "checksum verified" fi fi chmod 0755 "$TMP" if [ "$OS" = "darwin" ]; then xattr -d com.apple.quarantine "$TMP" >/dev/null 2>&1 || true; fi # Sanity: the engine binary runs (it prints usage on --help). "$TMP" --help >/dev/null 2>&1 || { rm -f "$TMP"; die "the downloaded engine did not run (wrong platform or corrupt download)."; } # -- install on PATH --------------------------------------------------------- mkdir -p "$OPENENG_BIN_DIR" mv -f "$TMP" "$OPENENG_BIN_DIR/openeng" chmod 0755 "$OPENENG_BIN_DIR/openeng" ok "installed ${OPENENG_BIN_DIR}/openeng" case ":$PATH:" in *":$OPENENG_BIN_DIR:"*) ;; *) LINE="export PATH=\"$OPENENG_BIN_DIR:\$PATH\"" for rc in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do if [ -f "$rc" ] && ! grep -qF "$OPENENG_BIN_DIR" "$rc" 2>/dev/null; then printf '\n# OpenEng Engine\n%s\n' "$LINE" >> "$rc" fi done warn "Added ${OPENENG_BIN_DIR} to your shell rc - run \`${LINE}\` or open a new terminal." ;; esac mkdir -p "$HOME/.openeng" ok "agent root at ${HOME}/.openeng" # -- Default on-device model (GGUF) ------------------------------------------ # The engine downloads whatever a config references on `serve --config`, but pulling # the default chat weight now means `openeng serve` answers a turn out of the box. if [ "${OPENENG_NO_MODELS:-0}" = "1" ]; then say "skipping model pull (OPENENG_NO_MODELS=1) - serve downloads what a config needs" else say "Pulling the default chat model (a few GB; can take a while)..." "$OPENENG_BIN_DIR/openeng" pull chat || warn "model pull did not finish - run 'openeng pull chat' later" fi printf "\n" ok "Done. Start the engine: ${BLUE}openeng serve${NC} ${DIM}(then open https://app.openeng.ai — it connects automatically)${NC}"