mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-04 23:00:20 +00:00
Extract platform-specific install logic from monolithic installer functions into small, focused helpers. Both functions had nested OS/package-manager cascades (depth 3-4) that made the control flow hard to follow. ensure_jq (shared/common.sh): - Extract _install_jq_brew, _install_jq_apt, _install_jq_dnf, _install_jq_apk - Extract _report_jq_not_found for the fallthrough error message - Main function becomes a clean dispatcher + verification ensure_gh_cli + _install_gh_binary (shared/github-auth.sh): - Extract _install_gh_brew, _install_gh_apt, _install_gh_dnf - Extract _detect_gh_platform, _fetch_gh_latest_version, _download_and_install_gh - _install_gh_binary drops from 71 to 12 lines as a clean orchestrator - ensure_gh_cli drops from 57 to 29 lines No behavior changes. All tests pass, bash -n passes. Agent: complexity-hunter Co-authored-by: A <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
260 lines
8.1 KiB
Bash
Executable file
260 lines
8.1 KiB
Bash
Executable file
#!/bin/bash
|
|
# Standalone GitHub auth helper — installs gh CLI and runs OAuth login
|
|
# Sourceable by any agent script, or executable directly via curl|bash
|
|
#
|
|
# Usage (sourced):
|
|
# source shared/github-auth.sh
|
|
# ensure_github_auth
|
|
#
|
|
# Usage (direct):
|
|
# bash shared/github-auth.sh
|
|
# curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/shared/github-auth.sh | bash
|
|
|
|
# ============================================================
|
|
# Source shared/common.sh for logging (local-or-remote fallback)
|
|
# ============================================================
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
|
|
if [[ -n "${SCRIPT_DIR:-}" && -f "${SCRIPT_DIR}/common.sh" ]]; then
|
|
source "${SCRIPT_DIR}/common.sh"
|
|
else
|
|
eval "$(curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/shared/common.sh)"
|
|
fi
|
|
|
|
# Fallback log functions if common.sh failed to load
|
|
if ! type log_info &>/dev/null 2>&1; then
|
|
log_info() { printf '[github-auth] %s\n' "$*" >&2; }
|
|
log_step() { printf '[github-auth] %s\n' "$*" >&2; }
|
|
log_warn() { printf '[github-auth] WARNING: %s\n' "$*" >&2; }
|
|
log_error() { printf '[github-auth] ERROR: %s\n' "$*" >&2; }
|
|
fi
|
|
|
|
# ============================================================
|
|
# ensure_gh_cli — Install gh CLI if not already present
|
|
# ============================================================
|
|
|
|
# Install gh via Homebrew (macOS)
|
|
_install_gh_brew() {
|
|
if command -v brew &>/dev/null; then
|
|
brew install gh || {
|
|
log_error "Failed to install gh via Homebrew"
|
|
return 1
|
|
}
|
|
else
|
|
log_error "Homebrew not found. Install Homebrew first: https://brew.sh"
|
|
log_error "Then run: brew install gh"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Install gh via APT with GitHub's official repository (Debian/Ubuntu)
|
|
_install_gh_apt() {
|
|
log_step "Adding GitHub CLI APT repository..."
|
|
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
|
|
| sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg 2>/dev/null
|
|
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
|
|
printf 'deb [arch=%s signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main\n' \
|
|
"$(dpkg --print-architecture)" \
|
|
| sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
|
|
sudo apt-get update -qq
|
|
sudo apt-get install -y gh || {
|
|
log_error "Failed to install gh via apt"
|
|
return 1
|
|
}
|
|
}
|
|
|
|
# Install gh via DNF (Fedora/RHEL)
|
|
_install_gh_dnf() {
|
|
sudo dnf install -y gh || {
|
|
log_error "Failed to install gh via dnf"
|
|
return 1
|
|
}
|
|
}
|
|
|
|
ensure_gh_cli() {
|
|
if command -v gh &>/dev/null; then
|
|
log_info "GitHub CLI (gh) available: $(gh --version | head -1)"
|
|
return 0
|
|
fi
|
|
|
|
log_step "Installing GitHub CLI (gh)..."
|
|
|
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
_install_gh_brew || return 1
|
|
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
if command -v apt-get &>/dev/null; then
|
|
_install_gh_apt || return 1
|
|
elif command -v dnf &>/dev/null; then
|
|
_install_gh_dnf || return 1
|
|
else
|
|
_install_gh_binary || return 1
|
|
fi
|
|
else
|
|
_install_gh_binary || return 1
|
|
fi
|
|
|
|
if ! command -v gh &>/dev/null; then
|
|
log_error "gh not found in PATH after installation"
|
|
return 1
|
|
fi
|
|
|
|
log_info "GitHub CLI (gh) installed: $(gh --version | head -1)"
|
|
}
|
|
|
|
# ============================================================
|
|
# Binary fallback installer (non-apt/non-brew systems)
|
|
# ============================================================
|
|
|
|
# Detect OS and architecture for binary downloads, outputting "os arch" on stdout.
|
|
# Returns 1 with error message if platform is unsupported.
|
|
_detect_gh_platform() {
|
|
local os arch gh_os gh_arch
|
|
os="$(uname -s)"
|
|
arch="$(uname -m)"
|
|
|
|
case "${os}" in
|
|
Linux) gh_os="linux" ;;
|
|
Darwin) gh_os="macOS" ;;
|
|
*)
|
|
log_error "Unsupported OS: ${os}. Install manually from https://cli.github.com/"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
case "${arch}" in
|
|
x86_64|amd64) gh_arch="amd64" ;;
|
|
aarch64|arm64) gh_arch="arm64" ;;
|
|
*)
|
|
log_error "Unsupported architecture: ${arch}. Install manually from https://cli.github.com/"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
echo "${gh_os} ${gh_arch}"
|
|
}
|
|
|
|
# Fetch the latest gh release version string from GitHub API
|
|
_fetch_gh_latest_version() {
|
|
local latest_version
|
|
latest_version=$(curl -fsSL "https://api.github.com/repos/cli/cli/releases/latest" \
|
|
| grep '"tag_name"' | sed 's/.*"v\([^"]*\)".*/\1/') || {
|
|
log_error "Failed to fetch latest gh release version"
|
|
return 1
|
|
}
|
|
|
|
if [[ -z "${latest_version}" ]]; then
|
|
log_error "Could not determine latest gh version"
|
|
return 1
|
|
fi
|
|
|
|
echo "${latest_version}"
|
|
}
|
|
|
|
# Download and extract a gh release tarball into ~/.local/bin
|
|
# Usage: _download_and_install_gh VERSION GH_OS GH_ARCH
|
|
_download_and_install_gh() {
|
|
local version="${1}" gh_os="${2}" gh_arch="${3}"
|
|
|
|
log_step "Downloading gh v${version} for ${gh_os}/${gh_arch}..."
|
|
|
|
local tarball="gh_${version}_${gh_os}_${gh_arch}.tar.gz"
|
|
local url="https://github.com/cli/cli/releases/download/v${version}/${tarball}"
|
|
local tmpdir
|
|
tmpdir=$(mktemp -d)
|
|
|
|
curl -fsSL "${url}" -o "${tmpdir}/${tarball}" || {
|
|
log_error "Failed to download ${url}"
|
|
rm -rf "${tmpdir}"
|
|
return 1
|
|
}
|
|
|
|
tar -xzf "${tmpdir}/${tarball}" -C "${tmpdir}" || {
|
|
log_error "Failed to extract ${tarball}"
|
|
rm -rf "${tmpdir}"
|
|
return 1
|
|
}
|
|
|
|
mkdir -p "${HOME}/.local/bin"
|
|
cp "${tmpdir}/gh_${version}_${gh_os}_${gh_arch}/bin/gh" "${HOME}/.local/bin/gh"
|
|
chmod +x "${HOME}/.local/bin/gh"
|
|
rm -rf "${tmpdir}"
|
|
|
|
# Add ~/.local/bin to PATH if not already there
|
|
case ":${PATH}:" in
|
|
*":${HOME}/.local/bin:"*) ;;
|
|
*) export PATH="${HOME}/.local/bin:${PATH}" ;;
|
|
esac
|
|
|
|
log_info "gh installed to ${HOME}/.local/bin/gh"
|
|
}
|
|
|
|
_install_gh_binary() {
|
|
log_step "Installing gh from GitHub releases (binary fallback)..."
|
|
|
|
local platform
|
|
platform=$(_detect_gh_platform) || return 1
|
|
local gh_os gh_arch
|
|
read -r gh_os gh_arch <<< "${platform}"
|
|
|
|
local latest_version
|
|
latest_version=$(_fetch_gh_latest_version) || return 1
|
|
|
|
_download_and_install_gh "${latest_version}" "${gh_os}" "${gh_arch}"
|
|
}
|
|
|
|
# ============================================================
|
|
# ensure_gh_auth — Authenticate with GitHub via gh auth login
|
|
# ============================================================
|
|
|
|
ensure_gh_auth() {
|
|
if gh auth status &>/dev/null; then
|
|
log_info "Authenticated with GitHub CLI"
|
|
return 0
|
|
fi
|
|
|
|
log_step "Not authenticated with GitHub CLI"
|
|
|
|
# Non-interactive: use GITHUB_TOKEN if set
|
|
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
|
log_step "Authenticating with GITHUB_TOKEN..."
|
|
printf '%s\n' "${GITHUB_TOKEN}" | gh auth login --with-token || {
|
|
log_error "Failed to authenticate with GITHUB_TOKEN"
|
|
return 1
|
|
}
|
|
else
|
|
# Interactive: browser-based OAuth flow
|
|
log_step "Initiating GitHub CLI authentication..."
|
|
gh auth login || {
|
|
log_error "Failed to authenticate with GitHub CLI"
|
|
log_error "Run manually: gh auth login"
|
|
return 1
|
|
}
|
|
fi
|
|
|
|
if ! gh auth status &>/dev/null; then
|
|
log_error "gh auth status check failed after login"
|
|
return 1
|
|
fi
|
|
|
|
log_info "Authenticated with GitHub CLI"
|
|
return 0
|
|
}
|
|
|
|
# ============================================================
|
|
# ensure_github_auth — Combined convenience wrapper
|
|
# ============================================================
|
|
|
|
ensure_github_auth() {
|
|
ensure_gh_cli || return 1
|
|
ensure_gh_auth || return 1
|
|
}
|
|
|
|
# ============================================================
|
|
# Direct execution support
|
|
# ============================================================
|
|
|
|
# If executed directly (not sourced), run ensure_github_auth
|
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
set -eo pipefail
|
|
ensure_github_auth
|
|
fi
|