#!/bin/sh # # get-synapcores-ce.sh — one-line remote installer for SynapCores # Community Edition. # # Hosted at: https://get.synapcores.com # # Usage: # curl -fsSL https://get.synapcores.com/install.sh | sh # # Quick userland test (no sudo, no system service, no /opt or /etc): # curl -fsSL https://get.synapcores.com/install.sh | SYNAPCORES_USER_INSTALL=1 sh # # Or, to pin a specific release: # curl -fsSL https://get.synapcores.com/install.sh | SYNAPCORES_VERSION=v1.2.0 sh # # Environment variables: # SYNAPCORES_USER_INSTALL=1 Install for the current user only: # binary → ~/.local/bin, config + data → # ~/.synapcores, no system user, no # systemd unit. Good for kicking the # tires without touching system state. # SYNAPCORES_PREFIX= Override the binary install dir # (default: /usr/local/bin in system # mode, ~/.local/bin in user mode). # SYNAPCORES_BINARY_ONLY=1 Drop the binary and stop. Operator # handles config and service themselves. # SYNAPCORES_VERSION= Pin a specific release tag. # SYNAPCORES_NONINTERACTIVE=1 Skip prompts; assume safe defaults. # SYNAPCORES_WITH_NLI=1 Install and enable semantic verification. # SYNAPCORES_NLI_MODEL=... Curated alias (default nli-default; nli-xsmall optional). # SYNAPCORES_NO_MODEL_DOWNLOAD=1 Use previously imported models, without pulling. # # Written in POSIX sh on purpose: when invoked via `curl ... | sh`, # Debian/Ubuntu's /bin/sh is dash, not bash. Bash-only constructs like # `[[ ... ]]`, `set -o pipefail`, `local`, `$EUID`, and arrays MUST NOT # appear in this file. If you need them, branch out into a helper that # bash can run after the binary is on disk. # # What it does: # 1. Detects the OS and architecture # 2. Resolves the latest GitHub release tag (or honors $SYNAPCORES_VERSION) # 3. Downloads the matching binary tarball + checksum # 4. Verifies the checksum # 5. Drops the binary at /usr/local/bin/synapcores (system mode, # requires sudo) or ~/.local/bin/synapcores (user mode, no sudo). # 6. Hands off to install-ce.sh for setup (system: useradd, /opt # layout, /etc/systemd unit; user: ~/.synapcores layout, no # systemd unit). Skipped entirely if SYNAPCORES_BINARY_ONLY is set. # # Requirements: # - curl # - tar # - sha256sum (or shasum -a 256) set -eu # --------------------------------------------------------------------- # Configuration # --------------------------------------------------------------------- GITHUB_REPO="${SYNAPCORES_REPO:-SynapCores/synapcores-releases}" RELEASE_BASE="https://github.com/${GITHUB_REPO}/releases" PINNED_VERSION="${SYNAPCORES_VERSION:-}" BINARY_ONLY="${SYNAPCORES_BINARY_ONLY:-}" # User-mode install: opt in via SYNAPCORES_USER_INSTALL=1, or via # interactive prompt (Linux only) when stdin is a tty and the operator # hasn't set the env var. Default = system mode. USER_INSTALL="${SYNAPCORES_USER_INSTALL:-}" # INSTALL_PREFIX default depends on USER_INSTALL — resolved after the # (possibly interactive) prompt below so we honor a late opt-in. INSTALL_PREFIX="${SYNAPCORES_PREFIX:-}" NLI_OPT_IN="${SYNAPCORES_WITH_NLI:-0}" NLI_MODEL="${SYNAPCORES_NLI_MODEL:-nli-default}" # --------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------- log() { printf '\033[1;34m[get-synapcores]\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[get-synapcores]\033[0m %s\n' "$*" >&2; } fail() { printf '\033[1;31m[get-synapcores]\033[0m %s\n' "$*" >&2; exit 1; } case "$NLI_OPT_IN" in 1|true|yes) NLI_OPT_IN=1 ;; 0|false|no|"") NLI_OPT_IN=0 ;; *) fail "SYNAPCORES_WITH_NLI must be 0 or 1" ;; esac case "$NLI_MODEL" in nli-default|nli-small|nli-xsmall) ;; *) fail "Unknown NLI model; use nli-default or nli-xsmall" ;; esac require() { command -v "$1" >/dev/null 2>&1 || fail "missing required tool: $1" } require curl require tar if command -v sha256sum >/dev/null 2>&1; then SHA256_TOOL="sha256sum" elif command -v shasum >/dev/null 2>&1; then SHA256_TOOL="shasum -a 256" else fail "missing required tool: sha256sum (or shasum)" fi # --------------------------------------------------------------------- # Platform detection # --------------------------------------------------------------------- # detect_platform: prints "-" to stdout. Exits non-zero if # the platform is unsupported. POSIX sh has no `local`, so we use # uppercase variable names to make it obvious these survive the call. detect_platform() { DETECTED_OS=$(uname -s) DETECTED_ARCH=$(uname -m) case "$DETECTED_OS" in Linux) DETECTED_OS=linux ;; Darwin) DETECTED_OS=darwin ;; *) fail "unsupported OS: $DETECTED_OS (CE binaries are published for Linux and macOS)" ;; esac case "$DETECTED_ARCH" in x86_64|amd64) DETECTED_ARCH=x86_64 ;; aarch64|arm64) DETECTED_ARCH=aarch64 ;; *) fail "unsupported architecture: $DETECTED_ARCH" ;; esac printf '%s-%s\n' "$DETECTED_OS" "$DETECTED_ARCH" } # perf-wave-10B: detect_gpu — pick between CPU and CUDA tarball variants. # # Sets DETECTED_GPU to "cpu" or "cuda". Override via SYNAPCORES_GPU env # var: # SYNAPCORES_GPU=cpu curl ... | sh (force CPU even on a GPU host) # SYNAPCORES_GPU=cuda curl ... | sh (skip detection, demand CUDA) # # CUDA variant is published only for linux-x86_64. macOS / aarch64 # always resolve to "cpu" — Apple Silicon uses Metal (separate codepath), # and we don't ship aarch64 CUDA tarballs. detect_gpu() { DETECTED_GPU=cpu # Allow explicit override before we run any detection. if [ -n "${SYNAPCORES_GPU:-}" ]; then DETECTED_GPU="$SYNAPCORES_GPU" log "GPU override: SYNAPCORES_GPU=${SYNAPCORES_GPU}" return fi # No CUDA build for macOS or for aarch64 — Apple uses Metal, and # we don't currently publish aarch64 CUDA tarballs. if [ "$DETECTED_OS" = "darwin" ]; then return; fi if [ "$DETECTED_ARCH" = "aarch64" ]; then return; fi # Primary signal: nvidia-smi works (driver is loaded + GPU visible). if command -v nvidia-smi >/dev/null 2>&1; then if nvidia-smi >/dev/null 2>&1; then DETECTED_GPU=cuda log "NVIDIA GPU detected via nvidia-smi" return fi fi # Fallback: libcuda.so.1 present (covers headless servers without # nvidia-smi installed but with the runtime driver lib in place). if [ -f /usr/lib/x86_64-linux-gnu/libcuda.so.1 ] \ || [ -f /usr/lib64/libcuda.so.1 ] \ || [ -f /usr/lib/libcuda.so.1 ]; then DETECTED_GPU=cuda log "CUDA runtime library (libcuda.so.1) detected" return fi log "No CUDA detected — using CPU build" } # detect_intel_mac_unsupported: bail out on Intel Macs (no native binary # published). Split out from detect_platform() because POSIX `$(...)` runs # the function in a subshell and `exit` from there only kills the subshell. detect_intel_mac_unsupported() { # Intel Mac native binary is not published as of v1.3.0-ce. The # source migration to ffmpeg-next 7 unblocked the build, but # GitHub-hosted macos-13 runner availability has been unreliable # on personal-account repos (multi-hour queue waits with no # allocation). Apple Silicon (macos-14) builds reliably, so we # ship Apple Silicon native + recommend Docker for Intel Macs. if [ "$DETECTED_OS" = "darwin" ] && [ "$DETECTED_ARCH" = "x86_64" ]; then cat >&2 <&2 <&2 <&2 <&2 <&2 <) to extract the tag. PINNED_VERSION=$( curl -fsS -o /dev/null -w '%{redirect_url}' "${RELEASE_BASE}/latest" \ | sed 's@^.*/tag/@@' ) [ -n "$PINNED_VERSION" ] || fail "could not resolve latest release" fi log "Version: $PINNED_VERSION" # --------------------------------------------------------------------- # Anonymous install counter ping (no PII, opt-out via COUNTER_OPT_OUT=1) # --------------------------------------------------------------------- # One fire-and-forget GET after platform + version are resolved and # before the download starts. curl-pipe-sh leaves no trace on GitHub # Pages, so this is the only way we can count CLI installs. Sends only # the version tag, OS and arch (already public in the tarball URL). # 5-second timeout; `|| true` means a down counter never blocks install. if [ -z "${COUNTER_OPT_OUT:-}" ]; then _counter_url="https://synapcores.com/api/install-counter?v=${PINNED_VERSION}&os=${DETECTED_OS}&arch=${DETECTED_ARCH}&channel=cli" curl -fsSL --max-time 5 -o /dev/null "$_counter_url" 2>/dev/null || true fi # --------------------------------------------------------------------- # Download # --------------------------------------------------------------------- # DISTRO_TAG is "" on Ubuntu 22.04 / Debian 12 / macOS / unknown distros # (FFmpeg 4 / native Mac tarball) and "-ubuntu24" on Ubuntu 24.04 / Debian 13 # (FFmpeg 6 tarball variant). Set by check_distro() above. # # perf-wave-10B: GPU_TAG is "-cuda" when DETECTED_GPU=cuda (libcuda # present or explicit override) and "" otherwise. The CUDA variant is # only published for linux-x86_64; detect_gpu() already short-circuits # to "cpu" elsewhere, so this concat is safe everywhere. GPU_TAG="" if [ "$DETECTED_GPU" = "cuda" ]; then GPU_TAG="-cuda" # Not every release ships a CUDA tarball — the linux-x86_64-cuda # build OOM-kills the 16 GB GitHub-hosted runner during the LTO # link step (see release-ce.yml note on CARGO_PROFILE_RELEASE_LTO). # When the GPU choice was auto-detected, probe the release asset # before committing to the CUDA tarball; on 404, fall back to CPU. # An explicit SYNAPCORES_GPU=cuda override is honored — we let the # download fail loudly so the operator notices instead of silently # getting a CPU binary. if [ "$GPU_AUTO_DETECTED" = "true" ]; then CUDA_TARBALL="synapcores-ce-${PINNED_VERSION}-${PLATFORM}${DISTRO_TAG}-cuda.tar.gz" CUDA_URL="${RELEASE_BASE}/download/${PINNED_VERSION}/${CUDA_TARBALL}" if curl -sfI "$CUDA_URL" >/dev/null 2>&1; then log "CUDA tarball confirmed for ${PINNED_VERSION}" else warn "CUDA tarball not published for ${PINNED_VERSION} — falling back to CPU build" DETECTED_GPU=cpu GPU_TAG="" fi fi fi TARBALL="synapcores-ce-${PINNED_VERSION}-${PLATFORM}${DISTRO_TAG}${GPU_TAG}.tar.gz" TARBALL_URL="${RELEASE_BASE}/download/${PINNED_VERSION}/${TARBALL}" CHECKSUM_URL="${TARBALL_URL}.sha256" WORK_DIR=$(mktemp -d) trap 'rm -rf "$WORK_DIR"' EXIT INT TERM log "Downloading ${TARBALL}..." curl -fsSL "$TARBALL_URL" -o "${WORK_DIR}/${TARBALL}" \ || fail "download failed: $TARBALL_URL" log "Downloading checksum..." curl -fsSL "$CHECKSUM_URL" -o "${WORK_DIR}/${TARBALL}.sha256" \ || fail "checksum download failed: $CHECKSUM_URL" # --------------------------------------------------------------------- # Verify # --------------------------------------------------------------------- log "Verifying checksum..." ( cd "$WORK_DIR" EXPECTED=$(awk '{print $1}' "${TARBALL}.sha256") ACTUAL=$($SHA256_TOOL "${TARBALL}" | awk '{print $1}') if [ "$EXPECTED" != "$ACTUAL" ]; then fail "checksum mismatch: expected $EXPECTED got $ACTUAL" fi ) log "Checksum OK." # --------------------------------------------------------------------- # Extract # --------------------------------------------------------------------- log "Extracting..." tar -xzf "${WORK_DIR}/${TARBALL}" -C "$WORK_DIR" # The workflow packages the binary inside a single top-level dir: # synapcores-ce--/synapcores # Locate it without hardcoding the dir name so the script keeps # working if the packaging convention changes. BINARY_SRC=$(find "$WORK_DIR" -maxdepth 3 -type f -name synapcores -perm -u+x 2>/dev/null | head -1) if [ -z "$BINARY_SRC" ] || [ ! -x "$BINARY_SRC" ]; then fail "extracted archive does not contain a 'synapcores' executable" fi # --------------------------------------------------------------------- # Install binary # --------------------------------------------------------------------- # POSIX sh has no $EUID — use `id -u`. Also POSIX has no arrays, so we # use SUDO_PREFIX as a (possibly empty) string that is word-split into # argv when the install command runs. # # User mode never needs sudo — the binary lands in the user's own # ~/.local/bin (or whatever they set via SYNAPCORES_PREFIX). if [ -n "$USER_INSTALL" ] || [ "$(id -u)" -eq 0 ]; then SUDO_PREFIX="" else log "Installing to ${INSTALL_PREFIX} requires sudo..." SUDO_PREFIX="sudo" fi # shellcheck disable=SC2086 $SUDO_PREFIX install -m 0755 "$BINARY_SRC" "${INSTALL_PREFIX}/synapcores" \ || fail "binary install failed" log "Installed: ${INSTALL_PREFIX}/synapcores" # ONNX Runtime is loaded relative to the installed executable. Preserve it # before WORK_DIR is deleted, including macOS, user and binary-only installs. # Existing tarballs without an ONNX manifest keep their previous capability; # a new manifest with missing runtime/attribution is a broken package. ONNX_PACKAGE_DIR="$(dirname "$BINARY_SRC")" if [ -f "${ONNX_PACKAGE_DIR}/onnx-runtime-manifest.json" ]; then case "$DETECTED_OS" in linux) ONNX_LIBRARY_NAME="libonnxruntime.so" ;; darwin) ONNX_LIBRARY_NAME="libonnxruntime.dylib" ;; esac ONNX_RUNTIME_DIR="${INSTALL_PREFIX}/lib/synapcores" ONNX_NOTICE_DIR="${INSTALL_PREFIX}/share/synapcores/licenses/onnxruntime" [ -r "${ONNX_PACKAGE_DIR}/lib/${ONNX_LIBRARY_NAME}" ] \ || fail "ONNX package is missing ${ONNX_LIBRARY_NAME}" for ONNX_NOTICE in LICENSE ThirdPartyNotices.txt VERSION_NUMBER; do [ -r "${ONNX_PACKAGE_DIR}/licenses/onnxruntime/${ONNX_NOTICE}" ] \ || fail "ONNX package is missing native attribution: ${ONNX_NOTICE}" done # shellcheck disable=SC2086 $SUDO_PREFIX mkdir -p "$ONNX_RUNTIME_DIR" "$ONNX_NOTICE_DIR" \ || fail "cannot create ONNX runtime install directories" # macOS loader dependencies/symlink targets travel as one runtime folder. if [ "$DETECTED_OS" = darwin ]; then $SUDO_PREFIX cp -R "${ONNX_PACKAGE_DIR}/lib/." "$ONNX_RUNTIME_DIR/" \ || fail "macOS runtime folder install failed" fi for ONNX_LIBRARY in "${ONNX_PACKAGE_DIR}"/lib/libonnxruntime*.so* "${ONNX_PACKAGE_DIR}"/lib/libonnxruntime*.dylib; do [ -f "$ONNX_LIBRARY" ] || continue # shellcheck disable=SC2086 $SUDO_PREFIX install -m 0644 "$ONNX_LIBRARY" "${ONNX_RUNTIME_DIR}/$(basename "$ONNX_LIBRARY")" \ || fail "ONNX runtime library install failed" done for ONNX_NOTICE in LICENSE ThirdPartyNotices.txt VERSION_NUMBER; do # shellcheck disable=SC2086 $SUDO_PREFIX install -m 0644 "${ONNX_PACKAGE_DIR}/licenses/onnxruntime/${ONNX_NOTICE}" "${ONNX_NOTICE_DIR}/${ONNX_NOTICE}" \ || fail "ONNX runtime attribution install failed" done # shellcheck disable=SC2086 $SUDO_PREFIX install -m 0644 "${ONNX_PACKAGE_DIR}/onnx-runtime-manifest.json" "${ONNX_RUNTIME_DIR}/onnx-runtime-manifest.json" \ || fail "ONNX runtime provenance install failed" log "Installed CPU ONNX runtime: ${ONNX_RUNTIME_DIR}/${ONNX_LIBRARY_NAME}" fi # The checksum-verified package carries the platform startup helper. It is # installed beside the executable for relocatable foreground/launchd/systemd use. START_HELPER_SRC="${ONNX_PACKAGE_DIR}/synapcores-start.sh" if [ -f "$START_HELPER_SRC" ]; then $SUDO_PREFIX install -m 0755 "$START_HELPER_SRC" "${INSTALL_PREFIX}/synapcores-start" \ || fail "startup helper install failed" fi # In user mode, warn if the install dir isn't on PATH. ~/.local/bin is # included by ~/.profile on most modern distros, but only after the # next login — so a fresh-from-`useradd` account might not see it yet. if [ -n "$USER_INSTALL" ]; then case ":${PATH}:" in *":${INSTALL_PREFIX}:"*) : # already on PATH ;; *) warn "${INSTALL_PREFIX} is not on your PATH for this shell." warn "Run synapcores via its full path, or add to your shell rc:" warn " export PATH=\"${INSTALL_PREFIX}:\$PATH\"" ;; esac fi # --------------------------------------------------------------------- # Verify install (deferred on both OSes) # --------------------------------------------------------------------- # # Skipped here intentionally. The CE binary is dynamically linked # against FFmpeg / Tesseract / Leptonica that aren't installed yet — # - macOS: Homebrew may not have them installed # - Linux: install-ce.sh runs apt-get for them next # So `synapcores --version` would fail with "error while loading # shared libraries: libavutil.so.56" on a fresh box and the # resulting "binary did not report 'Community'" warning would mislead # the operator into thinking the wrong edition was downloaded. # install-ce.sh does the version check AFTER its install_runtime_deps # step, which is the right point. # --------------------------------------------------------------------- # System setup (or skip) # --------------------------------------------------------------------- if [ -n "$BINARY_ONLY" ]; then log "SYNAPCORES_BINARY_ONLY set; skipping system setup." exit 0 fi # macOS path: detect missing Homebrew deps and offer to install them. # install-ce.sh is a Linux installer (useradd, apt-get, systemd) and # would fail with "useradd: command not found" on macOS — we don't # call it here. # # The CE macOS binary is dynamically linked against Homebrew's # ffmpeg, tesseract, and leptonica. Without them the binary's # dynamic linker fails silently and `--version` returns nothing. # # Set SYNAPCORES_NONINTERACTIVE=1 to skip the prompt and install # missing deps automatically (CI / automation). if [ "$DETECTED_OS" = "darwin" ]; then if ! command -v brew >/dev/null 2>&1; then warn "Homebrew is required for macOS runtime deps (ffmpeg/tesseract/leptonica)." warn "Install Homebrew first: https://brew.sh" warn "Then re-run: curl -fsSL https://get.synapcores.com/install.sh | sh" exit 1 fi # Pick the right ffmpeg formula. We try ffmpeg@7 first because # that's what the GitHub Actions release built against; if it's # not in the user's Homebrew tap we fall back to plain ffmpeg # (ffmpeg-next 7 supports both 7.x and 8.x at runtime). if brew info ffmpeg@7 >/dev/null 2>&1; then FFMPEG_FORMULA="ffmpeg@7" else FFMPEG_FORMULA="ffmpeg" fi REQUIRED_DEPS="$FFMPEG_FORMULA tesseract leptonica" MISSING_DEPS="" for dep in $REQUIRED_DEPS; do if ! brew list --formula "$dep" >/dev/null 2>&1; then MISSING_DEPS="$MISSING_DEPS $dep" fi done # Strip leading whitespace MISSING_DEPS=$(printf '%s' "$MISSING_DEPS" | sed 's/^ *//') if [ -n "$MISSING_DEPS" ]; then log "Missing Homebrew deps: $MISSING_DEPS" if [ -n "${SYNAPCORES_NONINTERACTIVE:-}" ]; then log "Non-interactive mode (SYNAPCORES_NONINTERACTIVE set); installing..." ANSWER="y" elif [ ! -t 0 ] && [ ! -e /dev/tty ]; then warn "stdin is not a terminal and /dev/tty is unavailable; cannot prompt." warn "Install manually: brew install $MISSING_DEPS" warn "Or set SYNAPCORES_NONINTERACTIVE=1 to install automatically." exit 1 else printf '\033[1;34m[get-synapcores]\033[0m Install them now? (brew install %s) [Y/n] ' "$MISSING_DEPS" # Read from /dev/tty so the prompt works under # `curl ... | sh` where stdin is the script itself. read -r ANSWER < /dev/tty || ANSWER="n" fi case "$ANSWER" in ""|[Yy]*) log "Running: brew install $MISSING_DEPS" # shellcheck disable=SC2086 brew install $MISSING_DEPS || { warn "brew install failed. Install manually: brew install $MISSING_DEPS" exit 1 } ;; *) log "Skipping. Install manually: brew install $MISSING_DEPS" log "Then run: ${INSTALL_PREFIX}/synapcores --version" exit 0 ;; esac else log "All Homebrew runtime deps present." fi # With deps installed, the binary should now run. if "${INSTALL_PREFIX}/synapcores" --version 2>/dev/null | grep -q "Community"; then log "Edition check: $("${INSTALL_PREFIX}/synapcores" --version)" else warn "Binary still won't run cleanly. Diagnose with:" warn " otool -L ${INSTALL_PREFIX}/synapcores | head -20" warn "Look for any 'not found' lines pointing at missing dylibs." fi # ----- Drop default config + data dir ----- SC_HOME="${HOME}/.synapcores" SC_CONFIG="${SC_HOME}/gateway.toml" SC_DATA_DIR="${AIDB_DATA_DIR:-${SC_HOME}/data}" case "$SC_DATA_DIR" in /*) ;; *) fail "AIDB_DATA_DIR must be an absolute path for installation" ;; esac SC_MODELS_DIR="${SC_HOME}/models/text" mkdir -p "$SC_HOME" "$SC_DATA_DIR" "$SC_MODELS_DIR" chmod 700 "$SC_HOME" START_HELPER="${INSTALL_PREFIX}/synapcores-start" [ -x "$START_HELPER" ] || fail "release is missing synapcores-start.sh; install a complete current release folder" # The bundled template (v1.3.1+) lives next to the binary in the # extracted tarball. Older tarballs don't have it, so fall through # to the inline minimal config in that case. BUNDLED_TEMPLATE="$(dirname "$BINARY_SRC")/community.toml.template" if [ -f "$SC_CONFIG" ]; then log "Config already exists at ${SC_CONFIG} — leaving as-is." elif [ -f "$BUNDLED_TEMPLATE" ]; then # Adapt the template for macOS: rewrite the data_dir to live # under the user's home (the template defaults to # /opt/synapcores/aidb_data, which would need root and isn't # the macOS convention). SC_DATA_TOML=$(printf %s "$SC_DATA_DIR" | sed 's/\\/\\\\/g; s/"/\\"/g') while IFS= read -r template_line || [ -n "$template_line" ]; do case "$template_line" in data_dir\ =*) printf 'data_dir = "%s"\n' "$SC_DATA_TOML" ;; *) printf '%s\n' "$template_line" ;; esac done < "$BUNDLED_TEMPLATE" > "$SC_CONFIG" log "Wrote default config to ${SC_CONFIG}" else warn "No config template bundled (older tarball?). Generating minimal config." SC_DATA_TOML=$(printf %s "$SC_DATA_DIR" | sed 's/\\/\\\\/g; s/"/\\"/g') cat >"$SC_CONFIG" </dev/null; then chmod +x "$INSTALLER" set -- "$INSTALLER" --binary "${INSTALL_PREFIX}/synapcores" if [ "$NLI_OPT_IN" = 1 ]; then set -- "$@" --with-nli --nli-model "$NLI_MODEL" fi if [ -n "$USER_INSTALL" ]; then log "Running install-ce.sh --user (no sudo)..." set -- "$@" --user fi # Pass only installer controls across sudo; do not forward the caller's # unrelated environment. The model itself is pulled by the service user. set -- env "SYNAPCORES_WITH_NLI=${SYNAPCORES_WITH_NLI:-}" \ "SYNAPCORES_NO_MODEL_DOWNLOAD=${SYNAPCORES_NO_MODEL_DOWNLOAD:-}" \ "SYNAPCORES_NONINTERACTIVE=${SYNAPCORES_NONINTERACTIVE:-}" "$@" if [ -n "${AIDB_DATA_DIR:-}" ]; then set -- env "AIDB_DATA_DIR=$AIDB_DATA_DIR" "$@" fi if [ -n "$USER_INSTALL" ] || [ "$(id -u)" -eq 0 ]; then "$@" else log "System setup requires sudo..." sudo "$@" fi else warn "system installer not found at $INSTALLER_URL" warn "binary is installed; complete setup manually per the docs" fi log "Done. Try: synapcores --version"