Prefer releases.openai.com for Codex installs

This commit is contained in:
Zsolt Dollenstein
2026-07-15 17:27:51 +02:00
parent f1affbac5e
commit 01be10e61f
3 changed files with 663 additions and 80 deletions
+218 -37
View File
@@ -12,6 +12,13 @@ if ([string]::IsNullOrWhiteSpace($Release)) {
}
$NonInteractive = $env:CODEX_NON_INTERACTIVE -match "^(?i:1|true|yes)$"
$DefaultPreferReleasesOpenAICom = $false
$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"
function Write-Step {
param(
@@ -79,7 +86,8 @@ function Assert-ValidReleaseVersion {
function Find-ReleaseAssetMetadata {
param(
[string]$AssetName,
[object]$ReleaseMetadata
[object]$ReleaseMetadata,
[string]$FallbackUrl = $null
)
$asset = $ReleaseMetadata.assets | Where-Object { $_.name -eq $AssetName } | Select-Object -First 1
@@ -94,10 +102,112 @@ function Find-ReleaseAssetMetadata {
return [PSCustomObject]@{
Url = $asset.browser_download_url
FallbackUrl = $FallbackUrl
Sha256 = $digestMatch.Groups[1].Value.ToLowerInvariant()
}
}
function New-ReleasesAssetMetadata {
param(
[string]$AssetName,
[string]$Version
)
return [PSCustomObject]@{
Url = "$ReleasesBaseUri/releases/$Version/$AssetName"
FallbackUrl = "https://github.com/openai/codex/releases/download/rust-v$Version/$AssetName"
Sha256 = $null
}
}
function Invoke-WebRequestWithFallback {
param(
[object]$Metadata,
[string]$OutFile,
[switch]$AllowFailure
)
try {
Invoke-WebRequest -UseBasicParsing -Uri $Metadata.Url -OutFile $OutFile
} catch {
if ([string]::IsNullOrWhiteSpace($Metadata.FallbackUrl)) {
if ($AllowFailure) {
return $false
}
throw
}
Write-WarningStep "Could not download $($Metadata.Url); retrying from GitHub Releases."
try {
Invoke-WebRequest -UseBasicParsing -Uri $Metadata.FallbackUrl -OutFile $OutFile
} catch {
if ($AllowFailure) {
return $false
}
throw
}
}
return $true
}
function Resolve-ReleaseAssetSelection {
param(
[object]$ResolvedRelease,
[string]$Target,
[string]$NpmTag
)
$version = $ResolvedRelease.Version
$releaseMetadata = $ResolvedRelease.Metadata
$packageAsset = "codex-package-$Target.tar.gz"
$checksumAsset = "codex-package_SHA256SUMS"
if ($ResolvedRelease.Source -eq "ReleasesOpenAICom" -and $null -eq $releaseMetadata) {
return [PSCustomObject]@{
PackageAsset = $packageAsset
PackageMetadata = New-ReleasesAssetMetadata -AssetName $packageAsset -Version $version
ChecksumMetadata = New-ReleasesAssetMetadata -AssetName $checksumAsset -Version $version
InstallLayout = "Package"
}
}
$packageFallbackUrl = $null
$checksumFallbackUrl = $null
if ($ResolvedRelease.Source -eq "ReleasesOpenAICom") {
$packageFallbackUrl = "https://github.com/openai/codex/releases/download/rust-v$version/$packageAsset"
$checksumFallbackUrl = "https://github.com/openai/codex/releases/download/rust-v$version/$checksumAsset"
}
$packageMetadata = Find-ReleaseAssetMetadata -AssetName $packageAsset -ReleaseMetadata $releaseMetadata -FallbackUrl $packageFallbackUrl
$checksumMetadata = Find-ReleaseAssetMetadata -AssetName $checksumAsset -ReleaseMetadata $releaseMetadata -FallbackUrl $checksumFallbackUrl
if ($null -ne $packageMetadata -and $null -ne $checksumMetadata) {
return [PSCustomObject]@{
PackageAsset = $packageAsset
PackageMetadata = $packageMetadata
ChecksumMetadata = $checksumMetadata
InstallLayout = "Package"
}
}
$packageAsset = "codex-npm-$NpmTag-$version.tgz"
$packageFallbackUrl = if ($ResolvedRelease.Source -eq "ReleasesOpenAICom") {
"https://github.com/openai/codex/releases/download/rust-v$version/$packageAsset"
} else {
$null
}
$packageMetadata = Find-ReleaseAssetMetadata -AssetName $packageAsset -ReleaseMetadata $releaseMetadata -FallbackUrl $packageFallbackUrl
if ($null -eq $packageMetadata) {
throw "Could not find Codex package or platform npm release assets for Codex $version."
}
return [PSCustomObject]@{
PackageAsset = $packageAsset
PackageMetadata = $packageMetadata
ChecksumMetadata = $null
InstallLayout = "LegacyPlatformNpm"
}
}
function Test-ArchiveDigest {
param(
[string]$ArchivePath,
@@ -201,15 +311,30 @@ function Remove-StaleInstallArtifacts {
}
}
function Resolve-Release {
$normalizedVersion = Normalize-Version -RawVersion $Release
Assert-ValidReleaseVersion -Version $normalizedVersion
function Resolve-VersionFromReleaseMetadata {
param(
[object]$ReleaseMetadata
)
if ($normalizedVersion -eq "latest") {
if (-not $ReleaseMetadata.tag_name) {
throw "Failed to resolve the latest Codex release version."
}
$resolvedVersion = Normalize-Version -RawVersion $ReleaseMetadata.tag_name
Assert-ValidReleaseVersion -Version $resolvedVersion
return $resolvedVersion
}
function Resolve-ReleaseFromGitHub {
param(
[string]$NormalizedVersion
)
if ($NormalizedVersion -eq "latest") {
$requestedRelease = "latest"
$metadataUri = "https://api.github.com/repos/openai/codex/releases/latest"
} else {
$resolvedVersion = $normalizedVersion
$resolvedVersion = $NormalizedVersion
$requestedRelease = $resolvedVersion
$metadataUri = "https://api.github.com/repos/openai/codex/releases/tags/rust-v$resolvedVersion"
}
@@ -220,21 +345,61 @@ function Resolve-Release {
throw "Could not fetch GitHub release metadata for Codex $requestedRelease. GitHub API may be unavailable or rate limited. $($_.Exception.Message)"
}
if ($normalizedVersion -eq "latest") {
if (-not $releaseMetadata.tag_name) {
throw "Failed to resolve the latest Codex release version."
}
$resolvedVersion = Normalize-Version -RawVersion $releaseMetadata.tag_name
Assert-ValidReleaseVersion -Version $resolvedVersion
if ($NormalizedVersion -eq "latest") {
$resolvedVersion = Resolve-VersionFromReleaseMetadata -ReleaseMetadata $releaseMetadata
}
return [PSCustomObject]@{
Version = $resolvedVersion
Metadata = $releaseMetadata
Source = "GitHub"
}
}
function Resolve-ReleaseFromReleases {
param(
[string]$NormalizedVersion
)
if ($NormalizedVersion -eq "latest") {
try {
$channelResponse = Invoke-WebRequest -UseBasicParsing -Uri "$ReleasesBaseUri/channels/latest"
} catch {
return $null
}
try {
$releaseMetadata = [string]$channelResponse.Content | ConvertFrom-Json -ErrorAction Stop
} catch {
throw "Invalid release metadata from releases.openai.com."
}
$resolvedVersion = Resolve-VersionFromReleaseMetadata -ReleaseMetadata $releaseMetadata
} else {
$resolvedVersion = $NormalizedVersion
$releaseMetadata = $null
}
return [PSCustomObject]@{
Version = $resolvedVersion
Metadata = $releaseMetadata
Source = "ReleasesOpenAICom"
}
}
function Resolve-Release {
$normalizedVersion = Normalize-Version -RawVersion $Release
Assert-ValidReleaseVersion -Version $normalizedVersion
if ($PreferReleasesOpenAICom) {
$release = Resolve-ReleaseFromReleases -NormalizedVersion $normalizedVersion
if ($null -ne $release) {
return $release
}
Write-WarningStep "releases.openai.com is unavailable; falling back to GitHub Releases."
}
return Resolve-ReleaseFromGitHub -NormalizedVersion $normalizedVersion
}
function Get-VersionFromBinary {
param(
[string]$CodexPath
@@ -767,21 +932,12 @@ Write-Step "Resolved version: $resolvedVersion"
$conflictingInstall = Get-ConflictingInstall -VisibleBinDir $visibleBinDir
$oldStandaloneBackup = $null
$packageAsset = "codex-package-$target.tar.gz"
$checksumAsset = "codex-package_SHA256SUMS"
$packageMetadata = Find-ReleaseAssetMetadata -AssetName $packageAsset -ReleaseMetadata $releaseMetadata
$checksumMetadata = Find-ReleaseAssetMetadata -AssetName $checksumAsset -ReleaseMetadata $releaseMetadata
$installLayout = "Package"
if ($null -eq $packageMetadata -or $null -eq $checksumMetadata) {
$packageAsset = "codex-npm-$npmTag-$resolvedVersion.tgz"
$packageMetadata = Find-ReleaseAssetMetadata -AssetName $packageAsset -ReleaseMetadata $releaseMetadata
if ($null -ne $packageMetadata) {
$installLayout = "LegacyPlatformNpm"
} else {
throw "Could not find Codex package or platform npm release assets for Codex $resolvedVersion."
}
$checksumMetadata = $null
}
$assetSelection = Resolve-ReleaseAssetSelection -ResolvedRelease $resolvedRelease -Target $target -NpmTag $npmTag
$packageAsset = $assetSelection.PackageAsset
$packageMetadata = $assetSelection.PackageMetadata
$checksumMetadata = $assetSelection.ChecksumMetadata
$installLayout = $assetSelection.InstallLayout
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("codex-install-" + [System.Guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
@@ -794,20 +950,45 @@ try {
Write-WarningStep "Found incomplete existing release at $releaseDir. Reinstalling."
}
$archivePath = Join-Path $tempDir $packageAsset
$checksumPath = Join-Path $tempDir $checksumAsset
$stagingDir = Join-Path $releasesDir ".staging.$releaseName.$PID"
Write-Step "Downloading Codex CLI"
if ($installLayout -eq "Package") {
Invoke-WebRequest -Uri $checksumMetadata.Url -OutFile $checksumPath
Test-ArchiveDigest -ArchivePath $checksumPath -ExpectedDigest $checksumMetadata.Sha256
$expectedPackageDigest = Get-PackageArchiveDigest -ManifestPath $checksumPath -AssetName $packageAsset
} else {
$expectedPackageDigest = $packageMetadata.Sha256
$canDiscoverLegacyAsset = $resolvedRelease.Source -eq "ReleasesOpenAICom" -and $null -eq $releaseMetadata
while ($true) {
$archivePath = Join-Path $tempDir $packageAsset
$checksumPath = Join-Path $tempDir $checksumAsset
if ($installLayout -eq "Package") {
$downloaded = Invoke-WebRequestWithFallback -Metadata $checksumMetadata -OutFile $checksumPath -AllowFailure:$canDiscoverLegacyAsset
if ($downloaded -and $null -ne $checksumMetadata.Sha256) {
Test-ArchiveDigest -ArchivePath $checksumPath -ExpectedDigest $checksumMetadata.Sha256
}
if ($downloaded) {
$expectedPackageDigest = Get-PackageArchiveDigest -ManifestPath $checksumPath -AssetName $packageAsset
}
} else {
$downloaded = $true
$expectedPackageDigest = $packageMetadata.Sha256
}
if ($downloaded) {
$downloaded = Invoke-WebRequestWithFallback -Metadata $packageMetadata -OutFile $archivePath -AllowFailure:$canDiscoverLegacyAsset
}
if ($downloaded) {
Test-ArchiveDigest -ArchivePath $archivePath -ExpectedDigest $expectedPackageDigest
break
}
Write-WarningStep "The package layout is unavailable for Codex $resolvedVersion; checking GitHub release metadata for a legacy asset."
$resolvedRelease = Resolve-ReleaseFromGitHub -NormalizedVersion $resolvedVersion
$releaseMetadata = $resolvedRelease.Metadata
$assetSelection = Resolve-ReleaseAssetSelection -ResolvedRelease $resolvedRelease -Target $target -NpmTag $npmTag
$packageAsset = $assetSelection.PackageAsset
$packageMetadata = $assetSelection.PackageMetadata
$checksumMetadata = $assetSelection.ChecksumMetadata
$installLayout = $assetSelection.InstallLayout
$canDiscoverLegacyAsset = $false
}
Invoke-WebRequest -Uri $packageMetadata.Url -OutFile $archivePath
Test-ArchiveDigest -ArchivePath $archivePath -ExpectedDigest $expectedPackageDigest
New-Item -ItemType Directory -Force -Path $releasesDir | Out-Null
if (Test-Path -LiteralPath $stagingDir) {
+146 -36
View File
@@ -4,6 +4,11 @@ set -eu
RELEASE="${CODEX_RELEASE:-latest}"
NON_INTERACTIVE="${CODEX_NON_INTERACTIVE:-false}"
DEFAULT_PREFER_RELEASES_OPENAI_COM="false"
PREFER_RELEASES_OPENAI_COM="${CODEX_INSTALLER_USE_RELEASES_OPENAI_COM:-$DEFAULT_PREFER_RELEASES_OPENAI_COM}"
RELEASES_BASE_URL="https://releases.openai.com/codex"
release_json=""
release_metadata_available="false"
BIN_DIR="${CODEX_INSTALL_DIR:-$HOME/.local/bin}"
BIN_PATH="$BIN_DIR/codex"
@@ -125,6 +130,23 @@ download_text() {
exit 1
}
download_file_with_fallback() {
primary_url="$1"
fallback_url="$2"
output="$3"
if download_file "$primary_url" "$output"; then
return
fi
if [ -z "$fallback_url" ]; then
return 1
fi
warn "Could not download $primary_url; retrying from GitHub Releases."
download_file "$fallback_url" "$output"
}
release_url_for_asset() {
asset="$1"
resolved_version="$2"
@@ -132,16 +154,31 @@ release_url_for_asset() {
printf 'https://github.com/openai/codex/releases/download/rust-v%s/%s\n' "$resolved_version" "$asset"
}
releases_url_for_asset() {
asset="$1"
resolved_version="$2"
printf '%s/releases/%s/%s\n' "$RELEASES_BASE_URL" "$resolved_version" "$asset"
}
release_metadata_url() {
resolved_version="$1"
printf 'https://api.github.com/repos/openai/codex/releases/tags/rust-v%s\n' "$resolved_version"
}
resolve_release() {
normalized_version="$(normalize_version "$RELEASE")"
validate_version "$normalized_version"
resolve_version_from_release_json() {
release_json="$1"
resolved_version="$(printf '%s\n' "$release_json" | sed -n 's/.*"tag_name":[[:space:]]*"rust-v\([^"]*\)".*/\1/p' | head -n 1)"
if [ -z "$resolved_version" ]; then
echo "Failed to resolve the latest Codex release version." >&2
exit 1
fi
validate_version "$resolved_version"
}
resolve_release_from_github() {
normalized_version="$1"
if [ "$normalized_version" = "latest" ]; then
requested_release="latest"
metadata_url="https://api.github.com/repos/openai/codex/releases/latest"
@@ -155,17 +192,50 @@ resolve_release() {
echo "Could not fetch GitHub release metadata for Codex $requested_release. GitHub API may be unavailable or rate limited." >&2
exit 1
fi
release_metadata_available="true"
release_source="github"
if [ "$normalized_version" = "latest" ]; then
resolved_version="$(printf '%s\n' "$release_json" | sed -n 's/.*"tag_name":[[:space:]]*"rust-v\([^"]*\)".*/\1/p' | head -n 1)"
if [ -z "$resolved_version" ]; then
echo "Failed to resolve the latest Codex release version." >&2
exit 1
fi
validate_version "$resolved_version"
resolve_version_from_release_json "$release_json"
fi
}
resolve_release_from_releases() {
normalized_version="$1"
if [ "$normalized_version" = "latest" ]; then
if ! release_json="$(download_text "$RELEASES_BASE_URL/channels/latest")"; then
return 1
fi
release_metadata_available="true"
resolve_version_from_release_json "$release_json"
else
resolved_version="$normalized_version"
release_json=""
release_metadata_available="false"
fi
requested_release="$resolved_version"
release_source="releases.openai.com"
}
resolve_release() {
normalized_version="$(normalize_version "$RELEASE")"
validate_version "$normalized_version"
release_source="github"
case "$PREFER_RELEASES_OPENAI_COM" in
1 | [Tt][Rr][Uu][Ee] | [Yy][Ee][Ss])
if resolve_release_from_releases "$normalized_version"; then
return
fi
warn "releases.openai.com is unavailable; falling back to GitHub Releases."
;;
esac
resolve_release_from_github "$normalized_version"
}
release_asset_digest_or_empty() {
asset="$1"
@@ -232,6 +302,43 @@ release_asset_digest() {
printf '%s\n' "$digest"
}
select_release_assets() {
package_asset="codex-package-$vendor_target.tar.gz"
checksum_asset="codex-package_SHA256SUMS"
download_fallback_url=""
checksum_fallback_url=""
if [ "$release_source" = "releases.openai.com" ] &&
[ "$release_metadata_available" = "false" ]; then
install_layout="package"
asset="$package_asset"
elif release_asset_exists "$package_asset" &&
release_asset_exists "$checksum_asset"; then
install_layout="package"
asset="$package_asset"
elif release_asset_exists "codex-npm-$npm_tag-$resolved_version.tgz"; then
install_layout="legacy-platform-npm"
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
fi
if [ "$release_source" = "releases.openai.com" ]; then
download_url="$(releases_url_for_asset "$asset" "$resolved_version")"
download_fallback_url="$(release_url_for_asset "$asset" "$resolved_version")"
if [ "$install_layout" = "package" ]; then
checksum_url="$(releases_url_for_asset "$checksum_asset" "$resolved_version")"
checksum_fallback_url="$(release_url_for_asset "$checksum_asset" "$resolved_version")"
fi
else
download_url="$(release_url_for_asset "$asset" "$resolved_version")"
if [ "$install_layout" = "package" ]; then
checksum_url="$(release_url_for_asset "$checksum_asset" "$resolved_version")"
fi
fi
}
package_archive_digest() {
asset="$1"
manifest_path="$2"
@@ -292,6 +399,25 @@ verify_archive_digest() {
fi
}
download_selected_release() {
archive_path="$tmp_dir/$asset"
checksum_path="$tmp_dir/$checksum_asset"
if [ "$install_layout" = "package" ]; then
download_file_with_fallback "$checksum_url" "$checksum_fallback_url" "$checksum_path" || return 1
if [ "$release_metadata_available" = "true" ]; then
checksum_digest="$(release_asset_digest "$checksum_asset")"
verify_archive_digest "$checksum_path" "$checksum_digest"
fi
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" || return 1
verify_archive_digest "$archive_path" "$expected_digest"
}
require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "$1 is required to install Codex." >&2
@@ -847,21 +973,7 @@ else
fi
resolve_release
package_asset="codex-package-$vendor_target.tar.gz"
checksum_asset="codex-package_SHA256SUMS"
if release_asset_exists "$package_asset" &&
release_asset_exists "$checksum_asset"; then
install_layout="package"
asset="$package_asset"
elif release_asset_exists "codex-npm-$npm_tag-$resolved_version.tgz"; then
install_layout="legacy-platform-npm"
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
fi
download_url="$(release_url_for_asset "$asset" "$resolved_version")"
checksum_url="$(release_url_for_asset "$checksum_asset" "$resolved_version")"
select_release_assets
release_name="$resolved_version-$vendor_target"
release_dir="$RELEASES_DIR/$release_name"
current_version="$(current_installed_version)"
@@ -895,20 +1007,18 @@ if ! release_dir_is_complete "$release_dir" "$resolved_version" "$vendor_target"
warn "Found incomplete existing release at $release_dir; reinstalling."
fi
archive_path="$tmp_dir/$asset"
checksum_path="$tmp_dir/$checksum_asset"
step "Downloading Codex CLI"
if [ "$install_layout" = "package" ]; then
checksum_digest="$(release_asset_digest "$checksum_asset")"
download_file "$checksum_url" "$checksum_path"
verify_archive_digest "$checksum_path" "$checksum_digest"
expected_digest="$(package_archive_digest "$asset" "$checksum_path")"
else
expected_digest="$(release_asset_digest "$asset")"
if ! download_selected_release; then
if [ "$release_source" != "releases.openai.com" ] ||
[ "$release_metadata_available" != "false" ]; then
exit 1
fi
warn "The package layout is unavailable for Codex $resolved_version; checking GitHub release metadata for a legacy asset."
resolve_release_from_github "$resolved_version"
select_release_assets
download_selected_release
fi
download_file "$download_url" "$archive_path"
verify_archive_digest "$archive_path" "$expected_digest"
step "Installing standalone package to $release_dir"
if [ "$install_layout" = "package" ]; then
+299 -7
View File
@@ -1,9 +1,12 @@
#!/usr/bin/env python3
import hashlib
import io
import json
import os
from pathlib import Path
import subprocess
import tarfile
import tempfile
import textwrap
import unittest
@@ -11,6 +14,7 @@ import unittest
INSTALL_SCRIPT = Path(__file__).with_name("install.sh")
VERSION = "0.142.5"
LEGACY_VERSION = "0.125.0"
class InstallShTest(unittest.TestCase):
@@ -60,14 +64,119 @@ class InstallShTest(unittest.TestCase):
)
self.assertIn(f"Resolved version: {VERSION}", result.stdout)
def test_releases_channel_unavailable_falls_back_to_github(self) -> None:
result, requests = run_installer(
"latest", use_releases=True, releases_mode="channel_failure"
)
self.assertNotEqual(result.returncode, 0)
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",
],
)
self.assertIn("falling back to GitHub Releases", result.stderr)
def test_releases_integrity_failure_does_not_fall_back(self) -> None:
result, requests = run_installer(
"latest", use_releases=True, releases_mode="corrupt_package"
)
self.assertNotEqual(result.returncode, 0)
package = f"codex-package-{current_target()}.tar.gz"
self.assertEqual(
requests,
[
"https://releases.openai.com/codex/channels/latest",
f"https://releases.openai.com/codex/releases/{VERSION}/codex-package_SHA256SUMS",
f"https://releases.openai.com/codex/releases/{VERSION}/{package}",
],
)
self.assertIn("checksum did not match", result.stderr)
def test_releases_latest_installs_verified_package(self) -> None:
result, requests = run_installer("latest", use_releases=True)
package = f"codex-package-{current_target()}.tar.gz"
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",
f"https://releases.openai.com/codex/releases/{VERSION}/{package}",
],
)
def test_releases_latest_rejects_corrupt_checksum_manifest(self) -> None:
result, requests = run_installer(
"latest", use_releases=True, releases_mode="corrupt_manifest"
)
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",
],
)
self.assertIn("checksum did not match", result.stderr)
def test_exact_releases_path_discovers_legacy_github_asset(self) -> None:
result, requests = run_installer(
LEGACY_VERSION, use_releases=True, releases_mode="legacy"
)
target = current_target()
package = f"codex-package-{target}.tar.gz"
legacy_package = f"codex-npm-{current_npm_tag()}-{LEGACY_VERSION}.tgz"
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(
requests,
[
f"https://releases.openai.com/codex/releases/{LEGACY_VERSION}/codex-package_SHA256SUMS",
f"https://releases.openai.com/codex/releases/{LEGACY_VERSION}/{package}",
"https://github.com/openai/codex/releases/download/"
f"rust-v{LEGACY_VERSION}/{package}",
"https://api.github.com/repos/openai/codex/releases/tags/"
f"rust-v{LEGACY_VERSION}",
"https://github.com/openai/codex/releases/download/"
f"rust-v{LEGACY_VERSION}/{legacy_package}",
],
)
self.assertIn("retrying from GitHub Releases", result.stderr)
self.assertIn("checking GitHub release metadata", result.stderr)
def run_installer(
release: str, *, metadata_failure: bool = False
release: str,
*,
metadata_failure: bool = False,
use_releases: bool = False,
releases_mode: str = "",
) -> tuple[subprocess.CompletedProcess[str], list[str]]:
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
bin_dir = root / "bin"
bin_dir.mkdir()
(root / "home").mkdir()
package_archive = root / "package.tar.gz"
legacy_archive = root / "legacy.tgz"
create_package_archive(package_archive, VERSION)
create_legacy_archive(legacy_archive, LEGACY_VERSION)
package_digest = sha256_file(package_archive)
legacy_digest = sha256_file(legacy_archive)
checksum_file = root / "codex-package_SHA256SUMS"
checksum_file.write_text(
checksum_manifest(package_digest),
encoding="utf-8",
)
checksum_digest = sha256_file(checksum_file)
request_log = root / "requests.log"
fake_curl = bin_dir / "curl"
fake_curl.write_text(
@@ -75,10 +184,18 @@ def run_installer(
"""\
#!/bin/sh
url=""
output=""
next_is_output="false"
for arg in "$@"; do
case "$arg" in
https://*) url="$arg" ;;
esac
if [ "$next_is_output" = "true" ]; then
output="$arg"
next_is_output="false"
elif [ "$arg" = "-o" ]; then
next_is_output="true"
fi
done
printf '%s\n' "$url" >>"$CODEX_TEST_REQUEST_LOG"
@@ -88,7 +205,43 @@ def run_installer(
echo "curl: (22) The requested URL returned error: 403" >&2
exit 22
fi
printf '%s\n' "$CODEX_TEST_METADATA_JSON"
if [ "$CODEX_TEST_RELEASES_MODE" = "legacy" ]; then
printf '%s\n' "$CODEX_TEST_LEGACY_METADATA_JSON"
else
printf '%s\n' "$CODEX_TEST_GITHUB_METADATA_JSON"
fi
;;
https://releases.openai.com/codex/channels/latest)
if [ "$CODEX_TEST_RELEASES_MODE" = "channel_failure" ]; then
exit 22
fi
printf '%s\n' "$CODEX_TEST_LATEST_METADATA_JSON"
;;
https://releases.openai.com/codex/releases/*/codex-package_SHA256SUMS)
if [ "$CODEX_TEST_RELEASES_MODE" = "corrupt_manifest" ]; then
printf 'corrupt' >"$output"
else
cp "$CODEX_TEST_CHECKSUM_FILE" "$output"
fi
;;
https://releases.openai.com/codex/releases/*/codex-package-*.tar.gz)
if [ "$CODEX_TEST_RELEASES_MODE" = "corrupt_package" ]; then
printf 'corrupt' >"$output"
exit 0
fi
if [ "$CODEX_TEST_RELEASES_MODE" = "legacy" ]; then
exit 22
fi
cp "$CODEX_TEST_PACKAGE_ARCHIVE" "$output"
;;
https://github.com/openai/codex/releases/download/*/codex-package_SHA256SUMS)
exit 22
;;
https://github.com/openai/codex/releases/download/*/codex-package-*.tar.gz)
exit 22
;;
https://github.com/openai/codex/releases/download/*/codex-npm-*.tgz)
cp "$CODEX_TEST_LEGACY_ARCHIVE" "$output"
;;
*)
exit 22
@@ -107,9 +260,31 @@ def run_installer(
"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_CHECKSUM_FILE": str(checksum_file),
"CODEX_TEST_GITHUB_METADATA_JSON": release_metadata(
VERSION,
package_digest,
checksum_digest,
"https://github.com/openai/codex/releases/download",
),
"CODEX_TEST_LEGACY_ARCHIVE": str(legacy_archive),
"CODEX_TEST_LEGACY_METADATA_JSON": legacy_release_metadata(
LEGACY_VERSION,
legacy_digest,
),
"CODEX_TEST_METADATA_FAILURE": "1" if metadata_failure else "0",
"CODEX_TEST_METADATA_JSON": release_metadata(),
"CODEX_TEST_PACKAGE_ARCHIVE": str(package_archive),
"CODEX_TEST_RELEASES_MODE": releases_mode,
"CODEX_TEST_REQUEST_LOG": str(request_log),
"CODEX_TEST_LATEST_METADATA_JSON": release_metadata(
VERSION,
package_digest,
checksum_digest,
"https://releases.openai.com/codex/releases",
),
"HOME": str(root / "home"),
"PATH": f"{bin_dir}:/usr/bin:/bin",
"SHELL": "/bin/sh",
@@ -130,11 +305,19 @@ def run_installer(
return result, requests
def release_metadata() -> str:
def release_metadata(
version: str,
package_digest: str,
checksum_digest: str,
download_base_url: str,
) -> str:
release_path = f"rust-v{version}" if "github.com" in download_base_url else version
asset_base_url = f"{download_base_url}/{release_path}"
assets = [
{
"name": f"codex-package-{target}.tar.gz",
"digest": f"sha256:{'a' * 64}",
"digest": f"sha256:{package_digest}",
"browser_download_url": f"{asset_base_url}/codex-package-{target}.tar.gz",
}
for target in (
"aarch64-apple-darwin",
@@ -146,14 +329,123 @@ def release_metadata() -> str:
assets.append(
{
"name": "codex-package_SHA256SUMS",
"digest": f"sha256:{'b' * 64}",
"digest": f"sha256:{checksum_digest}",
"browser_download_url": f"{asset_base_url}/codex-package_SHA256SUMS",
}
)
return json.dumps(
{"tag_name": f"rust-v{VERSION}", "assets": assets},
{"tag_name": f"rust-v{version}", "assets": assets},
indent=2,
)
def legacy_release_metadata(version: str, digest: str) -> str:
asset = f"codex-npm-{current_npm_tag()}-{version}.tgz"
return json.dumps(
{
"tag_name": f"rust-v{version}",
"assets": [
{
"name": asset,
"digest": f"sha256:{digest}",
"browser_download_url": (
"https://github.com/openai/codex/releases/download/"
f"rust-v{version}/{asset}"
),
}
],
},
indent=2,
)
def current_target() -> str:
os_name = subprocess.run(
["uname", "-s"], capture_output=True, check=True, text=True
).stdout.strip()
architecture = subprocess.run(
["uname", "-m"], capture_output=True, check=True, text=True
).stdout.strip()
arch = "aarch64" if architecture in ("arm64", "aarch64") else "x86_64"
return (
f"{arch}-apple-darwin" if os_name == "Darwin" else f"{arch}-unknown-linux-musl"
)
def current_npm_tag() -> str:
target = current_target()
return {
"aarch64-apple-darwin": "darwin-arm64",
"x86_64-apple-darwin": "darwin-x64",
"aarch64-unknown-linux-musl": "linux-arm64",
"x86_64-unknown-linux-musl": "linux-x64",
}[target]
def checksum_manifest(package_digest: str) -> str:
return "".join(
f"{package_digest} codex-package-{target}.tar.gz\n"
for target in (
"aarch64-apple-darwin",
"x86_64-apple-darwin",
"aarch64-unknown-linux-musl",
"x86_64-unknown-linux-musl",
"aarch64-pc-windows-msvc",
"x86_64-pc-windows-msvc",
)
)
def create_package_archive(path: Path, version: str) -> None:
executable = f"#!/bin/sh\necho 'codex-cli {version}'\n".encode()
with tarfile.open(path, "w:gz") as archive:
add_archive_file(archive, "codex-package.json", b"{}\n")
add_archive_file(archive, "bin/codex", executable, mode=0o755)
add_archive_file(archive, "bin/codex-code-mode-host", executable, mode=0o755)
add_archive_file(archive, "codex-path/rg", executable, mode=0o755)
add_archive_file(archive, "codex-resources/bwrap", executable, mode=0o755)
def create_legacy_archive(path: Path, version: str) -> None:
target = current_target()
executable = f"#!/bin/sh\necho 'codex-cli {version}'\n".encode()
with tarfile.open(path, "w:gz") as archive:
add_archive_file(
archive,
f"package/vendor/{target}/codex/codex",
executable,
mode=0o755,
)
add_archive_file(
archive,
f"package/vendor/{target}/path/rg",
executable,
mode=0o755,
)
add_archive_file(
archive,
f"package/vendor/{target}/codex-resources/bwrap",
executable,
mode=0o755,
)
def add_archive_file(
archive: tarfile.TarFile,
name: str,
content: bytes,
*,
mode: int = 0o644,
) -> None:
info = tarfile.TarInfo(name)
info.size = len(content)
info.mode = mode
archive.addfile(info, io.BytesIO(content))
def sha256_file(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
if __name__ == "__main__":
unittest.main()