Reconcile Gitea distribution with current alpha behavior and release tooling
Hop-State: A_06FN55REVAR7VQG4BWJBCD8 Hop-Proposal: R_06FN55QTEWKH16NSR59YJYG Hop-Task: T_06FN3MBF98GWD4NA5PA1RWG Hop-Attempt: AT_06FN55BQ80H2S8WHNDD24G0
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$GiteaUrl = $(if ($env:HOP_GITEA_URL) { $env:HOP_GITEA_URL } else { "https://githop.xyz" }),
|
||||
[string]$Repository = $(if ($env:HOP_REPOSITORY) { $env:HOP_REPOSITORY } else { "hop/hop" }),
|
||||
[string]$Version = $(if ($env:HOP_VERSION) { $env:HOP_VERSION } else { "latest" }),
|
||||
[string]$InstallDir = $(if ($env:HOP_INSTALL_DIR) { $env:HOP_INSTALL_DIR } else { Join-Path $env:LOCALAPPDATA "Programs\Hop" }),
|
||||
[switch]$SkipSkill,
|
||||
[switch]$SkipPath
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$GiteaUrl = $GiteaUrl.TrimEnd("/")
|
||||
|
||||
switch ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString()) {
|
||||
"X64" { $arch = "amd64" }
|
||||
"Arm64" { $arch = "arm64" }
|
||||
default { throw "Unsupported Windows architecture: $($_)" }
|
||||
}
|
||||
|
||||
$asset = "hop_windows_${arch}.zip"
|
||||
if ($Version -eq "latest") {
|
||||
# Gitea's /releases/latest endpoint omits prereleases. Select the newest
|
||||
# published release so the normal installer also works during Hop's alpha.
|
||||
$releases = @(Invoke-RestMethod -Uri "$GiteaUrl/api/v1/repos/$Repository/releases?draft=false&page=1&limit=1")
|
||||
$latestRelease = $releases | Select-Object -First 1
|
||||
$tag = $latestRelease.tag_name
|
||||
if (-not $tag) { throw "Could not determine the latest published release" }
|
||||
} else {
|
||||
$tag = if ($Version.StartsWith("v")) { $Version } else { "v$Version" }
|
||||
}
|
||||
if ($tag -notmatch '^[A-Za-z0-9._-]+$') {
|
||||
throw "Release API returned an unsafe tag: $tag"
|
||||
}
|
||||
$releaseUrl = "$GiteaUrl/$Repository/releases/download/$tag"
|
||||
|
||||
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("hop-install-" + [guid]::NewGuid())
|
||||
New-Item -ItemType Directory -Path $tempDir | Out-Null
|
||||
try {
|
||||
$archivePath = Join-Path $tempDir $asset
|
||||
$checksumsPath = Join-Path $tempDir "checksums.txt"
|
||||
Write-Host "Downloading $asset..."
|
||||
Invoke-WebRequest -UseBasicParsing -Uri "$releaseUrl/$asset" -OutFile $archivePath
|
||||
Invoke-WebRequest -UseBasicParsing -Uri "$releaseUrl/checksums.txt" -OutFile $checksumsPath
|
||||
|
||||
$escapedAsset = [regex]::Escape($asset)
|
||||
$checksumLine = Get-Content $checksumsPath | Where-Object { $_ -match "^([a-fA-F0-9]{64})\s+\*?$escapedAsset$" } | Select-Object -First 1
|
||||
if (-not $checksumLine) { throw "checksums.txt does not contain $asset" }
|
||||
$expected = ([regex]::Match($checksumLine, "^[a-fA-F0-9]{64}")).Value.ToLowerInvariant()
|
||||
$actual = (Get-FileHash -Algorithm SHA256 $archivePath).Hash.ToLowerInvariant()
|
||||
if ($actual -ne $expected) { throw "Checksum verification failed for $asset" }
|
||||
|
||||
$extractPath = Join-Path $tempDir "archive"
|
||||
Expand-Archive -Path $archivePath -DestinationPath $extractPath
|
||||
$binary = Join-Path $extractPath "hop.exe"
|
||||
if (-not (Test-Path $binary)) { throw "Release archive does not contain hop.exe" }
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
|
||||
$installedBinary = Join-Path $InstallDir "hop.exe"
|
||||
Copy-Item -Force $binary $installedBinary
|
||||
|
||||
if (-not $SkipPath) {
|
||||
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
|
||||
$pathEntries = @($userPath -split ";" | Where-Object { $_ })
|
||||
if ($pathEntries -notcontains $InstallDir) {
|
||||
$newUserPath = (($pathEntries + $InstallDir) -join ";")
|
||||
[Environment]::SetEnvironmentVariable("Path", $newUserPath, "User")
|
||||
Write-Host "Added $InstallDir to your user PATH."
|
||||
}
|
||||
if (($env:Path -split ";") -notcontains $InstallDir) {
|
||||
$env:Path = "$InstallDir;$env:Path"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $SkipSkill) {
|
||||
& $installedBinary skill install --force
|
||||
}
|
||||
Write-Host "Installed $(& $installedBinary version)"
|
||||
Write-Host "Binary: $installedBinary"
|
||||
if (-not $SkipSkill) {
|
||||
Write-Host "Restart Codex if it is open, then use it normally in any Git repository."
|
||||
}
|
||||
} finally {
|
||||
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $tempDir
|
||||
}
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
gitea_url=${HOP_GITEA_URL:-https://githop.xyz}
|
||||
gitea_url=${gitea_url%/}
|
||||
repository=${HOP_REPOSITORY:-hop/hop}
|
||||
requested_version=${HOP_VERSION:-latest}
|
||||
install_dir=${HOP_INSTALL_DIR:-"$HOME/.local/bin"}
|
||||
install_skill=${HOP_INSTALL_SKILL:-1}
|
||||
modify_path=${HOP_MODIFY_PATH:-1}
|
||||
|
||||
fail() {
|
||||
printf 'hop installer: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
command -v curl >/dev/null 2>&1 || fail "curl is required"
|
||||
command -v tar >/dev/null 2>&1 || fail "tar is required"
|
||||
|
||||
case $(uname -s) in
|
||||
Darwin) os=darwin ;;
|
||||
Linux) os=linux ;;
|
||||
*) fail "unsupported operating system: $(uname -s)" ;;
|
||||
esac
|
||||
|
||||
case $(uname -m) in
|
||||
x86_64 | amd64) arch=amd64 ;;
|
||||
arm64 | aarch64) arch=arm64 ;;
|
||||
*) fail "unsupported architecture: $(uname -m)" ;;
|
||||
esac
|
||||
|
||||
asset="hop_${os}_${arch}.tar.gz"
|
||||
if [ "$requested_version" = latest ]; then
|
||||
# Gitea's /releases/latest endpoint omits prereleases. Hop is distributed as
|
||||
# an alpha today, so select the newest published release from the list API.
|
||||
latest_json=$(curl -fL --retry 3 --proto '=https' --tlsv1.2 \
|
||||
"$gitea_url/api/v1/repos/$repository/releases?draft=false&page=1&limit=1")
|
||||
tag=$(printf '%s' "$latest_json" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
|
||||
[ -n "$tag" ] || fail "could not determine the latest published release"
|
||||
else
|
||||
case "$requested_version" in
|
||||
v*) tag=$requested_version ;;
|
||||
*) tag="v${requested_version}" ;;
|
||||
esac
|
||||
fi
|
||||
case "$tag" in
|
||||
*[!A-Za-z0-9._-]*) fail "release API returned an unsafe tag: $tag" ;;
|
||||
esac
|
||||
release_url="$gitea_url/$repository/releases/download/${tag}"
|
||||
|
||||
tmp_dir=$(mktemp -d 2>/dev/null || mktemp -d -t hop-install)
|
||||
cleanup() {
|
||||
rm -rf "$tmp_dir"
|
||||
}
|
||||
trap cleanup EXIT HUP INT TERM
|
||||
|
||||
printf 'Downloading %s...\n' "$asset"
|
||||
curl -fL --retry 3 --proto '=https' --tlsv1.2 \
|
||||
-o "$tmp_dir/$asset" "$release_url/$asset"
|
||||
curl -fL --retry 3 --proto '=https' --tlsv1.2 \
|
||||
-o "$tmp_dir/checksums.txt" "$release_url/checksums.txt"
|
||||
|
||||
expected=$(awk -v name="$asset" '$2 == name || $2 == "*" name { print $1; exit }' "$tmp_dir/checksums.txt")
|
||||
[ -n "$expected" ] || fail "checksums.txt does not contain $asset"
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
actual=$(sha256sum "$tmp_dir/$asset" | awk '{print $1}')
|
||||
elif command -v shasum >/dev/null 2>&1; then
|
||||
actual=$(shasum -a 256 "$tmp_dir/$asset" | awk '{print $1}')
|
||||
else
|
||||
fail "sha256sum or shasum is required to verify the download"
|
||||
fi
|
||||
[ "$actual" = "$expected" ] || fail "checksum verification failed for $asset"
|
||||
|
||||
tar -xzf "$tmp_dir/$asset" -C "$tmp_dir"
|
||||
[ -f "$tmp_dir/hop" ] || fail "release archive does not contain hop"
|
||||
mkdir -p "$install_dir"
|
||||
cp "$tmp_dir/hop" "$install_dir/hop"
|
||||
chmod 0755 "$install_dir/hop"
|
||||
|
||||
case ":${PATH:-}:" in
|
||||
*":$install_dir:"*) ;;
|
||||
*)
|
||||
if [ "$modify_path" = 1 ] && [ "$install_dir" = "$HOME/.local/bin" ]; then
|
||||
case ${SHELL:-} in
|
||||
*/zsh) profile="$HOME/.zprofile" ;;
|
||||
*) profile="$HOME/.profile" ;;
|
||||
esac
|
||||
path_line='export PATH="$HOME/.local/bin:$PATH"'
|
||||
if ! grep -F "$path_line" "$profile" >/dev/null 2>&1; then
|
||||
printf '\n%s\n' "$path_line" >>"$profile"
|
||||
printf 'Added ~/.local/bin to PATH in %s.\n' "$profile"
|
||||
fi
|
||||
else
|
||||
printf 'Add %s to PATH before opening a new terminal.\n' "$install_dir" >&2
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$install_skill" = 1 ]; then
|
||||
"$install_dir/hop" skill install --force
|
||||
fi
|
||||
|
||||
printf 'Installed %s\n' "$("$install_dir/hop" version)"
|
||||
printf 'Binary: %s\n' "$install_dir/hop"
|
||||
if [ "$install_skill" = 1 ]; then
|
||||
printf 'Restart Codex if it is open, then use it normally in any Git repository.\n'
|
||||
fi
|
||||
@@ -0,0 +1,72 @@
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("hop-installer-test-" + [guid]::NewGuid())
|
||||
$fixtures = Join-Path $tempDir "fixtures"
|
||||
$payload = Join-Path $tempDir "payload"
|
||||
$installDir = Join-Path $tempDir "install"
|
||||
$testArch = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() -eq "Arm64") { "arm64" } else { "amd64" }
|
||||
$asset = "hop_windows_${testArch}.zip"
|
||||
|
||||
New-Item -ItemType Directory -Path $tempDir | Out-Null
|
||||
New-Item -ItemType Directory -Path $fixtures, $payload | Out-Null
|
||||
try {
|
||||
$binary = Join-Path $payload "hop.exe"
|
||||
& go build -trimpath `
|
||||
-ldflags "-X githop.xyz/hop/hop/internal/hop.Version=9.9.9-installer-test" `
|
||||
-o $binary (Join-Path $root "cmd/hop")
|
||||
if ($LASTEXITCODE -ne 0) { throw "Could not build installer test binary" }
|
||||
|
||||
$archive = Join-Path $fixtures $asset
|
||||
Compress-Archive -Path $binary -DestinationPath $archive
|
||||
$hash = (Get-FileHash -Algorithm SHA256 $archive).Hash.ToLowerInvariant()
|
||||
"$hash $asset" | Set-Content -Encoding ascii (Join-Path $fixtures "checksums.txt")
|
||||
|
||||
function Invoke-RestMethod {
|
||||
[CmdletBinding()]
|
||||
param([Parameter(Mandatory)][string]$Uri)
|
||||
if (-not $Uri.EndsWith("/releases?draft=false&page=1&limit=1")) {
|
||||
throw "Unexpected installer API URL: $Uri"
|
||||
}
|
||||
return ,([pscustomobject]@{
|
||||
tag_name = "v9.9.9-installer-test"
|
||||
prerelease = $true
|
||||
draft = $false
|
||||
})
|
||||
}
|
||||
|
||||
function Invoke-WebRequest {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$UseBasicParsing,
|
||||
[Parameter(Mandatory)][string]$Uri,
|
||||
[Parameter(Mandatory)][string]$OutFile
|
||||
)
|
||||
if ($Uri.EndsWith("/checksums.txt")) {
|
||||
Copy-Item (Join-Path $fixtures "checksums.txt") $OutFile
|
||||
} elseif ($Uri.EndsWith("/$asset")) {
|
||||
Copy-Item (Join-Path $fixtures $asset) $OutFile
|
||||
} else {
|
||||
throw "Unexpected installer asset URL: $Uri"
|
||||
}
|
||||
}
|
||||
|
||||
& (Join-Path $root "scripts/install.ps1") `
|
||||
-GiteaUrl "https://gitea.test" `
|
||||
-Repository "hop/hop" `
|
||||
-InstallDir $installDir `
|
||||
-SkipSkill `
|
||||
-SkipPath
|
||||
|
||||
$installed = Join-Path $installDir "hop.exe"
|
||||
if (-not (Test-Path $installed)) { throw "Installer did not install hop.exe" }
|
||||
$version = & $installed version
|
||||
if ($LASTEXITCODE -ne 0 -or $version -ne "hop 9.9.9-installer-test") {
|
||||
throw "Unexpected installed version: $version"
|
||||
}
|
||||
Write-Host "PowerShell installer smoke test passed."
|
||||
} finally {
|
||||
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $tempDir
|
||||
}
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||
cd "$root"
|
||||
tmp_dir=$(mktemp -d 2>/dev/null || mktemp -d -t hop-installer-test)
|
||||
cleanup() {
|
||||
rm -rf "$tmp_dir"
|
||||
}
|
||||
trap cleanup EXIT HUP INT TERM
|
||||
|
||||
case $(uname -s) in
|
||||
Darwin) os=darwin ;;
|
||||
Linux) os=linux ;;
|
||||
*) printf 'installer smoke test does not support %s\n' "$(uname -s)" >&2; exit 1 ;;
|
||||
esac
|
||||
case $(uname -m) in
|
||||
x86_64 | amd64) arch=amd64 ;;
|
||||
arm64 | aarch64) arch=arm64 ;;
|
||||
*) printf 'installer smoke test does not support %s\n' "$(uname -m)" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
asset="hop_${os}_${arch}.tar.gz"
|
||||
mkdir -p "$tmp_dir/payload" "$tmp_dir/fixtures" "$tmp_dir/mock-bin" "$tmp_dir/home"
|
||||
go build -trimpath \
|
||||
-ldflags '-X githop.xyz/hop/hop/internal/hop.Version=9.9.9-installer-test' \
|
||||
-o "$tmp_dir/payload/hop" "$root/cmd/hop"
|
||||
tar -czf "$tmp_dir/fixtures/$asset" -C "$tmp_dir/payload" hop
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
hash=$(sha256sum "$tmp_dir/fixtures/$asset" | awk '{print $1}')
|
||||
else
|
||||
hash=$(shasum -a 256 "$tmp_dir/fixtures/$asset" | awk '{print $1}')
|
||||
fi
|
||||
printf '%s %s\n' "$hash" "$asset" >"$tmp_dir/fixtures/checksums.txt"
|
||||
|
||||
cat >"$tmp_dir/mock-bin/curl" <<'MOCK_CURL'
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
output=
|
||||
url=
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
-o) shift; output=$1 ;;
|
||||
http://* | https://*) url=$1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
case "$url" in
|
||||
*/api/v1/repos/*/releases\?draft=false\&page=1\&limit=1)
|
||||
printf '[{"tag_name":"v9.9.9-installer-test","prerelease":true,"draft":false}]'
|
||||
;;
|
||||
*/checksums.txt)
|
||||
cp "$HOP_TEST_FIXTURES/checksums.txt" "$output"
|
||||
;;
|
||||
*/hop_*.tar.gz)
|
||||
cp "$HOP_TEST_FIXTURES/${url##*/}" "$output"
|
||||
;;
|
||||
*)
|
||||
printf 'unexpected installer URL: %s\n' "$url" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
MOCK_CURL
|
||||
chmod 0755 "$tmp_dir/mock-bin/curl"
|
||||
|
||||
HOME="$tmp_dir/home" \
|
||||
PATH="$tmp_dir/mock-bin:$PATH" \
|
||||
HOP_TEST_FIXTURES="$tmp_dir/fixtures" \
|
||||
HOP_GITEA_URL="https://gitea.test" \
|
||||
HOP_REPOSITORY="hop/hop" \
|
||||
HOP_INSTALL_DIR="$tmp_dir/home/bin" \
|
||||
HOP_MODIFY_PATH=0 \
|
||||
sh "$root/scripts/install.sh"
|
||||
|
||||
version=$($tmp_dir/home/bin/hop version)
|
||||
[ "$version" = "hop 9.9.9-installer-test" ] || {
|
||||
printf 'unexpected installed version: %s\n' "$version" >&2
|
||||
exit 1
|
||||
}
|
||||
[ -s "$tmp_dir/home/.codex/skills/hop/SKILL.md" ] || {
|
||||
printf 'installer did not install the embedded Hop skill\n' >&2
|
||||
exit 1
|
||||
}
|
||||
Reference in New Issue
Block a user