mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
Prefer releases.openai.com for Codex installs
This commit is contained in:
+218
-37
@@ -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
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user