install: consume Codex package archives (#23636)

## Summary

Standalone installs should exercise the same canonical package archive
layout that release builds produce, rather than unpacking npm platform
packages and reconstructing a parallel install tree.

This updates `install.sh` and `install.ps1` to prefer
`codex-package-<target>.tar.gz` plus `codex-package_SHA256SUMS`
introduced in https://github.com/openai/codex/pull/23635, authenticate
the checksum manifest against GitHub release metadata, verify the
selected package archive against the authenticated manifest, and install
the package archive directly.

## Compatibility Notes

Package installs still leave a compatibility command at `current/codex`
for managed daemon flows, while visible command shims point at
`bin/codex` inside the package layout.

Recent releases that predate package archives still publish per-platform
npm artifacts, so both installers keep a legacy platform npm fallback
for those versions and verify those archives against release metadata
directly.

Releases old enough to publish only the single root
`codex-npm-<version>.tgz` archive are intentionally out of scope. The
installers fail clearly when neither package archives nor per-platform
npm archives are present.

On Windows, the runtime helper lookups now recognize package-layout
installs where `codex.exe` runs from `bin/`, so
`codex-command-runner.exe` and `codex-windows-sandbox-setup.exe` resolve
from the top-level `codex-resources/` directory. The direct-sibling and
older sibling-resource fallbacks are preserved.

## Test plan

- `sh -n scripts/install/install.sh`
- `bash -n scripts/install/install.sh`
- `pwsh -NoProfile -Command '$tokens=$null; $errors=$null; $null =
[System.Management.Automation.Language.Parser]::ParseFile("scripts/install/install.ps1",
[ref]$tokens, [ref]$errors); if ($errors.Count) { $errors | Format-List
*; exit 1 }'`
- `HOME="$home_dir" CODEX_HOME="$tmp_dir/codex-home"
CODEX_INSTALL_DIR="$bin_dir" PATH="$bin_dir:$PATH" sh
scripts/install/install.sh --release 0.125.0`
- Verified the 0.125.0 isolated install leaves the visible command
pointed at `current/codex` and includes the legacy `codex-resources/rg`
payload.
- `cargo test -p codex-windows-sandbox`
- `just fix -p codex-windows-sandbox`

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23636).
* #23638
* #23637
* __->__ #23636
This commit is contained in:
Michael Bolin
2026-05-20 11:20:11 -07:00
committed by GitHub
parent c5bd131567
commit 110b30d545
4 changed files with 426 additions and 79 deletions
+152 -29
View File
@@ -55,7 +55,7 @@ function Normalize-Version {
return $RawVersion
}
function Get-ReleaseAssetMetadata {
function Find-ReleaseAssetMetadata {
param(
[string]$AssetName,
[string]$ResolvedVersion
@@ -64,7 +64,7 @@ function Get-ReleaseAssetMetadata {
$release = Invoke-RestMethod -Uri "https://api.github.com/repos/openai/codex/releases/tags/rust-v$ResolvedVersion"
$asset = $release.assets | Where-Object { $_.name -eq $AssetName } | Select-Object -First 1
if ($null -eq $asset) {
throw "Could not find release asset $AssetName for Codex $ResolvedVersion."
return $null
}
$digestMatch = [regex]::Match([string]$asset.digest, "^sha256:([0-9a-fA-F]{64})$")
@@ -78,6 +78,20 @@ function Get-ReleaseAssetMetadata {
}
}
function Get-ReleaseAssetMetadata {
param(
[string]$AssetName,
[string]$ResolvedVersion
)
$metadata = Find-ReleaseAssetMetadata -AssetName $AssetName -ResolvedVersion $ResolvedVersion
if ($null -eq $metadata) {
throw "Could not find release asset $AssetName for Codex $ResolvedVersion."
}
return $metadata
}
function Test-ArchiveDigest {
param(
[string]$ArchivePath,
@@ -86,10 +100,27 @@ function Test-ArchiveDigest {
$actualDigest = (Get-FileHash -LiteralPath $ArchivePath -Algorithm SHA256).Hash.ToLowerInvariant()
if ($actualDigest -ne $ExpectedDigest) {
throw "Downloaded Codex archive checksum did not match release metadata. Expected $ExpectedDigest but got $actualDigest."
throw "Downloaded Codex archive checksum did not match expected digest. Expected $ExpectedDigest but got $actualDigest."
}
}
function Get-PackageArchiveDigest {
param(
[string]$ManifestPath,
[string]$AssetName
)
$escapedAssetName = [regex]::Escape($AssetName)
foreach ($line in Get-Content -LiteralPath $ManifestPath) {
$match = [regex]::Match($line, "^\s*([0-9a-fA-F]{64})\s+$escapedAssetName\s*$")
if ($match.Success) {
return $match.Groups[1].Value.ToLowerInvariant()
}
}
throw "Could not find SHA-256 digest for $AssetName in codex-package_SHA256SUMS."
}
function Path-Contains {
param(
[string]$PathValue,
@@ -190,6 +221,11 @@ function Get-CurrentInstalledVersion {
[string]$StandaloneCurrentDir
)
$standaloneVersion = Get-VersionFromBinary -CodexPath (Join-Path $StandaloneCurrentDir "bin\codex.exe")
if (-not [string]::IsNullOrWhiteSpace($standaloneVersion)) {
return $standaloneVersion
}
$standaloneVersion = Get-VersionFromBinary -CodexPath (Join-Path $StandaloneCurrentDir "codex.exe")
if (-not [string]::IsNullOrWhiteSpace($standaloneVersion)) {
return $standaloneVersion
@@ -449,14 +485,37 @@ function Ensure-Junction {
throw "Refusing to replace file at $LinkPath with a junction."
}
function Test-ReleaseIsComplete {
function Test-PackageContentsAreComplete {
param(
[string]$ReleaseDir,
[string]$ExpectedVersion,
[string]$ExpectedTarget
[string]$PackageDir
)
if (-not (Test-Path -LiteralPath $ReleaseDir -PathType Container)) {
if (-not (Test-Path -LiteralPath $PackageDir -PathType Container)) {
return $false
}
$expectedFiles = @(
"codex-package.json",
"bin\codex.exe",
"codex-path\rg.exe",
"codex-resources\codex-command-runner.exe",
"codex-resources\codex-windows-sandbox-setup.exe"
)
foreach ($name in $expectedFiles) {
if (-not (Test-Path -LiteralPath (Join-Path $PackageDir $name) -PathType Leaf)) {
return $false
}
}
return $true
}
function Test-LegacyPlatformNpmContentsAreComplete {
param(
[string]$PackageDir
)
if (-not (Test-Path -LiteralPath $PackageDir -PathType Container)) {
return $false
}
@@ -467,11 +526,38 @@ function Test-ReleaseIsComplete {
"codex-resources\rg.exe"
)
foreach ($name in $expectedFiles) {
if (-not (Test-Path -LiteralPath (Join-Path $ReleaseDir $name) -PathType Leaf)) {
if (-not (Test-Path -LiteralPath (Join-Path $PackageDir $name) -PathType Leaf)) {
return $false
}
}
return $true
}
function Test-ReleaseIsComplete {
param(
[string]$ReleaseDir,
[string]$ExpectedVersion,
[string]$ExpectedTarget,
[string]$Layout
)
switch ($Layout) {
"Package" {
if (-not (Test-PackageContentsAreComplete -PackageDir $ReleaseDir)) {
return $false
}
}
"LegacyPlatformNpm" {
if (-not (Test-LegacyPlatformNpmContentsAreComplete -PackageDir $ReleaseDir)) {
return $false
}
}
default {
throw "Unknown Codex installer layout: $Layout"
}
}
return (Split-Path -Leaf $ReleaseDir) -eq "$ExpectedVersion-$ExpectedTarget"
}
@@ -637,7 +723,21 @@ Write-Step "Resolved version: $resolvedVersion"
$conflictingInstall = Get-ConflictingInstall -VisibleBinDir $visibleBinDir
$oldStandaloneBackup = $null
$packageAsset = "codex-npm-$npmTag-$resolvedVersion.tgz"
$packageAsset = "codex-package-$target.tar.gz"
$checksumAsset = "codex-package_SHA256SUMS"
$packageMetadata = Find-ReleaseAssetMetadata -AssetName $packageAsset -ResolvedVersion $resolvedVersion
$checksumMetadata = Find-ReleaseAssetMetadata -AssetName $checksumAsset -ResolvedVersion $resolvedVersion
$installLayout = "Package"
if ($null -eq $packageMetadata -or $null -eq $checksumMetadata) {
$packageAsset = "codex-npm-$npmTag-$resolvedVersion.tgz"
$packageMetadata = Find-ReleaseAssetMetadata -AssetName $packageAsset -ResolvedVersion $resolvedVersion
if ($null -ne $packageMetadata) {
$installLayout = "LegacyPlatformNpm"
} else {
throw "Could not find Codex package or platform npm release assets for Codex $resolvedVersion."
}
$checksumMetadata = $null
}
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("codex-install-" + [System.Guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
@@ -645,40 +745,58 @@ try {
Invoke-WithInstallLock -LockPath $lockPath -Script {
Remove-StaleInstallArtifacts -ReleasesDir $releasesDir
if (-not (Test-ReleaseIsComplete -ReleaseDir $releaseDir -ExpectedVersion $resolvedVersion -ExpectedTarget $target)) {
if (-not (Test-ReleaseIsComplete -ReleaseDir $releaseDir -ExpectedVersion $resolvedVersion -ExpectedTarget $target -Layout $installLayout)) {
if (Test-Path -LiteralPath $releaseDir) {
Write-WarningStep "Found incomplete existing release at $releaseDir. Reinstalling."
}
$archivePath = Join-Path $tempDir $packageAsset
$extractDir = Join-Path $tempDir "extract"
$checksumPath = Join-Path $tempDir $checksumAsset
$stagingDir = Join-Path $releasesDir ".staging.$releaseName.$PID"
$assetMetadata = Get-ReleaseAssetMetadata -AssetName $packageAsset -ResolvedVersion $resolvedVersion
Write-Step "Downloading Codex CLI"
Invoke-WebRequest -Uri $assetMetadata.Url -OutFile $archivePath
Test-ArchiveDigest -ArchivePath $archivePath -ExpectedDigest $assetMetadata.Sha256
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
}
Invoke-WebRequest -Uri $packageMetadata.Url -OutFile $archivePath
Test-ArchiveDigest -ArchivePath $archivePath -ExpectedDigest $expectedPackageDigest
New-Item -ItemType Directory -Force -Path $extractDir | Out-Null
New-Item -ItemType Directory -Force -Path $releasesDir | Out-Null
if (Test-Path -LiteralPath $stagingDir) {
Remove-Item -LiteralPath $stagingDir -Recurse -Force
}
New-Item -ItemType Directory -Force -Path $stagingDir | Out-Null
tar -xzf $archivePath -C $extractDir
if ($installLayout -eq "Package") {
tar -xzf $archivePath -C $stagingDir
if (-not (Test-PackageContentsAreComplete -PackageDir $stagingDir)) {
throw "Downloaded Codex package archive did not contain the expected package layout."
}
} else {
$extractDir = Join-Path $tempDir "extract"
New-Item -ItemType Directory -Force -Path $extractDir | Out-Null
tar -xzf $archivePath -C $extractDir
$vendorRoot = Join-Path $extractDir "package/vendor/$target"
$resourcesDir = Join-Path $stagingDir "codex-resources"
New-Item -ItemType Directory -Force -Path $resourcesDir | Out-Null
$copyMap = @{
"codex/codex.exe" = "codex.exe"
"codex/codex-command-runner.exe" = "codex-resources\codex-command-runner.exe"
"codex/codex-windows-sandbox-setup.exe" = "codex-resources\codex-windows-sandbox-setup.exe"
"path/rg.exe" = "codex-resources\rg.exe"
}
$vendorRoot = Join-Path $extractDir "package/vendor/$target"
$resourcesDir = Join-Path $stagingDir "codex-resources"
New-Item -ItemType Directory -Force -Path $resourcesDir | Out-Null
$copyMap = @{
"codex/codex.exe" = "codex.exe"
"codex/codex-command-runner.exe" = "codex-resources\codex-command-runner.exe"
"codex/codex-windows-sandbox-setup.exe" = "codex-resources\codex-windows-sandbox-setup.exe"
"path/rg.exe" = "codex-resources\rg.exe"
}
foreach ($relativeSource in $copyMap.Keys) {
Copy-Item -LiteralPath (Join-Path $vendorRoot $relativeSource) -Destination (Join-Path $stagingDir $copyMap[$relativeSource])
foreach ($relativeSource in $copyMap.Keys) {
Copy-Item -LiteralPath (Join-Path $vendorRoot $relativeSource) -Destination (Join-Path $stagingDir $copyMap[$relativeSource])
}
if (-not (Test-LegacyPlatformNpmContentsAreComplete -PackageDir $stagingDir)) {
throw "Downloaded Codex npm archive did not contain the expected legacy platform package layout."
}
}
if (Test-Path -LiteralPath $releaseDir) {
@@ -691,10 +809,15 @@ try {
Ensure-Junction -LinkPath $currentDir -TargetPath $releaseDir -InstallerOwnedTargetPrefix $releasesDir
$visibleParent = Split-Path -Parent $visibleBinDir
$currentBinDir = if ($installLayout -eq "Package") {
Join-Path $currentDir "bin"
} else {
$currentDir
}
New-Item -ItemType Directory -Force -Path $visibleParent | Out-Null
$oldStandaloneBackup = Move-OldStandaloneBinIfApproved -VisibleBinDir $visibleBinDir -DefaultVisibleBinDir $defaultVisibleBinDir
try {
Ensure-Junction -LinkPath $visibleBinDir -TargetPath $currentDir -InstallerOwnedTargetPrefix $standaloneRoot
Ensure-Junction -LinkPath $visibleBinDir -TargetPath $currentBinDir -InstallerOwnedTargetPrefix $standaloneRoot
Test-VisibleCodexCommand -VisibleBinDir $visibleBinDir
} catch {
if ($null -ne $oldStandaloneBackup -and (Test-Path -LiteralPath $oldStandaloneBackup)) {