mirror of
https://github.com/openai/codex.git
synced 2026-05-23 12:34:25 +00:00
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:
@@ -2,6 +2,7 @@ use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use anyhow::anyhow;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
@@ -15,6 +16,7 @@ use crate::logging::log_note;
|
||||
use crate::sandbox_bin_dir;
|
||||
|
||||
const DEV_BUILD_VERSION_SENTINEL: &str = "0.0.0";
|
||||
const BIN_DIRNAME: &str = "bin";
|
||||
const RESOURCES_DIRNAME: &str = "codex-resources";
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
@@ -188,12 +190,21 @@ fn sibling_source_path(kind: HelperExecutable) -> Result<PathBuf> {
|
||||
fn source_path_for_exe(exe: &Path, file_name: &str) -> Option<PathBuf> {
|
||||
let dir = exe.parent()?;
|
||||
let direct_candidate = dir.join(file_name);
|
||||
if direct_candidate.exists() {
|
||||
if direct_candidate.is_file() {
|
||||
return Some(direct_candidate);
|
||||
}
|
||||
|
||||
if dir.file_name() == Some(OsStr::new(BIN_DIRNAME))
|
||||
&& let Some(package_dir) = dir.parent()
|
||||
{
|
||||
let package_resource_candidate = package_dir.join(RESOURCES_DIRNAME).join(file_name);
|
||||
if package_resource_candidate.is_file() {
|
||||
return Some(package_resource_candidate);
|
||||
}
|
||||
}
|
||||
|
||||
let resource_candidate = dir.join(RESOURCES_DIRNAME).join(file_name);
|
||||
resource_candidate.exists().then_some(resource_candidate)
|
||||
resource_candidate.is_file().then_some(resource_candidate)
|
||||
}
|
||||
|
||||
fn helper_destination_for_source(
|
||||
@@ -345,6 +356,7 @@ fn destination_is_fresh(source: &Path, destination: &Path) -> Result<bool> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::BIN_DIRNAME;
|
||||
use super::CopyOutcome;
|
||||
use super::DEV_BUILD_VERSION_SENTINEL;
|
||||
use super::HelperExecutable;
|
||||
@@ -463,6 +475,47 @@ mod tests {
|
||||
assert_eq!(resolved, helper);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn helper_source_lookup_checks_package_resource_dir_for_bin_exe() {
|
||||
let tmp = TempDir::new().expect("tempdir");
|
||||
let package_dir = tmp.path().join("package");
|
||||
let bin_dir = package_dir.join(BIN_DIRNAME);
|
||||
let resources_dir = package_dir.join(RESOURCES_DIRNAME);
|
||||
fs::create_dir_all(&bin_dir).expect("create bin dir");
|
||||
fs::create_dir_all(&resources_dir).expect("create resources dir");
|
||||
let exe = bin_dir.join("codex.exe");
|
||||
let helper = resources_dir.join("codex-command-runner.exe");
|
||||
fs::write(&exe, b"codex").expect("write exe");
|
||||
fs::write(&helper, b"runner").expect("write helper");
|
||||
|
||||
let resolved = source_path_for_exe(&exe, /*file_name*/ "codex-command-runner.exe")
|
||||
.expect("helper path");
|
||||
|
||||
assert_eq!(resolved, helper);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn helper_source_lookup_prefers_package_resource_dir_over_bin_resource_dir() {
|
||||
let tmp = TempDir::new().expect("tempdir");
|
||||
let package_dir = tmp.path().join("package");
|
||||
let bin_dir = package_dir.join(BIN_DIRNAME);
|
||||
let package_resources_dir = package_dir.join(RESOURCES_DIRNAME);
|
||||
let bin_resources_dir = bin_dir.join(RESOURCES_DIRNAME);
|
||||
fs::create_dir_all(&package_resources_dir).expect("create package resources dir");
|
||||
fs::create_dir_all(&bin_resources_dir).expect("create bin resources dir");
|
||||
let exe = bin_dir.join("codex.exe");
|
||||
let package_helper = package_resources_dir.join("codex-command-runner.exe");
|
||||
let bin_helper = bin_resources_dir.join("codex-command-runner.exe");
|
||||
fs::write(&exe, b"codex").expect("write exe");
|
||||
fs::write(&package_helper, b"package runner").expect("write package helper");
|
||||
fs::write(&bin_helper, b"bin runner").expect("write bin helper");
|
||||
|
||||
let resolved = source_path_for_exe(&exe, /*file_name*/ "codex-command-runner.exe")
|
||||
.expect("helper path");
|
||||
|
||||
assert_eq!(resolved, package_helper);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn helper_source_lookup_prefers_direct_sibling_over_resource_dir() {
|
||||
let tmp = TempDir::new().expect("tempdir");
|
||||
|
||||
@@ -3,6 +3,7 @@ use serde::Serialize;
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::OsStr;
|
||||
use std::ffi::c_void;
|
||||
use std::os::windows::process::CommandExt;
|
||||
use std::path::Path;
|
||||
@@ -39,9 +40,11 @@ use windows_sys::Win32::Security::SECURITY_NT_AUTHORITY;
|
||||
pub const SETUP_VERSION: u32 = 5;
|
||||
pub const OFFLINE_USERNAME: &str = "CodexSandboxOffline";
|
||||
pub const ONLINE_USERNAME: &str = "CodexSandboxOnline";
|
||||
const BIN_DIRNAME: &str = "bin";
|
||||
const ERROR_CANCELLED: u32 = 1223;
|
||||
const SECURITY_BUILTIN_DOMAIN_RID: u32 = 0x0000_0020;
|
||||
const DOMAIN_ALIAS_RID_ADMINS: u32 = 0x0000_0220;
|
||||
const RESOURCES_DIRNAME: &str = "codex-resources";
|
||||
const USERPROFILE_ROOT_EXCLUSIONS: &[&str] = &[
|
||||
".ssh",
|
||||
".tsh",
|
||||
@@ -579,26 +582,40 @@ fn quote_arg(arg: &str) -> String {
|
||||
|
||||
fn find_setup_exe() -> PathBuf {
|
||||
if let Ok(exe) = std::env::current_exe()
|
||||
&& let Some(dir) = exe.parent()
|
||||
&& let Some(setup_exe) = find_setup_exe_for_current_exe(&exe)
|
||||
{
|
||||
let candidate = dir.join("codex-windows-sandbox-setup.exe");
|
||||
if candidate.exists() {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
// Standalone installs keep Windows helper binaries under
|
||||
// `codex-resources/` next to `codex.exe`, so elevation needs to probe
|
||||
// that sibling folder before falling back to PATH.
|
||||
let resource_candidate = dir
|
||||
.join("codex-resources")
|
||||
.join("codex-windows-sandbox-setup.exe");
|
||||
if resource_candidate.exists() {
|
||||
return resource_candidate;
|
||||
}
|
||||
return setup_exe;
|
||||
}
|
||||
PathBuf::from("codex-windows-sandbox-setup.exe")
|
||||
}
|
||||
|
||||
fn find_setup_exe_for_current_exe(exe: &Path) -> Option<PathBuf> {
|
||||
let dir = exe.parent()?;
|
||||
let candidate = dir.join("codex-windows-sandbox-setup.exe");
|
||||
if candidate.is_file() {
|
||||
return Some(candidate);
|
||||
}
|
||||
|
||||
if dir.file_name() == Some(OsStr::new(BIN_DIRNAME))
|
||||
&& let Some(package_dir) = dir.parent()
|
||||
{
|
||||
let package_resource_candidate = package_dir
|
||||
.join(RESOURCES_DIRNAME)
|
||||
.join("codex-windows-sandbox-setup.exe");
|
||||
if package_resource_candidate.is_file() {
|
||||
return Some(package_resource_candidate);
|
||||
}
|
||||
}
|
||||
|
||||
// Older standalone installs keep Windows helper binaries under
|
||||
// `codex-resources/` next to `codex.exe`, so elevation still probes that
|
||||
// sibling folder before falling back to PATH.
|
||||
let resource_candidate = dir
|
||||
.join(RESOURCES_DIRNAME)
|
||||
.join("codex-windows-sandbox-setup.exe");
|
||||
resource_candidate.is_file().then_some(resource_candidate)
|
||||
}
|
||||
|
||||
fn report_helper_failure(
|
||||
codex_home: &Path,
|
||||
cleared_report: bool,
|
||||
@@ -959,8 +976,11 @@ fn filter_sensitive_write_roots(mut roots: Vec<PathBuf>, codex_home: &Path) -> V
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::BIN_DIRNAME;
|
||||
use super::RESOURCES_DIRNAME;
|
||||
use super::WINDOWS_PLATFORM_DEFAULT_READ_ROOTS;
|
||||
use super::build_payload_roots;
|
||||
use super::find_setup_exe_for_current_exe;
|
||||
use super::gather_legacy_full_read_roots;
|
||||
use super::gather_read_roots;
|
||||
use super::loopback_proxy_port_from_url;
|
||||
@@ -1000,6 +1020,24 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn setup_exe_lookup_checks_package_resource_dir_for_bin_exe() {
|
||||
let tmp = TempDir::new().expect("tempdir");
|
||||
let package_dir = tmp.path().join("package");
|
||||
let bin_dir = package_dir.join(BIN_DIRNAME);
|
||||
let resources_dir = package_dir.join(RESOURCES_DIRNAME);
|
||||
fs::create_dir_all(&bin_dir).expect("create bin dir");
|
||||
fs::create_dir_all(&resources_dir).expect("create resources dir");
|
||||
let exe = bin_dir.join("codex.exe");
|
||||
let setup_exe = resources_dir.join("codex-windows-sandbox-setup.exe");
|
||||
fs::write(&exe, b"codex").expect("write exe");
|
||||
fs::write(&setup_exe, b"setup").expect("write setup");
|
||||
|
||||
let resolved = find_setup_exe_for_current_exe(&exe).expect("setup exe");
|
||||
|
||||
assert_eq!(resolved, setup_exe);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loopback_proxy_url_parsing_rejects_non_loopback_and_zero_port() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -120,24 +120,29 @@ release_metadata_url() {
|
||||
printf 'https://api.github.com/repos/openai/codex/releases/tags/rust-v%s\n' "$resolved_version"
|
||||
}
|
||||
|
||||
release_asset_digest() {
|
||||
release_asset_digest_or_empty() {
|
||||
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 "\"") {
|
||||
/"name":[[:space:]]*"[^"]+"/ {
|
||||
name = $0
|
||||
sub(/^.*"name":[[:space:]]*"/, "", name)
|
||||
sub(/".*$/, "", name)
|
||||
if (name == asset) {
|
||||
in_asset = 1
|
||||
asset_depth = depth
|
||||
}
|
||||
}
|
||||
|
||||
if (in_asset && /"digest":[[:space:]]*"[^"]+"/) {
|
||||
sub(/^.*"digest":[[:space:]]*"/, "")
|
||||
sub(/".*$/, "")
|
||||
digest = $0
|
||||
}
|
||||
in_asset && /"digest":[[:space:]]*"[^"]+"/ {
|
||||
digest = $0
|
||||
sub(/^.*"digest":[[:space:]]*"/, "", digest)
|
||||
sub(/".*$/, "", digest)
|
||||
}
|
||||
|
||||
{
|
||||
line = $0
|
||||
opens = gsub(/\{/, "{", line)
|
||||
closes = gsub(/\}/, "}", line)
|
||||
@@ -147,6 +152,7 @@ release_asset_digest() {
|
||||
in_asset = 0
|
||||
}
|
||||
}
|
||||
|
||||
END {
|
||||
if (digest != "") {
|
||||
print digest
|
||||
@@ -159,12 +165,56 @@ release_asset_digest() {
|
||||
printf '%s\n' "${digest#sha256:}"
|
||||
;;
|
||||
*)
|
||||
echo "Could not find SHA-256 digest for release asset $asset." >&2
|
||||
exit 1
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
release_asset_exists() {
|
||||
asset="$1"
|
||||
resolved_version="$2"
|
||||
|
||||
release_asset_digest_or_empty "$asset" "$resolved_version" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
release_asset_digest() {
|
||||
asset="$1"
|
||||
resolved_version="$2"
|
||||
|
||||
digest="$(release_asset_digest_or_empty "$asset" "$resolved_version" || true)"
|
||||
if [ -z "$digest" ]; then
|
||||
echo "Could not find SHA-256 digest for release asset $asset." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$digest"
|
||||
}
|
||||
|
||||
package_archive_digest() {
|
||||
asset="$1"
|
||||
manifest_path="$2"
|
||||
|
||||
digest="$(awk -v asset="$asset" '
|
||||
$2 == asset && $1 ~ /^[0-9a-fA-F]{64}$/ {
|
||||
print tolower($1)
|
||||
found = 1
|
||||
exit
|
||||
}
|
||||
END {
|
||||
if (!found) {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
' "$manifest_path" 2>/dev/null || true)"
|
||||
|
||||
if [ -z "$digest" ]; then
|
||||
echo "Could not find SHA-256 digest for $asset in codex-package_SHA256SUMS." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$digest"
|
||||
}
|
||||
|
||||
file_sha256() {
|
||||
path="$1"
|
||||
|
||||
@@ -193,7 +243,7 @@ verify_archive_digest() {
|
||||
actual_digest="$(file_sha256 "$archive_path")"
|
||||
|
||||
if [ "$actual_digest" != "$expected_digest" ]; then
|
||||
echo "Downloaded Codex archive checksum did not match release metadata." >&2
|
||||
echo "Downloaded Codex archive checksum did not match expected digest." >&2
|
||||
echo "expected: $expected_digest" >&2
|
||||
echo "actual: $actual_digest" >&2
|
||||
exit 1
|
||||
@@ -441,6 +491,12 @@ version_from_binary() {
|
||||
}
|
||||
|
||||
current_installed_version() {
|
||||
version="$(version_from_binary "$CURRENT_LINK/bin/codex" || true)"
|
||||
if [ -n "$version" ]; then
|
||||
printf '%s\n' "$version"
|
||||
return 0
|
||||
fi
|
||||
|
||||
version="$(version_from_binary "$CURRENT_LINK/codex" || true)"
|
||||
if [ -n "$version" ]; then
|
||||
printf '%s\n' "$version"
|
||||
@@ -584,18 +640,43 @@ handle_conflicting_install() {
|
||||
fi
|
||||
}
|
||||
|
||||
install_release() {
|
||||
install_package_release() {
|
||||
release_dir="$1"
|
||||
vendor_root="$2"
|
||||
archive_path="$2"
|
||||
stage_release="$RELEASES_DIR/.staging.$(basename "$release_dir").$$"
|
||||
|
||||
mkdir -p "$RELEASES_DIR"
|
||||
rm -rf "$stage_release"
|
||||
mkdir -p "$stage_release/codex-resources"
|
||||
mkdir -p "$stage_release"
|
||||
tar -xzf "$archive_path" -C "$stage_release"
|
||||
chmod 0755 "$stage_release/bin/codex" "$stage_release/codex-path/rg"
|
||||
if [ -f "$stage_release/codex-resources/bwrap" ]; then
|
||||
chmod 0755 "$stage_release/codex-resources/bwrap"
|
||||
fi
|
||||
ln -sf "bin/codex" "$stage_release/codex"
|
||||
|
||||
if [ -e "$release_dir" ] || [ -L "$release_dir" ]; then
|
||||
rm -rf "$release_dir"
|
||||
fi
|
||||
mv "$stage_release" "$release_dir"
|
||||
}
|
||||
|
||||
install_legacy_platform_npm_release() {
|
||||
release_dir="$1"
|
||||
archive_path="$2"
|
||||
target="$3"
|
||||
stage_release="$RELEASES_DIR/.staging.$(basename "$release_dir").$$"
|
||||
extract_dir="$tmp_dir/extract"
|
||||
vendor_root="$extract_dir/package/vendor/$target"
|
||||
|
||||
mkdir -p "$RELEASES_DIR"
|
||||
rm -rf "$stage_release" "$extract_dir"
|
||||
mkdir -p "$stage_release/codex-resources" "$extract_dir"
|
||||
tar -xzf "$archive_path" -C "$extract_dir"
|
||||
|
||||
cp "$vendor_root/codex/codex" "$stage_release/codex"
|
||||
cp "$vendor_root/path/rg" "$stage_release/codex-resources/rg"
|
||||
chmod 0755 "$stage_release/codex"
|
||||
chmod 0755 "$stage_release/codex-resources/rg"
|
||||
chmod 0755 "$stage_release/codex" "$stage_release/codex-resources/rg"
|
||||
if [ -f "$vendor_root/codex-resources/bwrap" ]; then
|
||||
cp "$vendor_root/codex-resources/bwrap" "$stage_release/codex-resources/bwrap"
|
||||
chmod 0755 "$stage_release/codex-resources/bwrap"
|
||||
@@ -611,15 +692,34 @@ release_dir_is_complete() {
|
||||
release_dir="$1"
|
||||
expected_version="$2"
|
||||
expected_target="$3"
|
||||
layout="$4"
|
||||
|
||||
[ -d "$release_dir" ] &&
|
||||
[ -x "$release_dir/codex" ] &&
|
||||
[ -x "$release_dir/codex-resources/rg" ] &&
|
||||
[ "$(basename "$release_dir")" = "$expected_version-$expected_target" ] &&
|
||||
case "$expected_target" in
|
||||
*linux*) [ -x "$release_dir/codex-resources/bwrap" ] ;;
|
||||
*) true ;;
|
||||
esac
|
||||
[ "$(basename "$release_dir")" = "$expected_version-$expected_target" ] ||
|
||||
return 1
|
||||
|
||||
case "$layout" in
|
||||
package)
|
||||
[ -f "$release_dir/codex-package.json" ] &&
|
||||
[ -x "$release_dir/bin/codex" ] &&
|
||||
[ -x "$release_dir/codex" ] &&
|
||||
[ -x "$release_dir/codex-path/rg" ] ||
|
||||
return 1
|
||||
;;
|
||||
legacy-platform-npm)
|
||||
[ -x "$release_dir/codex" ] &&
|
||||
[ -x "$release_dir/codex-resources/rg" ] ||
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$layout:$expected_target" in
|
||||
package:*linux* | legacy-platform-npm:*linux*) [ -x "$release_dir/codex-resources/bwrap" ] ;;
|
||||
*) true ;;
|
||||
esac
|
||||
}
|
||||
|
||||
update_current_link() {
|
||||
@@ -629,11 +729,23 @@ update_current_link() {
|
||||
replace_path_with_symlink "$CURRENT_LINK" "$release_dir" "$tmp_link"
|
||||
}
|
||||
|
||||
release_codex_relative_path() {
|
||||
release_dir="$1"
|
||||
|
||||
if [ -x "$release_dir/bin/codex" ]; then
|
||||
printf 'bin/codex\n'
|
||||
else
|
||||
printf 'codex\n'
|
||||
fi
|
||||
}
|
||||
|
||||
update_visible_command() {
|
||||
release_dir="$1"
|
||||
mkdir -p "$BIN_DIR"
|
||||
tmp_link="$BIN_DIR/.codex.$$"
|
||||
codex_relative_path="$(release_codex_relative_path "$release_dir")"
|
||||
|
||||
replace_path_with_symlink "$BIN_PATH" "$CURRENT_LINK/codex" "$tmp_link"
|
||||
replace_path_with_symlink "$BIN_PATH" "$CURRENT_LINK/$codex_relative_path" "$tmp_link"
|
||||
}
|
||||
|
||||
verify_visible_command() {
|
||||
@@ -700,8 +812,21 @@ else
|
||||
fi
|
||||
|
||||
resolved_version="$(resolve_version)"
|
||||
asset="codex-npm-$npm_tag-$resolved_version.tgz"
|
||||
package_asset="codex-package-$vendor_target.tar.gz"
|
||||
checksum_asset="codex-package_SHA256SUMS"
|
||||
if release_asset_exists "$package_asset" "$resolved_version" &&
|
||||
release_asset_exists "$checksum_asset" "$resolved_version"; then
|
||||
install_layout="package"
|
||||
asset="$package_asset"
|
||||
elif release_asset_exists "codex-npm-$npm_tag-$resolved_version.tgz" "$resolved_version"; 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")"
|
||||
release_name="$resolved_version-$vendor_target"
|
||||
release_dir="$RELEASES_DIR/$release_name"
|
||||
current_version="$(current_installed_version)"
|
||||
@@ -730,27 +855,35 @@ 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 ! release_dir_is_complete "$release_dir" "$resolved_version" "$vendor_target" "$install_layout"; 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"
|
||||
checksum_path="$tmp_dir/$checksum_asset"
|
||||
|
||||
step "Downloading Codex CLI"
|
||||
expected_digest="$(release_asset_digest "$asset" "$resolved_version")"
|
||||
if [ "$install_layout" = "package" ]; then
|
||||
checksum_digest="$(release_asset_digest "$checksum_asset" "$resolved_version")"
|
||||
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" "$resolved_version")"
|
||||
fi
|
||||
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"
|
||||
if [ "$install_layout" = "package" ]; then
|
||||
install_package_release "$release_dir" "$archive_path"
|
||||
else
|
||||
install_legacy_platform_npm_release "$release_dir" "$archive_path" "$vendor_target"
|
||||
fi
|
||||
fi
|
||||
update_current_link "$release_dir"
|
||||
update_visible_command
|
||||
update_visible_command "$release_dir"
|
||||
add_to_path
|
||||
verify_visible_command
|
||||
release_install_lock
|
||||
|
||||
Reference in New Issue
Block a user