Created
September 23, 2025 16:18
-
-
Save ben-vargas/cf8d00c8521bd2bdcf94150095c23004 to your computer and use it in GitHub Desktop.
Codex Binary Install Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| # ============================== | |
| # Codex Installer Script | |
| # Repository: https://github.com/openai/codex | |
| # ============================== | |
| # Config (overridable via env or flags) | |
| REPO="${REPO:-openai/codex}" | |
| INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}" | |
| PREFERRED_LINUX_LIBC="${PREFERRED_LINUX_LIBC:-musl}" # musl | gnu | |
| # Flags: -d|--install-dir DIR, --prefer-libc musl|gnu | |
| while [ $# -gt 0 ]; do | |
| case "$1" in | |
| -d|--install-dir) INSTALL_DIR="$2"; shift 2;; | |
| --prefer-libc) PREFERRED_LINUX_LIBC="$2"; shift 2;; | |
| -h|--help) | |
| cat <<EOF | |
| Codex Installer - Install the OpenAI Codex terminal coding agent | |
| Usage: $0 [options] | |
| Options: | |
| -d, --install-dir DIR Installation directory (default: ~/.local/bin) | |
| --prefer-libc musl|gnu Linux libc preference (default: musl) | |
| -h, --help Show this help message | |
| Environment Variables: | |
| GITHUB_TOKEN Set to increase API rate limit (optional) | |
| INSTALL_DIR Override default installation directory | |
| PREFERRED_LINUX_LIBC Set Linux libc preference (musl or gnu) | |
| Examples: | |
| $0 # Install latest version to ~/.local/bin | |
| $0 -d /usr/local/bin # Install to /usr/local/bin | |
| $0 --prefer-libc gnu # Prefer GNU libc on Linux | |
| EOF | |
| exit 0;; | |
| *) echo "Unknown option: $1" >&2; exit 1;; | |
| esac | |
| done | |
| # ============================== | |
| # Helper Functions | |
| # ============================== | |
| have() { command -v "$1" >/dev/null 2>&1; } | |
| api_get() { | |
| local url="$1" | |
| local headers=(-H "Accept: application/vnd.github+json" | |
| -H "X-GitHub-Api-Version: 2022-11-28" | |
| -H "User-Agent: codex-installer") | |
| [ -n "${GITHUB_TOKEN:-}" ] && headers+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") | |
| curl -fsSL "${headers[@]}" "$url" | |
| } | |
| # JSON parsing helper: prefer jq, fallback to python3 | |
| json_q() { | |
| local filter="$1" | |
| if have jq; then jq -r "$filter" | |
| elif have python3; then | |
| python3 - "$filter" <<'PY' | |
| import json, sys | |
| flt = sys.argv[1] | |
| data = json.load(sys.stdin) | |
| def non_draft(items): return [x for x in items if not x.get("draft")] | |
| def field(items, key): return [x.get(key, "") for x in items] | |
| def assets(items, idx): return items[idx].get("assets", []) | |
| def names(assets): return [a.get("name","") for a in assets] | |
| def url_for(assets, name): | |
| for a in assets: | |
| if a.get("name")==name: | |
| return a.get("browser_download_url","") | |
| return "" | |
| if flt == 'tags': | |
| for t in field(non_draft(data), "tag_name"): print(t) | |
| elif flt == 'dates': | |
| for d in field(non_draft(data), "published_at"): | |
| print((d or "").split("T")[0]) | |
| elif flt == 'pre': | |
| for p in field(non_draft(data), "prerelease"): | |
| print("true" if p else "false") | |
| elif flt.startswith('asset-names:'): | |
| idx = int(flt.split(':',1)[1]) | |
| for n in names(assets(non_draft(data), idx)): print(n) | |
| elif flt.startswith('asset-url:'): | |
| _, idx, name = flt.split(':',2) | |
| idx = int(idx) | |
| print(url_for(assets(non_draft(data), idx), name)) | |
| PY | |
| else | |
| echo "Error: need 'jq' or 'python3' to parse GitHub JSON." >&2 | |
| exit 1 | |
| fi | |
| } | |
| # ============================== | |
| # Detect OS/Architecture | |
| # ============================== | |
| OS="$(uname -s)" | |
| ARCH_RAW="$(uname -m)" | |
| case "$ARCH_RAW" in | |
| x86_64|amd64) ARCH="x86_64";; | |
| arm64|aarch64) ARCH="aarch64";; | |
| *) echo "Unsupported architecture: $ARCH_RAW" >&2; exit 1;; | |
| esac | |
| case "$OS" in | |
| Linux) PLATFORM="unknown-linux";; | |
| Darwin) PLATFORM="apple-darwin";; | |
| *) echo "Unsupported OS: $OS" >&2; exit 1;; | |
| esac | |
| # Build asset candidates in order of preference | |
| CANDIDATES=() | |
| if [ "$OS" = "Linux" ]; then | |
| if [ "$PREFERRED_LINUX_LIBC" = "gnu" ]; then | |
| CANDIDATES+=("codex-${ARCH}-${PLATFORM}-gnu.tar.gz" "codex-${ARCH}-${PLATFORM}-musl.tar.gz") | |
| else | |
| CANDIDATES+=("codex-${ARCH}-${PLATFORM}-musl.tar.gz" "codex-${ARCH}-${PLATFORM}-gnu.tar.gz") | |
| fi | |
| else # macOS | |
| CANDIDATES+=("codex-${ARCH}-${PLATFORM}.tar.gz") | |
| fi | |
| # ============================== | |
| # Fetch Releases | |
| # ============================== | |
| API_URL="https://api.github.com/repos/${REPO}/releases?per_page=10" | |
| printf "🚀 Codex Installer\n" | |
| printf "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" | |
| printf "Repository: %s\n" "https://github.com/$REPO" | |
| printf "Platform: %s (%s)\n" "$OS" "$ARCH" | |
| printf "Install to: %s\n" "$INSTALL_DIR" | |
| printf "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n" | |
| echo "📦 Fetching available releases..." | |
| JSON="$(api_get "$API_URL")" || { | |
| echo "❌ Failed to fetch releases from GitHub" | |
| echo " This might be due to rate limiting. Try setting GITHUB_TOKEN environment variable." | |
| exit 1 | |
| } | |
| # Parse release data | |
| if have jq; then | |
| mapfile -t TAGS < <(printf '%s' "$JSON" | jq -r 'map(select(.draft==false)) | .[].tag_name') | |
| mapfile -t DATES < <(printf '%s' "$JSON" | jq -r 'map(select(.draft==false)) | .[].published_at | split("T")[0]') | |
| mapfile -t PRE < <(printf '%s' "$JSON" | jq -r 'map(select(.draft==false)) | .[].prerelease') | |
| else | |
| mapfile -t TAGS < <(printf '%s' "$JSON" | json_q 'tags') | |
| mapfile -t DATES < <(printf '%s' "$JSON" | json_q 'dates') | |
| mapfile -t PRE < <(printf '%s' "$JSON" | json_q 'pre') | |
| fi | |
| COUNT="${#TAGS[@]}" | |
| [ "$COUNT" -eq 0 ] && { echo "❌ No releases found."; exit 1; } | |
| [ "$COUNT" -gt 10 ] && COUNT=10 | |
| # Build choice menu | |
| CHOICES=() | |
| for ((i=0; i<COUNT; i++)); do | |
| label="${TAGS[$i]} — ${DATES[$i]:-}" | |
| [ "$i" -eq 0 ] && label="$label ✨ latest" | |
| [ "${PRE[$i]}" = "true" ] && label="$label ⚠️ pre-release" | |
| CHOICES+=("$label") | |
| done | |
| # ============================== | |
| # Interactive Selection | |
| # ============================== | |
| echo | |
| if have fzf; then | |
| echo "📋 Select a version (use arrow keys or type to filter):" | |
| SEL_LINE=$(printf '%s\n' "${CHOICES[@]}" | fzf --prompt="Version ▶ " \ | |
| --header="↑/↓ navigate • Enter to select • Ctrl-C to cancel" \ | |
| --border --height=50% --reverse --no-multi \ | |
| --color=prompt:cyan,header:gray,border:gray \ | |
| || true) | |
| if [ -z "${SEL_LINE:-}" ]; then | |
| echo "Installation cancelled." | |
| exit 0 | |
| fi | |
| # Map selection back to index | |
| INDEX=0 | |
| for i in "${!CHOICES[@]}"; do | |
| if [ "${CHOICES[$i]}" = "$SEL_LINE" ]; then INDEX="$i"; break; fi | |
| done | |
| else | |
| echo "📋 Available versions:" | |
| for i in "${!CHOICES[@]}"; do | |
| printf " %2d) %s\n" "$((i+1))" "${CHOICES[$i]}" | |
| done | |
| echo | |
| read -r -p "Select version number [1]: " NUM | |
| NUM="${NUM:-1}" | |
| [[ "$NUM" =~ ^[0-9]+$ ]] && [ "$NUM" -ge 1 ] && [ "$NUM" -le "$COUNT" ] || { | |
| echo "❌ Invalid selection." | |
| exit 1 | |
| } | |
| INDEX=$((NUM-1)) | |
| fi | |
| TAG="${TAGS[$INDEX]}" | |
| echo | |
| echo "✅ Selected: ${TAG}" | |
| # ============================== | |
| # Find Compatible Asset | |
| # ============================== | |
| echo "🔍 Finding compatible binary..." | |
| if have jq; then | |
| ASSET_NAMES="$(printf '%s' "$JSON" | jq -r "map(select(.draft==false)) | .[$INDEX].assets | .[].name")" | |
| else | |
| ASSET_NAMES="$(printf '%s' "$JSON" | json_q "asset-names:$INDEX")" | |
| fi | |
| ASSET_NAME="" | |
| ASSET_URL="" | |
| for cand in "${CANDIDATES[@]}"; do | |
| if printf '%s\n' "$ASSET_NAMES" | grep -qx "$cand"; then | |
| ASSET_NAME="$cand" | |
| if have jq; then | |
| ASSET_URL="$(printf '%s' "$JSON" | jq -r \ | |
| "map(select(.draft==false)) | .[$INDEX].assets | .[] | select(.name==\"$ASSET_NAME\") | .browser_download_url" | head -n1)" | |
| else | |
| ASSET_URL="$(printf '%s' "$JSON" | json_q "asset-url:$INDEX:$ASSET_NAME")" | |
| fi | |
| break | |
| fi | |
| done | |
| if [ -z "$ASSET_URL" ]; then | |
| echo "❌ No compatible binary found for your system." | |
| echo " OS: $OS, Architecture: $ARCH" | |
| echo | |
| echo "Available assets for this release:" | |
| printf ' • %s\n' $ASSET_NAMES | |
| exit 1 | |
| fi | |
| echo "✅ Found: $ASSET_NAME" | |
| # ============================== | |
| # Download and Install | |
| # ============================== | |
| TMPDIR="$(mktemp -d)" | |
| trap "rm -rf $TMPDIR" EXIT | |
| TARBALL="$TMPDIR/$ASSET_NAME" | |
| echo "⬇️ Downloading..." | |
| curl -fL --progress-bar -o "$TARBALL" "$ASSET_URL" || { | |
| echo "❌ Download failed" | |
| exit 1 | |
| } | |
| echo "📦 Extracting..." | |
| tar -xzf "$TARBALL" -C "$TMPDIR" || { | |
| echo "❌ Extraction failed" | |
| exit 1 | |
| } | |
| # Find the binary | |
| BIN_SRC="$(find "$TMPDIR" -maxdepth 1 -type f -name 'codex*' ! -name '*.tar.gz' ! -name '*.zst' | head -n1)" | |
| [ -n "$BIN_SRC" ] || { | |
| echo "❌ Could not locate codex binary in archive" | |
| exit 1 | |
| } | |
| # Create install directory and backup existing binary | |
| mkdir -p "$INSTALL_DIR" | |
| BIN_DST="$INSTALL_DIR/codex" | |
| if [ -f "$BIN_DST" ]; then | |
| BACKUP="$BIN_DST.bak.$(date +%Y%m%d%H%M%S)" | |
| cp -f "$BIN_DST" "$BACKUP" | |
| echo "📋 Backed up existing binary to: $BACKUP" | |
| fi | |
| # Install the binary | |
| install -m 0755 "$BIN_SRC" "$BIN_DST" || { | |
| echo "❌ Installation failed" | |
| exit 1 | |
| } | |
| # ============================== | |
| # Verify Installation | |
| # ============================== | |
| echo | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo "✅ Installation complete!" | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo | |
| echo "📍 Installed to: $BIN_DST" | |
| # Check version | |
| if "$BIN_DST" --version 2>/dev/null; then | |
| : | |
| else | |
| echo " Version check failed (binary might still work)" | |
| fi | |
| # PATH check | |
| echo | |
| case ":$PATH:" in | |
| *":$INSTALL_DIR:"*) | |
| echo "✅ $INSTALL_DIR is already in your PATH" | |
| echo | |
| echo "🎉 You can now run: codex" | |
| ;; | |
| *) | |
| echo "⚠️ $INSTALL_DIR is not in your PATH" | |
| echo | |
| echo "To use codex, add this line to your shell config:" | |
| echo | |
| echo " For bash (~/.bashrc):" | |
| echo " export PATH=\"$INSTALL_DIR:\$PATH\"" | |
| echo | |
| echo " For zsh (~/.zshrc):" | |
| echo " export PATH=\"$INSTALL_DIR:\$PATH\"" | |
| echo | |
| echo "Then reload your shell or run:" | |
| echo " source ~/.bashrc # or ~/.zshrc" | |
| ;; | |
| esac | |
| echo | |
| echo "📚 Documentation: https://github.com/openai/codex" | |
| echo "🐛 Report issues: https://github.com/openai/codex/issues" | |
| echo |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment