Files
dify/cli/scripts/install-cli.sh
GareArc d84fad76ed feat(cli): trigger-agnostic difyctl release pipeline + tokenless installers
- cli-release.yml: workflow_dispatch/workflow_call/repository_dispatch/tag
  triggers; version sourced from cli/package.json (not the trigger). Split into
  validate + release jobs with an idempotency guard (gh release view) and a
  tag-vs-manifest match guard so a difyctl-v* tag push must equal the manifest
  version (prevents a dangling/mismatched tag). Releases may be cut from any
  branch. Concurrency serialized (fixed group, cancel-in-progress: false) so
  releases never double-publish.
- install-cli.sh: tokenless resolution via git/matching-refs, channel-aware
  (stable/rc) selection, fail-closed sha256 verification; distinguishes a fetch
  failure (network / API rate limit) from "no release found".
- install.ps1: Windows installer at parity with install-cli.sh.
- install-cli.test.ts: resolver unit tests (rc ordering, channel filter).
- .bun-version: pin Bun 1.3.11 for reproducible --compile builds.
2026-06-03 19:23:55 -07:00

128 lines
4.6 KiB
Bash
Executable File

#!/bin/sh
# install-cli.sh — one-line difyctl installer from public GitHub Releases.
#
# usage:
# curl -fsSL https://raw.githubusercontent.com/langgenius/dify/main/cli/scripts/install-cli.sh | sh
#
# env:
# DIFYCTL_CHANNEL track to install: stable (default) | rc
# DIFYCTL_VERSION exact version pin (e.g. 0.2.0); overrides DIFYCTL_CHANNEL
# DIFYCTL_PREFIX install dir (default $HOME/.local); binary -> $PREFIX/bin/difyctl
# DIFYCTL_REPO release source repo (default langgenius/dify)
# requires: curl, uname, sort -V, and sha256sum or shasum.
set -eu
REPO="${DIFYCTL_REPO:-langgenius/dify}"
PREFIX="${DIFYCTL_PREFIX:-${HOME}/.local}"
CHANNEL="${DIFYCTL_CHANNEL:-stable}"
VERSION="${DIFYCTL_VERSION:-}"
TAG_PREFIX="difyctl-v"
err() { printf '%s\n' "install-cli: $*" >&2; }
die() { err "$*"; exit 1; }
need() { command -v "$1" >/dev/null 2>&1 || die "$1 is required"; }
# select_version CHANNEL (reads git/matching-refs JSON on stdin) -> highest matching version
select_version() {
_channel="$1"
_versions=$(grep -oE '"ref"[[:space:]]*:[[:space:]]*"refs/tags/difyctl-v[^"]*"' \
| sed -E 's#.*"refs/tags/difyctl-v([^"]*)".*#\1#')
case "$_channel" in
rc) _versions=$(printf '%s\n' "$_versions" | grep -E -- '-rc\.[0-9]+$' || true) ;;
stable) _versions=$(printf '%s\n' "$_versions" | grep -vE -- '-' || true) ;;
*) die "invalid DIFYCTL_CHANNEL: ${_channel} (expected stable | rc)" ;;
esac
printf '%s\n' "$_versions" | sed '/^$/d' | sort -V | tail -1
}
detect_target() {
case "$(uname -s)" in
Linux*) _os=linux ;;
Darwin*) _os=darwin ;;
*) die "unsupported OS: $(uname -s) (use install.ps1 on Windows)" ;;
esac
case "$(uname -m)" in
x86_64|amd64) _arch=x64 ;;
arm64|aarch64) _arch=arm64 ;;
*) die "unsupported arch: $(uname -m)" ;;
esac
printf '%s-%s' "$_os" "$_arch"
}
resolve_version() {
if [ -n "$VERSION" ]; then
printf '%s' "$VERSION"
return 0
fi
_api="https://api.github.com/repos/${REPO}/git/matching-refs/tags/difyctl-v"
if ! _refs=$(curl -fsSL -H "Accept: application/vnd.github+json" "$_api"); then
die "failed to query ${REPO} releases (network error or GitHub API rate limit); set DIFYCTL_VERSION to pin a version"
fi
_resolved=$(printf '%s' "$_refs" | select_version "$CHANNEL")
[ -n "$_resolved" ] || die "no ${CHANNEL} difyctl release found in ${REPO}"
printf '%s' "$_resolved"
}
main() {
need curl
need uname
need sort
if command -v sha256sum >/dev/null 2>&1; then
HASH="sha256sum"
elif command -v shasum >/dev/null 2>&1; then
HASH="shasum -a 256"
else
die "need sha256sum or shasum"
fi
target=$(detect_target)
version=$(resolve_version)
tag="${TAG_PREFIX}${version}"
asset="difyctl-v${version}-${target}"
checksums="difyctl-v${version}-checksums.txt"
base="https://github.com/${REPO}/releases/download/${tag}"
tmp=$(mktemp -d 2>/dev/null || mktemp -d -t difyctl-install)
trap 'rm -rf "$tmp"' EXIT INT TERM
printf 'downloading %s (%s)...\n' "$asset" "$tag"
curl -fsSL "${base}/${asset}" -o "${tmp}/${asset}" \
|| die "download failed: ${base}/${asset}"
curl -fsSL "${base}/${checksums}" -o "${tmp}/${checksums}" \
|| die "checksum manifest download failed: ${base}/${checksums}"
# exact, end-anchored, regex-safe match of the asset's checksum line; fail-closed on no match
_pattern=$(printf '%s' "$asset" | sed 's/[][\\.^$*+?(){}|/]/\\&/g')
_sumline=$(grep -E -- "[[:space:]]${_pattern}\$" "${tmp}/${checksums}") || true
[ -n "$_sumline" ] || die "no checksum entry for ${asset}"
(
cd "$tmp"
printf '%s\n' "$_sumline" | $HASH -c -
) || die "checksum mismatch for ${asset}"
bin_dir="${PREFIX}/bin"
mkdir -p "$bin_dir"
target_bin="${bin_dir}/difyctl"
cp "${tmp}/${asset}" "$target_bin"
chmod +x "$target_bin"
printf '\ndifyctl v%s installed: %s\n' "$version" "$target_bin"
case ":${PATH}:" in
*":${bin_dir}:"*)
"$target_bin" version >/dev/null 2>&1 \
&& printf 'verify: run "difyctl version"\n' \
|| err "binary present but failed to execute; check ${target_bin}"
;;
*)
printf '\n%s is not on your PATH. Add this to your shell profile:\n' "$bin_dir"
printf ' export PATH="%s:$PATH"\n' "$bin_dir"
;;
esac
}
# Run main unless sourced as a library (tests set DIFYCTL_INSTALL_LIB=1).
if [ "${DIFYCTL_INSTALL_LIB:-0}" != "1" ]; then
main "$@"
fi