Files
james.maesandClaude Opus 5.5 0414e46a60
node / node (push) Successful in 1m12s
build / node (push) Successful in 1m12s
ci(release): build no musl binaries; the gate passes, musl fails
v1.0.1-rc.1 passed scripts/release.mjs in CI, then build-binaries stopped:
Bun on the glibc container installs no OpenTUI musl native package
(@opentui/core-linux-x64-musl), and the script calls musl best effort. The
release ships linux x64/arm64, darwin x64/arm64 and windows x64; install.sh
says so on musl hosts and points at the npm package.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 07:52:03 -06:00

146 lines
6.0 KiB
Bash
Executable File

#!/bin/sh
# Copyright 2026 QRun.IO, Inc.
# Licensed under the Apache License, Version 2.0 (see LICENSE).
#
# Installs the qqq CLI single-file binary for this machine from the Gitea releases of
# QRun-IO/qqq-cli (git.kof22.com), after checking it against the release's SHA256SUMS.
#
# curl -fsSL https://git.kof22.com/QRun-IO/qqq-cli/raw/branch/main/scripts/install.sh | sh
# curl -fsSL .../install.sh | sh -s -- --version 1.0.0 --dir /usr/local/bin
#
# By default it installs the newest stable v* release (the channel Homebrew and Scoop follow);
# --pre takes the newest release including prereleases. --help lists the options.
set -eu
repo="QRun-IO/qqq-cli"
version="${QQQ_VERSION:-}"
dir="${QQQ_INSTALL_DIR:-$HOME/.local/bin}"
base="${QQQ_DOWNLOAD_BASE:-https://git.kof22.com/$repo/releases/download}"
musl=""
pre=""
dry_run=""
releases_url="${QQQ_RELEASES_URL:-https://git.kof22.com/api/v1/repos/$repo/releases?limit=30}"
usage() {
cat <<'USAGE'
Install qqq (the CLI and the terminal UI, one binary) from https://git.kof22.com/QRun-IO/qqq-cli/releases
curl -fsSL https://git.kof22.com/QRun-IO/qqq-cli/raw/branch/main/scripts/install.sh | sh
curl -fsSL https://git.kof22.com/QRun-IO/qqq-cli/raw/branch/main/scripts/install.sh | sh -s -- [options]
Options (or environment):
--version V (QQQ_VERSION) a release such as 1.0.0; default: the newest stable v* release
--pre (QQQ_PRE=1) with no --version, take the newest release including prereleases
--dir D (QQQ_INSTALL_DIR) where qqq goes; default: ~/.local/bin
--musl take the musl build on Linux (detected on Alpine and other musl hosts)
--dry-run print the version and download URL, change nothing
-h, --help this help
QQQ_DOWNLOAD_BASE release download root (default https://git.kof22.com/QRun-IO/qqq-cli/releases/download),
for mirrors and testing
Supported: macOS arm64 and x64, Linux x64 and arm64 (glibc and musl). On Windows use Scoop:
scoop bucket add qrun https://git.kof22.com/QRun-IO/scoop-bucket
scoop install qqq
USAGE
}
say() { printf '%s\n' "$*" >&2; }
die() { say "qqq install: $*"; exit 1; }
while [ $# -gt 0 ]; do
case "$1" in
--version) [ $# -ge 2 ] || die "--version needs a value"; version="$2"; shift 2 ;;
--dir) [ $# -ge 2 ] || die "--dir needs a value"; dir="$2"; shift 2 ;;
--musl) musl="-musl"; shift ;;
--pre) pre=1; shift ;;
--dry-run) dry_run=1; shift ;;
-h|--help) usage; exit 0 ;;
*) die "unknown option $1" ;;
esac
done
if command -v curl >/dev/null 2>&1; then
fetch() { curl -fsSL --retry 3 -o "$2" "$1"; }
fetch_text() { curl -fsSL --retry 3 "$1"; }
elif command -v wget >/dev/null 2>&1; then
fetch() { wget -q -O "$2" "$1"; }
fetch_text() { wget -q -O - "$1"; }
else
die "needs curl or wget"
fi
case "$(uname -s)" in
Darwin) os=darwin ;;
Linux) os=linux ;;
MINGW*|MSYS*|CYGWIN*) die "on Windows install with Scoop (scoop bucket add qrun https://git.kof22.com/QRun-IO/scoop-bucket; scoop install qqq) or download qqq-windows-x64.exe from https://git.kof22.com/$repo/releases" ;;
*) die "unsupported operating system $(uname -s)" ;;
esac
case "$(uname -m)" in
x86_64|amd64) arch=x64 ;;
arm64|aarch64) arch=arm64 ;;
*) die "unsupported architecture $(uname -m)" ;;
esac
if [ "$os" = darwin ] && [ "$arch" = x64 ] && [ "$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)" = 1 ]; then
arch=arm64 # a shell running under Rosetta on Apple silicon: install the native build
fi
if [ "$os" = linux ] && [ -z "$musl" ]; then
if [ -f /etc/alpine-release ] || (ldd --version 2>&1 | grep -qi musl); then musl="-musl"; fi
fi
[ "$os" = linux ] || musl=""
[ -n "${QQQ_PRE:-}" ] && [ "${QQQ_PRE}" != 0 ] && pre=1
if [ -z "$version" ]; then
# The Gitea releases API lists newest first; each release's "tag_name" comes before its "prerelease".
# Escaped quotes inside release notes never match these patterns.
version="$(fetch_text "$releases_url" | tr ',' '\n' | awk -v pre="$pre" '
/"tag_name": *"/ { tag = $0; sub(/.*"tag_name": *"/, "", tag); sub(/".*/, "", tag); next }
/"prerelease": *(true|false)/ {
if (tag ~ /^v[0-9]/ && (pre != "" || $0 ~ /false/)) { sub(/^v/, "", tag); print tag; exit }
tag = ""
}')" || true
if [ -z "$version" ]; then
if [ -n "$pre" ]; then die "could not find a v* release of $repo; pass --version"; fi
die "could not find a stable v* release of $repo; pass --version, or --pre for prereleases"
fi
fi
version="${version#v}"
version="${version#cli-v}"
file="qqq-$os-$arch$musl"
url="$base/v$version/$file"
if [ -n "$dry_run" ]; then
say "qqq $version: $url (checked against $base/v$version/SHA256SUMS), into $dir/qqq"
exit 0
fi
tmp="$(mktemp -d 2>/dev/null || mktemp -d -t qqq)"
trap 'rm -rf "$tmp"' EXIT
trap 'exit 130' INT TERM
say "Downloading qqq $version ($file)"
fetch "$url" "$tmp/$file" || die "download failed: $url${musl:+ (releases since 1.0.1 have no musl build; on musl hosts install the CLI with npm: @qrun-io/qqq-cli)}"
fetch "$base/v$version/SHA256SUMS" "$tmp/SHA256SUMS" || die "download failed: $base/v$version/SHA256SUMS"
expected="$(awk -v f="$file" '$2 == f { print $1 }' "$tmp/SHA256SUMS")"
[ -n "$expected" ] || die "SHA256SUMS has no entry for $file"
if command -v sha256sum >/dev/null 2>&1; then
actual="$(sha256sum "$tmp/$file" | awk '{ print $1 }')"
elif command -v shasum >/dev/null 2>&1; then
actual="$(shasum -a 256 "$tmp/$file" | awk '{ print $1 }')"
else
die "needs sha256sum or shasum to check the download"
fi
[ "$actual" = "$expected" ] || die "checksum mismatch for $file (expected $expected, got $actual)"
mkdir -p "$dir"
chmod 755 "$tmp/$file"
mv "$tmp/$file" "$dir/qqq"
say "Installed $dir/qqq"
"$dir/qqq" --version >&2 || die "$dir/qqq does not run on this machine${musl:+ (musl builds need libstdc++ and libgcc: apk add libstdc++ libgcc)}"
case ":$PATH:" in
*":$dir:"*) ;;
*) say "Add $dir to your PATH, for example: echo 'export PATH=\"$dir:\$PATH\"' >> ~/.profile" ;;
esac
say "Next: qqq login --server https://your-qqq-server"