Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ GitHub CLI 확장 프로그램 - LLM을 위한 GitHub 도구
gh extension install pleaseai/gh-please
```

**CI / Docker (인증 불필요):** `gh extension install`은 GitHub API를 사용하는데,
비인증 요청의 속도 제한(IP당 시간당 60회)은 공유 CI 러너에서 쉽게 소진되어 gh가
`HTTP 403`을 인증 오류처럼 표시합니다. 공개 릴리스 CDN에서 토큰 없이 바이너리를
내려받는 독립 설치 스크립트를 사용하세요:

```bash
curl -fsSL https://raw.githubusercontent.com/pleaseai/gh-please/main/scripts/install.sh | bash
```

> CI에서 `gh extension install`을 그대로 쓰려면 `GH_TOKEN`(GitHub Actions의 경우 `${{ github.token }}`)을 설정해 속도 제한을 해제하세요.

### 5분 안에 시작하기

```bash
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ English | [한국어](./README.ko.md)
gh extension install pleaseai/gh-please
```

**CI / Docker (no authentication needed):** `gh extension install` uses the GitHub
API, whose unauthenticated rate limit (60 req/hour per IP) is easily exhausted on
shared CI runners — gh then reports the `HTTP 403` as an auth error. Use the
standalone installer, which downloads the binary from the public release CDN with
no token:

```bash
curl -fsSL https://raw.githubusercontent.com/pleaseai/gh-please/main/scripts/install.sh | bash
```

> Prefer `gh extension install` in CI? Set `GH_TOKEN` (e.g. `${{ github.token }}` in GitHub Actions) to lift the rate limit.

### Get Started in 5 Minutes

```bash
Expand Down
24 changes: 24 additions & 0 deletions docs/content/en/1.guide/1.getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,30 @@ Before installing `gh-please`, make sure you have:
gh extension install pleaseai/gh-please
```

### Install in CI / Docker (no authentication)

`gh extension install` works fine on a personal machine, but in CI or during a
Docker build it talks to the GitHub API, whose **unauthenticated** rate limit
(60 requests/hour per IP) is shared across runners and is easily exhausted —
gh then reports the resulting `HTTP 403` as an authentication error.

Use the standalone installer instead. It downloads the precompiled binary from
the public release CDN, so it needs **no token** and bakes no secret into your
image:

```bash
curl -fsSL https://raw.githubusercontent.com/pleaseai/gh-please/main/scripts/install.sh | bash

# Pin a specific version (reproducible builds) — pin the script and the binary to the same tag:
TAG=github-v0.32.0
curl -fsSL "https://raw.githubusercontent.com/pleaseai/gh-please/${TAG}/scripts/install.sh" | bash -s -- --version "${TAG}"
```

::callout{type="info"}
Prefer `gh extension install` in CI? Provide a token to lift the rate limit — in
GitHub Actions set `env: { GH_TOKEN: ${{ github.token }} }`.
::

### Install for Development

```bash
Expand Down
25 changes: 25 additions & 0 deletions docs/content/ko/1.guide/1.getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,31 @@ description: gh-please 확장 프로그램 설치 및 설정
gh extension install pleaseai/gh-please
```

### CI / Docker에서 설치 (인증 불필요)

`gh extension install`은 개인 PC에서는 잘 동작하지만, CI나 Docker 빌드에서는
GitHub API를 호출합니다. **비인증** 요청의 속도 제한(IP당 시간당 60회)은 러너
간에 공유되어 쉽게 소진되며, 이때 발생하는 `HTTP 403`을 gh가 인증 오류처럼
표시합니다.

이 경우 독립 설치 스크립트를 사용하세요. 공개 릴리스 CDN에서 사전 컴파일된
바이너리를 내려받으므로 **토큰이 필요 없고** 이미지에 비밀값을 넣지 않아도
됩니다:

```bash
curl -fsSL https://raw.githubusercontent.com/pleaseai/gh-please/main/scripts/install.sh | bash

# 특정 버전 고정 (재현 가능한 빌드) — 스크립트와 바이너리를 같은 태그로 고정:
TAG=github-v0.32.0
curl -fsSL "https://raw.githubusercontent.com/pleaseai/gh-please/${TAG}/scripts/install.sh" | bash -s -- --version "${TAG}"
```

::callout{type="info"}
CI에서 `gh extension install`을 그대로 쓰고 싶다면 토큰을 제공해 속도 제한을
해제하세요. GitHub Actions에서는 `env: { GH_TOKEN: ${{ github.token }} }`로
설정합니다.
::

### 개발용 설치

```bash
Expand Down
205 changes: 205 additions & 0 deletions scripts/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
#!/usr/bin/env bash
#
# Auth-free installer for the gh-please GitHub CLI extension.
#
# Why this exists:
# `gh extension install pleaseai/gh-please` works without authentication for
# a single machine, but in CI/Docker the *unauthenticated* GitHub API rate
# limit (60 req/hour per IP) is quickly exhausted on shared runner IPs. gh
# then surfaces the resulting HTTP 403 as an authentication-looking error.
#
# This script bypasses api.github.com entirely. It resolves the release tag
# via the public `releases/latest` web redirect and downloads the precompiled
# binary from GitHub's release-download CDN. Neither endpoint is rate-limited
# like the API nor requires a token, so it works at Docker build time with no
# credentials baked into the image.
#
# Usage:
# curl -fsSL https://raw.githubusercontent.com/pleaseai/gh-please/main/scripts/install.sh | bash
# curl -fsSL .../install.sh | bash -s -- --version github-v0.32.0 # pin a version
#
# Environment overrides:
# GH_PLEASE_VERSION Pin a release tag (same as --version).

set -euo pipefail

# --- Constants ---------------------------------------------------------------
readonly OWNER="pleaseai"
readonly REPO="gh-please"
readonly EXT_NAME="gh-please" # gh extension name -> `gh please`
readonly BIN_NAME="gh-please" # executable + release asset prefix
readonly HOST="github.com"
readonly RELEASES_URL="https://github.com/${OWNER}/${REPO}/releases"

# --- Output helpers ----------------------------------------------------------
# Only emit ANSI colors when stderr is a terminal. In CI/Docker (this script's
# primary target) output is redirected to log files, where escape sequences would
# show up as literal garbage.
if [[ -t 2 ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
else
RED=''
GREEN=''
YELLOW=''
NC=''
fi

# Temp paths cleaned up by the EXIT trap. Declared at file scope so the trap can
# reference them after main()'s locals go out of scope. WORKDIR holds downloads;
# STAGING holds the new extension copy until it is atomically moved into place.
WORKDIR=""
STAGING=""

info() { printf '%b\n' "${YELLOW}==>${NC} $*"; }
ok() { printf '%b\n' "${GREEN}✓${NC} $*"; }
err() { printf '%b\n' "${RED}✗${NC} $*" >&2; }

die() {
err "$*"
exit 1
}

# --- Platform detection ------------------------------------------------------
# Maps `uname` output to the `{os}-{arch}` suffix used in release asset names.
detect_platform() {
local os arch
case "$(uname -s)" in
Linux) os="linux" ;;
Darwin) os="darwin" ;;
*) die "Unsupported OS: $(uname -s). For Windows use 'gh extension install ${OWNER}/${REPO}'." ;;
esac

case "$(uname -m)" in
x86_64 | amd64) arch="amd64" ;;
arm64 | aarch64) arch="arm64" ;;
*) die "Unsupported architecture: $(uname -m)." ;;
esac

printf '%s-%s' "$os" "$arch"
}

# --- Tag resolution ----------------------------------------------------------
# Resolves the latest tag from the `releases/latest` redirect (no API call).
resolve_latest_tag() {
local redirect
redirect="$(curl -fsS -o /dev/null -w '%{redirect_url}' "${RELEASES_URL}/latest" || true)"
[[ -n "$redirect" ]] || die "Could not resolve the latest release tag from ${RELEASES_URL}/latest"
printf '%s' "${redirect##*/tag/}"
}

# --- Checksum verification ---------------------------------------------------
sha256_of() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$1" | awk '{print $1}'

Check warning on line 96 in scripts/install.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=pleaseai_gh-please&issues=AZ8YNuBDJ7ef2LGKiijZ&open=AZ8YNuBDJ7ef2LGKiijZ&pullRequest=288
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$1" | awk '{print $1}'

Check warning on line 98 in scripts/install.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=pleaseai_gh-please&issues=AZ8YNuBDJ7ef2LGKiija&open=AZ8YNuBDJ7ef2LGKiija&pullRequest=288
else
die "Neither sha256sum nor shasum is available for checksum verification."
fi
}

verify_checksum() {
local binary="$1" checksums="$2" asset="$3"
local expected actual
expected="$(awk -v f="$asset" '$2 == f {print $1}' "$checksums")"
# Fail closed: a missing entry must abort, never silently install an unverified
# binary. The release workflow generates checksums for every gh-please_* asset,
# so an absent entry means asset-name drift or a corrupt/partial checksums file.
if [[ -z "$expected" ]]; then
die "No checksum entry found for ${asset} in checksums.txt — cannot verify binary integrity."
fi
actual="$(sha256_of "$binary")"
[[ "$expected" == "$actual" ]] || die "Checksum mismatch for ${asset} (expected ${expected}, got ${actual})."
ok "Checksum verified."
}

# --- gh data directory -------------------------------------------------------
# Resolves the directory gh uses to store extensions (XDG data, not config).
gh_extensions_dir() {
printf '%s/gh/extensions' "${XDG_DATA_HOME:-$HOME/.local/share}"
}

# Writes manifest.yml. The file location and the recorded `path` are separate so
# the manifest can be staged in a temp dir while still pointing at the final path.
write_manifest() {
local manifest_file="$1" install_path="$2" tag="$3" pinned="$4"
cat >"$manifest_file" <<EOF
owner: ${OWNER}
name: ${EXT_NAME}
host: ${HOST}
tag: ${tag}
ispinned: ${pinned}
path: ${install_path}
EOF
}

# --- Main --------------------------------------------------------------------
main() {
local version="${GH_PLEASE_VERSION:-}"
local pinned="false"

while [[ $# -gt 0 ]]; do
case "$1" in

Check warning on line 145 in scripts/install.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=pleaseai_gh-please&issues=AZ8YNuBDJ7ef2LGKiije&open=AZ8YNuBDJ7ef2LGKiije&pullRequest=288
--version)
[[ $# -ge 2 ]] || die "--version requires a tag argument."
version="$2"
shift 2
;;
Comment thread
amondnet marked this conversation as resolved.
-h | --help)
printf 'Usage: install.sh [--version <tag>]\n'
exit 0
;;
*) die "Unknown argument: $1" ;;

Check warning on line 155 in scripts/install.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=pleaseai_gh-please&issues=AZ8YNuBDJ7ef2LGKiijf&open=AZ8YNuBDJ7ef2LGKiijf&pullRequest=288
esac
done

command -v curl >/dev/null 2>&1 || die "curl is required but not installed."

local platform tag asset base binary checksums
platform="$(detect_platform)"

if [[ -n "$version" ]]; then
tag="$version"
pinned="true"
else
info "Resolving latest release..."
tag="$(resolve_latest_tag)"
fi

asset="${BIN_NAME}_${tag}_${platform}"
base="${RELEASES_URL}/download/${tag}"
info "Installing ${EXT_NAME} ${tag} (${platform})..."

WORKDIR="$(mktemp -d)"
trap 'rm -rf "${WORKDIR:-}" "${STAGING:-}"' EXIT

binary="${WORKDIR}/${BIN_NAME}"
checksums="${WORKDIR}/checksums.txt"

curl -fsSL -o "$binary" "${base}/${asset}" \
|| die "Failed to download ${base}/${asset} (does the release have a ${platform} binary?)."
curl -fsSL -o "$checksums" "${base}/checksums.txt" \
|| die "Failed to download checksums from ${base}/checksums.txt"

verify_checksum "$binary" "$checksums" "$asset"

# Stage the new copy fully, then swap it into place. This keeps any existing
# install intact if writing the binary or manifest fails partway through.
local dir
dir="$(gh_extensions_dir)/${EXT_NAME}"
STAGING="$(dirname "$dir")/.${EXT_NAME}.tmp.$$"
rm -rf "$STAGING"
mkdir -p "$STAGING"
install -m 0755 "$binary" "${STAGING}/${BIN_NAME}"
write_manifest "${STAGING}/manifest.yml" "${dir}/${BIN_NAME}" "$tag" "$pinned"
rm -rf "$dir"
mv "$STAGING" "$dir"

ok "Installed to ${dir}/${BIN_NAME}"
info "Run: gh please --version"
}

main "$@"
Loading