← Files FreckleARCHIVED FILE
bin/freckle
4.85 KB · Oct 3, 2026 · 06:34 UTC
#!/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