The README always shipped graph engineering enabled; the enablement was
delegated to installer env plumbing that diverged per platform:
install.sh exported KIGI_GRAPH=1 into shell rc (worked), install.ps1
wrote the User registry variable — which running Windows terminals (and
new tabs of an open Windows Terminal) never pick up, so /graph was
'missing on Windows' despite a successful install.
With resolve_graph() defaulting true in the binary (e53a66d), the env
writes are redundant complexity: drop them from both installers, keep
KIGI_GRAPH=0 as the documented opt-out (env still beats the default),
and correct the flag comment to tell this story instead of a
'gray release' one. Both scripts syntax-checked (sh -n / pwsh parser).
Installers are served from main (raw.githubusercontent), so this takes
effect for all new installs immediately — no retag needed; the running
v0.1.5 build already carries the binary-default fix.
251 lines
9.5 KiB
Bash
251 lines
9.5 KiB
Bash
#!/bin/sh
|
|
#
|
|
# Kigi installer (macOS / Linux) — PRD F8.
|
|
#
|
|
# Downloads the matching platform artifact from this repo's GitHub Releases,
|
|
# verifies its SHA-256 against the release's SHA256SUMS manifest, and installs
|
|
# the binary as ~/.kigi/bin/kigi (the same managed layout the self-updater
|
|
# maintains: versioned binary in ~/.kigi/downloads/, atomic symlink in bin/).
|
|
#
|
|
# Usage:
|
|
# curl -fsSL https://raw.githubusercontent.com/ZacharyZhang-NY/Kigi-CLI/main/install.sh | sh
|
|
# sh install.sh --version v0.1.0 # pin a specific release
|
|
#
|
|
# Environment:
|
|
# KIGI_SHARE_DIR install root (default: ~/.kigi)
|
|
# KIGI_UPDATE_BASE_URL GitHub-Releases-shaped API base (default:
|
|
# https://api.github.com/repos/ZacharyZhang-NY/Kigi-CLI/releases)
|
|
#
|
|
# Fails fast on any error; never leaves a partial binary as the active kigi.
|
|
|
|
set -eu
|
|
|
|
REPO="ZacharyZhang-NY/Kigi-CLI"
|
|
API_BASE="${KIGI_UPDATE_BASE_URL:-https://api.github.com/repos/${REPO}/releases}"
|
|
KIGI_HOME="${KIGI_SHARE_DIR:-$HOME/.kigi}"
|
|
|
|
err() {
|
|
printf 'install.sh: error: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
usage() {
|
|
sed -n '2,20p' "$0" 2>/dev/null | sed 's/^# \{0,1\}//'
|
|
}
|
|
|
|
# ── Arguments ────────────────────────────────────────────────────────────────
|
|
VERSION=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--version)
|
|
[ $# -ge 2 ] || err "--version requires an argument (e.g. --version v0.1.0)"
|
|
VERSION="$2"
|
|
shift
|
|
;;
|
|
--version=*)
|
|
VERSION="${1#--version=}"
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
err "unknown argument: $1 (supported: --version vX.Y.Z)"
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
VERSION="${VERSION#v}"
|
|
if [ -n "$VERSION" ]; then
|
|
case "$VERSION" in
|
|
[0-9]*.[0-9]*.[0-9]*) ;;
|
|
*) err "invalid version '$VERSION' (expected X.Y.Z or vX.Y.Z)" ;;
|
|
esac
|
|
fi
|
|
|
|
# ── Platform detection ───────────────────────────────────────────────────────
|
|
OS="$(uname -s)"
|
|
ARCH="$(uname -m)"
|
|
case "$OS" in
|
|
Darwin)
|
|
PLATFORM_OS="macos"
|
|
case "$ARCH" in
|
|
arm64|aarch64) TRIPLE="aarch64-apple-darwin"; PLATFORM_ARCH="aarch64" ;;
|
|
x86_64) TRIPLE="x86_64-apple-darwin"; PLATFORM_ARCH="x86_64" ;;
|
|
*) err "unsupported macOS architecture: $ARCH" ;;
|
|
esac
|
|
;;
|
|
Linux)
|
|
PLATFORM_OS="linux"
|
|
case "$ARCH" in
|
|
arm64|aarch64) TRIPLE="aarch64-unknown-linux-gnu"; PLATFORM_ARCH="aarch64" ;;
|
|
x86_64|amd64) TRIPLE="x86_64-unknown-linux-gnu"; PLATFORM_ARCH="x86_64" ;;
|
|
*) err "unsupported Linux architecture: $ARCH" ;;
|
|
esac
|
|
;;
|
|
*)
|
|
err "unsupported OS: $OS (Windows: use install.ps1)"
|
|
;;
|
|
esac
|
|
|
|
# ── Downloader ───────────────────────────────────────────────────────────────
|
|
if command -v curl >/dev/null 2>&1; then
|
|
fetch() { curl -fsSL -o "$2" "$1"; }
|
|
fetch_stdout() { curl -fsSL "$1"; }
|
|
elif command -v wget >/dev/null 2>&1; then
|
|
fetch() { wget -q -O "$2" "$1"; }
|
|
fetch_stdout() { wget -q -O - "$1"; }
|
|
else
|
|
err "either curl or wget is required"
|
|
fi
|
|
|
|
# ── SHA-256 tool ─────────────────────────────────────────────────────────────
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
sha256_of() { sha256sum "$1" | cut -d' ' -f1; }
|
|
elif command -v shasum >/dev/null 2>&1; then
|
|
sha256_of() { shasum -a 256 "$1" | cut -d' ' -f1; }
|
|
else
|
|
err "either sha256sum or shasum is required to verify the download"
|
|
fi
|
|
|
|
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/kigi-install.XXXXXX")"
|
|
trap 'rm -rf "$TMP_DIR"' EXIT INT TERM
|
|
|
|
# ── Resolve the release ──────────────────────────────────────────────────────
|
|
if [ -n "$VERSION" ]; then
|
|
RELEASE_URL="$API_BASE/tags/v$VERSION"
|
|
else
|
|
RELEASE_URL="$API_BASE/latest"
|
|
fi
|
|
printf 'Resolving release from %s\n' "$RELEASE_URL"
|
|
RELEASE_JSON="$(fetch_stdout "$RELEASE_URL")" \
|
|
|| err "could not fetch release metadata from $RELEASE_URL"
|
|
|
|
TAG="$(printf '%s' "$RELEASE_JSON" \
|
|
| tr ',' '\n' \
|
|
| sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
|
|
| head -n 1)"
|
|
[ -n "$TAG" ] || err "release metadata has no tag_name (endpoint: $RELEASE_URL)"
|
|
RESOLVED_VERSION="${TAG#v}"
|
|
if [ -n "$VERSION" ] && [ "$RESOLVED_VERSION" != "$VERSION" ]; then
|
|
err "requested version $VERSION but release tag is $TAG"
|
|
fi
|
|
|
|
ASSET="kigi-${RESOLVED_VERSION}-${TRIPLE}.tar.gz"
|
|
|
|
# Pull every browser_download_url out of the JSON, then select by asset name.
|
|
URLS="$(printf '%s' "$RELEASE_JSON" \
|
|
| tr ',' '\n' \
|
|
| sed -n 's/.*"browser_download_url"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
|
|
ARCHIVE_URL="$(printf '%s\n' "$URLS" | grep -F "/$ASSET" | head -n 1 || true)"
|
|
SUMS_URL="$(printf '%s\n' "$URLS" | grep -F "/SHA256SUMS" | head -n 1 || true)"
|
|
[ -n "$ARCHIVE_URL" ] || err "release $TAG has no asset $ASSET (this platform may not be published yet)"
|
|
[ -n "$SUMS_URL" ] || err "release $TAG has no SHA256SUMS asset; refusing to install unverified binaries"
|
|
|
|
# ── Download + verify ────────────────────────────────────────────────────────
|
|
printf 'Downloading kigi v%s (%s)...\n' "$RESOLVED_VERSION" "$TRIPLE"
|
|
fetch "$ARCHIVE_URL" "$TMP_DIR/$ASSET" || err "download failed: $ARCHIVE_URL"
|
|
fetch "$SUMS_URL" "$TMP_DIR/SHA256SUMS" || err "download failed: $SUMS_URL"
|
|
|
|
EXPECTED=""
|
|
while IFS=' ' read -r hash name; do
|
|
name="${name#\*}"
|
|
if [ "$name" = "$ASSET" ]; then
|
|
EXPECTED="$hash"
|
|
fi
|
|
done < "$TMP_DIR/SHA256SUMS"
|
|
[ -n "$EXPECTED" ] || err "SHA256SUMS has no entry for $ASSET"
|
|
|
|
ACTUAL="$(sha256_of "$TMP_DIR/$ASSET")"
|
|
if [ "$ACTUAL" != "$EXPECTED" ]; then
|
|
err "SHA256 mismatch for $ASSET: expected $EXPECTED, got $ACTUAL"
|
|
fi
|
|
printf 'Checksum verified.\n'
|
|
|
|
# ── Extract + install ────────────────────────────────────────────────────────
|
|
tar -xzf "$TMP_DIR/$ASSET" -C "$TMP_DIR" || err "failed to extract $ASSET"
|
|
[ -f "$TMP_DIR/kigi" ] || err "archive $ASSET does not contain a 'kigi' binary"
|
|
chmod 0755 "$TMP_DIR/kigi"
|
|
|
|
DOWNLOADS_DIR="$KIGI_HOME/downloads"
|
|
BIN_DIR="$KIGI_HOME/bin"
|
|
mkdir -p "$DOWNLOADS_DIR" "$BIN_DIR"
|
|
|
|
# Versioned binary + atomic symlink swap — the exact layout the self-updater
|
|
# maintains, so `kigi update` takes over seamlessly from here.
|
|
VERSIONED="kigi-${RESOLVED_VERSION}-${PLATFORM_OS}-${PLATFORM_ARCH}"
|
|
mv -f "$TMP_DIR/kigi" "$DOWNLOADS_DIR/$VERSIONED"
|
|
|
|
TMP_LINK="$BIN_DIR/kigi.install.$$"
|
|
ln -s "../downloads/$VERSIONED" "$TMP_LINK"
|
|
mv -f "$TMP_LINK" "$BIN_DIR/kigi"
|
|
|
|
# Smoke-test the installed binary through the managed link.
|
|
"$BIN_DIR/kigi" --version >/dev/null 2>&1 \
|
|
|| err "installed binary failed to run; your PATH still has no working kigi"
|
|
|
|
printf '\nkigi v%s installed to %s\n' "$RESOLVED_VERSION" "$BIN_DIR/kigi"
|
|
|
|
# Persist a line into an rc file. Idempotent via the grep guard; on write
|
|
# failure the manual command is printed and the script fails loudly (the
|
|
# binary itself is already installed).
|
|
persist_line() {
|
|
rc="$1"
|
|
line="$2"
|
|
guard="$3"
|
|
what="$4"
|
|
if [ -f "$rc" ] && grep -qF "$guard" "$rc"; then
|
|
printf '\n%s is already configured in %s.\n' "$what" "$rc"
|
|
return 0
|
|
fi
|
|
printf '\n# Added by the kigi installer\n%s\n' "$line" >> "$rc" \
|
|
|| err "could not write $rc — configure it manually: $line"
|
|
printf '\nAdded %s in %s.\n' "$what" "$rc"
|
|
}
|
|
|
|
# Resolve the login shell's rc file and env-line syntax once; PATH and
|
|
# feature-flag persistence below both write to the same place.
|
|
case "${SHELL:-}" in
|
|
*/zsh)
|
|
RC_FILE="${ZDOTDIR:-$HOME}/.zshrc"
|
|
PATH_LINE="export PATH=\"$BIN_DIR:\$PATH\""
|
|
;;
|
|
*/bash)
|
|
# macOS login shells read ~/.bash_profile; Linux reads ~/.bashrc.
|
|
if [ "$PLATFORM_OS" = "macos" ]; then
|
|
RC_FILE="$HOME/.bash_profile"
|
|
else
|
|
RC_FILE="$HOME/.bashrc"
|
|
fi
|
|
PATH_LINE="export PATH=\"$BIN_DIR:\$PATH\""
|
|
;;
|
|
*/fish)
|
|
# fish_add_path in config.fish is fish's own idempotent way
|
|
# to persist a PATH entry.
|
|
FISH_CONF_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/fish"
|
|
mkdir -p "$FISH_CONF_DIR"
|
|
RC_FILE="$FISH_CONF_DIR/config.fish"
|
|
PATH_LINE="fish_add_path $BIN_DIR"
|
|
;;
|
|
*)
|
|
RC_FILE="$HOME/.profile"
|
|
PATH_LINE="export PATH=\"$BIN_DIR:\$PATH\""
|
|
;;
|
|
esac
|
|
|
|
case ":$PATH:" in
|
|
*":$BIN_DIR:"*)
|
|
printf 'Run `kigi` to get started.\n'
|
|
;;
|
|
*)
|
|
persist_line "$RC_FILE" "$PATH_LINE" "$BIN_DIR" "$BIN_DIR (PATH)"
|
|
printf 'Open a new terminal, then run `kigi` to get started.\n'
|
|
;;
|
|
esac
|
|
|
|
# Graph engineering is enabled by default IN THE BINARY (resolve_graph
|
|
# defaults true) — the installer no longer writes KIGI_GRAPH=1 into shell
|
|
# rc files (per-shell env plumbing was fragile and diverged per platform).
|
|
# Disable any time with:
|
|
# echo 'export KIGI_GRAPH=0' >> <your shell rc>
|