Files
dify/cli/scripts/install-cli.sh
GareArc 1bbd638c15 refactor(cli): single-source difyctl release artifact naming
Centralize release naming (tag, asset filenames, checksum file, target
list, arch/os tokens) so producers derive from one source instead of
duplicating literals across build, checksum, and workflow files.

- cli/package.json: add difyctl.release block (tagPrefix, binName,
  checksumsSuffix, targets[id/bunTarget/exe]) as the naming data source.
- cli/scripts/release-naming.mjs: new format source + validator; subcommands
  tag/asset/checksums/tag-prefix/targets/validate.
- release-build.sh, release-write-checksums.sh: derive target list and asset
  names from release-naming.mjs; drop hardcoded maps and literals.
- release-validate-manifest.sh: run release-naming.mjs validate, rejecting a
  malformed difyctl.release before any build derives from it.
- cli-release.yml: emit tagPrefix from the manifest step; tag guard,
  idempotency check, tag_name, and files glob all derive from it.
- install-cli.sh, install.ps1: standalone installers stay hardcoded by
  convention; add a comment pointing at the source.
2026-06-03 20:38:21 -07:00

131 lines
4.8 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:-}"
# Asset/tag naming is convention-locked to cli/package.json `difyctl.release`
# (and release-naming.mjs). This installer ships standalone (curl | sh) so it
# cannot read that source at runtime — keep these in sync by hand if it changes.
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