#!/usr/bin/env bash # install.sh — install the `gho` CLI from GitHub Releases. # # Usage: # curl -fsSL https://githosted.sh | sh # curl -fsSL https://githosted.sh | sh -s -- --version v0.1.0 # curl -fsSL https://githosted.sh | INSTALL_DIR=$HOME/.local/bin sh # # Resolves the latest release (or a pinned --version) on # github.com/janneh/githosted, fetches the archive matching this # host's OS+arch, verifies the SHA-256 against the published # checksum file, and drops both `gho` and `git-credential-githosted` # in INSTALL_DIR (default /usr/local/bin). No sudo unless writing # to that directory requires it; the script tries direct write # first and falls back to `sudo install` on permission failure. # # Trust chain # ----------- # 1. TLS to githosted.sh authenticates this script (Cloudflare). # 2. TLS to github.com authenticates the release tarball + checksums. # 3. SHA-256 in checksums.txt verifies the tarball byte-for-byte; # a mismatch aborts the install with a non-zero exit. Mirrors # how rustup, deno, and homebrew handle binary installs. # # Want to verify this script itself before running it? Cross-check # the Cloudflare-served copy against the GitHub-raw copy: # # curl -fsSL -o install.sh https://githosted.sh # diff install.sh <(curl -fsSL https://raw.githubusercontent.com/janneh/githosted/main/scripts/install.sh) # sh install.sh # # Two independent TLS endpoints both have to be compromised to slip # a tampered script past that check. Full walkthrough at # https://docs.githosted.dev/sdks/cli/#verify-the-install-script-paranoid-mode- set -euo pipefail REPO="${REPO:-janneh/githosted}" VERSION="${VERSION:-latest}" INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}" # Both binaries ship in the same archive: `gho` (the full CLI) # and `git-credential-githosted` (the standalone Git credential # helper). Installing both keeps the two flows in sync — users # who configure git via gho automatically have the helper # available, and users who reach for the helper directly can # graduate to the full CLI without a second install. BINARIES=("gho" "git-credential-githosted") while [ $# -gt 0 ]; do case "$1" in --version) VERSION="$2"; shift 2;; --install-dir) INSTALL_DIR="$2"; shift 2;; --help|-h) sed -n '/^# Usage/,/^$/p' "$0" | sed 's/^# \?//' exit 0 ;; *) echo "unknown option: $1" >&2; exit 2;; esac done err() { echo "install.sh: $*" >&2; exit 1; } require() { command -v "$1" >/dev/null 2>&1 || err "missing required tool: $1" } require curl require tar require uname # shasum on macOS, sha256sum on Linux; pick whichever exists. SHA_TOOL="" if command -v sha256sum >/dev/null 2>&1; then SHA_TOOL="sha256sum" elif command -v shasum >/dev/null 2>&1; then SHA_TOOL="shasum -a 256" else err "missing sha256sum / shasum"; fi # Map uname output to release-asset triples. The Rust target # triple in the asset filename must match what the release # workflow produces — keep this list in sync with # .github/workflows/cli-release.yml. os="$(uname -s)" arch="$(uname -m)" case "$os/$arch" in Linux/x86_64) triple="x86_64-unknown-linux-gnu";; Linux/aarch64|Linux/arm64) triple="aarch64-unknown-linux-gnu";; Darwin/x86_64) triple="x86_64-apple-darwin";; Darwin/arm64) triple="aarch64-apple-darwin";; *) err "unsupported host: $os/$arch (open an issue if you need this target)";; esac # Resolve the version. `latest` hits the redirect endpoint and # parses the resulting tag. Pinned values pass through. if [ "$VERSION" = "latest" ]; then redirect="$(curl -fsSI -o /dev/null -w '%{redirect_url}' "https://github.com/${REPO}/releases/latest")" VERSION="${redirect##*/}" [ -n "$VERSION" ] || err "could not resolve latest release tag" fi asset="gho-${VERSION}-${triple}.tar.gz" url="https://github.com/${REPO}/releases/download/${VERSION}/${asset}" checksum_url="https://github.com/${REPO}/releases/download/${VERSION}/checksums.txt" tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT echo "Downloading $asset…" curl -fsSL -o "$tmp/$asset" "$url" || err "download failed: $url" echo "Verifying checksum…" curl -fsSL -o "$tmp/checksums.txt" "$checksum_url" || err "download failed: $checksum_url" # checksums.txt format: " " (sha256sum # convention). Filter to the asset we just downloaded. expected="$(grep " $asset\$" "$tmp/checksums.txt" | awk '{print $1}')" [ -n "$expected" ] || err "no checksum entry for $asset" actual="$($SHA_TOOL "$tmp/$asset" | awk '{print $1}')" [ "$expected" = "$actual" ] || err "checksum mismatch (expected $expected, got $actual)" echo "Extracting…" tar -xzf "$tmp/$asset" -C "$tmp" "${BINARIES[@]}" for bin in "${BINARIES[@]}"; do chmod +x "$tmp/$bin" done echo "Installing to $INSTALL_DIR…" if [ -w "$INSTALL_DIR" ] || [ ! -e "$INSTALL_DIR" ]; then install -d "$INSTALL_DIR" for bin in "${BINARIES[@]}"; do install -m 0755 "$tmp/$bin" "$INSTALL_DIR/$bin" done else echo "(needs sudo to write $INSTALL_DIR)" sudo install -d "$INSTALL_DIR" for bin in "${BINARIES[@]}"; do sudo install -m 0755 "$tmp/$bin" "$INSTALL_DIR/$bin" done fi echo echo "✓ Installed ${VERSION} to ${INSTALL_DIR}/:" for bin in "${BINARIES[@]}"; do echo " - $bin" done echo echo "Next: run 'gho auth login' to authorize this machine."