Ctrl K
ComunidadGuías…

update-local-ai.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bash
set -Eeuo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# shellcheck source=/dev/null
. "$SCRIPT_DIR/lib/bootstrap.sh" 2>/dev/null ||
  . "$SCRIPT_DIR/../lib/bootstrap.sh" 2>/dev/null ||
  { echo "Error: missing LocalAI bootstrap library: lib/bootstrap.sh" >&2; exit 1; }
source_localai_lib install.sh
source_localai_lib cuda.sh

AI_DIR=""
BIN_DIR=""
LIB_DIR=""
CONF_DIR=""
LLAMA_CPP_BACKEND="${LLAMA_CPP_BACKEND:-}"
LLAMA_CPP_ASSET_RE=""
LOCALAI_SOURCE_DIR=""
START_AFTER_UPDATE=1
UPDATE_ALL=0
UPDATE_COMPLETE=0
SERVICE_STOPPED=0
SERVICE_STARTED=0
CHECK_ONLY=0
SNAPSHOT_ACTIVE=0
PENDING_SNAPSHOT=""
# Captured before anything resolves "latest" to a concrete tag, so the
# installation manifest records the policy, not just the result.
LLAMA_CPP_POLICY="${LLAMA_CPP_VERSION:-latest}"
LLAMA_SWAP_POLICY="${LLAMA_SWAP_VERSION:-latest}"

while [ "$#" -gt 0 ]; do
  case "$1" in
    --no-start) START_AFTER_UPDATE=0 ;;
    --all) UPDATE_ALL=1 ;;
    --check) CHECK_ONLY=1 ;;
    -h|--help)
      cat <<EOF
Usage: $0 [--check] [--no-start] [--all]

Installs the newest llama.cpp build for the active backend and the newest
llama-swap, then restarts LocalAI. If the restarted service does not become
ready, the previous versions are restored automatically.

  --check     Only report installed and latest versions; change nothing
  --no-start  Leave LocalAI stopped after updating
  --all       Update every installed backend, not just the active one
EOF
      exit 0
      ;;
    *)
      echo "Usage: $0 [--check] [--no-start] [--all]" >&2
      exit 2
      ;;
  esac
  shift
done

log() {
  printf '\n==> %s\n' "$*"
}

fail() {
  echo "Error: $*" >&2
  exit 1
}

have() {
  command -v "$1" >/dev/null 2>&1
}

localai_archive_url() {
  case "${LOCALAI_REF:-main}" in
    v*|refs/tags/*)
      printf '%s/refs/tags/%s.tar.gz\n' "${LOCALAI_TARBALL_BASE:-https://github.com/hossbit/local-ai-server/archive}" "${LOCALAI_REF#refs/tags/}"
      ;;
    refs/heads/*)
      printf '%s/%s.tar.gz\n' "${LOCALAI_TARBALL_BASE:-https://github.com/hossbit/local-ai-server/archive}" "$LOCALAI_REF"
      ;;
    *)
      printf '%s/refs/heads/%s.tar.gz\n' "${LOCALAI_TARBALL_BASE:-https://github.com/hossbit/local-ai-server/archive}" "${LOCALAI_REF:-main}"
      ;;
  esac
}

resolve_localai_source_dir() {
  local archive_file extracted

  if [ -f "$SCRIPT_DIR/install-local-ai.sh" ] && [ -f "$SCRIPT_DIR/localai.conf" ]; then
    LOCALAI_SOURCE_DIR="$SCRIPT_DIR"
    return
  fi

  # Opt-in escape hatch for a local dev checkout (e.g. one with unreleased
  # changes not yet pushed to LOCALAI_REPO_URL): if set, use it instead of
  # fetching from GitHub. Without this, `localai update` always refreshes
  # helper scripts from the public repo and would silently overwrite local
  # changes to lib/*.sh with the upstream version.
  if [ -n "${LOCALAI_LOCAL_SOURCE_DIR:-}" ]; then
    LOCALAI_SOURCE_DIR="$(expand_path "$LOCALAI_LOCAL_SOURCE_DIR")"
    if [ ! -f "$LOCALAI_SOURCE_DIR/install-local-ai.sh" ] || [ ! -f "$LOCALAI_SOURCE_DIR/localai.conf" ]; then
      fail "LOCALAI_LOCAL_SOURCE_DIR=$LOCALAI_SOURCE_DIR does not look like a LocalAI checkout (missing install-local-ai.sh or localai.conf)"
    fi
    log "Using local LocalAI checkout: $LOCALAI_SOURCE_DIR"
    return
  fi

  log "Fetching LocalAI helper scripts"
  LOCALAI_SOURCE_DIR="$TMP_DIR/localai-source"

  if have git; then
    git clone --depth 1 --branch "${LOCALAI_REF:-main}" \
      "${LOCALAI_REPO_URL:-https://github.com/hossbit/local-ai-server.git}" \
      "$LOCALAI_SOURCE_DIR"
  else
    archive_file="$TMP_DIR/localai.tar.gz"
    curl -4 -fsSL "$(localai_archive_url)" -o "$archive_file"
    tar -xzf "$archive_file" -C "$TMP_DIR"
    extracted="$(find "$TMP_DIR" -mindepth 1 -maxdepth 1 -type d -name 'local-ai-server-*' | head -n 1)"
    [ -n "$extracted" ] || fail "could not find extracted LocalAI source directory"
    mv "$extracted" "$LOCALAI_SOURCE_DIR"
  fi

  [ -f "$LOCALAI_SOURCE_DIR/localai.conf" ] || fail "downloaded LocalAI source is missing localai.conf"
  [ -x "$LOCALAI_SOURCE_DIR/localai" ] || fail "downloaded LocalAI source is missing localai"
}

stop_localai() {
  if has_user_service; then
    log "Stopping LocalAI service"
    systemctl --user stop "$LOCALAI_SERVICE_NAME"
  elif [ -x "$BIN_DIR/stop.sh" ]; then
    log "Stopping LocalAI"
    "$BIN_DIR/stop.sh"
  elif [ -x "$AI_DIR/stop.sh" ]; then
    log "Stopping LocalAI"
    "$AI_DIR/stop.sh"
  elif [ -x "$SCRIPT_DIR/stop.sh" ]; then
    log "Stopping LocalAI"
    "$SCRIPT_DIR/stop.sh"
  fi
}

start_localai() {
  if has_user_service; then
    log "Starting LocalAI service"
    systemctl --user start "$LOCALAI_SERVICE_NAME"
  elif [ -x "$BIN_DIR/start.sh" ]; then
    log "Starting LocalAI"
    "$BIN_DIR/start.sh"
  elif [ -x "$AI_DIR/start.sh" ]; then
    log "Starting LocalAI"
    "$AI_DIR/start.sh"
  else
    log "Updated successfully; run the installer to create the service and helper scripts"
  fi
}

# cleanup_update: always remove the temporary workspace, and if a failure
# exits after the running service was stopped, bring the previous install
# back up instead of leaving LocalAI down.
#
# When components were already replaced, swap the pending snapshot back in
# first, so the restarted service runs the versions that worked before
# rather than the ones that just failed.
cleanup_update() {
  local status=$?

  trap - EXIT
  [ -z "${TMP_DIR:-}" ] || rm -rf -- "$TMP_DIR"
  if [ "$UPDATE_COMPLETE" -eq 0 ] && [ "$SNAPSHOT_ACTIVE" -eq 1 ]; then
    if [ "$SERVICE_STARTED" -eq 1 ]; then
      stop_localai >/dev/null 2>&1 || true
    fi
    if localai_snapshot_has_content "$PENDING_SNAPSHOT"; then
      echo "Update failed; restoring the previous llama.cpp / llama-swap versions..." >&2
      if localai_snapshot_swap "$PENDING_SNAPSHOT"; then
        rm -rf -- "$PENDING_SNAPSHOT"
        echo "Previous versions restored." >&2
      else
        echo "Warning: some components could not be restored automatically." >&2
        echo "The pre-update files are kept in $PENDING_SNAPSHOT; run 'localai rollback' to retry." >&2
      fi
    else
      rm -rf -- "$PENDING_SNAPSHOT"
    fi
    # --all repoints the active backend while it works; put back the one
    # that was active before the update.
    if [ -n "${ORIGINAL_BACKEND:-}" ] && [ -d "$BIN_DIR/llama.cpp.d/$ORIGINAL_BACKEND" ]; then
      LLAMA_CPP_BACKEND="$ORIGINAL_BACKEND"
      activate_llama_cpp_backend || true
    fi
  fi
  if [ "$SERVICE_STOPPED" -eq 1 ] && [ "$START_AFTER_UPDATE" -eq 1 ] && [ "$UPDATE_COMPLETE" -eq 0 ]; then
    echo "Restarting the previous LocalAI service..." >&2
    start_localai || echo "Warning: LocalAI did not restart. Check: localai logs" >&2
  fi
  exit "$status"
}

# finish_snapshot: after a successful update, keep the replaced components
# as the rollback point (or discard them when LOCALAI_KEEP_PREVIOUS=0).
finish_snapshot() {
  local previous

  [ "$SNAPSHOT_ACTIVE" -eq 1 ] || return 0
  previous="$(localai_previous_dir)"
  if [ "${LOCALAI_KEEP_PREVIOUS:-1}" = "1" ] && localai_snapshot_has_content "$PENDING_SNAPSHOT"; then
    rm -rf -- "$previous"
    mv -- "$PENDING_SNAPSHOT" "$previous"
    echo "Previous versions kept for 'localai rollback' in $previous"
  else
    rm -rf -- "$PENDING_SNAPSHOT"
  fi
  SNAPSHOT_ACTIVE=0
}

# recover_interrupted_update: a pending snapshot left behind means an
# earlier update was killed before it could finish or roll back (power loss,
# SIGKILL). Every component swap is atomic, so restoring the snapshot returns
# to the exact pre-update state instead of a guessed mix.
recover_interrupted_update() {
  local pending

  pending="$(localai_previous_dir).pending"
  [ -d "$pending" ] || return 0
  if [ "$CHECK_ONLY" -eq 1 ]; then
    echo "Warning: an earlier update did not finish; run 'localai update' to recover it." >&2
    return 0
  fi
  log "Recovering from an interrupted update"
  if localai_snapshot_has_content "$pending"; then
    localai_snapshot_swap "$pending" ||
      fail "could not restore the components saved in $pending. Inspect it, or run 'localai rollback'."
  fi
  rm -rf -- "$pending"
}

print_current_versions() {
  local localai_version

  echo
  echo "Current versions:"
  if [ -f "$CONF_DIR/localai.conf" ]; then
    if ! localai_version="$(localai_conf_default_version "$CONF_DIR/localai.conf")"; then
      localai_version=""
    fi
    echo "LocalAI: ${localai_version:-$LOCALAI_VERSION}"
  fi
  llama_cpp_display_version "$LLAMA_CPP_BACKEND"
  echo "llama.cpp backend: $LLAMA_CPP_BACKEND"
  "$LLAMA_SWAP_BIN" --version 2>&1 | awk 'NR == 1 {print; exit}'
}

cleanup_bin_artifacts() {
  log "Cleaning old llama.cpp folders and archives"

  find "$BIN_DIR" -mindepth 1 -maxdepth 1 -type d ! -name llama.cpp ! -name llama.cpp.d ! -name "${LOCALAI_PREVIOUS_SUBDIR:-previous}" ! -name "${LOCALAI_PREVIOUS_SUBDIR:-previous}.pending" -exec rm -rf -- {} +
  find "$BIN_DIR" -mindepth 1 -maxdepth 1 -type f -name '*.tar.gz' -delete
}

# update_llama_cpp_backend: brings one backend's own slot
# ($BIN_DIR/llama.cpp.d/<backend>) up to $LLAMA_CPP_TAG if it isn't already,
# leaving every other installed backend untouched. Used both for the normal
# single-backend update (called once for $LLAMA_CPP_BACKEND) and for --all
# (called once per already-installed backend). Sets LLAMA_CPP_BACKEND as a
# side effect (read by verify_llama_server/install_llama_cpp_release_dir/
# activate_llama_cpp_backend), and always leaves the symlink pointing at
# whatever it just installed -- callers doing more than one backend must
# restore the real active one afterward.
update_llama_cpp_backend() {
  local backend="$1"
  local current_version url compare_tag

  LLAMA_CPP_BACKEND="$backend"
  if [ "$backend" != "cuda" ]; then
    select_llama_cpp_asset_regex ""
    url="$(release_asset_url "$LLAMA_CPP_BINARY_JSON" "$LLAMA_CPP_ASSET_RE")"
    [ -n "$url" ] || fail "no llama.cpp asset found for backend: $backend"
  fi

  if [ "$backend" = "cuda" ]; then
    current_version="$(cuda_installed_revision)"
    compare_tag="$LLAMA_CPP_TAG"
  else
    current_version="$(llama_cpp_backend_version "$backend")"
    compare_tag="$LLAMA_CPP_BINARY_TAG"
  fi

  if [ -n "$current_version" ] && llama_cpp_versions_match "$compare_tag" "$current_version"; then
    return 0
  fi

  log "Installing llama.cpp $compare_tag ($backend)"
  if [ "$backend" = "cuda" ]; then
    LLAMA_CPP_VERSION="$LLAMA_CPP_TAG"
    cuda_build_and_install "$TMP_DIR" ||
      fail "CUDA build failed for backend cuda. See the error above."
  else
    mkdir -p "$TMP_DIR/llama.cpp-$backend"
    download_verified_asset "$LLAMA_CPP_BINARY_JSON" "$url" "$TMP_DIR/llama.cpp-$backend.tar.gz" "llama.cpp ($backend)"
    tar -xzf "$TMP_DIR/llama.cpp-$backend.tar.gz" -C "$TMP_DIR/llama.cpp-$backend" || fail "failed to extract llama.cpp ($backend)"
    install_llama_cpp_release_dir "$TMP_DIR/llama.cpp-$backend" "$TMP_DIR"
  fi
  verify_llama_server || exit 1
}

refresh_localai_libs() {
  local path rel

  [ -d "$LOCALAI_SOURCE_DIR/lib" ] || return 0
  mkdir -p "$LIB_DIR"
  while IFS= read -r path; do
    rel="${path#"$LOCALAI_SOURCE_DIR/lib/"}"
    mkdir -p "$LIB_DIR/$(dirname "$rel")"
    install -m644 "$path" "$LIB_DIR/$rel"
  done < <(find "$LOCALAI_SOURCE_DIR/lib" -type f | sort)
  # Remove the retired custom workspace, preserving other user files.
  rm -f -- "$LIB_DIR/ui.html" "$CONF_DIR/ui/index.html"
  rmdir -- "$CONF_DIR/ui" 2>/dev/null || true
}

###############################################################################
# CHECK REQUIREMENTS
###############################################################################

for COMMAND in curl jq tar; do
  command -v "$COMMAND" >/dev/null 2>&1 || fail "required command not found: $COMMAND"
done

[ "$(uname -m)" = "x86_64" ] || fail "this updater currently supports x86_64 Linux only"

AI_DIR="$(resolve_ai_dir)"
BIN_DIR="$AI_DIR/$LOCALAI_BIN_SUBDIR"
LIB_DIR="$AI_DIR/$LOCALAI_LIB_SUBDIR"
CONF_DIR="$AI_DIR/$LOCALAI_CONF_SUBDIR"
resolve_llama_swap_paths
select_llama_cpp_asset_regex --detect-installed

###############################################################################
# PREPARE WORKSPACE
###############################################################################

mkdir -p "$AI_DIR" "$BIN_DIR" "$LIB_DIR" "$CONF_DIR"
if [ ! -f "$CONF_DIR/$LOCALAI_PORT_FILE" ] && [ -f "$AI_DIR/$LOCALAI_PORT_FILE" ]; then
  cp "$AI_DIR/$LOCALAI_PORT_FILE" "$CONF_DIR/$LOCALAI_PORT_FILE"
fi
TMP_DIR=$(mktemp -d)
trap cleanup_update EXIT
# Saved before update_llama_cpp_backend changes it per backend, so the
# manifest can name the active backend's release asset.
ACTIVE_ASSET_RE="$LLAMA_CPP_ASSET_RE"
recover_interrupted_update
if [ "$CHECK_ONLY" -eq 0 ]; then
  resolve_localai_source_dir
else
  # --check changes nothing, so it skips the helper-script refresh below.
  LOCALAI_SOURCE_DIR="$AI_DIR"
fi

###############################################################################
# REFRESH INSTALLED HELPER SCRIPTS
###############################################################################

if [ "$LOCALAI_SOURCE_DIR" != "$AI_DIR" ]; then
  PREVIOUS_LOCALAI_CONF="$CONF_DIR/localai.conf"
  if [ -f "$PREVIOUS_LOCALAI_CONF" ]; then
    cp "$PREVIOUS_LOCALAI_CONF" "$TMP_DIR/localai.conf.previous"
    PREVIOUS_LOCALAI_CONF="$TMP_DIR/localai.conf.previous"
  elif [ -f "$AI_DIR/localai.conf" ]; then
    cp "$AI_DIR/localai.conf" "$TMP_DIR/localai.conf.previous"
    PREVIOUS_LOCALAI_CONF="$TMP_DIR/localai.conf.previous"
  fi
  for SCRIPT in localai start.sh stop.sh rebuild-config.sh update-local-ai.sh uninstall-local-ai.sh; do
    if [ -f "$LOCALAI_SOURCE_DIR/$SCRIPT" ]; then
      install -m755 "$LOCALAI_SOURCE_DIR/$SCRIPT" "$BIN_DIR/$SCRIPT"
    fi
  done
  if [ -f "$LOCALAI_SOURCE_DIR/localai.conf" ]; then
    install -m644 "$LOCALAI_SOURCE_DIR/localai.conf" "$CONF_DIR/localai.conf"
    append_runtime_tuning "$PREVIOUS_LOCALAI_CONF" "updater"
  fi
  refresh_localai_libs
  if [ -x "$BIN_DIR/localai" ]; then
    mkdir -p "$LOCALAI_USER_BIN_DIR"
    ln -sfn "$BIN_DIR/localai" "$LOCALAI_USER_BIN_DIR/$LOCALAI_CLI_NAME"
  fi
  mkdir -p "$LOCALAI_SYSTEMD_USER_DIR"
  write_systemd_user_service "$LOCALAI_SYSTEMD_USER_DIR/$LOCALAI_SERVICE_NAME" "$BIN_DIR" "$CONF_DIR/$LOCALAI_PID_FILE"
  systemctl --user daemon-reload >/dev/null 2>&1 || true
  for OLD_HELPER in start.sh stop.sh rebuild-config.sh update-local-ai.sh uninstall-local-ai.sh "$LOCALAI_CLI_NAME"; do
    rm -f -- "$AI_DIR/$OLD_HELPER"
  done
  for OLD_CONFIG in localai.conf "$LOCALAI_BACKEND_FILE" "$LOCALAI_CONFIG_FILE" "$LOCALAI_PORT_FILE" "$LOCALAI_PID_FILE"; do
    rm -f -- "$AI_DIR/$OLD_CONFIG"
  done
fi

###############################################################################
# FETCH LATEST RELEASE METADATA
###############################################################################

log "Fetching release metadata"
if [ "${LLAMA_CPP_VERSION:-latest}" = "latest" ]; then
  LLAMA_CPP_JSON="$(resolve_llama_cpp_latest_json)"
else
  log "Using pinned llama.cpp version $LLAMA_CPP_VERSION"
  LLAMA_CPP_JSON="$(github_api_get "$(release_api_for_version "$LLAMA_CPP_VERSION" "$LLAMA_CPP_RELEASE_API" "$LLAMA_CPP_LATEST_API")")"
fi

if [ "${LLAMA_SWAP_VERSION:-latest}" = "latest" ]; then
  LLAMA_SWAP_JSON="$(github_api_get "$LLAMA_SWAP_LATEST_API")"
else
  log "Using pinned llama-swap version $LLAMA_SWAP_VERSION"
  LLAMA_SWAP_JSON="$(github_api_get "$(release_api_for_version "$LLAMA_SWAP_VERSION" "$LLAMA_SWAP_RELEASE_API" "$LLAMA_SWAP_LATEST_API")")"
fi

LLAMA_CPP_TAG=$(jq -er '.tag_name' <<<"$LLAMA_CPP_JSON")
LLAMA_SWAP_TAG=$(jq -er '.tag_name' <<<"$LLAMA_SWAP_JSON")

# A stable vX.Y.Z release carries no binaries of its own -- resolve to the
# bleeding-edge release its assets actually live in. Used for every non-cuda
# backend below; cuda builds from source and checks out $LLAMA_CPP_TAG
# directly regardless of channel.
LLAMA_CPP_BINARY_JSON="$(resolve_llama_cpp_binary_json "$LLAMA_CPP_JSON")"
LLAMA_CPP_BINARY_TAG=$(jq -er '.tag_name' <<<"$LLAMA_CPP_BINARY_JSON")
LLAMA_SWAP_URL="${LLAMA_SWAP_URL:-$(release_asset_url "$LLAMA_SWAP_JSON" "$LLAMA_SWAP_ASSET_RE")}"

[ -n "$LLAMA_SWAP_URL" ] || fail "no llama-swap Linux amd64 asset found"

###############################################################################
# DECIDE WHICH BACKEND SLOT(S) TO UPDATE
###############################################################################

# The currently-active backend, same value select_llama_cpp_asset_regex
# --detect-installed already resolved above -- restored as active again once
# every requested backend has been brought up to date, since
# update_llama_cpp_backend/install_llama_cpp_release_dir always repoints the
# active symlink at whatever it just installed.
ORIGINAL_BACKEND="$LLAMA_CPP_BACKEND"

BACKENDS_TO_UPDATE=()
if [ "$UPDATE_ALL" -eq 1 ] && [ -d "$BIN_DIR/llama.cpp.d" ] &&
  [ -n "$(find "$BIN_DIR/llama.cpp.d" -mindepth 1 -maxdepth 1 -type d -print -quit)" ]; then
  while IFS= read -r BACKEND_DIR; do
    BACKENDS_TO_UPDATE+=("$(basename "$BACKEND_DIR")")
  done < <(find "$BIN_DIR/llama.cpp.d" -mindepth 1 -maxdepth 1 -type d | sort)
else
  BACKENDS_TO_UPDATE=("$LLAMA_CPP_BACKEND")
fi

###############################################################################
# DETECT INSTALLED VERSIONS
###############################################################################

CURRENT_LLAMA_SWAP="$(llama_swap_version "$LLAMA_SWAP_BIN")"
printf 'llama-swap: installed=%s latest=%s\n' "${CURRENT_LLAMA_SWAP:-none}" "$LLAMA_SWAP_TAG"

NEED_CPP_FOR=()
for BACKEND in "${BACKENDS_TO_UPDATE[@]}"; do
  if [ "$BACKEND" = "cuda" ]; then
    CURRENT_VERSION="$(cuda_installed_revision)"
    COMPARE_TAG="$LLAMA_CPP_TAG"
  else
    CURRENT_VERSION="$(llama_cpp_backend_version "$BACKEND")"
    COMPARE_TAG="$LLAMA_CPP_BINARY_TAG"
  fi
  printf 'llama.cpp (%s): installed=%s latest=%s\n' "$BACKEND" "${CURRENT_VERSION:-none}" "$COMPARE_TAG"
  if [ -z "$CURRENT_VERSION" ] || ! llama_cpp_versions_match "$COMPARE_TAG" "$CURRENT_VERSION"; then
    NEED_CPP_FOR+=("$BACKEND")
  fi
done

###############################################################################
# DECIDE WHAT NEEDS UPDATING
###############################################################################

NEED_SWAP=1
if [ -n "$CURRENT_LLAMA_SWAP" ]; then
  if [ "${LLAMA_SWAP_TAG#v}" = "${CURRENT_LLAMA_SWAP#v}" ]; then
    NEED_SWAP=0
  fi
fi

# write_update_manifest: record what is now installed for the active backend.
write_update_manifest() {
  local release url="" digest=""

  if [ "$ORIGINAL_BACKEND" = "cuda" ]; then
    release="$LLAMA_CPP_TAG"
  else
    release="$LLAMA_CPP_BINARY_TAG"
    if [ -n "$ACTIVE_ASSET_RE" ]; then
      url="$(release_asset_url "$LLAMA_CPP_BINARY_JSON" "$ACTIVE_ASSET_RE")"
      [ -z "$url" ] || digest="$(release_asset_digest "$LLAMA_CPP_BINARY_JSON" "$url")"
    fi
  fi
  localai_write_manifest "$LLAMA_CPP_POLICY" "$release" "$url" "$digest" \
    "$LLAMA_SWAP_POLICY" "$LLAMA_SWAP_TAG" "$LLAMA_SWAP_URL" ||
    echo "Warning: could not write $CONF_DIR/$LOCALAI_MANIFEST_FILE" >&2
}

if [ "$CHECK_ONLY" -eq 1 ]; then
  echo
  if ((${#NEED_CPP_FOR[@]} == 0 && NEED_SWAP == 0)); then
    echo "LocalAI is up to date. Nothing was changed."
  else
    echo "Updates available:"
    for BACKEND in "${NEED_CPP_FOR[@]}"; do
      echo "  llama.cpp ($BACKEND)"
    done
    ((NEED_SWAP == 0)) || echo "  llama-swap"
    echo "Nothing was changed. Run 'localai update' to install them."
  fi
  exit 0
fi

if ((${#NEED_CPP_FOR[@]} == 0 && NEED_SWAP == 0)); then
  log "Everything is already up to date"
  cleanup_bin_artifacts
  # Refresh the record even when nothing changed: helper scripts (and so the
  # LocalAI version) may have been updated above.
  write_update_manifest
  print_current_versions
  exit 0
fi

###############################################################################
# START THE UPDATE SNAPSHOT
###############################################################################

# Everything this update replaces is recorded here first, so any later
# failure can put the previous versions back (see cleanup_update).
PENDING_SNAPSHOT="$(localai_previous_dir).pending"
localai_snapshot_begin "$PENDING_SNAPSHOT" ||
  fail "could not create the update snapshot in $PENDING_SNAPSHOT; nothing was changed"
# shellcheck disable=SC2034 # Read by install_llama_cpp_release_dir (lib/install.sh).
LOCALAI_PREVIOUS_DIR="$PENDING_SNAPSHOT"
SNAPSHOT_ACTIVE=1

###############################################################################
# STOP RUNNING SERVICE
###############################################################################

SERVICE_WAS_RUNNING=0
if pid_file_matches_process "$CONF_DIR/$LOCALAI_PID_FILE" "$CONF_DIR/$LOCALAI_PID_FILE.start"; then
  SERVICE_WAS_RUNNING=1
elif has_user_service && systemctl --user is-active --quiet "$LOCALAI_SERVICE_NAME"; then
  SERVICE_WAS_RUNNING=1
fi

if [ "$SERVICE_WAS_RUNNING" -eq 1 ]; then
  SERVICE_STOPPED=1
  stop_localai
fi

###############################################################################
# UPDATE LLAMA.CPP BACKEND(S)
###############################################################################

for BACKEND in "${NEED_CPP_FOR[@]}"; do
  update_llama_cpp_backend "$BACKEND"
done

LLAMA_CPP_BACKEND="$ORIGINAL_BACKEND"
activate_llama_cpp_backend

###############################################################################
# CLEAN OLD LLAMA.CPP FOLDERS AND ARCHIVES
###############################################################################

cleanup_bin_artifacts

###############################################################################
# INSTALL LLAMA-SWAP
###############################################################################

if ((NEED_SWAP)); then
  log "Installing llama-swap $LLAMA_SWAP_TAG"
  mkdir -p "$TMP_DIR/llama-swap"
  download_verified_asset "$LLAMA_SWAP_JSON" "$LLAMA_SWAP_URL" "$TMP_DIR/llama-swap.tar.gz" "llama-swap"
  tar -xzf "$TMP_DIR/llama-swap.tar.gz" -C "$TMP_DIR/llama-swap"

  LLAMA_SWAP_REAL=$(find "$TMP_DIR/llama-swap" -type f -name llama-swap | head -n1)
  [ -n "$LLAMA_SWAP_REAL" ] || fail "llama-swap was not found in the downloaded archive"
  chmod +x "$LLAMA_SWAP_REAL"
  [ -n "$(llama_swap_version "$LLAMA_SWAP_REAL")" ] ||
    fail "the downloaded llama-swap $LLAMA_SWAP_TAG did not run on this system"
  localai_snapshot_add_file "$PENDING_SNAPSHOT" llama-swap "$LLAMA_SWAP_INSTALL_PATH" ||
    fail "could not save the current llama-swap before replacing it"
  install -m755 "$LLAMA_SWAP_REAL" "$LLAMA_SWAP_INSTALL_PATH.new"
  mv -f -- "$LLAMA_SWAP_INSTALL_PATH.new" "$LLAMA_SWAP_INSTALL_PATH"
fi

###############################################################################
# RESTART SERVICE
###############################################################################

# start.sh (run directly or by the systemd unit) only returns success once
# llama-swap answers on its API, so a failure here means the new versions do
# not work on this system; cleanup_update then restores the previous ones.
if [ "$START_AFTER_UPDATE" -eq 1 ]; then
  SERVICE_STARTED=1
  if ! start_localai; then
    echo "Error: LocalAI did not become ready with the updated components." >&2
    exit 1
  fi
else
  log "Service left stopped (--no-start)"
fi

UPDATE_COMPLETE=1
finish_snapshot
write_update_manifest

log "LocalAI update completed"
print_current_versions