mirror of
https://github.com/boshu2/agentops.git
synced 2026-09-14 15:08:13 +08:00
23c0026500
## Summary Closes the findings from the v3.3.0 release-viability audit of main. **Surface truth (High):** `ao constraint`, `ao goals trace`, `ao session memory`, and `ao skills edit` are production tombstones but docs/cli-surface.md documented them as `public-tested`/`covered` (the test binary keeps constraint live, and the generated doc reflected the test build). Added `deprecated` allowlist rows, dropped the stale constraint-subcommand rows, regrouped constraint with its tombstone peers in the default spine, regenerated both surface projections, and added `TestCathedralCutTombstonesSurvivePruning` pinning the invariant that every Cathedral Cut verb keeps an executable tombstone. **Installer hardening (Medium):** `install-bd.sh` now fails closed when no published checksum exists for a release asset (explicit `--no-verify` overrides), and the curl|bash path verifies the fetched `installer-common.sh` against a pinned SHA-256 before sourcing it (drift-guarded by a new install-bd.bats test). **Docs (Medium/Low):** UPGRADING.md no longer links the MIGRATION-3.0.md file the Cathedral Cut deleted; CHANGELOG 3.3.0 `### Removed` now names `yield`, `constraint`, and the 3.2 `verify` front door and links the MIGRATION.md map. **Gate scripts (Low):** flywheel/provenance gate scripts build `ao` into `mktemp -d` scratch dirs instead of fixed world-known /tmp paths (symlink/TOCTOU on shared hosts). **Bonus (surfaced by the push gate):** `scripts/lib/preamble.sh` mis-resolved REPO_ROOT under git hook env (GIT_DIR without GIT_WORK_TREE), which made `go.cli-architecture` refuse every worktree-origin push touching `cli/cmd/ao/**`. Fixed by scrubbing the hook-injected discovery env around the rev-parse. ## Validation - `go build && go vet && go test ./...`: 5,085 tests green, golangci-lint clean - `ao gate check --fast`: 27/27 pass, including under simulated hook env - bats install-bd suite 6/6 incl. new pin drift test; shellcheck clean on all touched scripts - markdownlint clean; doc-release gate passes; tombstone exit codes verified on the built binary
354 lines
12 KiB
Bash
Executable File
354 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# install-bd.sh — install the `bd` (beads) CLI to ~/.local/bin/bd.
|
|
#
|
|
# Beads has been "unavailable" in three consecutive nightly runs (2026-04-26
|
|
# retro task 5). Upstream publishes binaries for darwin/linux/windows under
|
|
# https://github.com/steveyegge/beads/releases. This script detects the
|
|
# platform, downloads the matching tarball, verifies checksums when published,
|
|
# and verifies the binary launches.
|
|
#
|
|
# One-liner (cache buster):
|
|
# curl -fsSL "https://raw.githubusercontent.com/boshu2/agentops/main/scripts/install-bd.sh?$(date +%s)" | bash
|
|
#
|
|
# Idempotent: re-running with bd already on PATH at the requested version is
|
|
# a no-op (exit 0). Use --force to redownload.
|
|
#
|
|
# Examples:
|
|
# scripts/install-bd.sh # install latest tagged release
|
|
# scripts/install-bd.sh --version v1.0.3 # pin a version
|
|
# scripts/install-bd.sh --force # redownload even if installed
|
|
# scripts/install-bd.sh --offline /path/to/beads_tarball.tar.gz
|
|
# scripts/install-bd.sh --quiet --no-gum
|
|
#
|
|
# Exit codes:
|
|
# 0 bd installed (or already present at the requested version)
|
|
# 1 download or extraction failed
|
|
# 2 unsupported platform / arch
|
|
# 3 verification failed (binary did not launch)
|
|
|
|
set -euo pipefail
|
|
shopt -s lastpipe 2>/dev/null || true
|
|
umask 022
|
|
|
|
BD_REPO="steveyegge/beads"
|
|
INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}"
|
|
VERSION=""
|
|
FORCE_INSTALL=0
|
|
# shellcheck disable=SC2034 # consumed by the sourced installer-common.sh
|
|
QUIET=0
|
|
# shellcheck disable=SC2034 # consumed by the sourced installer-common.sh
|
|
NO_GUM=0
|
|
NO_VERIFY=0
|
|
OFFLINE=0
|
|
OFFLINE_TARBALL=""
|
|
# shellcheck disable=SC2034 # consumed by the sourced installer-common.sh
|
|
EASY_MODE=0
|
|
FROM_SOURCE=0
|
|
# shellcheck disable=SC2034 # consumed by the sourced installer-common.sh
|
|
INSTALLER_NAME="bd"
|
|
# shellcheck disable=SC2034 # consumed by the sourced installer-common.sh
|
|
INSTALLER_TAGLINE="Install the beads (bd) CLI"
|
|
|
|
# ── Load installer-workmanship common scaffold ────────────────────────────
|
|
# Pin of scripts/lib/installer-common.sh for the curl|bash path. The drift
|
|
# test in tests/scripts/install-bd.bats recomputes this on every change, so
|
|
# an edit to installer-common.sh without a matching bump here fails CI.
|
|
INSTALLER_COMMON_SHA256="6de10b1997a17e546cf4a9a75943b257de66df20708e881431917239b4bb2e28"
|
|
|
|
_sha256_of() {
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
sha256sum "$1" | awk '{print $1}'
|
|
elif command -v shasum >/dev/null 2>&1; then
|
|
shasum -a 256 "$1" | awk '{print $1}'
|
|
else
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
_load_installer_common() {
|
|
local here
|
|
# shellcheck disable=SC1007
|
|
here="$(CDPATH= cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd || true)"
|
|
if [[ -n "$here" && -f "$here/lib/installer-bootstrap.sh" ]]; then
|
|
# shellcheck source=lib/installer-bootstrap.sh
|
|
. "$here/lib/installer-bootstrap.sh"
|
|
agentops_source_installer_common
|
|
return 0
|
|
fi
|
|
# curl|bash: fetch common lib (curl honors HTTPS_PROXY/HTTP_PROXY)
|
|
local url tmp actual
|
|
url="${AGENTOPS_INSTALLER_COMMON_URL:-https://raw.githubusercontent.com/boshu2/agentops/main/scripts/lib/installer-common.sh}"
|
|
tmp="$(mktemp "${TMPDIR:-/tmp}/agentops-installer-common.XXXXXX")"
|
|
if command -v curl >/dev/null 2>&1 && curl -fsSL --connect-timeout 10 "${url}?$(date +%s)" -o "$tmp"; then
|
|
if [[ -z "${AGENTOPS_INSTALLER_COMMON_URL:-}" ]]; then
|
|
# Default URL: verify the fetched lib against the pin before sourcing.
|
|
# A custom AGENTOPS_INSTALLER_COMMON_URL is an explicit operator
|
|
# override and is sourced as supplied.
|
|
actual="$(_sha256_of "$tmp")"
|
|
if [[ -z "$actual" ]]; then
|
|
rm -f "$tmp"
|
|
echo "FATAL: no sha256sum/shasum available to verify installer-common.sh" >&2
|
|
exit 1
|
|
fi
|
|
if [[ "$actual" != "$INSTALLER_COMMON_SHA256" ]]; then
|
|
rm -f "$tmp"
|
|
echo "FATAL: installer-common.sh checksum mismatch (got $actual)" >&2
|
|
echo "Re-fetch this installer from the repo — the two files ship and update together." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
# shellcheck disable=SC1090
|
|
. "$tmp"
|
|
rm -f "$tmp"
|
|
return 0
|
|
fi
|
|
rm -f "$tmp"
|
|
echo "FATAL: could not load installer-common.sh" >&2
|
|
exit 1
|
|
}
|
|
_load_installer_common
|
|
|
|
# Cosign identity targets the beads upstream repo (not AgentOps).
|
|
# shellcheck disable=SC2034 # all four consumed by the sourced installer-common.sh
|
|
OWNER="steveyegge"
|
|
# shellcheck disable=SC2034 # consumed by the sourced installer-common.sh
|
|
REPO="beads"
|
|
# shellcheck disable=SC2034 # consumed by the sourced installer-common.sh
|
|
INSTALLER_NAME="bd"
|
|
# shellcheck disable=SC2034 # consumed by the sourced installer-common.sh
|
|
INSTALLER_TAGLINE="Install the beads (bd) CLI"
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
install-bd.sh — install the \`bd\` (beads) CLI
|
|
|
|
One-liner:
|
|
curl -fsSL "https://raw.githubusercontent.com/boshu2/agentops/main/scripts/install-bd.sh?\$(date +%s)" | bash
|
|
|
|
Options:
|
|
--version <tag> Install a specific release tag (default: latest)
|
|
--install-dir <dir> Install directory (default: ~/.local/bin)
|
|
--from-source Build with \`go install\` instead of downloading a binary
|
|
$(common_installer_flags_help)
|
|
-h, --help Show this help
|
|
|
|
Uninstall:
|
|
rm -f "\${INSTALL_DIR:-$HOME/.local/bin}/bd"
|
|
EOF
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
if parse_common_installer_flag "$@"; then
|
|
shift "$CONSUMED"
|
|
continue
|
|
fi
|
|
case "$1" in
|
|
--version) VERSION="$2"; shift 2 ;;
|
|
--install-dir) INSTALL_DIR="$2"; shift 2 ;;
|
|
--from-source) FROM_SOURCE=1; shift ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) echo "unknown flag: $1" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
FORCE="$FORCE_INSTALL"
|
|
|
|
print_branded_header "bd installer" "Install the beads (bd) CLI"
|
|
setup_proxy
|
|
detect_platform_go
|
|
|
|
if [[ -z "${GOOS:-}" || -z "${GOARCH:-}" ]]; then
|
|
err "unsupported OS/arch: $(uname -s)/$(uname -m)"
|
|
exit 2
|
|
fi
|
|
|
|
acquire_install_lock "bd"
|
|
|
|
# --- resolve version ---
|
|
resolve_version() {
|
|
if [[ -n "$VERSION" ]]; then
|
|
return 0
|
|
fi
|
|
if [[ "$OFFLINE" -eq 1 && -n "$OFFLINE_TARBALL" ]]; then
|
|
VERSION="offline"
|
|
return 0
|
|
fi
|
|
if ! command -v curl >/dev/null 2>&1; then
|
|
fail "curl is required to resolve latest version"
|
|
fi
|
|
# Tier 1: GitHub API
|
|
VERSION="$(curl -fsSL --connect-timeout 5 "${PROXY_ARGS[@]}" \
|
|
"https://api.github.com/repos/${BD_REPO}/releases/latest" 2>/dev/null \
|
|
| sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | head -1 || true)"
|
|
if [[ -n "$VERSION" ]]; then
|
|
return 0
|
|
fi
|
|
# Tier 2: redirect URL
|
|
VERSION="$(curl -fsSL --connect-timeout 5 -o /dev/null -w '%{url_effective}' "${PROXY_ARGS[@]}" \
|
|
"https://github.com/${BD_REPO}/releases/latest" 2>/dev/null \
|
|
| sed -E 's|.*/tag/||' || true)"
|
|
if [[ -z "$VERSION" ]]; then
|
|
fail "could not resolve latest beads release tag"
|
|
fi
|
|
}
|
|
|
|
run_with_spinner "Resolving latest beads version..." resolve_version
|
|
info "Target version: $VERSION ($GOOS/$GOARCH)"
|
|
|
|
# --- short-circuit if already installed at the requested version ---
|
|
if [[ "$FORCE" -eq 0 && "$FROM_SOURCE" -eq 0 && "$VERSION" != "offline" ]] \
|
|
&& command -v bd >/dev/null 2>&1; then
|
|
have="$(bd version 2>&1 | head -1 || true)"
|
|
if [[ "$have" == *"${VERSION#v}"* ]]; then
|
|
ok "bd ${VERSION} already installed at $(command -v bd) — skipping (--force to override)"
|
|
summary_add "bd: already installed ($VERSION)"
|
|
print_summary "bd already present"
|
|
print_uninstall_hint "rm -f $(command -v bd)"
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
preflight_checks "$INSTALL_DIR" "https://github.com/${BD_REPO}"
|
|
|
|
tmp_dir="$(installer_mktemp_dir "bd-install")"
|
|
|
|
build_from_source() {
|
|
if ! command -v go >/dev/null 2>&1; then
|
|
fail "go is required for --from-source (or when no prebuilt binary is available)"
|
|
fi
|
|
local mod="github.com/${BD_REPO}/cmd/bd"
|
|
local ver="$VERSION"
|
|
[[ "$ver" == "offline" || -z "$ver" ]] && ver="latest"
|
|
info "Building bd from source ($mod@$ver)..."
|
|
GOBIN="$tmp_dir" run_with_spinner "go install $mod@$ver..." \
|
|
go install "${mod}@${ver}"
|
|
if [[ ! -x "$tmp_dir/bd" ]]; then
|
|
fail "go install did not produce $tmp_dir/bd"
|
|
fi
|
|
mkdir -p "$INSTALL_DIR"
|
|
install -m 0755 "$tmp_dir/bd" "$INSTALL_DIR/bd"
|
|
ok "Built and installed bd from source → $INSTALL_DIR/bd"
|
|
}
|
|
|
|
fetch_checksum_for_asset() {
|
|
local asset="$1"
|
|
local ver="$2"
|
|
local sums_url candidate expected
|
|
# Try common checksum artifact names published next to the release asset.
|
|
for candidate in \
|
|
"checksums.txt" \
|
|
"SHA256SUMS" \
|
|
"sha256sums.txt" \
|
|
"beads_${ver#v}_checksums.txt" \
|
|
"${asset}.sha256"
|
|
do
|
|
sums_url="https://github.com/${BD_REPO}/releases/download/${ver}/${candidate}"
|
|
if curl -fsSL --connect-timeout 5 "${PROXY_ARGS[@]}" "$sums_url" -o "$tmp_dir/sums.txt" 2>/dev/null; then
|
|
if [[ "$candidate" == *.sha256 ]]; then
|
|
expected="$(awk '{print $1}' "$tmp_dir/sums.txt" | head -1)"
|
|
else
|
|
expected="$(awk -v a="$asset" '$2 == a || $2 == ("*" a) || $NF == a {print $1; exit}' "$tmp_dir/sums.txt")"
|
|
fi
|
|
if [[ -n "$expected" ]]; then
|
|
printf '%s\n' "$expected"
|
|
return 0
|
|
fi
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
install_from_tarball() {
|
|
local archive="$1"
|
|
[[ -f "$archive" ]] || fail "Tarball not found: $archive"
|
|
|
|
info "extracting $(basename "$archive")"
|
|
if ! tar -xzf "$archive" -C "$tmp_dir"; then
|
|
fail "extraction failed"
|
|
fi
|
|
|
|
local binary="" candidate
|
|
for candidate in "$tmp_dir/bd" "$tmp_dir/beads" "$tmp_dir"/*/bd "$tmp_dir"/*/beads; do
|
|
if [[ -f "$candidate" && -x "$candidate" ]]; then
|
|
binary="$candidate"
|
|
break
|
|
fi
|
|
done
|
|
if [[ -z "$binary" ]]; then
|
|
err "could not locate bd binary in tarball"
|
|
ls -R "$tmp_dir" >&2 || true
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$INSTALL_DIR"
|
|
install -m 0755 "$binary" "$INSTALL_DIR/bd"
|
|
ok "installed bd to $INSTALL_DIR/bd"
|
|
}
|
|
|
|
download_and_install() {
|
|
if [[ -n "$OFFLINE_TARBALL" ]]; then
|
|
install_from_tarball "$OFFLINE_TARBALL"
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$FROM_SOURCE" -eq 1 ]]; then
|
|
build_from_source
|
|
return 0
|
|
fi
|
|
|
|
local ver_no_v="${VERSION#v}"
|
|
local asset="beads_${ver_no_v}_${GOOS}_${GOARCH}.tar.gz"
|
|
local url="https://github.com/${BD_REPO}/releases/download/${VERSION}/${asset}"
|
|
local archive="$tmp_dir/$asset"
|
|
|
|
info "downloading $url"
|
|
if ! run_with_spinner "Downloading $asset..." \
|
|
curl -fsSL --connect-timeout 30 "${PROXY_ARGS[@]}" "$url" -o "$archive"; then
|
|
warn "download failed: $url"
|
|
warn "Falling back to build-from-source"
|
|
build_from_source
|
|
return 0
|
|
fi
|
|
|
|
local expected=""
|
|
if expected="$(fetch_checksum_for_asset "$asset" "$VERSION")"; then
|
|
verify_checksum "$archive" "$expected" || exit 1
|
|
else
|
|
if [[ "$NO_VERIFY" -eq 1 ]]; then
|
|
warn "No published checksum found; continuing because --no-verify was set"
|
|
else
|
|
err "No published checksum found for $asset; refusing to install an unverified binary"
|
|
err "Re-run with --no-verify to accept the download without SHA256 verification"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Best-effort Sigstore (soft-skip without cosign / bundle)
|
|
verify_sigstore "$archive" \
|
|
"https://github.com/${BD_REPO}/releases/download/${VERSION}/${asset}.sigstore.json" \
|
|
|| true
|
|
|
|
install_from_tarball "$archive"
|
|
}
|
|
|
|
download_and_install
|
|
|
|
# --- verify ---
|
|
target="$INSTALL_DIR/bd"
|
|
if ! "$target" version >/dev/null 2>&1; then
|
|
err "verification failed: $target version did not launch"
|
|
exit 3
|
|
fi
|
|
|
|
actual="$("$target" version 2>&1 | head -1)"
|
|
ok "verified: $actual"
|
|
maybe_add_path "$INSTALL_DIR"
|
|
|
|
summary_add "Binary: $target"
|
|
summary_add "Version: $actual"
|
|
summary_add "Platform: ${GOOS}/${GOARCH}"
|
|
print_summary "bd install complete"
|
|
print_uninstall_hint "rm -f $target"
|
|
|
|
exit 0
|