mirror of
https://github.com/openai/codex.git
synced 2026-08-23 13:09:46 +00:00
Prefer releases.openai.com in standalone installers (#34910)
## What changed - Make the shell and PowerShell installers download release metadata and assets from `releases.openai.com` by default, with bounded request timeouts. - Fall back to GitHub Releases when metadata is unusable, required assets are missing, or downloaded assets fail checksum or manifest validation. - Document `CODEX_INSTALLER_USE_RELEASES_OPENAI_COM=false` for opting out of the default release source. ## Testing - Cover default-source installation, malformed and incomplete metadata, version mismatches, corrupt assets, invalid checksums, incomplete manifests, and GitHub fallback failures in the shell installer tests. GitOrigin-RevId: 1c65d527ca79761358c3a4cd7a5fd675e1ef75b2
This commit is contained in:
10
README.md
10
README.md
@@ -25,6 +25,16 @@ Run the following on Windows to install Codex CLI:
|
||||
powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"
|
||||
```
|
||||
|
||||
The standalone installers download from `https://releases.openai.com/codex` by default and fall back to GitHub Releases if a metadata or asset download is unavailable. To force GitHub Releases, set `CODEX_INSTALLER_USE_RELEASES_OPENAI_COM` to `false` (`0` and `no` are also accepted):
|
||||
|
||||
```shell
|
||||
curl -fsSL https://chatgpt.com/codex/install.sh | CODEX_INSTALLER_USE_RELEASES_OPENAI_COM=false sh
|
||||
```
|
||||
|
||||
```powershell
|
||||
$env:CODEX_INSTALLER_USE_RELEASES_OPENAI_COM='false'; irm https://chatgpt.com/codex/install.ps1 | iex
|
||||
```
|
||||
|
||||
Codex CLI can also be installed via the following package managers:
|
||||
|
||||
```shell
|
||||
|
||||
@@ -12,13 +12,15 @@ if ([string]::IsNullOrWhiteSpace($Release)) {
|
||||
}
|
||||
|
||||
$NonInteractive = $env:CODEX_NON_INTERACTIVE -match "^(?i:1|true|yes)$"
|
||||
$DefaultPreferReleasesOpenAICom = $false
|
||||
$DefaultPreferReleasesOpenAICom = $true
|
||||
$PreferReleasesOpenAICom = if ([string]::IsNullOrWhiteSpace($env:CODEX_INSTALLER_USE_RELEASES_OPENAI_COM)) {
|
||||
$DefaultPreferReleasesOpenAICom
|
||||
} else {
|
||||
$env:CODEX_INSTALLER_USE_RELEASES_OPENAI_COM -match "^(?i:1|true|yes)$"
|
||||
}
|
||||
$ReleasesBaseUri = "https://releases.openai.com/codex"
|
||||
$ReleasesMetadataTimeoutSec = 30
|
||||
$ReleasesAssetTimeoutSec = 300
|
||||
|
||||
function Write-Step {
|
||||
param(
|
||||
@@ -111,17 +113,45 @@ function Find-ReleaseAssetMetadata {
|
||||
function Invoke-WebRequestWithFallback {
|
||||
param(
|
||||
[object]$Metadata,
|
||||
[string]$OutFile
|
||||
[string]$OutFile,
|
||||
[string]$ExpectedDigest,
|
||||
[string]$AssetName,
|
||||
[string]$ReleaseVersion,
|
||||
[string]$RequiredManifestAsset
|
||||
)
|
||||
|
||||
try {
|
||||
Invoke-WebRequest -UseBasicParsing -Uri $Metadata.Url -OutFile $OutFile
|
||||
if ($Metadata.Url.StartsWith("$ReleasesBaseUri/", [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
Invoke-WebRequest -UseBasicParsing -Uri $Metadata.Url -OutFile $OutFile -TimeoutSec $ReleasesAssetTimeoutSec
|
||||
} else {
|
||||
Invoke-WebRequest -UseBasicParsing -Uri $Metadata.Url -OutFile $OutFile
|
||||
}
|
||||
Test-ArchiveDigest -ArchivePath $OutFile -ExpectedDigest $ExpectedDigest
|
||||
if (-not [string]::IsNullOrWhiteSpace($RequiredManifestAsset)) {
|
||||
$null = Get-PackageArchiveDigest -ManifestPath $OutFile -AssetName $RequiredManifestAsset
|
||||
}
|
||||
} catch {
|
||||
if ([string]::IsNullOrWhiteSpace($Metadata.FallbackUrl)) {
|
||||
throw
|
||||
}
|
||||
Write-WarningStep "Could not download $($Metadata.Url); retrying from GitHub Releases."
|
||||
Write-WarningStep "Could not download or verify $($Metadata.Url); retrying from GitHub Releases."
|
||||
Invoke-WebRequest -UseBasicParsing -Uri $Metadata.FallbackUrl -OutFile $OutFile
|
||||
try {
|
||||
Test-ArchiveDigest -ArchivePath $OutFile -ExpectedDigest $ExpectedDigest
|
||||
if (-not [string]::IsNullOrWhiteSpace($RequiredManifestAsset)) {
|
||||
$null = Get-PackageArchiveDigest -ManifestPath $OutFile -AssetName $RequiredManifestAsset
|
||||
}
|
||||
} catch {
|
||||
$githubRelease = Resolve-ReleaseFromGitHub -NormalizedVersion $ReleaseVersion
|
||||
$githubAssetMetadata = Find-ReleaseAssetMetadata -AssetName $AssetName -ReleaseMetadata $githubRelease.Metadata
|
||||
if ($null -eq $githubAssetMetadata) {
|
||||
throw "Could not find GitHub release metadata for asset $AssetName."
|
||||
}
|
||||
Test-ArchiveDigest -ArchivePath $OutFile -ExpectedDigest $githubAssetMetadata.Sha256
|
||||
if (-not [string]::IsNullOrWhiteSpace($RequiredManifestAsset)) {
|
||||
$null = Get-PackageArchiveDigest -ManifestPath $OutFile -AssetName $RequiredManifestAsset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,25 +367,22 @@ function Resolve-ReleaseFromReleases {
|
||||
"$ReleasesBaseUri/releases/$NormalizedVersion/release.json"
|
||||
}
|
||||
try {
|
||||
$metadataResponse = Invoke-WebRequest -UseBasicParsing -Uri $metadataUri
|
||||
$metadataResponse = Invoke-WebRequest -UseBasicParsing -Uri $metadataUri -TimeoutSec $ReleasesMetadataTimeoutSec
|
||||
$releaseMetadata = [string]$metadataResponse.Content | ConvertFrom-Json -ErrorAction Stop
|
||||
$resolvedVersion = Resolve-VersionFromReleaseMetadata -ReleaseMetadata $releaseMetadata
|
||||
if ($NormalizedVersion -ne "latest" -and $resolvedVersion -cne $NormalizedVersion) {
|
||||
throw "Release metadata version did not match requested Codex version $NormalizedVersion."
|
||||
}
|
||||
$resolvedRelease = [PSCustomObject]@{
|
||||
Version = $resolvedVersion
|
||||
Metadata = $releaseMetadata
|
||||
Source = "ReleasesOpenAICom"
|
||||
}
|
||||
$null = Resolve-ReleaseAssetSelection -ResolvedRelease $resolvedRelease -Target $target -NpmTag $npmTag
|
||||
} catch {
|
||||
return $null
|
||||
}
|
||||
try {
|
||||
$releaseMetadata = [string]$metadataResponse.Content | ConvertFrom-Json -ErrorAction Stop
|
||||
} catch {
|
||||
throw "Invalid release metadata from releases.openai.com."
|
||||
}
|
||||
|
||||
$resolvedVersion = Resolve-VersionFromReleaseMetadata -ReleaseMetadata $releaseMetadata
|
||||
if ($NormalizedVersion -ne "latest" -and $resolvedVersion -cne $NormalizedVersion) {
|
||||
throw "Release metadata version did not match requested Codex version $NormalizedVersion."
|
||||
}
|
||||
return [PSCustomObject]@{
|
||||
Version = $resolvedVersion
|
||||
Metadata = $releaseMetadata
|
||||
Source = "ReleasesOpenAICom"
|
||||
}
|
||||
return $resolvedRelease
|
||||
}
|
||||
|
||||
function Resolve-Release {
|
||||
@@ -932,14 +959,12 @@ try {
|
||||
|
||||
Write-Step "Downloading Codex CLI"
|
||||
if ($installLayout -eq "Package") {
|
||||
Invoke-WebRequestWithFallback -Metadata $checksumMetadata -OutFile $checksumPath
|
||||
Test-ArchiveDigest -ArchivePath $checksumPath -ExpectedDigest $checksumMetadata.Sha256
|
||||
Invoke-WebRequestWithFallback -Metadata $checksumMetadata -OutFile $checksumPath -ExpectedDigest $checksumMetadata.Sha256 -AssetName $checksumAsset -ReleaseVersion $resolvedVersion -RequiredManifestAsset $packageAsset
|
||||
$expectedPackageDigest = Get-PackageArchiveDigest -ManifestPath $checksumPath -AssetName $packageAsset
|
||||
} else {
|
||||
$expectedPackageDigest = $packageMetadata.Sha256
|
||||
}
|
||||
Invoke-WebRequestWithFallback -Metadata $packageMetadata -OutFile $archivePath
|
||||
Test-ArchiveDigest -ArchivePath $archivePath -ExpectedDigest $expectedPackageDigest
|
||||
Invoke-WebRequestWithFallback -Metadata $packageMetadata -OutFile $archivePath -ExpectedDigest $expectedPackageDigest -AssetName $packageAsset -ReleaseVersion $resolvedVersion
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $releasesDir | Out-Null
|
||||
if (Test-Path -LiteralPath $stagingDir) {
|
||||
|
||||
@@ -4,9 +4,12 @@ set -eu
|
||||
|
||||
RELEASE="${CODEX_RELEASE:-latest}"
|
||||
NON_INTERACTIVE="${CODEX_NON_INTERACTIVE:-false}"
|
||||
DEFAULT_PREFER_RELEASES_OPENAI_COM="false"
|
||||
DEFAULT_PREFER_RELEASES_OPENAI_COM="true"
|
||||
PREFER_RELEASES_OPENAI_COM="${CODEX_INSTALLER_USE_RELEASES_OPENAI_COM:-$DEFAULT_PREFER_RELEASES_OPENAI_COM}"
|
||||
RELEASES_BASE_URL="https://releases.openai.com/codex"
|
||||
RELEASES_CONNECT_TIMEOUT=10
|
||||
RELEASES_METADATA_TIMEOUT=30
|
||||
RELEASES_ASSET_TIMEOUT=300
|
||||
release_source="github"
|
||||
|
||||
BIN_DIR="${CODEX_INSTALL_DIR:-$HOME/.local/bin}"
|
||||
@@ -61,7 +64,7 @@ validate_version() {
|
||||
|
||||
if ! printf '%s\n' "$version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-alpha(\.[0-9]+){0,2}|-beta(\.[0-9]+)?)?$'; then
|
||||
echo "Invalid Codex release version: $version. Expected latest or x.y.z[-alpha[.N[.M]]|-beta[.N]]." >&2
|
||||
exit 1
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -83,6 +86,8 @@ Usage: install.sh [--release VERSION]
|
||||
Environment:
|
||||
CODEX_RELEASE Version to install; overridden by --release.
|
||||
CODEX_NON_INTERACTIVE Set to 1, true, or yes to skip prompts.
|
||||
CODEX_INSTALLER_USE_RELEASES_OPENAI_COM
|
||||
Set to 0, false, or no to use GitHub Releases.
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
@@ -100,12 +105,26 @@ download_file() {
|
||||
output="$2"
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsSL "$url" -o "$output"
|
||||
case "$url" in
|
||||
"$RELEASES_BASE_URL"/*)
|
||||
curl -fsSL --connect-timeout "$RELEASES_CONNECT_TIMEOUT" --max-time "$RELEASES_ASSET_TIMEOUT" "$url" -o "$output"
|
||||
;;
|
||||
*)
|
||||
curl -fsSL "$url" -o "$output"
|
||||
;;
|
||||
esac
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v wget >/dev/null 2>&1; then
|
||||
wget -q -O "$output" "$url"
|
||||
case "$url" in
|
||||
"$RELEASES_BASE_URL"/*)
|
||||
wget -q -t 1 -T "$RELEASES_ASSET_TIMEOUT" -O "$output" "$url"
|
||||
;;
|
||||
*)
|
||||
wget -q -O "$output" "$url"
|
||||
;;
|
||||
esac
|
||||
return
|
||||
fi
|
||||
|
||||
@@ -117,12 +136,26 @@ download_text() {
|
||||
url="$1"
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsSL "$url"
|
||||
case "$url" in
|
||||
"$RELEASES_BASE_URL"/*)
|
||||
curl -fsSL --connect-timeout "$RELEASES_CONNECT_TIMEOUT" --max-time "$RELEASES_METADATA_TIMEOUT" "$url"
|
||||
;;
|
||||
*)
|
||||
curl -fsSL "$url"
|
||||
;;
|
||||
esac
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v wget >/dev/null 2>&1; then
|
||||
wget -q -O - "$url"
|
||||
case "$url" in
|
||||
"$RELEASES_BASE_URL"/*)
|
||||
wget -q -t 1 -T "$RELEASES_METADATA_TIMEOUT" -O - "$url"
|
||||
;;
|
||||
*)
|
||||
wget -q -O - "$url"
|
||||
;;
|
||||
esac
|
||||
return
|
||||
fi
|
||||
|
||||
@@ -134,8 +167,13 @@ download_file_with_fallback() {
|
||||
primary_url="$1"
|
||||
fallback_url="$2"
|
||||
output="$3"
|
||||
expected_digest="$4"
|
||||
fallback_asset="$5"
|
||||
required_manifest_asset="${6:-}"
|
||||
|
||||
if download_file "$primary_url" "$output"; then
|
||||
if download_file "$primary_url" "$output" &&
|
||||
verify_archive_digest "$output" "$expected_digest" &&
|
||||
{ [ -z "$required_manifest_asset" ] || package_archive_digest "$required_manifest_asset" "$output" >/dev/null; }; then
|
||||
return
|
||||
fi
|
||||
|
||||
@@ -143,8 +181,19 @@ download_file_with_fallback() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
warn "Could not download $primary_url; retrying from GitHub Releases."
|
||||
warn "Could not download or verify $primary_url; retrying from GitHub Releases."
|
||||
download_file "$fallback_url" "$output"
|
||||
if verify_archive_digest "$output" "$expected_digest" &&
|
||||
{ [ -z "$required_manifest_asset" ] || package_archive_digest "$required_manifest_asset" "$output" >/dev/null; }; then
|
||||
return
|
||||
fi
|
||||
|
||||
resolve_release_from_github "$resolved_version"
|
||||
fallback_digest="$(release_asset_digest "$fallback_asset")"
|
||||
verify_archive_digest "$output" "$fallback_digest"
|
||||
if [ -n "$required_manifest_asset" ]; then
|
||||
package_archive_digest "$required_manifest_asset" "$output" >/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
parse_release_metadata() {
|
||||
@@ -279,7 +328,7 @@ parse_downloaded_release_metadata() {
|
||||
source_name="$2"
|
||||
if ! release_metadata="$(printf '%s\n' "$release_json" | parse_release_metadata)"; then
|
||||
echo "Could not parse $source_name release metadata for Codex $requested_release." >&2
|
||||
exit 1
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -291,7 +340,7 @@ resolve_metadata_version() {
|
||||
esac
|
||||
if [ -z "$metadata_version" ]; then
|
||||
echo "Failed to resolve the latest Codex release version." >&2
|
||||
exit 1
|
||||
return 1
|
||||
fi
|
||||
validate_version "$metadata_version"
|
||||
}
|
||||
@@ -337,11 +386,15 @@ resolve_release_from_releases() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
parse_downloaded_release_metadata "$requested_release" "releases.openai.com"
|
||||
resolve_metadata_version
|
||||
if ! parse_downloaded_release_metadata "$requested_release" "releases.openai.com"; then
|
||||
return 1
|
||||
fi
|
||||
if ! resolve_metadata_version; then
|
||||
return 1
|
||||
fi
|
||||
if [ "$normalized_version" != "latest" ] && [ "$metadata_version" != "$normalized_version" ]; then
|
||||
echo "Release metadata version did not match requested Codex version $normalized_version." >&2
|
||||
exit 1
|
||||
return 1
|
||||
fi
|
||||
resolved_version="$metadata_version"
|
||||
release_source="releases.openai.com"
|
||||
@@ -353,7 +406,8 @@ resolve_release() {
|
||||
|
||||
case "$PREFER_RELEASES_OPENAI_COM" in
|
||||
1 | [Tt][Rr][Uu][Ee] | [Yy][Ee][Ss])
|
||||
if resolve_release_from_releases "$normalized_version"; then
|
||||
if resolve_release_from_releases "$normalized_version" &&
|
||||
select_release_assets; then
|
||||
return
|
||||
fi
|
||||
warn "releases.openai.com is unavailable; falling back to GitHub Releases."
|
||||
@@ -361,6 +415,7 @@ resolve_release() {
|
||||
esac
|
||||
|
||||
resolve_release_from_github "$normalized_version"
|
||||
select_release_assets
|
||||
}
|
||||
|
||||
release_asset_digest_or_empty() {
|
||||
@@ -375,7 +430,11 @@ release_asset_digest_or_empty() {
|
||||
|
||||
case "$digest" in
|
||||
sha256:????????????????????????????????????????????????????????????????)
|
||||
printf '%s\n' "${digest#sha256:}"
|
||||
digest="${digest#sha256:}"
|
||||
case "$digest" in
|
||||
*[!0-9a-fA-F]*) return 1 ;;
|
||||
esac
|
||||
printf '%s\n' "$digest"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
@@ -416,7 +475,7 @@ select_release_assets() {
|
||||
asset="codex-npm-$npm_tag-$resolved_version.tgz"
|
||||
else
|
||||
echo "Could not find Codex package or platform npm release assets for Codex $resolved_version." >&2
|
||||
exit 1
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$release_source" = "releases.openai.com" ]; then
|
||||
@@ -453,7 +512,7 @@ package_archive_digest() {
|
||||
|
||||
if [ -z "$digest" ]; then
|
||||
echo "Could not find SHA-256 digest for $asset in codex-package_SHA256SUMS." >&2
|
||||
exit 1
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$digest"
|
||||
@@ -490,7 +549,7 @@ verify_archive_digest() {
|
||||
echo "Downloaded Codex archive checksum did not match expected digest." >&2
|
||||
echo "expected: $expected_digest" >&2
|
||||
echo "actual: $actual_digest" >&2
|
||||
exit 1
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -1066,7 +1125,6 @@ else
|
||||
fi
|
||||
|
||||
resolve_release
|
||||
select_release_assets
|
||||
release_name="$resolved_version-$vendor_target"
|
||||
release_dir="$RELEASES_DIR/$release_name"
|
||||
current_version="$(current_installed_version)"
|
||||
@@ -1106,14 +1164,12 @@ if ! release_dir_is_complete "$release_dir" "$resolved_version" "$vendor_target"
|
||||
step "Downloading Codex CLI"
|
||||
if [ "$install_layout" = "package" ]; then
|
||||
checksum_digest="$(release_asset_digest "$checksum_asset")"
|
||||
download_file_with_fallback "$checksum_url" "$checksum_fallback_url" "$checksum_path"
|
||||
verify_archive_digest "$checksum_path" "$checksum_digest"
|
||||
download_file_with_fallback "$checksum_url" "$checksum_fallback_url" "$checksum_path" "$checksum_digest" "$checksum_asset" "$asset"
|
||||
expected_digest="$(package_archive_digest "$asset" "$checksum_path")"
|
||||
else
|
||||
expected_digest="$(release_asset_digest "$asset")"
|
||||
fi
|
||||
download_file_with_fallback "$download_url" "$download_fallback_url" "$archive_path"
|
||||
verify_archive_digest "$archive_path" "$expected_digest"
|
||||
download_file_with_fallback "$download_url" "$download_fallback_url" "$archive_path" "$expected_digest" "$asset"
|
||||
|
||||
step "Installing standalone package to $release_dir"
|
||||
if [ "$install_layout" = "package" ]; then
|
||||
|
||||
@@ -34,8 +34,8 @@ class InstallShTest(unittest.TestCase):
|
||||
)
|
||||
self.assertNotIn("Could not find Codex package", result.stderr)
|
||||
|
||||
def test_exact_release_fetches_metadata_once(self) -> None:
|
||||
result, requests = run_installer(VERSION)
|
||||
def test_exact_release_opt_out_uses_github_metadata_once(self) -> None:
|
||||
result, requests = run_installer(VERSION, use_mirror=False)
|
||||
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertEqual(
|
||||
@@ -131,7 +131,7 @@ class InstallShTest(unittest.TestCase):
|
||||
)
|
||||
self.assertTrue(os.access(host_path, os.X_OK))
|
||||
|
||||
def test_releases_latest_installs_verified_package(self) -> None:
|
||||
def test_releases_latest_installs_verified_package_by_default(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
root = Path(temp_dir)
|
||||
archive_path, checksum_path, metadata_json = create_package_release(root)
|
||||
@@ -143,7 +143,7 @@ class InstallShTest(unittest.TestCase):
|
||||
archive_path=archive_path,
|
||||
checksum_path=checksum_path,
|
||||
force_macos=True,
|
||||
use_releases=True,
|
||||
use_mirror=None,
|
||||
)
|
||||
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
@@ -156,6 +156,101 @@ class InstallShTest(unittest.TestCase):
|
||||
],
|
||||
)
|
||||
|
||||
def test_releases_unusable_metadata_falls_back_to_github(self) -> None:
|
||||
unusable_metadata = {
|
||||
"html": "<html>proxy error</html>",
|
||||
"empty": "",
|
||||
"malformed_json": '{"tag_name":',
|
||||
"missing_tag": json.dumps({"assets": []}),
|
||||
"missing_assets": json.dumps(
|
||||
{"tag_name": f"rust-v{VERSION}", "assets": []}
|
||||
),
|
||||
"invalid_checksum_digest": json.dumps(
|
||||
{
|
||||
"tag_name": f"rust-v{VERSION}",
|
||||
"assets": [
|
||||
{
|
||||
"name": "codex-package-aarch64-apple-darwin.tar.gz",
|
||||
"digest": "sha256:" + "a" * 64,
|
||||
},
|
||||
{
|
||||
"name": "codex-package_SHA256SUMS",
|
||||
"digest": "sha256:" + "z" * 64,
|
||||
},
|
||||
],
|
||||
}
|
||||
),
|
||||
"invalid_version": json.dumps({"tag_name": "rust-vinvalid"}),
|
||||
}
|
||||
|
||||
for name, releases_metadata_json in unusable_metadata.items():
|
||||
with self.subTest(metadata=name):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
root = Path(temp_dir)
|
||||
archive_path, checksum_path, metadata_json = create_package_release(
|
||||
root
|
||||
)
|
||||
|
||||
result, requests = run_installer_in(
|
||||
root,
|
||||
"latest",
|
||||
metadata_json=metadata_json,
|
||||
releases_metadata_json=releases_metadata_json,
|
||||
archive_path=archive_path,
|
||||
checksum_path=checksum_path,
|
||||
force_macos=True,
|
||||
use_mirror=None,
|
||||
)
|
||||
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertEqual(
|
||||
requests,
|
||||
[
|
||||
"https://releases.openai.com/codex/channels/latest",
|
||||
"https://api.github.com/repos/openai/codex/releases/latest",
|
||||
"https://github.com/openai/codex/releases/download/"
|
||||
f"rust-v{VERSION}/codex-package_SHA256SUMS",
|
||||
"https://github.com/openai/codex/releases/download/"
|
||||
f"rust-v{VERSION}/codex-package-aarch64-apple-darwin.tar.gz",
|
||||
],
|
||||
)
|
||||
self.assertIn("falling back to GitHub Releases", result.stderr)
|
||||
|
||||
def test_releases_exact_metadata_version_mismatch_falls_back_to_github(
|
||||
self,
|
||||
) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
root = Path(temp_dir)
|
||||
archive_path, checksum_path, metadata_json = create_package_release(root)
|
||||
releases_metadata = json.loads(metadata_json)
|
||||
releases_metadata["tag_name"] = f"rust-v{MISMATCH_VERSION}"
|
||||
|
||||
result, requests = run_installer_in(
|
||||
root,
|
||||
VERSION,
|
||||
metadata_json=metadata_json,
|
||||
releases_metadata_json=json.dumps(releases_metadata),
|
||||
archive_path=archive_path,
|
||||
checksum_path=checksum_path,
|
||||
force_macos=True,
|
||||
use_mirror=None,
|
||||
)
|
||||
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertEqual(
|
||||
requests,
|
||||
[
|
||||
f"https://releases.openai.com/codex/releases/{VERSION}/release.json",
|
||||
"https://api.github.com/repos/openai/codex/releases/tags/"
|
||||
f"rust-v{VERSION}",
|
||||
"https://github.com/openai/codex/releases/download/"
|
||||
f"rust-v{VERSION}/codex-package_SHA256SUMS",
|
||||
"https://github.com/openai/codex/releases/download/"
|
||||
f"rust-v{VERSION}/codex-package-aarch64-apple-darwin.tar.gz",
|
||||
],
|
||||
)
|
||||
self.assertIn("falling back to GitHub Releases", result.stderr)
|
||||
|
||||
def test_releases_asset_download_falls_back_to_github(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
root = Path(temp_dir)
|
||||
@@ -168,7 +263,7 @@ class InstallShTest(unittest.TestCase):
|
||||
archive_path=archive_path,
|
||||
checksum_path=checksum_path,
|
||||
force_macos=True,
|
||||
use_releases=True,
|
||||
use_mirror=None,
|
||||
releases_mode="asset_fallback",
|
||||
)
|
||||
|
||||
@@ -187,6 +282,147 @@ class InstallShTest(unittest.TestCase):
|
||||
)
|
||||
self.assertIn("retrying from GitHub Releases", result.stderr)
|
||||
|
||||
def test_releases_corrupt_assets_fall_back_to_github(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
root = Path(temp_dir)
|
||||
archive_path, checksum_path, metadata_json = create_package_release(root)
|
||||
|
||||
result, requests = run_installer_in(
|
||||
root,
|
||||
"latest",
|
||||
metadata_json=metadata_json,
|
||||
archive_path=archive_path,
|
||||
checksum_path=checksum_path,
|
||||
force_macos=True,
|
||||
use_mirror=None,
|
||||
releases_mode="corrupt_assets",
|
||||
)
|
||||
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertEqual(
|
||||
requests,
|
||||
[
|
||||
"https://releases.openai.com/codex/channels/latest",
|
||||
f"https://releases.openai.com/codex/releases/{VERSION}/codex-package_SHA256SUMS",
|
||||
"https://github.com/openai/codex/releases/download/"
|
||||
f"rust-v{VERSION}/codex-package_SHA256SUMS",
|
||||
f"https://releases.openai.com/codex/releases/{VERSION}/codex-package-aarch64-apple-darwin.tar.gz",
|
||||
"https://github.com/openai/codex/releases/download/"
|
||||
f"rust-v{VERSION}/codex-package-aarch64-apple-darwin.tar.gz",
|
||||
],
|
||||
)
|
||||
self.assertIn("checksum did not match expected digest", result.stderr)
|
||||
self.assertIn("retrying from GitHub Releases", result.stderr)
|
||||
|
||||
def test_releases_wrong_checksum_digest_uses_github_metadata(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
root = Path(temp_dir)
|
||||
archive_path, checksum_path, metadata_json = create_package_release(root)
|
||||
mirror_metadata = json.loads(metadata_json)
|
||||
for release_asset in mirror_metadata["assets"]:
|
||||
if release_asset["name"] == "codex-package_SHA256SUMS":
|
||||
release_asset["digest"] = "sha256:" + "0" * 64
|
||||
|
||||
result, requests = run_installer_in(
|
||||
root,
|
||||
"latest",
|
||||
metadata_json=metadata_json,
|
||||
releases_metadata_json=json.dumps(mirror_metadata),
|
||||
archive_path=archive_path,
|
||||
checksum_path=checksum_path,
|
||||
force_macos=True,
|
||||
use_mirror=None,
|
||||
)
|
||||
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertEqual(
|
||||
requests,
|
||||
[
|
||||
"https://releases.openai.com/codex/channels/latest",
|
||||
f"https://releases.openai.com/codex/releases/{VERSION}/codex-package_SHA256SUMS",
|
||||
"https://github.com/openai/codex/releases/download/"
|
||||
f"rust-v{VERSION}/codex-package_SHA256SUMS",
|
||||
"https://api.github.com/repos/openai/codex/releases/tags/"
|
||||
f"rust-v{VERSION}",
|
||||
f"https://releases.openai.com/codex/releases/{VERSION}/codex-package-aarch64-apple-darwin.tar.gz",
|
||||
],
|
||||
)
|
||||
self.assertIn("checksum did not match expected digest", result.stderr)
|
||||
|
||||
def test_releases_incomplete_checksum_manifest_falls_back_to_github(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
root = Path(temp_dir)
|
||||
archive_path, checksum_path, metadata_json = create_package_release(root)
|
||||
mirror_checksum_path = root / "mirror-SHA256SUMS"
|
||||
mirror_checksum_path.write_text(
|
||||
f"{'a' * 64} codex-package-other-platform.tar.gz\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
mirror_metadata = json.loads(metadata_json)
|
||||
for release_asset in mirror_metadata["assets"]:
|
||||
if release_asset["name"] == "codex-package_SHA256SUMS":
|
||||
release_asset["digest"] = (
|
||||
"sha256:"
|
||||
+ hashlib.sha256(mirror_checksum_path.read_bytes()).hexdigest()
|
||||
)
|
||||
|
||||
result, requests = run_installer_in(
|
||||
root,
|
||||
"latest",
|
||||
metadata_json=metadata_json,
|
||||
releases_metadata_json=json.dumps(mirror_metadata),
|
||||
archive_path=archive_path,
|
||||
checksum_path=checksum_path,
|
||||
releases_checksum_path=mirror_checksum_path,
|
||||
force_macos=True,
|
||||
use_mirror=None,
|
||||
)
|
||||
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertEqual(
|
||||
requests,
|
||||
[
|
||||
"https://releases.openai.com/codex/channels/latest",
|
||||
f"https://releases.openai.com/codex/releases/{VERSION}/codex-package_SHA256SUMS",
|
||||
"https://github.com/openai/codex/releases/download/"
|
||||
f"rust-v{VERSION}/codex-package_SHA256SUMS",
|
||||
"https://api.github.com/repos/openai/codex/releases/tags/"
|
||||
f"rust-v{VERSION}",
|
||||
f"https://releases.openai.com/codex/releases/{VERSION}/codex-package-aarch64-apple-darwin.tar.gz",
|
||||
],
|
||||
)
|
||||
self.assertIn("retrying from GitHub Releases", result.stderr)
|
||||
|
||||
def test_releases_corrupt_github_fallback_still_fails(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
root = Path(temp_dir)
|
||||
archive_path, checksum_path, metadata_json = create_package_release(root)
|
||||
|
||||
result, requests = run_installer_in(
|
||||
root,
|
||||
"latest",
|
||||
metadata_json=metadata_json,
|
||||
archive_path=archive_path,
|
||||
checksum_path=checksum_path,
|
||||
force_macos=True,
|
||||
use_mirror=None,
|
||||
releases_mode="corrupt_checksum_and_github",
|
||||
)
|
||||
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertEqual(
|
||||
requests,
|
||||
[
|
||||
"https://releases.openai.com/codex/channels/latest",
|
||||
f"https://releases.openai.com/codex/releases/{VERSION}/codex-package_SHA256SUMS",
|
||||
"https://github.com/openai/codex/releases/download/"
|
||||
f"rust-v{VERSION}/codex-package_SHA256SUMS",
|
||||
"https://api.github.com/repos/openai/codex/releases/tags/"
|
||||
f"rust-v{VERSION}",
|
||||
],
|
||||
)
|
||||
self.assertIn("checksum did not match expected digest", result.stderr)
|
||||
|
||||
def test_releases_exact_rejects_wrong_binary_version(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
root = Path(temp_dir)
|
||||
@@ -202,7 +438,7 @@ class InstallShTest(unittest.TestCase):
|
||||
archive_path=archive_path,
|
||||
checksum_path=checksum_path,
|
||||
force_macos=True,
|
||||
use_releases=True,
|
||||
use_mirror=True,
|
||||
)
|
||||
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
@@ -231,7 +467,7 @@ class InstallShTest(unittest.TestCase):
|
||||
metadata_json=metadata_json,
|
||||
legacy_archive_path=archive_path,
|
||||
force_macos=True,
|
||||
use_releases=True,
|
||||
use_mirror=True,
|
||||
releases_mode="channel_failure",
|
||||
)
|
||||
|
||||
@@ -253,7 +489,7 @@ class InstallShTest(unittest.TestCase):
|
||||
VERSION,
|
||||
metadata_json=metadata_json,
|
||||
force_macos=True,
|
||||
use_releases=True,
|
||||
use_mirror=True,
|
||||
releases_mode="channel_failure",
|
||||
)
|
||||
|
||||
@@ -274,6 +510,7 @@ def run_installer(
|
||||
*,
|
||||
metadata_failure: bool = False,
|
||||
metadata_json: str | None = None,
|
||||
use_mirror: bool | None = False,
|
||||
) -> tuple[subprocess.CompletedProcess[str], list[str]]:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
return run_installer_in(
|
||||
@@ -281,6 +518,7 @@ def run_installer(
|
||||
release,
|
||||
metadata_failure=metadata_failure,
|
||||
metadata_json=metadata_json,
|
||||
use_mirror=use_mirror,
|
||||
)
|
||||
|
||||
|
||||
@@ -290,11 +528,13 @@ def run_installer_in(
|
||||
*,
|
||||
metadata_failure: bool = False,
|
||||
metadata_json: str | None = None,
|
||||
releases_metadata_json: str | None = None,
|
||||
archive_path: Path | None = None,
|
||||
checksum_path: Path | None = None,
|
||||
releases_checksum_path: Path | None = None,
|
||||
legacy_archive_path: Path | None = None,
|
||||
force_macos: bool = False,
|
||||
use_releases: bool = False,
|
||||
use_mirror: bool | None = False,
|
||||
releases_mode: str = "",
|
||||
) -> tuple[subprocess.CompletedProcess[str], list[str]]:
|
||||
bin_dir = root / "bin"
|
||||
@@ -337,8 +577,13 @@ def run_installer_in(
|
||||
if [ "$CODEX_TEST_RELEASES_MODE" = "asset_fallback" ]; then
|
||||
exit 22
|
||||
fi
|
||||
if [ -n "$CODEX_TEST_CHECKSUM_PATH" ]; then
|
||||
cp "$CODEX_TEST_CHECKSUM_PATH" "$output"
|
||||
if [ "$CODEX_TEST_RELEASES_MODE" = "corrupt_assets" ] ||
|
||||
[ "$CODEX_TEST_RELEASES_MODE" = "corrupt_checksum_and_github" ]; then
|
||||
printf '<html>proxy error</html>\n' >"$output"
|
||||
exit 0
|
||||
fi
|
||||
if [ -n "$CODEX_TEST_RELEASES_CHECKSUM_PATH" ]; then
|
||||
cp "$CODEX_TEST_RELEASES_CHECKSUM_PATH" "$output"
|
||||
else
|
||||
exit 22
|
||||
fi
|
||||
@@ -347,6 +592,10 @@ def run_installer_in(
|
||||
if [ "$CODEX_TEST_RELEASES_MODE" = "asset_fallback" ]; then
|
||||
exit 22
|
||||
fi
|
||||
if [ "$CODEX_TEST_RELEASES_MODE" = "corrupt_assets" ]; then
|
||||
printf '<html>proxy error</html>\n' >"$output"
|
||||
exit 0
|
||||
fi
|
||||
if [ -n "$CODEX_TEST_ARCHIVE_PATH" ]; then
|
||||
cp "$CODEX_TEST_ARCHIVE_PATH" "$output"
|
||||
else
|
||||
@@ -354,6 +603,10 @@ def run_installer_in(
|
||||
fi
|
||||
;;
|
||||
https://github.com/openai/codex/releases/download/*/codex-package_SHA256SUMS)
|
||||
if [ "$CODEX_TEST_RELEASES_MODE" = "corrupt_checksum_and_github" ]; then
|
||||
printf '<html>proxy error</html>\n' >"$output"
|
||||
exit 0
|
||||
fi
|
||||
if [ -n "$CODEX_TEST_CHECKSUM_PATH" ]; then
|
||||
cp "$CODEX_TEST_CHECKSUM_PATH" "$output"
|
||||
else
|
||||
@@ -404,18 +657,22 @@ def run_installer_in(
|
||||
"CODEX_INSTALL_DIR": str(root / "install-bin"),
|
||||
"CODEX_NON_INTERACTIVE": "1",
|
||||
"CODEX_RELEASE": release,
|
||||
"CODEX_INSTALLER_USE_RELEASES_OPENAI_COM": (
|
||||
"TRUE" if use_releases else "0"
|
||||
),
|
||||
"CODEX_TEST_ARCHIVE_PATH": str(archive_path or ""),
|
||||
"CODEX_TEST_CHECKSUM_PATH": str(checksum_path or ""),
|
||||
"CODEX_TEST_RELEASES_CHECKSUM_PATH": str(
|
||||
releases_checksum_path or checksum_path or ""
|
||||
),
|
||||
"CODEX_TEST_LEGACY_ARCHIVE_PATH": str(legacy_archive_path or ""),
|
||||
"CODEX_TEST_METADATA_FAILURE": "1" if metadata_failure else "0",
|
||||
"CODEX_TEST_METADATA_JSON": (
|
||||
metadata_json if metadata_json is not None else release_metadata()
|
||||
),
|
||||
"CODEX_TEST_RELEASES_METADATA_JSON": (
|
||||
metadata_json if metadata_json is not None else release_metadata()
|
||||
releases_metadata_json
|
||||
if releases_metadata_json is not None
|
||||
else metadata_json
|
||||
if metadata_json is not None
|
||||
else release_metadata()
|
||||
),
|
||||
"CODEX_TEST_RELEASES_MODE": releases_mode,
|
||||
"CODEX_TEST_REQUEST_LOG": str(request_log),
|
||||
@@ -424,6 +681,12 @@ def run_installer_in(
|
||||
"SHELL": "/bin/sh",
|
||||
}
|
||||
)
|
||||
if use_mirror is None:
|
||||
env.pop("CODEX_INSTALLER_USE_RELEASES_OPENAI_COM", None)
|
||||
else:
|
||||
env["CODEX_INSTALLER_USE_RELEASES_OPENAI_COM"] = (
|
||||
"TRUE" if use_mirror else "false"
|
||||
)
|
||||
result = subprocess.run(
|
||||
["/bin/sh", str(INSTALL_SCRIPT)],
|
||||
capture_output=True,
|
||||
|
||||
Reference in New Issue
Block a user