← Files FreckleARCHIVED FILE

bin/freckle

4.85 KB · Oct 3, 2026 · 06:34 UTC

↓ Download file

#!/bin/sh
# Resolves bare `freckle` to the platform binary for the pinned CLI release (see
# the cli-version file next to this script), downloading + checksum-verifying it
# from the Freckle release server on first use and caching it.
set -eu

RELEASE_BASE_URL="https://releases.freckle.dev/cli"
PLUGIN_REPO="freckle-io/agent-plugins"

fail() {
  printf 'Error: %s\n' "$*" >&2
  exit 1
}

# Resolve our own path even when invoked as a bare command via PATH.
self="$0"
case "$self" in
  */*) ;;
  *) self="$(command -v -- "$self")" || fail "freckle launcher could not resolve its own path" ;;
esac
bin_dir="$(cd -- "$(dirname -- "$self")" && pwd)"

# The plugin pins which CLI release it bundles via this file; the matching
# binaries live on the Freckle release server (published by the CLI release
# pipeline, not the plugin), so the plugin and the CLI release are decoupled.
version_file="$bin_dir/cli-version"
if [ ! -f "$version_file" ]; then
  fail "missing $version_file (reinstall the Freckle plugin from $PLUGIN_REPO)"
fi
release_tag="$(tr -d '[:space:]' < "$version_file")"
if [ -z "$release_tag" ]; then
  fail "empty CLI release tag in $version_file"
fi
case "$release_tag" in
  "." | ".." | "" | *[!ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-]*)
    fail "invalid CLI release tag in $version_file"
    ;;
  *) ;;
esac

os="$(uname -s)"
arch="$(uname -m)"
case "$os-$arch" in
  Darwin-arm64) platform="darwin-arm64" ;;
  Linux-x86_64 | Linux-amd64) platform="linux-x64" ;;
  Darwin-*) fail "unsupported platform: macOS $arch (the Freckle CLI supports macOS ARM64)" ;;
  Linux-*) fail "unsupported platform: Linux $arch (the Freckle CLI supports Linux x64)" ;;
  *) fail "unsupported platform: $os-$arch. On native Windows 11 x64, install with: irm https://install.freckle.io/install.ps1 | iex" ;;
esac
# The Linux binary is glibc-linked. Detect the active libc by the glibc dynamic
# loader the binary actually needs; if that loader is absent (e.g. Alpine/musl),
# fail with a clear error instead of a cryptic loader crash.
if [ "$platform" = "linux-x64" ] && [ ! -e /lib64/ld-linux-x86-64.so.2 ]; then
  fail "missing the glibc dynamic loader (/lib64/ld-linux-x86-64.so.2); the Freckle CLI requires glibc (Alpine/musl is not supported)"
fi
asset="freckle-$platform"

if [ -n "${FRECKLE_CONFIG_HOME:-}" ]; then
  config_base="$FRECKLE_CONFIG_HOME"
elif [ -n "${XDG_CONFIG_HOME:-}" ]; then
  config_base="$XDG_CONFIG_HOME"
elif [ -n "${HOME:-}" ]; then
  config_base="$HOME/.config"
else
  fail "cannot resolve a config dir (set FRECKLE_CONFIG_HOME, XDG_CONFIG_HOME, or HOME)"
fi
cache_dir="$config_base/freckle/bin"
cached="$cache_dir/freckle-$release_tag-$platform"

if [ ! -x "$cached" ] || [ ! -s "$cached" ]; then
  checksums="$bin_dir/checksums.txt"
  if [ ! -f "$checksums" ]; then
    fail "missing $checksums (reinstall the Freckle plugin from $PLUGIN_REPO)"
  fi
  expected="$(awk -v a="$asset" '$2 == a {print $1}' "$checksums" | head -n1)"
  if [ -z "$expected" ]; then
    fail "no checksum for $asset ($release_tag) in $checksums"
  fi

  url="$RELEASE_BASE_URL/$release_tag/$asset"
  if [ -n "${FRECKLE_DEBUG:-}" ]; then
    echo "freckle: installing CLI $release_tag ($platform)..." >&2
  fi
  mkdir -p "$cache_dir"
  tmp="$cache_dir/.dl-$$"
  trap 'rm -f "$tmp"' EXIT INT TERM
  if command -v curl > /dev/null 2>&1; then
    # Timeouts + retries so a bad/stalled network fails cleanly instead of
    # hanging forever: cap the connect, and abort only if the transfer stalls
    # below ~1KB/s for 30s (not a flat --max-time, which would wrongly kill a
    # legitimately slow-but-progressing large download).
    curl -fsSL --connect-timeout 10 --speed-limit 1024 --speed-time 30 \
      --retry 3 --retry-delay 2 \
      "$url" -o "$tmp" || fail "could not download the Freckle CLI from $url"
  elif command -v wget > /dev/null 2>&1; then
    # --timeout is per-stall (read/connect), so a slow-but-progressing transfer survives.
    wget -qO "$tmp" --timeout=30 --tries=3 --waitretry=2 "$url" || fail "could not download the Freckle CLI from $url"
  else
    fail "need curl or wget to download the Freckle CLI"
  fi

  if command -v sha256sum > /dev/null 2>&1; then
    actual="$(sha256sum "$tmp" | awk '{print $1}')"
  elif command -v shasum > /dev/null 2>&1; then
    actual="$(shasum -a 256 "$tmp" | awk '{print $1}')"
  else
    fail "need sha256sum or shasum to verify the download"
  fi
  if [ "$actual" != "$expected" ]; then
    fail "checksum mismatch for $asset (expected $expected, got $actual)"
  fi

  chmod +x "$tmp" || fail "could not make the downloaded CLI executable"
  mv -f "$tmp" "$cached" || fail "could not install the CLI to $cached"
  trap - EXIT INT TERM
fi

# The cached binary is pinned by release tag; disable the CLI's self-updater so
# the file the checksum vouched for never rewrites itself in place. Plugin
# updates move the pin instead.
FRECKLE_CLI_AUTO_UPDATE=0 exec "$cached" "$@"

SHA-256: 9c782a34cf851aff84b8b7e55b8ce158a1c310a0e8e809fdf98bf51b914d8489