diff --git a/shared/github-auth.sh b/shared/github-auth.sh new file mode 100755 index 00000000..3fba6411 --- /dev/null +++ b/shared/github-auth.sh @@ -0,0 +1,225 @@ +#!/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 +# ============================================================ + +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 + # macOS — require Homebrew + 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 + elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + if command -v apt-get &>/dev/null; then + # Debian/Ubuntu — add GitHub's official APT repo + 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 + } + elif command -v dnf &>/dev/null; then + # Fedora/RHEL + sudo dnf install -y gh || { + log_error "Failed to install gh via dnf" + return 1 + } + else + # Fallback — download prebuilt binary + _install_gh_binary || return 1 + fi + else + # Unknown OS — try binary fallback + _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) +# ============================================================ + +_install_gh_binary() { + log_step "Installing gh from GitHub releases (binary fallback)..." + + 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 + + # Get latest release 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 + + log_step "Downloading gh v${latest_version} for ${gh_os}/${gh_arch}..." + + local tarball="gh_${latest_version}_${gh_os}_${gh_arch}.tar.gz" + local url="https://github.com/cli/cli/releases/download/v${latest_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 + } + + # Install to ~/.local/bin + mkdir -p "${HOME}/.local/bin" + cp "${tmpdir}/gh_${latest_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" +} + +# ============================================================ +# 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