diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 4ed08b077e3d..2d137de6ff72 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -494,6 +494,10 @@ jobs: --package codex-responses-api-proxy \ --package codex-sdk + - name: Stage macOS and Linux installer script + run: | + cp scripts/install/install.sh dist/install.sh + - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: diff --git a/scripts/install/install.sh b/scripts/install/install.sh new file mode 100755 index 000000000000..86e2940d5530 --- /dev/null +++ b/scripts/install/install.sh @@ -0,0 +1,244 @@ +#!/bin/sh + +set -eu + +VERSION="${1:-latest}" +INSTALL_DIR="${CODEX_INSTALL_DIR:-$HOME/.local/bin}" +path_action="already" +path_profile="" + +step() { + printf '==> %s\n' "$1" +} + +normalize_version() { + case "$1" in + "" | latest) + printf 'latest\n' + ;; + rust-v*) + printf '%s\n' "${1#rust-v}" + ;; + v*) + printf '%s\n' "${1#v}" + ;; + *) + printf '%s\n' "$1" + ;; + esac +} + +download_file() { + url="$1" + output="$2" + + if command -v curl >/dev/null 2>&1; then + curl -fsSL "$url" -o "$output" + return + fi + + if command -v wget >/dev/null 2>&1; then + wget -q -O "$output" "$url" + return + fi + + echo "curl or wget is required to install Codex." >&2 + exit 1 +} + +download_text() { + url="$1" + + if command -v curl >/dev/null 2>&1; then + curl -fsSL "$url" + return + fi + + if command -v wget >/dev/null 2>&1; then + wget -q -O - "$url" + return + fi + + echo "curl or wget is required to install Codex." >&2 + exit 1 +} + +add_to_path() { + path_action="already" + path_profile="" + + case ":$PATH:" in + *":$INSTALL_DIR:"*) + return + ;; + esac + + profile="$HOME/.profile" + case "${SHELL:-}" in + */zsh) + profile="$HOME/.zshrc" + ;; + */bash) + profile="$HOME/.bashrc" + ;; + esac + + path_profile="$profile" + path_line="export PATH=\"$INSTALL_DIR:\$PATH\"" + if [ -f "$profile" ] && grep -F "$path_line" "$profile" >/dev/null 2>&1; then + path_action="configured" + return + fi + + { + printf '\n# Added by Codex installer\n' + printf '%s\n' "$path_line" + } >>"$profile" + path_action="added" +} + +release_url_for_asset() { + asset="$1" + resolved_version="$2" + + printf 'https://github.com/openai/codex/releases/download/rust-v%s/%s\n' "$resolved_version" "$asset" +} + +require_command() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "$1 is required to install Codex." >&2 + exit 1 + fi +} + +require_command mktemp +require_command tar + +resolve_version() { + normalized_version="$(normalize_version "$VERSION")" + + if [ "$normalized_version" != "latest" ]; then + printf '%s\n' "$normalized_version" + return + fi + + release_json="$(download_text "https://api.github.com/repos/openai/codex/releases/latest")" + resolved="$(printf '%s\n' "$release_json" | sed -n 's/.*"tag_name":[[:space:]]*"rust-v\([^"]*\)".*/\1/p' | head -n 1)" + + if [ -z "$resolved" ]; then + echo "Failed to resolve the latest Codex release version." >&2 + exit 1 + fi + + printf '%s\n' "$resolved" +} + +case "$(uname -s)" in + Darwin) + os="darwin" + ;; + Linux) + os="linux" + ;; + *) + echo "install.sh supports macOS and Linux. Use install.ps1 on Windows." >&2 + exit 1 + ;; +esac + +case "$(uname -m)" in + x86_64 | amd64) + arch="x86_64" + ;; + arm64 | aarch64) + arch="aarch64" + ;; + *) + echo "Unsupported architecture: $(uname -m)" >&2 + exit 1 + ;; +esac + +if [ "$os" = "darwin" ] && [ "$arch" = "x86_64" ]; then + if [ "$(sysctl -n sysctl.proc_translated 2>/dev/null || true)" = "1" ]; then + arch="aarch64" + fi +fi + +if [ "$os" = "darwin" ]; then + if [ "$arch" = "aarch64" ]; then + npm_tag="darwin-arm64" + vendor_target="aarch64-apple-darwin" + platform_label="macOS (Apple Silicon)" + else + npm_tag="darwin-x64" + vendor_target="x86_64-apple-darwin" + platform_label="macOS (Intel)" + fi +else + if [ "$arch" = "aarch64" ]; then + npm_tag="linux-arm64" + vendor_target="aarch64-unknown-linux-musl" + platform_label="Linux (ARM64)" + else + npm_tag="linux-x64" + vendor_target="x86_64-unknown-linux-musl" + platform_label="Linux (x64)" + fi +fi + +if [ -x "$INSTALL_DIR/codex" ]; then + install_mode="Updating" +else + install_mode="Installing" +fi + +step "$install_mode Codex CLI" +step "Detected platform: $platform_label" + +resolved_version="$(resolve_version)" +asset="codex-npm-$npm_tag-$resolved_version.tgz" +download_url="$(release_url_for_asset "$asset" "$resolved_version")" + +step "Resolved version: $resolved_version" + +tmp_dir="$(mktemp -d)" +cleanup() { + rm -rf "$tmp_dir" +} +trap cleanup EXIT INT TERM + +archive_path="$tmp_dir/$asset" + +step "Downloading Codex CLI" +download_file "$download_url" "$archive_path" + +tar -xzf "$archive_path" -C "$tmp_dir" + +step "Installing to $INSTALL_DIR" +mkdir -p "$INSTALL_DIR" +cp "$tmp_dir/package/vendor/$vendor_target/codex/codex" "$INSTALL_DIR/codex" +cp "$tmp_dir/package/vendor/$vendor_target/path/rg" "$INSTALL_DIR/rg" +chmod 0755 "$INSTALL_DIR/codex" +chmod 0755 "$INSTALL_DIR/rg" + +add_to_path + +case "$path_action" in + added) + step "PATH updated for future shells in $path_profile" + step "Run now: export PATH=\"$INSTALL_DIR:\$PATH\" && codex" + step "Or open a new terminal and run: codex" + ;; + configured) + step "PATH is already configured for future shells in $path_profile" + step "Run now: export PATH=\"$INSTALL_DIR:\$PATH\" && codex" + step "Or open a new terminal and run: codex" + ;; + *) + step "$INSTALL_DIR is already on PATH" + step "Run: codex" + ;; +esac + +printf 'Codex CLI %s installed successfully.\n' "$resolved_version"