mirror of
https://github.com/VibedByKaKi/t3-code-android-nightly.git
synced 2026-10-09 11:51:15 +02:00
227 lines
9.2 KiB
Bash
Executable file
227 lines
9.2 KiB
Bash
Executable file
#!/bin/sh
|
|
# Installs the T3 Code CLI from a GitHub Release archive. Needs only sh, tar,
|
|
# sha256sum or shasum, and curl or wget; no Node, npm, or compiler.
|
|
#
|
|
# curl -fsSL https://t3.codes/install.sh | sh
|
|
#
|
|
# Environment:
|
|
# T3CODE_CHANNEL release train to follow: stable, nightly, or preview
|
|
# (default: stable; preview is a maintainers' test train)
|
|
# T3CODE_VERSION exact version to install (overrides T3CODE_CHANNEL)
|
|
# T3CODE_HOME T3 home directory (default: ~/.t3)
|
|
# T3CODE_INSTALL_BIN_DIR where the `t3` symlink goes (default: ~/.local/bin)
|
|
# T3CODE_RELEASE_BASE_URL mirror for releases/download (default: GitHub)
|
|
#
|
|
# The archive is unpacked into $T3CODE_HOME/runtime/versions/<version>, the
|
|
# same layout `t3 service install` uses, so the service reuses this download
|
|
# instead of fetching the release again.
|
|
set -eu
|
|
|
|
repo="pingdotgg/t3code"
|
|
base_url="${T3CODE_RELEASE_BASE_URL:-https://github.com/${repo}/releases/download}"
|
|
t3_home="${T3CODE_HOME:-$HOME/.t3}"
|
|
bin_dir="${T3CODE_INSTALL_BIN_DIR:-$HOME/.local/bin}"
|
|
|
|
fail() {
|
|
printf '\nt3 install: %s\n' "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
# ANSI stays on stderr, so `curl ... | sh` still gets progress.
|
|
interactive=false
|
|
if [ -t 2 ] && [ "${TERM:-}" != dumb ]; then interactive=true; fi
|
|
reset= bold= muted= accent= green=
|
|
if "$interactive" && [ -z "${NO_COLOR:-}" ]; then
|
|
reset="$(printf '\033[0m')"; bold="$(printf '\033[1m')"
|
|
muted="$(printf '\033[2m')"; accent="$(printf '\033[94m')"; green="$(printf '\033[32m')"
|
|
fi
|
|
step() {
|
|
if "$interactive"; then printf '\r\033[2K %s%s%s' "$muted" "$1" "$reset" >&2
|
|
else printf ' %s\n' "$1" >&2; fi
|
|
}
|
|
if "$interactive"; then
|
|
printf '\n%s' "$bold" >&2
|
|
printf ' %s\n' '██████████ ████████ ' >&2
|
|
printf ' %s\n' ' ███ ▄██▀ T3 Code' >&2
|
|
printf ' %s%s %sCLI installer%s\n' ' ███ ████▄ ' "$reset" "$muted" "$reset$bold" >&2
|
|
printf ' %s\n' ' ███ ▄ ███' >&2
|
|
printf ' %s\n' ' ███ ███████▀ ' >&2
|
|
printf '%s\n' "$reset" >&2
|
|
fi
|
|
step "Finding your release..."
|
|
|
|
# Exit 44 on a 404 so callers can tell "no such asset" from a network failure.
|
|
fetch() {
|
|
if command -v curl >/dev/null 2>&1; then
|
|
status="$(curl -sSL -w '%{http_code}' "$1" -o "$2")" || return 1
|
|
case "$status" in
|
|
2??) return 0 ;;
|
|
404) return 44 ;;
|
|
*) printf 'GET %s returned HTTP %s\n' "$1" "$status" >&2; return 1 ;;
|
|
esac
|
|
elif command -v wget >/dev/null 2>&1; then
|
|
wget -q --server-response "$1" -O "$2" 2>"$2.headers" && rm -f "$2.headers" && return 0
|
|
if grep -q ' 404 ' "$2.headers" 2>/dev/null; then rm -f "$2.headers"; return 44; fi
|
|
cat "$2.headers" >&2; rm -f "$2.headers"; return 1
|
|
else
|
|
fail "curl or wget is required"
|
|
fi
|
|
}
|
|
|
|
mb() {
|
|
tenths=$((($1 * 10 + 524288) / 1048576))
|
|
printf '%s.%s' "$((tenths / 10))" "$((tenths % 10))"
|
|
}
|
|
# Poll the file written by the downloader; no progress-output parsing or extra request.
|
|
download() {
|
|
if ! "$interactive"; then fetch "$1" "$2"; return; fi
|
|
if command -v curl >/dev/null 2>&1; then
|
|
curl -fsSL -D "$2.headers" "$1" -o "$2" 2>"$2.errors" &
|
|
else
|
|
wget -q --server-response "$1" -O "$2" 2>"$2.headers" &
|
|
fi
|
|
download_pid=$!
|
|
previous=-1
|
|
cr="$(printf '\r')"
|
|
while kill -0 "$download_pid" 2>/dev/null; do
|
|
bytes=0; total=0
|
|
if [ -f "$2" ]; then bytes="$(wc -c < "$2")"; fi
|
|
if [ -f "$2.headers" ]; then
|
|
while read -r key value; do
|
|
case "$key" in
|
|
HTTP/*) total=0 ;;
|
|
[Cc]ontent-[Ll]ength:) total="${value%"$cr"}" ;;
|
|
esac
|
|
done < "$2.headers"
|
|
fi
|
|
case "$total" in ''|*[!0-9]*) total=0 ;; esac
|
|
if [ "$bytes" -ne "$previous" ]; then
|
|
if [ "$total" -gt 0 ]; then
|
|
percent=$((bytes * 100 / total)); [ "$percent" -le 100 ] || percent=100
|
|
filled=$((percent * 32 / 100)); bar=; rest=; n=0
|
|
while [ "$n" -lt 32 ]; do
|
|
if [ "$n" -lt "$filled" ]; then bar="${bar}■"; else rest="${rest}·"; fi
|
|
n=$((n + 1))
|
|
done
|
|
printf '\r\033[2K %s%s%s%s%s %3d%% %s%s / %s MB%s' "$accent" "$bar" "$reset$muted" "$rest" "$reset" "$percent" "$muted" "$(mb "$bytes")" "$(mb "$total")" "$reset" >&2
|
|
else
|
|
printf '\r\033[2K %sDownloading%s %s MB' "$muted" "$reset" "$(mb "$bytes")" >&2
|
|
fi
|
|
previous="$bytes"
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
result=0; wait "$download_pid" || result=$?
|
|
download_pid=
|
|
if [ "$result" -ne 0 ]; then
|
|
printf '\n' >&2
|
|
if [ -f "$2.errors" ]; then cat "$2.errors" >&2; else cat "$2.headers" >&2; fi
|
|
return "$result"
|
|
fi
|
|
size="$(mb "$(wc -c < "$2")")"
|
|
printf '\r\033[2K %s■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■%s 100%% %s%s / %s MB%s\n' "$accent" "$reset" "$muted" "$size" "$size" "$reset" >&2
|
|
rm -f "$2.headers" "$2.errors"
|
|
}
|
|
|
|
case "$(uname -s)" in
|
|
Darwin) platform="darwin" ;;
|
|
Linux) platform="linux" ;;
|
|
*) fail "unsupported operating system $(uname -s); use the desktop app or npm" ;;
|
|
esac
|
|
case "$(uname -m)" in
|
|
arm64 | aarch64) arch="arm64" ;;
|
|
x86_64 | amd64) arch="x64" ;;
|
|
*) fail "unsupported architecture $(uname -m)" ;;
|
|
esac
|
|
command -v tar >/dev/null 2>&1 || fail "tar is required"
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
checksum() { sha256sum "$1" | cut -d' ' -f1; }
|
|
elif command -v shasum >/dev/null 2>&1; then
|
|
checksum() { shasum -a 256 "$1" | cut -d' ' -f1; }
|
|
else
|
|
fail "sha256sum or shasum is required"
|
|
fi
|
|
|
|
channel="${T3CODE_CHANNEL:-stable}"
|
|
version="${T3CODE_VERSION:-}"
|
|
if [ -z "$version" ]; then
|
|
# Tags are v<semver>; the channel is the prerelease identifier, or none for
|
|
# stable. Only tags of the requested train are considered, so a stable
|
|
# install can never pick up a nightly or preview build by accident.
|
|
case "$channel" in
|
|
stable) tag_pattern='v\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)' ;;
|
|
nightly | preview) tag_pattern="v\([0-9][^\"]*-${channel}\.[0-9]*\.[0-9]*\)" ;;
|
|
*) fail "T3CODE_CHANNEL must be stable, nightly, or preview" ;;
|
|
esac
|
|
tmp_index="$(mktemp)"
|
|
fetch "https://api.github.com/repos/${repo}/releases?per_page=100" "$tmp_index"
|
|
version="$(sed -n "s/.*\"tag_name\": *\"${tag_pattern}\".*/\1/p" "$tmp_index" | head -n 1)"
|
|
rm -f "$tmp_index"
|
|
[ -n "$version" ] || fail "could not find a ${channel} release; set T3CODE_VERSION"
|
|
fi
|
|
case "$version" in
|
|
*-preview.*)
|
|
printf '%s\n' \
|
|
"t3 ${version} is a preview build." \
|
|
" Preview builds are cut by maintainers from unreleased branches to exercise the release" \
|
|
" pipeline. They can be broken, receive no fixes, and are never offered as updates." \
|
|
" Set T3CODE_CHANNEL=stable (the default) for a supported build." >&2
|
|
if [ "$channel" != "preview" ] && [ -z "${T3CODE_VERSION:-}" ]; then
|
|
fail "refusing a preview build that was not explicitly requested"
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
stem="t3-${version}-${platform}-${arch}"
|
|
archive="${stem}.tar.gz"
|
|
versions_dir="${t3_home}/runtime/versions"
|
|
target_dir="${versions_dir}/${version}"
|
|
|
|
if [ -f "${target_dir}/.install-complete" ] && [ "$(cat "${target_dir}/.install-complete")" = "$version" ]; then
|
|
step "Version ${version} is already downloaded."
|
|
else
|
|
mkdir -p "$versions_dir"
|
|
staging="$(mktemp -d "${versions_dir}/.staging-XXXXXX")"
|
|
download_pid=
|
|
trap '[ -z "$download_pid" ] || { kill "$download_pid" 2>/dev/null || true; wait "$download_pid" 2>/dev/null || true; }; rm -rf "$staging"' EXIT
|
|
trap 'printf "\n" >&2; exit 130' INT
|
|
trap 'printf "\n" >&2; exit 143' TERM
|
|
|
|
if "$interactive"; then printf '\r\033[2K' >&2; fi
|
|
printf ' %sInstalling%s T3 Code %s%s%s\n\n' "$muted" "$reset" "$bold" "$version" "$reset" >&2
|
|
step "Downloading..."
|
|
fetch_status=0
|
|
fetch "${base_url}/v${version}/SHA256SUMS" "${staging}/SHA256SUMS" || fetch_status=$?
|
|
if [ "$fetch_status" -eq 44 ]; then
|
|
fail "t3 ${version} has no release archive for ${platform}-${arch}; releases before the self-contained CLI can only be installed with \`npm install -g t3@${version}\`"
|
|
elif [ "$fetch_status" -ne 0 ]; then
|
|
fail "could not download the release checksums"
|
|
fi
|
|
download "${base_url}/v${version}/${archive}" "${staging}/${archive}"
|
|
|
|
step "Verifying the download..."
|
|
expected="$(grep " \*\{0,1\}${archive}\$" "${staging}/SHA256SUMS" | cut -d' ' -f1)"
|
|
[ -n "$expected" ] || fail "${archive} is not listed in SHA256SUMS"
|
|
actual="$(checksum "${staging}/${archive}")"
|
|
[ "$actual" = "$expected" ] || fail "checksum mismatch for ${archive}"
|
|
|
|
step "Extracting T3 Code..."
|
|
tar -xzf "${staging}/${archive}" -C "$staging" --strip-components=1
|
|
rm -f "${staging}/${archive}" "${staging}/SHA256SUMS"
|
|
"${staging}/t3" --version >/dev/null || fail "the downloaded executable does not run"
|
|
printf '%s\n' "$version" > "${staging}/.install-complete"
|
|
|
|
rm -rf "$target_dir"
|
|
mv "$staging" "$target_dir"
|
|
trap - EXIT
|
|
fi
|
|
|
|
step "Setting up the t3 command..."
|
|
mkdir -p "$bin_dir"
|
|
ln -sfn "${target_dir}/t3" "${bin_dir}/t3"
|
|
if "$interactive"; then printf '\r\033[2K' >&2; fi
|
|
printf ' %sInstalled T3 Code %s%s\n\n' "$green" "$version" "$reset" >&2
|
|
case ":${PATH}:" in
|
|
*":${bin_dir}:"*) printf ' Run %st3%s to get started.\n\n' "$bold" "$reset" ;;
|
|
*) printf ' Add %s to your PATH, then run %st3%s.\n\n' "$bin_dir" "$bold" "$reset" ;;
|
|
esac
|