mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
It introduces a dedicated `installer/` folder for non-Rust install/update assets, a `CODEX_HOME`-aware on-disk layout, and a wrapper that makes helper CLIs available to Codex without globally polluting the user’s shell. ## Why Today the recommended install path is `npm i -g @openai/codex`, but the npm package bundles native binaries for all platforms (and `rg`), leading to very large installs (hundreds of MB unpacked) even though a single platform binary is much smaller. Homebrew avoids this on macOS by downloading only the matching artifact. This PR brings that same property to a cross-platform one-liner: ```sh curl -fsSL https://raw.githubusercontent.com/openai/codex/main/installer/install.sh | bash ``` Key goals: - Download only the user’s platform artifacts. - Keep curl-managed installs isolated under `CODEX_HOME`. - Preserve compatibility with npm/brew installs (shadowing is fine, breaking is not). - Provide a clean place to add helper CLIs that should be available when Codex runs. ## How It Works ### Install root and layout All curl-managed artifacts live under `CODEX_HOME` (default: `~/.codex`). The layout is: - `CODEX_HOME/bin/` - `CODEX_HOME/versions/<version>/bin/` - `CODEX_HOME/versions/current` (symlink) - `CODEX_HOME/tools/bin/` This design supports atomic version switches and helper CLIs: - The user’s global `PATH` only needs `CODEX_HOME/bin`. - The runtime `PATH` is extended inside the wrapper to include helper CLIs. See `installer/README.md:1`. ### Installer scripts #### `installer/install.sh` `installer/install.sh:1` is the public entrypoint: - Requires Bash explicitly (we use Bash features like `mapfile`). - Loads `installer/lib.sh` locally when run from a checkout, or downloads it when piped from curl. - Detects `arch`/`os`, resolves the version (via `CODEX_VERSION` or GitHub Releases), downloads the correct tarball, installs it, activates it, and updates the user’s rc file. #### `installer/lib.sh` `installer/lib.sh:10` centralizes the mechanics: - Resolves `CODEX_HOME` with a fallback to `~/.codex`. - Detects OS/arch and builds release URLs. - Idempotently updates the user rc file via a marker block that respects non-default `CODEX_HOME` values: `installer/lib.sh:106`. - Installs a wrapper at `CODEX_HOME/bin/codex` that: - Sets `CODEX_MANAGED_BY_CURL=1`. - Extends `PATH` at runtime to include: - `CODEX_HOME/bin` - `CODEX_HOME/tools/bin` - `CODEX_HOME/versions/current/bin` - Execs the active versioned binary: `installer/lib.sh:154`. - Installs the tarball into a versioned `bin/` directory and preserves any additional CLIs present in the tarball (not just `codex`): `installer/lib.sh:181`. - Keeps the last two installed versions and avoids deleting the `current` symlink: `installer/lib.sh:239`. #### `installer/update.sh` `installer/update.sh:1` mirrors the installer flow but always targets the latest release. ### Update UX integration in the CLI Codex’s update prompt already knows how to run a package-manager-specific update command after the TUI exits. This PR adds a curl-managed update action in `codex-rs/tui/src/update_action.rs:3`: - Adds `UpdateAction::CurlInstallerUpdate`. - Detects curl-managed installs via `CODEX_MANAGED_BY_CURL` (set by the wrapper): `codex-rs/tui/src/update_action.rs:40`. - Uses the curl updater one-liner for the update command: `codex-rs/tui/src/update_action.rs:21`. This keeps update behavior consistent with npm/brew while avoiding fragile path heuristics. ## Docs updates - The curl installer is now the first recommended install path in `README.md:1` and `README.md:18`. - `docs/install.md:11` adds a curl install section and points to `installer/README.md` for the detailed mechanics. ## Compatibility and safety notes - This does not modify npm or Homebrew installs. - The rc-file change only adds `CODEX_HOME/bin` to `PATH`. - Additional helper CLIs are available to Codex via the wrapper’s runtime `PATH`, rather than being forced into the user’s global shell. - The installer honors `CODEX_HOME` everywhere, falling back to `~/.codex`. ## Testing Commands run: ```sh cd codex-rs just fmt cargo test -p codex-tui ``` Notes: - In this environment, `cargo test -p codex-tui` required running outside the sandbox due to a macOS `SystemConfiguration` panic; it passed once run with escalated permissions. - No new VS Code diagnostics were reported. ## Follow-ups (explicitly not in this PR) - Ensure the release tarballs include any helper CLIs we expect to be available at runtime (for example, Windows sandbox helpers). - Add an explicit `codex self-update` command that delegates to `installer/update.sh`. - Decide how we want to manage third-party tools like `rg` under `CODEX_HOME/tools/` and whether the CLI should probe a managed fallback path.
257 lines
5.4 KiB
Bash
Executable File
257 lines
5.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
if [ -z "${BASH_VERSION:-}" ]; then
|
|
echo "Codex installer requires bash." >&2
|
|
exit 1
|
|
fi
|
|
|
|
set -euo pipefail
|
|
|
|
CODEX_HOME_DEFAULT="${HOME}/.codex"
|
|
CODEX_HOME_RESOLVED="${CODEX_HOME:-$CODEX_HOME_DEFAULT}"
|
|
|
|
codex_home() {
|
|
printf '%s\n' "$CODEX_HOME_RESOLVED"
|
|
}
|
|
|
|
codex_bin_dir() {
|
|
printf '%s/bin\n' "$(codex_home)"
|
|
}
|
|
|
|
codex_versions_dir() {
|
|
printf '%s/versions\n' "$(codex_home)"
|
|
}
|
|
|
|
codex_tools_dir() {
|
|
printf '%s/tools\n' "$(codex_home)"
|
|
}
|
|
|
|
codex_tools_bin_dir() {
|
|
printf '%s/bin\n' "$(codex_tools_dir)"
|
|
}
|
|
|
|
current_version_link() {
|
|
printf '%s/current\n' "$(codex_versions_dir)"
|
|
}
|
|
|
|
detect_os() {
|
|
local uname_s
|
|
uname_s="$(uname -s)"
|
|
case "$uname_s" in
|
|
Darwin) printf '%s\n' "apple-darwin" ;;
|
|
Linux) printf '%s\n' "unknown-linux-musl" ;;
|
|
*)
|
|
echo "Unsupported OS: $uname_s" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
detect_arch() {
|
|
local uname_m
|
|
uname_m="$(uname -m)"
|
|
case "$uname_m" in
|
|
arm64|aarch64) printf '%s\n' "aarch64" ;;
|
|
x86_64|amd64) printf '%s\n' "x86_64" ;;
|
|
*)
|
|
echo "Unsupported architecture: $uname_m" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
github_api() {
|
|
local path="$1"
|
|
curl -fsSL \
|
|
-H "Accept: application/vnd.github+json" \
|
|
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
"https://api.github.com/repos/openai/codex/${path}"
|
|
}
|
|
|
|
latest_tag() {
|
|
github_api "releases/latest" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1
|
|
}
|
|
|
|
normalize_version() {
|
|
local tag="$1"
|
|
tag="${tag#rust-v}"
|
|
tag="${tag#v}"
|
|
printf '%s\n' "$tag"
|
|
}
|
|
|
|
release_tag_for_version() {
|
|
local version="$1"
|
|
printf '%s\n' "rust-v${version}"
|
|
}
|
|
|
|
expected_binary_name() {
|
|
local arch="$1"
|
|
local os="$2"
|
|
printf '%s\n' "codex-${arch}-${os}"
|
|
}
|
|
|
|
release_url() {
|
|
local version="$1"
|
|
local arch="$2"
|
|
local os="$3"
|
|
local tag
|
|
tag="$(release_tag_for_version "$version")"
|
|
printf '%s\n' "https://github.com/openai/codex/releases/download/${tag}/codex-${arch}-${os}.tar.gz"
|
|
}
|
|
|
|
ensure_dirs() {
|
|
mkdir -p "$(codex_bin_dir)" "$(codex_versions_dir)" "$(codex_tools_bin_dir)"
|
|
}
|
|
|
|
ensure_path_block() {
|
|
local rc_file="$1"
|
|
local resolved_home="$2"
|
|
local start_marker="# >>> codex >>>"
|
|
|
|
if [ ! -f "$rc_file" ]; then
|
|
touch "$rc_file"
|
|
fi
|
|
|
|
if grep -Fq "$start_marker" "$rc_file"; then
|
|
return
|
|
fi
|
|
|
|
if [ "$resolved_home" = "${HOME}/.codex" ]; then
|
|
cat >>"$rc_file" <<'EOF'
|
|
# >>> codex >>>
|
|
export CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
|
|
export PATH="$CODEX_HOME/bin:$PATH"
|
|
# <<< codex <<<
|
|
EOF
|
|
else
|
|
cat >>"$rc_file" <<EOF
|
|
# >>> codex >>>
|
|
export CODEX_HOME="${resolved_home}"
|
|
export PATH="\$CODEX_HOME/bin:\$PATH"
|
|
# <<< codex <<<
|
|
EOF
|
|
fi
|
|
}
|
|
|
|
choose_rc_file() {
|
|
local shell_name
|
|
shell_name="$(basename "${SHELL:-}")"
|
|
case "$shell_name" in
|
|
zsh) printf '%s\n' "${HOME}/.zshrc" ;;
|
|
bash)
|
|
if [ -f "${HOME}/.bashrc" ]; then
|
|
printf '%s\n' "${HOME}/.bashrc"
|
|
else
|
|
printf '%s\n' "${HOME}/.bash_profile"
|
|
fi
|
|
;;
|
|
*)
|
|
printf '%s\n' "${HOME}/.profile"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
install_wrapper() {
|
|
local wrapper_path
|
|
wrapper_path="$(codex_bin_dir)/codex"
|
|
|
|
cat >"$wrapper_path" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
|
|
CURRENT_LINK="$CODEX_HOME/versions/current"
|
|
TARGET="$CURRENT_LINK/bin/codex"
|
|
|
|
if [ ! -x "$TARGET" ]; then
|
|
echo "Codex is not installed under $CODEX_HOME." >&2
|
|
echo "Run the installer again to repair the installation." >&2
|
|
exit 1
|
|
fi
|
|
|
|
export CODEX_MANAGED_BY_CURL=1
|
|
export PATH="$CODEX_HOME/bin:$CODEX_HOME/tools/bin:$CURRENT_LINK/bin:$PATH"
|
|
|
|
exec "$TARGET" "$@"
|
|
EOF
|
|
|
|
chmod +x "$wrapper_path"
|
|
}
|
|
|
|
install_version_from_tarball() {
|
|
local version="$1"
|
|
local tarball="$2"
|
|
local arch="$3"
|
|
local os="$4"
|
|
|
|
local versions_dir version_dir bin_dir expected_name tmpdir main_candidate
|
|
versions_dir="$(codex_versions_dir)"
|
|
version_dir="${versions_dir}/${version}"
|
|
bin_dir="${version_dir}/bin"
|
|
expected_name="$(expected_binary_name "$arch" "$os")"
|
|
|
|
rm -rf "$version_dir"
|
|
mkdir -p "$bin_dir"
|
|
|
|
tmpdir="$(mktemp -d)"
|
|
tar -xzf "$tarball" -C "$tmpdir"
|
|
|
|
if [ -f "$tmpdir/$expected_name" ]; then
|
|
main_candidate="$tmpdir/$expected_name"
|
|
elif [ -f "$tmpdir/codex" ]; then
|
|
main_candidate="$tmpdir/codex"
|
|
else
|
|
main_candidate="$(find "$tmpdir" -type f -name 'codex*' | head -n1 || true)"
|
|
fi
|
|
if [ -z "$main_candidate" ]; then
|
|
echo "Failed to locate codex binary in tarball." >&2
|
|
exit 1
|
|
fi
|
|
|
|
local files file base dest
|
|
mapfile -t files < <(find "$tmpdir" -type f)
|
|
for file in "${files[@]}"; do
|
|
base="$(basename "$file")"
|
|
if [ "$file" = "$main_candidate" ]; then
|
|
dest="$bin_dir/codex"
|
|
else
|
|
dest="$bin_dir/$base"
|
|
fi
|
|
mv "$file" "$dest"
|
|
chmod +x "$dest" 2>/dev/null || true
|
|
done
|
|
|
|
if [ ! -x "$bin_dir/codex" ]; then
|
|
echo "Failed to install codex binary." >&2
|
|
exit 1
|
|
fi
|
|
|
|
rm -rf "$tmpdir"
|
|
}
|
|
|
|
activate_version() {
|
|
local version="$1"
|
|
local link
|
|
link="$(current_version_link)"
|
|
ln -sfn "$(codex_versions_dir)/$version" "$link"
|
|
}
|
|
|
|
cleanup_old_versions() {
|
|
local keep="${1:-2}"
|
|
local versions_dir
|
|
versions_dir="$(codex_versions_dir)"
|
|
if [ ! -d "$versions_dir" ]; then
|
|
return
|
|
fi
|
|
if ! compgen -G "$versions_dir/*" >/dev/null; then
|
|
return
|
|
fi
|
|
|
|
# Remove older versions while keeping the most recent N directories.
|
|
# We intentionally ignore errors here so cleanup never blocks install.
|
|
ls -1dt "$versions_dir"/* 2>/dev/null \
|
|
| grep -v '/current$' \
|
|
| tail -n +"$((keep + 1))" \
|
|
| xargs -I{} rm -rf "{}" 2>/dev/null || true
|
|
}
|