From a569b9ec9e633a6c51645d1670647b7e2219e4bf Mon Sep 17 00:00:00 2001 From: Chris Busillo Date: Mon, 25 May 2026 20:30:41 -0400 Subject: [PATCH] chore(release): remove legacy OpenAI installers --- scripts/install/install.ps1 | 750 ---------------------------------- scripts/install/install.sh | 776 ------------------------------------ 2 files changed, 1526 deletions(-) delete mode 100644 scripts/install/install.ps1 delete mode 100755 scripts/install/install.sh diff --git a/scripts/install/install.ps1 b/scripts/install/install.ps1 deleted file mode 100644 index ed4a3e1a3201..000000000000 --- a/scripts/install/install.ps1 +++ /dev/null @@ -1,750 +0,0 @@ -param( - [string]$Release = "latest" -) - -Set-StrictMode -Version Latest -$ErrorActionPreference = "Stop" -$ProgressPreference = "SilentlyContinue" - -function Write-Step { - param( - [string]$Message - ) - - Write-Host "==> $Message" -} - -function Write-WarningStep { - param( - [string]$Message - ) - - Write-Warning $Message -} - -function Prompt-YesNo { - param( - [string]$Prompt - ) - - if ([Console]::IsInputRedirected -or [Console]::IsOutputRedirected) { - return $false - } - - $choice = Read-Host "$Prompt [y/N]" - return $choice -match "^(?i:y(?:es)?)$" -} - -function Normalize-Version { - param( - [string]$RawVersion - ) - - if ([string]::IsNullOrWhiteSpace($RawVersion) -or $RawVersion -eq "latest") { - return "latest" - } - - if ($RawVersion.StartsWith("rust-v")) { - return $RawVersion.Substring(6) - } - - if ($RawVersion.StartsWith("v")) { - return $RawVersion.Substring(1) - } - - return $RawVersion -} - -function Get-ReleaseAssetMetadata { - param( - [string]$AssetName, - [string]$ResolvedVersion - ) - - $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." - } - - $digestMatch = [regex]::Match([string]$asset.digest, "^sha256:([0-9a-fA-F]{64})$") - if (-not $digestMatch.Success) { - throw "Could not find SHA-256 digest for release asset $AssetName." - } - - return [PSCustomObject]@{ - Url = $asset.browser_download_url - Sha256 = $digestMatch.Groups[1].Value.ToLowerInvariant() - } -} - -function Test-ArchiveDigest { - param( - [string]$ArchivePath, - [string]$ExpectedDigest - ) - - $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." - } -} - -function Path-Contains { - param( - [string]$PathValue, - [string]$Entry - ) - - if ([string]::IsNullOrWhiteSpace($PathValue)) { - return $false - } - - $needle = $Entry.TrimEnd("\") - foreach ($segment in $PathValue.Split(";", [System.StringSplitOptions]::RemoveEmptyEntries)) { - if ($segment.TrimEnd("\") -ieq $needle) { - return $true - } - } - - return $false -} - -function Invoke-WithInstallLock { - param( - [string]$LockPath, - [scriptblock]$Script - ) - - New-Item -ItemType Directory -Force -Path (Split-Path -Parent $LockPath) | Out-Null - $lock = $null - while ($null -eq $lock) { - try { - $lock = [System.IO.File]::Open( - $LockPath, - [System.IO.FileMode]::OpenOrCreate, - [System.IO.FileAccess]::ReadWrite, - [System.IO.FileShare]::None - ) - } catch [System.IO.IOException] { - Start-Sleep -Milliseconds 250 - } - } - try { - & $Script - } finally { - $lock.Dispose() - } -} - -function Remove-StaleInstallArtifacts { - param( - [string]$ReleasesDir - ) - - if (Test-Path -LiteralPath $ReleasesDir -PathType Container) { - Get-ChildItem -LiteralPath $ReleasesDir -Force -Directory -Filter ".staging.*" -ErrorAction SilentlyContinue | - Remove-Item -Recurse -Force -ErrorAction SilentlyContinue - } -} - -function Resolve-Version { - $normalizedVersion = Normalize-Version -RawVersion $Release - if ($normalizedVersion -ne "latest") { - return $normalizedVersion - } - - $release = Invoke-RestMethod -Uri "https://api.github.com/repos/openai/codex/releases/latest" - if (-not $release.tag_name) { - Write-Error "Failed to resolve the latest Codex release version." - exit 1 - } - - return (Normalize-Version -RawVersion $release.tag_name) -} - -function Get-VersionFromBinary { - param( - [string]$CodexPath - ) - - if (-not (Test-Path -LiteralPath $CodexPath -PathType Leaf)) { - return $null - } - - try { - $versionOutput = & $CodexPath --version 2>$null - } catch { - return $null - } - - if ($versionOutput -match '([0-9][0-9A-Za-z.+-]*)$') { - return $matches[1] - } - - return $null -} - -function Get-CurrentInstalledVersion { - param( - [string]$StandaloneCurrentDir - ) - - $standaloneVersion = Get-VersionFromBinary -CodexPath (Join-Path $StandaloneCurrentDir "codex.exe") - if (-not [string]::IsNullOrWhiteSpace($standaloneVersion)) { - return $standaloneVersion - } - - return $null -} - -function Test-OldStandaloneBinLayout { - param( - [string]$VisibleBinDir, - [string]$DefaultVisibleBinDir - ) - - if (-not $VisibleBinDir.Equals($DefaultVisibleBinDir, [System.StringComparison]::OrdinalIgnoreCase)) { - return $false - } - if (-not (Test-Path -LiteralPath $VisibleBinDir -PathType Container)) { - return $false - } - - $item = Get-Item -LiteralPath $VisibleBinDir -Force - if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) { - return $false - } - - $requiredFiles = @("codex.exe", "rg.exe") - foreach ($fileName in $requiredFiles) { - if (-not (Test-Path -LiteralPath (Join-Path $VisibleBinDir $fileName) -PathType Leaf)) { - return $false - } - } - - $knownFiles = @( - "codex.exe", - "rg.exe", - "codex-command-runner.exe", - "codex-windows-sandbox.exe", - "codex-windows-sandbox-setup.exe" - ) - foreach ($child in Get-ChildItem -LiteralPath $VisibleBinDir -Force) { - if ($child.PSIsContainer) { - return $false - } - if ($knownFiles -notcontains $child.Name) { - return $false - } - } - - return $true -} - -function Move-OldStandaloneBinIfApproved { - param( - [string]$VisibleBinDir, - [string]$DefaultVisibleBinDir - ) - - if (-not (Test-OldStandaloneBinLayout -VisibleBinDir $VisibleBinDir -DefaultVisibleBinDir $DefaultVisibleBinDir)) { - return $null - } - - Write-Step "We found an older Codex install at $VisibleBinDir" - Write-WarningStep "To continue, Codex needs to update the install at this path." - if (-not (Prompt-YesNo "Replace it with the current Codex setup now?")) { - throw "Cannot replace older standalone install without confirmation: $VisibleBinDir" - } - - $backupDir = "$VisibleBinDir.backup.$([DateTimeOffset]::UtcNow.ToUnixTimeSeconds()).$PID" - Write-Step "Moving older standalone install to $backupDir" - Move-Item -LiteralPath $VisibleBinDir -Destination $backupDir - return $backupDir -} - -function Add-JunctionSupportType { - if (([System.Management.Automation.PSTypeName]'CodexInstaller.Junction').Type) { - return - } - - Add-Type -TypeDefinition @" -using System; -using System.ComponentModel; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; -using Microsoft.Win32.SafeHandles; - -namespace CodexInstaller -{ - public static class Junction - { - private const uint GENERIC_WRITE = 0x40000000; - private const uint FILE_SHARE_READ = 0x00000001; - private const uint FILE_SHARE_WRITE = 0x00000002; - private const uint FILE_SHARE_DELETE = 0x00000004; - private const uint OPEN_EXISTING = 3; - private const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; - private const uint FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000; - private const uint FSCTL_SET_REPARSE_POINT = 0x000900A4; - private const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003; - private const int HeaderLength = 20; - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - private static extern SafeFileHandle CreateFileW( - string lpFileName, - uint dwDesiredAccess, - uint dwShareMode, - IntPtr lpSecurityAttributes, - uint dwCreationDisposition, - uint dwFlagsAndAttributes, - IntPtr hTemplateFile); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool DeviceIoControl( - SafeFileHandle hDevice, - uint dwIoControlCode, - byte[] lpInBuffer, - int nInBufferSize, - IntPtr lpOutBuffer, - int nOutBufferSize, - out int lpBytesReturned, - IntPtr lpOverlapped); - - public static void SetTarget(string linkPath, string targetPath) - { - string substituteName = "\\??\\" + Path.GetFullPath(targetPath); - byte[] substituteNameBytes = Encoding.Unicode.GetBytes(substituteName); - if (substituteNameBytes.Length > ushort.MaxValue - HeaderLength) { - throw new ArgumentException("Junction target path is too long.", "targetPath"); - } - - byte[] reparseBuffer = new byte[substituteNameBytes.Length + HeaderLength]; - WriteUInt32(reparseBuffer, 0, IO_REPARSE_TAG_MOUNT_POINT); - WriteUInt16(reparseBuffer, 4, checked((ushort)(substituteNameBytes.Length + 12))); - WriteUInt16(reparseBuffer, 8, 0); - WriteUInt16(reparseBuffer, 10, checked((ushort)substituteNameBytes.Length)); - WriteUInt16(reparseBuffer, 12, checked((ushort)(substituteNameBytes.Length + 2))); - WriteUInt16(reparseBuffer, 14, 0); - Buffer.BlockCopy(substituteNameBytes, 0, reparseBuffer, 16, substituteNameBytes.Length); - - using (SafeFileHandle handle = CreateFileW( - linkPath, - GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - IntPtr.Zero, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, - IntPtr.Zero)) - { - if (handle.IsInvalid) { - throw new Win32Exception(Marshal.GetLastWin32Error()); - } - - int bytesReturned; - if (!DeviceIoControl( - handle, - FSCTL_SET_REPARSE_POINT, - reparseBuffer, - reparseBuffer.Length, - IntPtr.Zero, - 0, - out bytesReturned, - IntPtr.Zero)) - { - throw new Win32Exception(Marshal.GetLastWin32Error()); - } - } - } - - private static void WriteUInt16(byte[] buffer, int offset, ushort value) - { - buffer[offset] = (byte)value; - buffer[offset + 1] = (byte)(value >> 8); - } - - private static void WriteUInt32(byte[] buffer, int offset, uint value) - { - buffer[offset] = (byte)value; - buffer[offset + 1] = (byte)(value >> 8); - buffer[offset + 2] = (byte)(value >> 16); - buffer[offset + 3] = (byte)(value >> 24); - } - } -} -"@ -} - -function Set-JunctionTarget { - param( - [string]$LinkPath, - [string]$TargetPath - ) - - Add-JunctionSupportType - [CodexInstaller.Junction]::SetTarget($LinkPath, $TargetPath) -} - -function Test-IsJunction { - param( - [string]$Path - ) - - if (-not (Test-Path -LiteralPath $Path)) { - return $false - } - - $item = Get-Item -LiteralPath $Path -Force - return ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -and $item.LinkType -eq "Junction" -} - -function Ensure-Junction { - param( - [string]$LinkPath, - [string]$TargetPath, - [string]$InstallerOwnedTargetPrefix - ) - - if (-not (Test-Path -LiteralPath $LinkPath)) { - New-Item -ItemType Junction -Path $LinkPath -Target $TargetPath | Out-Null - return - } - - $item = Get-Item -LiteralPath $LinkPath -Force - if (Test-IsJunction -Path $LinkPath) { - $existingTarget = [string]$item.Target - if (-not [string]::IsNullOrWhiteSpace($InstallerOwnedTargetPrefix)) { - $ownedTargetPrefix = $InstallerOwnedTargetPrefix.TrimEnd("\\") - if (-not $existingTarget.StartsWith($ownedTargetPrefix, [System.StringComparison]::OrdinalIgnoreCase)) { - throw "Refusing to retarget junction at $LinkPath because it is not managed by this installer." - } - } - if ($existingTarget.Equals($TargetPath, [System.StringComparison]::OrdinalIgnoreCase)) { - return - } - - # Keep the path itself in place and only retarget the junction. That - # avoids a gap where current or the visible bin path disappears during - # an update. - Set-JunctionTarget -LinkPath $LinkPath -TargetPath $TargetPath - return - } - - if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) { - throw "Refusing to replace non-junction reparse point at $LinkPath." - } - - if ($item.PSIsContainer) { - if ((Get-ChildItem -LiteralPath $LinkPath -Force | Select-Object -First 1) -ne $null) { - throw "Refusing to replace non-empty directory at $LinkPath with a junction." - } - - Remove-Item -LiteralPath $LinkPath -Force - New-Item -ItemType Junction -Path $LinkPath -Target $TargetPath | Out-Null - return - } - - throw "Refusing to replace file at $LinkPath with a junction." -} - -function Test-ReleaseIsComplete { - param( - [string]$ReleaseDir, - [string]$ExpectedVersion, - [string]$ExpectedTarget - ) - - if (-not (Test-Path -LiteralPath $ReleaseDir -PathType Container)) { - return $false - } - - $expectedFiles = @( - "codex.exe", - "codex-resources\codex-command-runner.exe", - "codex-resources\codex-windows-sandbox-setup.exe", - "codex-resources\rg.exe" - ) - foreach ($name in $expectedFiles) { - if (-not (Test-Path -LiteralPath (Join-Path $ReleaseDir $name) -PathType Leaf)) { - return $false - } - } - - return (Split-Path -Leaf $ReleaseDir) -eq "$ExpectedVersion-$ExpectedTarget" -} - -function Get-ExistingCodexCommand { - $existing = Get-Command codex -ErrorAction SilentlyContinue - if ($null -eq $existing) { - return $null - } - - return $existing.Source -} - -function Get-ExistingCodexManager { - param( - [string]$ExistingPath, - [string]$VisibleBinDir - ) - - if ([string]::IsNullOrWhiteSpace($ExistingPath)) { - return $null - } - - if ($ExistingPath.StartsWith($VisibleBinDir, [System.StringComparison]::OrdinalIgnoreCase)) { - return $null - } - - if ($ExistingPath -match "\\.bun\\") { - return "bun" - } - - if ($ExistingPath -match "node_modules" -or $ExistingPath -match "\\npm\\") { - return "npm" - } - - return $null -} - -function Get-ConflictingInstall { - param( - [string]$VisibleBinDir - ) - - $existingPath = Get-ExistingCodexCommand - $manager = Get-ExistingCodexManager -ExistingPath $existingPath -VisibleBinDir $VisibleBinDir - if ($null -eq $manager) { - return $null - } - - Write-Step "Detected existing $manager-managed Codex at $existingPath" - Write-WarningStep "Multiple managed Codex installs can be ambiguous because PATH order decides which one runs." - - return [PSCustomObject]@{ - Manager = $manager - Path = $existingPath - } -} - -function Maybe-HandleConflictingInstall { - param( - [object]$Conflict - ) - - if ($null -eq $Conflict) { - return - } - - $manager = $Conflict.Manager - - $uninstallArgs = if ($manager -eq "bun") { - @("remove", "-g", "@openai/codex") - } else { - @("uninstall", "-g", "@openai/codex") - } - $uninstallCommand = if ($manager -eq "bun") { "bun" } else { "npm" } - - if (Prompt-YesNo "Uninstall the existing $manager-managed Codex now?") { - Write-Step "Running: $uninstallCommand $($uninstallArgs -join ' ')" - try { - & $uninstallCommand @uninstallArgs - } catch { - Write-WarningStep "Failed to uninstall the existing $manager-managed Codex. Continuing with the standalone install." - } - } else { - Write-WarningStep "Leaving the existing $manager-managed Codex installed. PATH order will determine which codex runs." - } -} - -function Test-VisibleCodexCommand { - param( - [string]$VisibleBinDir - ) - - $codexCommand = Join-Path $VisibleBinDir "codex.exe" - & $codexCommand --version *> $null - if ($LASTEXITCODE -ne 0) { - throw "Installed Codex command failed verification: $codexCommand --version" - } -} - -if ($env:OS -ne "Windows_NT") { - Write-Error "install.ps1 supports Windows only. Use install.sh on macOS or Linux." - exit 1 -} - -if (-not [Environment]::Is64BitOperatingSystem) { - Write-Error "Codex requires a 64-bit version of Windows." - exit 1 -} - -$architecture = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -$target = $null -$platformLabel = $null -$npmTag = $null -switch ($architecture) { - "Arm64" { - $target = "aarch64-pc-windows-msvc" - $platformLabel = "Windows (ARM64)" - $npmTag = "win32-arm64" - } - "X64" { - $target = "x86_64-pc-windows-msvc" - $platformLabel = "Windows (x64)" - $npmTag = "win32-x64" - } - default { - Write-Error "Unsupported architecture: $architecture" - exit 1 - } -} - -$codexHome = if ([string]::IsNullOrWhiteSpace($env:CODEX_HOME)) { - Join-Path $env:USERPROFILE ".codex" -} else { - $env:CODEX_HOME -} -$standaloneRoot = Join-Path $codexHome "packages\standalone" -$releasesDir = Join-Path $standaloneRoot "releases" -$currentDir = Join-Path $standaloneRoot "current" -$lockPath = Join-Path $standaloneRoot "install.lock" - -$defaultVisibleBinDir = Join-Path $env:LOCALAPPDATA "Programs\OpenAI\Codex\bin" -if ([string]::IsNullOrWhiteSpace($env:CODEX_INSTALL_DIR)) { - $visibleBinDir = $defaultVisibleBinDir -} else { - $visibleBinDir = $env:CODEX_INSTALL_DIR -} - -$currentVersion = Get-CurrentInstalledVersion -StandaloneCurrentDir $currentDir -$resolvedVersion = Resolve-Version -$releaseName = "$resolvedVersion-$target" -$releaseDir = Join-Path $releasesDir $releaseName - -if (-not [string]::IsNullOrWhiteSpace($currentVersion) -and $currentVersion -ne $resolvedVersion) { - Write-Step "Updating Codex CLI from $currentVersion to $resolvedVersion" -} elseif (-not [string]::IsNullOrWhiteSpace($currentVersion)) { - Write-Step "Updating Codex CLI" -} else { - Write-Step "Installing Codex CLI" -} -Write-Step "Detected platform: $platformLabel" -Write-Step "Resolved version: $resolvedVersion" - -$conflictingInstall = Get-ConflictingInstall -VisibleBinDir $visibleBinDir -$oldStandaloneBackup = $null - -$packageAsset = "codex-npm-$npmTag-$resolvedVersion.tgz" -$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("codex-install-" + [System.Guid]::NewGuid().ToString("N")) -New-Item -ItemType Directory -Force -Path $tempDir | Out-Null - -try { - Invoke-WithInstallLock -LockPath $lockPath -Script { - Remove-StaleInstallArtifacts -ReleasesDir $releasesDir - - if (-not (Test-ReleaseIsComplete -ReleaseDir $releaseDir -ExpectedVersion $resolvedVersion -ExpectedTarget $target)) { - if (Test-Path -LiteralPath $releaseDir) { - Write-WarningStep "Found incomplete existing release at $releaseDir. Reinstalling." - } - - $archivePath = Join-Path $tempDir $packageAsset - $extractDir = Join-Path $tempDir "extract" - $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 - - 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 - - $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]) - } - - if (Test-Path -LiteralPath $releaseDir) { - Remove-Item -LiteralPath $releaseDir -Recurse -Force - } - Move-Item -LiteralPath $stagingDir -Destination $releaseDir - } - - New-Item -ItemType Directory -Force -Path $standaloneRoot | Out-Null - Ensure-Junction -LinkPath $currentDir -TargetPath $releaseDir -InstallerOwnedTargetPrefix $releasesDir - - $visibleParent = Split-Path -Parent $visibleBinDir - 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 - Test-VisibleCodexCommand -VisibleBinDir $visibleBinDir - } catch { - if ($null -ne $oldStandaloneBackup -and (Test-Path -LiteralPath $oldStandaloneBackup)) { - if (Test-Path -LiteralPath $visibleBinDir) { - Remove-Item -LiteralPath $visibleBinDir -Recurse -Force - } - Move-Item -LiteralPath $oldStandaloneBackup -Destination $visibleBinDir - } - throw - } - if ($null -ne $oldStandaloneBackup) { - Remove-Item -LiteralPath $oldStandaloneBackup -Recurse -Force - } - } -} finally { - Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue -} - -Maybe-HandleConflictingInstall -Conflict $conflictingInstall - -$userPath = [Environment]::GetEnvironmentVariable("Path", "User") -if (-not (Path-Contains -PathValue $userPath -Entry $visibleBinDir)) { - if ([string]::IsNullOrWhiteSpace($userPath)) { - $newUserPath = $visibleBinDir - } else { - $newUserPath = "$visibleBinDir;$userPath" - } - - [Environment]::SetEnvironmentVariable("Path", $newUserPath, "User") - Write-Step "PATH updated for future PowerShell sessions." -} elseif (Path-Contains -PathValue $env:Path -Entry $visibleBinDir) { - Write-Step "$visibleBinDir is already on PATH." -} else { - Write-Step "PATH is already configured for future PowerShell sessions." -} - -if (-not (Path-Contains -PathValue $env:Path -Entry $visibleBinDir)) { - if ([string]::IsNullOrWhiteSpace($env:Path)) { - $env:Path = $visibleBinDir - } else { - $env:Path = "$visibleBinDir;$env:Path" - } -} - -Write-Step "Current PowerShell session: codex" -Write-Step "Future PowerShell windows: open a new PowerShell window and run: codex" -Write-Host "Codex CLI $resolvedVersion installed successfully." - -$codexCommand = Join-Path $visibleBinDir "codex.exe" -if (Prompt-YesNo "Start Codex now?") { - Write-Step "Launching Codex" - & $codexCommand -} diff --git a/scripts/install/install.sh b/scripts/install/install.sh deleted file mode 100755 index a21571cb7697..000000000000 --- a/scripts/install/install.sh +++ /dev/null @@ -1,776 +0,0 @@ -#!/bin/sh - -set -eu - -RELEASE="latest" - -BIN_DIR="${CODEX_INSTALL_DIR:-$HOME/.local/bin}" -BIN_PATH="$BIN_DIR/codex" -CODEX_HOME_DIR="${CODEX_HOME:-$HOME/.codex}" -STANDALONE_ROOT="$CODEX_HOME_DIR/packages/standalone" -RELEASES_DIR="$STANDALONE_ROOT/releases" -CURRENT_LINK="$STANDALONE_ROOT/current" -LOCK_FILE="$STANDALONE_ROOT/install.lock" -LOCK_DIR="$STANDALONE_ROOT/install.lock.d" -LOCK_STALE_AFTER_SECS=600 - -path_action="already" -path_profile="" -conflict_manager="" -conflict_path="" -lock_kind="" -tmp_dir="" - -step() { - printf '==> %s\n' "$1" -} - -warn() { - printf 'WARNING: %s\n' "$1" >&2 -} - -normalize_version() { - case "$1" in - "" | latest) - printf 'latest\n' - ;; - rust-v*) - printf '%s\n' "${1#rust-v}" - ;; - v*) - printf '%s\n' "${1#v}" - ;; - *) - printf '%s\n' "$1" - ;; - esac -} - -parse_args() { - while [ "$#" -gt 0 ]; do - case "$1" in - --release) - if [ "$#" -lt 2 ]; then - echo "--release requires a value." >&2 - exit 1 - fi - RELEASE="$2" - shift - ;; - --help | -h) - cat <&2 - exit 1 - ;; - esac - shift - done -} - -download_file() { - url="$1" - output="$2" - - if command -v curl >/dev/null 2>&1; then - curl -fsSL "$url" -o "$output" - return - fi - - if command -v wget >/dev/null 2>&1; then - wget -q -O "$output" "$url" - return - fi - - echo "curl or wget is required to install Codex." >&2 - exit 1 -} - -download_text() { - url="$1" - - if command -v curl >/dev/null 2>&1; then - curl -fsSL "$url" - return - fi - - if command -v wget >/dev/null 2>&1; then - wget -q -O - "$url" - return - fi - - echo "curl or wget is required to install Codex." >&2 - exit 1 -} - -release_url_for_asset() { - asset="$1" - resolved_version="$2" - - printf 'https://github.com/openai/codex/releases/download/rust-v%s/%s\n' "$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" -} - -release_asset_digest() { - asset="$1" - resolved_version="$2" - release_json="$(download_text "$(release_metadata_url "$resolved_version")")" - - digest="$(printf '%s\n' "$release_json" | awk -v asset="$asset" ' - { - if ($0 ~ "\"name\":[[:space:]]*\"" asset "\"") { - in_asset = 1 - asset_depth = depth - } - - if (in_asset && /"digest":[[:space:]]*"[^"]+"/) { - sub(/^.*"digest":[[:space:]]*"/, "") - sub(/".*$/, "") - digest = $0 - } - - line = $0 - opens = gsub(/\{/, "{", line) - closes = gsub(/\}/, "}", line) - depth += opens - closes - - if (in_asset && depth < asset_depth) { - in_asset = 0 - } - } - END { - if (digest != "") { - print digest - } - } - ')" - - case "$digest" in - sha256:????????????????????????????????????????????????????????????????) - printf '%s\n' "${digest#sha256:}" - ;; - *) - echo "Could not find SHA-256 digest for release asset $asset." >&2 - exit 1 - ;; - esac -} - -file_sha256() { - path="$1" - - if command -v sha256sum >/dev/null 2>&1; then - sha256sum "$path" | awk '{print $1}' - return - fi - - if command -v shasum >/dev/null 2>&1; then - shasum -a 256 "$path" | awk '{print $1}' - return - fi - - if command -v openssl >/dev/null 2>&1; then - openssl dgst -sha256 "$path" | sed 's/^.*= //' - return - fi - - echo "sha256sum, shasum, or openssl is required to verify the Codex download." >&2 - exit 1 -} - -verify_archive_digest() { - archive_path="$1" - expected_digest="$2" - actual_digest="$(file_sha256 "$archive_path")" - - if [ "$actual_digest" != "$expected_digest" ]; then - echo "Downloaded Codex archive checksum did not match release metadata." >&2 - echo "expected: $expected_digest" >&2 - echo "actual: $actual_digest" >&2 - exit 1 - fi -} - -require_command() { - if ! command -v "$1" >/dev/null 2>&1; then - echo "$1 is required to install Codex." >&2 - exit 1 - fi -} - -resolve_version() { - normalized_version="$(normalize_version "$RELEASE")" - - if [ "$normalized_version" != "latest" ]; then - printf '%s\n' "$normalized_version" - return - fi - - release_json="$(download_text "https://api.github.com/repos/openai/codex/releases/latest")" - resolved="$(printf '%s\n' "$release_json" | sed -n 's/.*"tag_name":[[:space:]]*"rust-v\([^"]*\)".*/\1/p' | head -n 1)" - - if [ -z "$resolved" ]; then - echo "Failed to resolve the latest Codex release version." >&2 - exit 1 - fi - - printf '%s\n' "$resolved" -} - -pick_profile() { - # There is no universal startup file across macOS/Linux login and interactive - # shells, so pick the profile that matches the current shell convention. - case "$os:${SHELL:-}" in - darwin:*/zsh) - printf '%s\n' "$HOME/.zprofile" - ;; - darwin:*/bash) - printf '%s\n' "$HOME/.bash_profile" - ;; - linux:*/zsh) - printf '%s\n' "$HOME/.zshrc" - ;; - linux:*/bash) - printf '%s\n' "$HOME/.bashrc" - ;; - *) - printf '%s\n' "$HOME/.profile" - ;; - esac -} - -add_to_path() { - path_action="already" - path_profile="" - - case ":$PATH:" in - *":$BIN_DIR:"*) - return - ;; - esac - - profile="$(pick_profile)" - path_profile="$profile" - begin_marker="# >>> Codex installer >>>" - end_marker="# <<< Codex installer <<<" - path_line="export PATH=\"$BIN_DIR:\$PATH\"" - - if [ -f "$profile" ] && grep -F "$begin_marker" "$profile" >/dev/null 2>&1; then - if grep -F "$path_line" "$profile" >/dev/null 2>&1; then - path_action="configured" - return - fi - - if grep -F "$end_marker" "$profile" >/dev/null 2>&1; then - rewrite_path_block "$profile" "$begin_marker" "$end_marker" "$path_line" - path_action="updated" - return - fi - fi - - append_path_block "$profile" "$begin_marker" "$end_marker" "$path_line" - path_action="added" -} - -append_path_block() { - profile="$1" - begin_marker="$2" - end_marker="$3" - path_line="$4" - - { - printf '\n%s\n' "$begin_marker" - printf '%s\n' "$path_line" - printf '%s\n' "$end_marker" - } >>"$profile" -} - -rewrite_path_block() { - profile="$1" - begin_marker="$2" - end_marker="$3" - path_line="$4" - tmp_profile="$tmp_dir/profile.$$.tmp" - - awk -v begin="$begin_marker" -v end="$end_marker" -v line="$path_line" ' - BEGIN { - in_block = 0 - replaced = 0 - } - $0 == begin { - if (!replaced) { - print begin - print line - print end - replaced = 1 - } - in_block = 1 - next - } - in_block { - if ($0 == end) { - in_block = 0 - } - next - } - { - print - } - END { - if (in_block != 0) { - exit 1 - } - } - ' "$profile" >"$tmp_profile" - mv "$tmp_profile" "$profile" -} - -mkdir_lock_is_stale() { - [ -d "$LOCK_DIR" ] || return 1 - - pid="$(cat "$LOCK_DIR/pid" 2>/dev/null || true)" - started_at="$(cat "$LOCK_DIR/started_at" 2>/dev/null || true)" - now="$(date +%s 2>/dev/null || printf '0')" - - case "$started_at" in - ''|*[!0-9]*) - started_at=0 - ;; - esac - - if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then - return 1 - fi - - if [ "$started_at" -eq 0 ] || [ "$now" -eq 0 ]; then - return 0 - fi - - [ $((now - started_at)) -ge "$LOCK_STALE_AFTER_SECS" ] -} - -acquire_install_lock() { - mkdir -p "$STANDALONE_ROOT" - - if [ "$os" = "darwin" ] && command -v lockf >/dev/null 2>&1; then - : >>"$LOCK_FILE" - exec 9<>"$LOCK_FILE" - lockf 9 - lock_kind="lockf" - return - fi - - if command -v flock >/dev/null 2>&1; then - exec 9>"$LOCK_FILE" - flock 9 - lock_kind="flock" - return - fi - - while ! mkdir "$LOCK_DIR" 2>/dev/null; do - if mkdir_lock_is_stale; then - warn "Removing stale installer lock at $LOCK_DIR" - rm -rf "$LOCK_DIR" - continue - fi - sleep 1 - done - - printf '%s\n' "$$" >"$LOCK_DIR/pid" - date +%s >"$LOCK_DIR/started_at" 2>/dev/null || true - lock_kind="mkdir" -} - -release_install_lock() { - if [ "$lock_kind" = "mkdir" ]; then - rm -rf "$LOCK_DIR" 2>/dev/null || true - elif [ "$lock_kind" = "flock" ] || [ "$lock_kind" = "lockf" ]; then - exec 9>&- 2>/dev/null || true - fi - lock_kind="" -} - -cleanup_stale_install_artifacts() { - mkdir -p "$RELEASES_DIR" "$STANDALONE_ROOT" - - find "$RELEASES_DIR" -mindepth 1 -maxdepth 1 -name '.staging.*' -exec rm -rf {} + - find "$STANDALONE_ROOT" -mindepth 1 -maxdepth 1 -name '.current.*' -exec rm -f {} + - - if [ -d "$BIN_DIR" ]; then - find "$BIN_DIR" -mindepth 1 -maxdepth 1 -name '.codex.*' -exec rm -f {} + - fi -} - -replace_path_with_symlink() { - link_path="$1" - link_target="$2" - tmp_link="$3" - - rm -f "$tmp_link" - ln -s "$link_target" "$tmp_link" - - if mv -Tf "$tmp_link" "$link_path" 2>/dev/null; then - return - fi - - if mv -hf "$tmp_link" "$link_path" 2>/dev/null; then - return - fi - - rm -f "$link_path" - mv -f "$tmp_link" "$link_path" -} - -version_from_binary() { - codex_path="$1" - - if [ ! -x "$codex_path" ]; then - return 1 - fi - - "$codex_path" --version 2>/dev/null | sed -n 's/.* \([0-9][0-9A-Za-z.+-]*\)$/\1/p' | head -n 1 -} - -current_installed_version() { - version="$(version_from_binary "$CURRENT_LINK/codex" || true)" - if [ -n "$version" ]; then - printf '%s\n' "$version" - return 0 - fi - - return 0 -} - -resolve_existing_codex() { - command -v codex 2>/dev/null || true -} - -classify_existing_codex() { - existing_path="$1" - - if [ -z "$existing_path" ] || [ "$existing_path" = "$BIN_PATH" ]; then - return 1 - fi - - case "$existing_path" in - /opt/homebrew/* | /usr/local/*) - if [ "$os" = "darwin" ]; then - printf 'brew\n' - return 0 - fi - ;; - esac - - if [ -f "$existing_path" ] && grep -F "#!/usr/bin/env node" "$existing_path" >/dev/null 2>&1; then - case "$existing_path" in - *".bun"*) - printf 'bun\n' - ;; - *) - printf 'npm\n' - ;; - esac - return 0 - fi - - return 1 -} - -prompt_yes_no() { - prompt="$1" - - if ( : /dev/null; then - printf '%s [y/N] ' "$prompt" >/dev/tty - if ! IFS= read -r answer /dev/null -} - -parse_args "$@" - -require_command mktemp -require_command tar - -case "$(uname -s)" in - Darwin) - os="darwin" - ;; - Linux) - os="linux" - ;; - *) - echo "install.sh supports macOS and Linux. Use install.ps1 on Windows." >&2 - exit 1 - ;; -esac - -case "$(uname -m)" in - x86_64 | amd64) - arch="x86_64" - ;; - arm64 | aarch64) - arch="aarch64" - ;; - *) - echo "Unsupported architecture: $(uname -m)" >&2 - exit 1 - ;; -esac - -if [ "$os" = "darwin" ] && [ "$arch" = "x86_64" ]; then - if [ "$(sysctl -n sysctl.proc_translated 2>/dev/null || true)" = "1" ]; then - arch="aarch64" - fi -fi - -if [ "$os" = "darwin" ]; then - if [ "$arch" = "aarch64" ]; then - npm_tag="darwin-arm64" - vendor_target="aarch64-apple-darwin" - platform_label="macOS (Apple Silicon)" - else - npm_tag="darwin-x64" - vendor_target="x86_64-apple-darwin" - platform_label="macOS (Intel)" - fi -else - if [ "$arch" = "aarch64" ]; then - npm_tag="linux-arm64" - vendor_target="aarch64-unknown-linux-musl" - platform_label="Linux (ARM64)" - else - npm_tag="linux-x64" - vendor_target="x86_64-unknown-linux-musl" - platform_label="Linux (x64)" - fi -fi - -resolved_version="$(resolve_version)" -asset="codex-npm-$npm_tag-$resolved_version.tgz" -download_url="$(release_url_for_asset "$asset" "$resolved_version")" -release_name="$resolved_version-$vendor_target" -release_dir="$RELEASES_DIR/$release_name" -current_version="$(current_installed_version)" - -if [ -n "$current_version" ] && [ "$current_version" != "$resolved_version" ]; then - step "Updating Codex CLI from $current_version to $resolved_version" -elif [ -n "$current_version" ]; then - step "Updating Codex CLI" -else - step "Installing Codex CLI" -fi -step "Detected platform: $platform_label" -step "Resolved version: $resolved_version" - -detect_conflicting_install - -tmp_dir="$(mktemp -d)" -cleanup() { - release_install_lock - if [ -n "$tmp_dir" ]; then - rm -rf "$tmp_dir" - fi -} -trap cleanup EXIT INT TERM - -acquire_install_lock -cleanup_stale_install_artifacts - -if ! release_dir_is_complete "$release_dir" "$resolved_version" "$vendor_target"; then - if [ -e "$release_dir" ] || [ -L "$release_dir" ]; then - warn "Found incomplete existing release at $release_dir; reinstalling." - fi - - archive_path="$tmp_dir/$asset" - extract_dir="$tmp_dir/extract" - - step "Downloading Codex CLI" - expected_digest="$(release_asset_digest "$asset" "$resolved_version")" - download_file "$download_url" "$archive_path" - verify_archive_digest "$archive_path" "$expected_digest" - - mkdir -p "$extract_dir" - tar -xzf "$archive_path" -C "$extract_dir" - - step "Installing standalone package to $release_dir" - install_release "$release_dir" "$extract_dir/package/vendor/$vendor_target" -fi -update_current_link "$release_dir" -update_visible_command -add_to_path -verify_visible_command -release_install_lock -handle_conflicting_install - -case "$path_action" in - added) - print_launch_instructions - ;; - updated) - print_launch_instructions - ;; - configured) - print_launch_instructions - ;; - *) - step "$BIN_DIR is already on PATH" - print_launch_instructions - ;; -esac - -printf 'Codex CLI %s installed successfully.\n' "$resolved_version" -maybe_launch_codex_now