diff --git a/scripts/install_coyote.ps1 b/scripts/install_coyote.ps1
index 3103789..052892a 100644
--- a/scripts/install_coyote.ps1
+++ b/scripts/install_coyote.ps1
@@ -16,6 +16,8 @@ param(
[string]$BinDir = $env:BIN_DIR
)
+if ($Version -and $Version -match '^[0-9]') { $Version = "v$Version" }
+
$Repo = 'Dark-Alex-17/coyote'
function Write-Info($msg) { Write-Host "[coyote-install] $msg" }
@@ -89,105 +91,127 @@ if ($os -eq 'windows') {
$tmp = New-Item -ItemType Directory -Force -Path ([IO.Path]::Combine([IO.Path]::GetTempPath(), "coyote-$(Get-Random)"))
-$exec = if ($isWin) { 'coyote.exe' } else { 'coyote' }
-$dest = Join-Path $BinDir $exec
+try {
+ $exec = if ($isWin) { 'coyote.exe' } else { 'coyote' }
+ $dest = Join-Path $BinDir $exec
-$installed = $false
-$tried = @()
-$attempt = 0
-foreach ($c in $candidates) {
- $asset = $release.assets | Where-Object { $_.name -eq $c } | Select-Object -First 1
- if (-not $asset) {
- $tried += "${c}: no matching release asset"
- continue
- }
+ $installed = $false
+ $tried = @()
+ $attempt = 0
+ foreach ($c in $candidates) {
+ $asset = $release.assets | Where-Object { $_.name -eq $c } | Select-Object -First 1
+ if (-not $asset) {
+ $tried += "${c}: no matching release asset"
+ continue
+ }
- $attempt++
- $work = New-Item -ItemType Directory -Force -Path (Join-Path $tmp.FullName "attempt-$attempt")
+ $attempt++
+ $work = New-Item -ItemType Directory -Force -Path (Join-Path $tmp.FullName "attempt-$attempt")
- Write-Info "Selected asset: $($asset.name)"
- Write-Info "Download URL: $($asset.browser_download_url)"
+ Write-Info "Selected asset: $($asset.name)"
+ Write-Info "Download URL: $($asset.browser_download_url)"
- $archive = Join-Path $work.FullName 'asset'
- try {
- Invoke-WebRequest -UseBasicParsing -Headers @{ 'User-Agent' = 'coyote-installer' } -Uri $asset.browser_download_url -OutFile $archive
- } catch {
- Write-Info "Failed to download ${c}; trying next candidate. $_"
- $tried += "${c}: download failed"
- continue
- }
+ $archive = Join-Path $work.FullName 'asset'
+ try {
+ Invoke-WebRequest -UseBasicParsing -Headers @{ 'User-Agent' = 'coyote-installer' } -Uri $asset.browser_download_url -OutFile $archive
+ } catch {
+ Write-Info "Failed to download ${c}; trying next candidate. $_"
+ $tried += "${c}: download failed"
+ continue
+ }
- $extractDir = Join-Path $work.FullName 'extract'; New-Item -ItemType Directory -Force -Path $extractDir | Out-Null
+ $extractDir = Join-Path $work.FullName 'extract'; New-Item -ItemType Directory -Force -Path $extractDir | Out-Null
- try {
- if ($asset.name -match '\.zip$') {
- Add-Type -AssemblyName System.IO.Compression.FileSystem
- [System.IO.Compression.ZipFile]::ExtractToDirectory($archive, $extractDir)
- } elseif ($asset.name -match '\.tar\.gz$' -or $asset.name -match '\.tgz$') {
- $tar = Get-Command tar -ErrorAction SilentlyContinue
- if ($tar) { & $tar.Source -xzf $archive -C $extractDir }
- else { throw "Asset is tar archive but 'tar' is not available." }
- } else {
- try { Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory($archive, $extractDir) }
- catch {
+ try {
+ if ($asset.name -match '\.zip$') {
+ Add-Type -AssemblyName System.IO.Compression.FileSystem
+ [System.IO.Compression.ZipFile]::ExtractToDirectory($archive, $extractDir)
+ } elseif ($asset.name -match '\.tar\.gz$' -or $asset.name -match '\.tgz$') {
$tar = Get-Command tar -ErrorAction SilentlyContinue
- if ($tar) { & $tar.Source -xf $archive -C $extractDir } else { throw "Unknown archive format; neither zip nor tar workable." }
+ if ($tar) { & $tar.Source -xzf $archive -C $extractDir }
+ else { throw "Asset is tar archive but 'tar' is not available." }
+ } else {
+ try { Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory($archive, $extractDir) }
+ catch {
+ $tar = Get-Command tar -ErrorAction SilentlyContinue
+ if ($tar) { & $tar.Source -xf $archive -C $extractDir } else { throw "Unknown archive format; neither zip nor tar workable." }
+ }
+ }
+ } catch {
+ Write-Info "Failed to extract ${c}; trying next candidate. $_"
+ $tried += "${c}: extract failed"
+ continue
+ }
+
+ $bin = $null
+ Get-ChildItem -Recurse -File $extractDir | ForEach-Object {
+ if ($isWin) { if ($_.Name -ieq 'coyote.exe') { $bin = $_.FullName } }
+ else { if ($_.Name -ieq 'coyote') { $bin = $_.FullName } }
+ }
+ if (-not $bin) {
+ Write-Info "Could not find coyote binary inside ${c}; trying next candidate"
+ $tried += "${c}: no coyote binary in archive"
+ continue
+ }
+
+ if (-not $isWin) { try { & chmod +x -- $bin } catch {} }
+
+ $works = $false
+ try { & $bin --version *> $null; if ($LASTEXITCODE -eq 0) { $works = $true } } catch { }
+ if (-not $works -and -not $isWin) {
+ # The temp dir may live on a noexec mount; retry from a probe file in
+ # the install directory before rejecting.
+ $probe = Join-Path $BinDir ".coyote-install-probe-$PID"
+ try {
+ Copy-Item -Force $bin $probe
+ & chmod +x -- $probe
+ & $probe --version *> $null
+ if ($LASTEXITCODE -eq 0) { $works = $true }
+ } catch { } finally {
+ Remove-Item -Force -ErrorAction SilentlyContinue $probe
}
}
- } catch {
- Write-Info "Failed to extract ${c}; trying next candidate. $_"
- $tried += "${c}: extract failed"
- continue
+ if (-not $works) {
+ Write-Info "Downloaded $c but it failed to run on this system; trying next candidate"
+ $tried += "${c}: binary failed to run on this system"
+ continue
+ }
+
+ Copy-Item -Force $bin $dest
+ Write-Info "Installed: $dest"
+ $installed = $true
+ break
}
- $bin = $null
- Get-ChildItem -Recurse -File $extractDir | ForEach-Object {
- if ($isWin) { if ($_.Name -ieq 'coyote.exe') { $bin = $_.FullName } }
- else { if ($_.Name -ieq 'coyote') { $bin = $_.FullName } }
- }
- if (-not $bin) {
- Write-Info "Could not find coyote binary inside ${c}; trying next candidate"
- $tried += "${c}: no coyote binary in archive"
- continue
+ if (-not $installed) {
+ Write-Error "No usable asset found for $os-$arch. Tried:"
+ $tried | ForEach-Object { Write-Error " - $_" }
+ exit 1
}
- if (-not $isWin) { try { & chmod +x -- $bin } catch {} }
-
- $works = $false
- try { & $bin --version *> $null; if ($LASTEXITCODE -eq 0) { $works = $true } } catch { }
- if (-not $works) {
- Write-Info "Downloaded $c but it failed to run on this system; trying next candidate"
- $tried += "${c}: binary failed to run on this system"
- continue
- }
-
- Copy-Item -Force $bin $dest
- Write-Info "Installed: $dest"
- $installed = $true
- break
-}
-
-if (-not $installed) {
- Write-Error "No usable asset found for $os-$arch. Tried:"
- $tried | ForEach-Object { Write-Error " - $_" }
- exit 1
-}
-
-if ($isWin) {
- $pathParts = ($env:Path -split ';') | Where-Object { $_ -ne '' }
- if ($pathParts -notcontains $BinDir) {
- $userPath = [Environment]::GetEnvironmentVariable('Path', 'User'); if (-not $userPath) { $userPath = '' }
- if (-not ($userPath -split ';' | Where-Object { $_ -eq $BinDir })) {
- $newUserPath = if ($userPath.Trim().Length -gt 0) { "$userPath;$BinDir" } else { $BinDir }
- [Environment]::SetEnvironmentVariable('Path', $newUserPath, 'User')
- Write-Info "Added to User PATH: $BinDir (restart shell to take effect)"
+ if ($isWin) {
+ $pathParts = ($env:Path -split ';') | Where-Object { $_ -ne '' }
+ if ($pathParts -notcontains $BinDir) {
+ # Read/write the User PATH via the registry directly: the [Environment]
+ # round-trip expands %VAR% entries and bakes them in on write.
+ $regKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment', $true)
+ if ($regKey) {
+ $userPath = [string]$regKey.GetValue('Path', '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
+ if (-not (($userPath -split ';') -contains $BinDir)) {
+ $newUserPath = if ($userPath.Trim().Length -gt 0) { "$userPath;$BinDir" } else { $BinDir }
+ $regKey.SetValue('Path', $newUserPath, [Microsoft.Win32.RegistryValueKind]::ExpandString)
+ Write-Info "Added to User PATH: $BinDir (restart shell to take effect)"
+ }
+ $regKey.Close()
+ }
+ }
+ } else {
+ if (-not ($env:PATH -split ':' | Where-Object { $_ -eq $BinDir })) {
+ Write-Info "Note: $BinDir is not in PATH. Add it to your shell profile."
}
}
-} else {
- if (-not ($env:PATH -split ':' | Where-Object { $_ -eq $BinDir })) {
- Write-Info "Note: $BinDir is not in PATH. Add it to your shell profile."
- }
+
+ Write-Info "Done. Try: coyote --help"
+} finally {
+ Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $tmp
}
-
-Write-Info "Done. Try: coyote --help"
-
diff --git a/scripts/install_coyote.sh b/scripts/install_coyote.sh
index 9692e36..58d68cf 100755
--- a/scripts/install_coyote.sh
+++ b/scripts/install_coyote.sh
@@ -13,8 +13,6 @@ set -euo pipefail
# --bin-dir
Install directory (default: /usr/local/bin or ~/.local/bin). Or set BIN_DIR.
REPO="Dark-Alex-17/coyote"
-VERSION="${COYOTE_VERSION:-}"
-BIN_DIR="${BIN_DIR:-}"
usage() {
echo "coyote installer (Linux/macOS)"
@@ -25,24 +23,6 @@ usage() {
echo " -h, --help Show help"
}
-while [[ $# -gt 0 ]]; do
- case "$1" in
- --version) VERSION="$2"; shift 2;;
- --bin-dir) BIN_DIR="$2"; shift 2;;
- -h|--help) usage; exit 0;;
- *) echo "Unknown argument: $1" >&2; usage; exit 2;;
- esac
-done
-
-if [[ -z "${BIN_DIR}" ]]; then
- if [[ -w "/usr/local/bin" ]]; then
- BIN_DIR="/usr/local/bin"
- else
- BIN_DIR="${HOME}/.local/bin"
- fi
-fi
-mkdir -p "${BIN_DIR}"
-
log() {
echo "[coyote-install] $*"
}
@@ -54,42 +34,6 @@ need_cmd() {
fi
}
-need_cmd uname
-need_cmd mktemp
-need_cmd tar
-
-if command -v curl >/dev/null 2>&1; then
- DL=curl
-elif command -v wget >/dev/null 2>&1; then
- DL=wget
-else
- echo "Error: need curl or wget" >&2
- exit 1
-fi
-
-UNAME_OS=$(uname -s | tr '[:upper:]' '[:lower:]')
-case "$UNAME_OS" in
- linux) OS=linux ;;
- darwin) OS=darwin ;;
- *) echo "Error: unsupported OS '$UNAME_OS'" >&2; exit 1;;
-esac
-
-UNAME_ARCH=$(uname -m)
-case "$UNAME_ARCH" in
- x86_64|amd64) ARCH=x86_64 ;;
- aarch64|arm64) ARCH=aarch64 ;;
- *) echo "Error: unsupported arch '$UNAME_ARCH'" >&2; exit 1;;
-esac
-
-log "Target: ${OS}-${ARCH}"
-
-API_BASE="https://api.github.com/repos/${REPO}/releases"
-if [[ -z "${VERSION}" ]]; then
- RELEASE_URL="${API_BASE}/latest"
-else
- RELEASE_URL="${API_BASE}/tags/${VERSION}"
-fi
-
http_get() {
if [[ "$DL" == "curl" ]]; then
curl -fsSL -H 'User-Agent: coyote-installer' "$1"
@@ -98,164 +42,240 @@ http_get() {
fi
}
-TMPDIR="$(mktemp -d)"
-trap 'rm -rf "$TMPDIR"' EXIT
-
-log "Fetching release metadata from $RELEASE_URL"
-JSON="$TMPDIR/release.json"
-if ! http_get "$RELEASE_URL" > "$JSON"; then
- echo "Error: failed to fetch release metadata. Check version tag." >&2
- exit 1
-fi
-
-ASSET_CANDIDATES=()
-if [[ "$OS" == "darwin" ]]; then
- if [[ "$ARCH" == "x86_64" ]]; then
- ASSET_CANDIDATES+=("coyote-x86_64-apple-darwin.tar.gz")
- else
- ASSET_CANDIDATES+=("coyote-aarch64-apple-darwin.tar.gz")
+smoke_test() {
+ # The scratch dir may live on a noexec mount; if running in place fails,
+ # retry from a probe file in the install directory before rejecting.
+ local bin="$1"
+ if "$bin" --version >/dev/null 2>&1; then return 0; fi
+ local probe="${BIN_DIR}/.coyote-install-probe.$$"
+ local ok=1
+ if cp "$bin" "$probe" 2>/dev/null && chmod +x "$probe" 2>/dev/null; then
+ if "$probe" --version >/dev/null 2>&1; then ok=0; fi
fi
-elif [[ "$OS" == "linux" ]]; then
- LIBC="musl"
- if command -v getconf >/dev/null 2>&1 && getconf GNU_LIBC_VERSION >/dev/null 2>&1; then LIBC="gnu"; fi
- if ldd --version 2>&1 | grep -qi glibc; then LIBC="gnu"; fi
+ rm -f "$probe"
+ return "$ok"
+}
- if [[ "$LIBC" == "gnu" ]]; then
- # The gnu binary dynamically links OpenSSL 3. On Debian/Ubuntu, ldconfig lives
- # in /usr/sbin, which is often missing from non-root PATHs, so try its known
- # locations and fall back to probing the usual library directories directly.
- LIBSSL3=""
- for LDCONFIG in ldconfig /sbin/ldconfig /usr/sbin/ldconfig; do
- if command -v "$LDCONFIG" >/dev/null 2>&1; then
- if "$LDCONFIG" -p 2>/dev/null | grep -q 'libssl\.so\.3'; then LIBSSL3="yes"; fi
+main() {
+ VERSION="${COYOTE_VERSION:-}"
+ BIN_DIR="${BIN_DIR:-}"
+
+ while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --version) VERSION="$2"; shift 2;;
+ --bin-dir) BIN_DIR="$2"; shift 2;;
+ -h|--help) usage; exit 0;;
+ *) echo "Unknown argument: $1" >&2; usage; exit 2;;
+ esac
+ done
+
+ if [[ -n "$VERSION" && "$VERSION" =~ ^[0-9] ]]; then VERSION="v${VERSION}"; fi
+
+ if [[ -z "${BIN_DIR}" ]]; then
+ if [[ -w "/usr/local/bin" ]]; then
+ BIN_DIR="/usr/local/bin"
+ else
+ BIN_DIR="${HOME}/.local/bin"
+ fi
+ fi
+ mkdir -p "${BIN_DIR}"
+
+ need_cmd uname
+ need_cmd mktemp
+ need_cmd tar
+
+ if command -v curl >/dev/null 2>&1; then
+ DL=curl
+ elif command -v wget >/dev/null 2>&1; then
+ DL=wget
+ else
+ echo "Error: need curl or wget" >&2
+ exit 1
+ fi
+
+ UNAME_OS=$(uname -s | tr '[:upper:]' '[:lower:]')
+ case "$UNAME_OS" in
+ linux) OS=linux ;;
+ darwin) OS=darwin ;;
+ *) echo "Error: unsupported OS '$UNAME_OS'" >&2; exit 1;;
+ esac
+
+ UNAME_ARCH=$(uname -m)
+ case "$UNAME_ARCH" in
+ x86_64|amd64) ARCH=x86_64 ;;
+ aarch64|arm64) ARCH=aarch64 ;;
+ *) echo "Error: unsupported arch '$UNAME_ARCH'" >&2; exit 1;;
+ esac
+
+ log "Target: ${OS}-${ARCH}"
+
+ API_BASE="https://api.github.com/repos/${REPO}/releases"
+ if [[ -z "${VERSION}" ]]; then
+ RELEASE_URL="${API_BASE}/latest"
+ else
+ RELEASE_URL="${API_BASE}/tags/${VERSION}"
+ fi
+
+ WORKDIR="$(mktemp -d)"
+ trap 'rm -rf "$WORKDIR"; rm -f "${BIN_DIR}/.coyote-install-probe.$$"' EXIT
+
+ log "Fetching release metadata from $RELEASE_URL"
+ JSON="$WORKDIR/release.json"
+ if ! http_get "$RELEASE_URL" > "$JSON"; then
+ echo "Error: failed to fetch release metadata. Check version tag." >&2
+ exit 1
+ fi
+
+ ASSET_CANDIDATES=()
+ if [[ "$OS" == "darwin" ]]; then
+ if [[ "$ARCH" == "x86_64" ]]; then
+ ASSET_CANDIDATES+=("coyote-x86_64-apple-darwin.tar.gz")
+ else
+ ASSET_CANDIDATES+=("coyote-aarch64-apple-darwin.tar.gz")
+ fi
+ elif [[ "$OS" == "linux" ]]; then
+ LIBC="musl"
+ if command -v getconf >/dev/null 2>&1 && getconf GNU_LIBC_VERSION >/dev/null 2>&1; then LIBC="gnu"; fi
+ if ldd --version 2>&1 | grep -qi glibc; then LIBC="gnu"; fi
+
+ if [[ "$LIBC" == "gnu" ]]; then
+ # The gnu binary dynamically links OpenSSL 3. On Debian/Ubuntu, ldconfig lives
+ # in /usr/sbin, which is often missing from non-root PATHs, so try its known
+ # locations and fall back to probing the usual library directories directly.
+ LIBSSL3=""
+ for LDCONFIG in ldconfig /sbin/ldconfig /usr/sbin/ldconfig; do
+ if command -v "$LDCONFIG" >/dev/null 2>&1; then
+ if "$LDCONFIG" -p 2>/dev/null | grep -q 'libssl\.so\.3'; then LIBSSL3="yes"; fi
+ break
+ fi
+ done
+ if [[ -z "$LIBSSL3" ]]; then
+ for LIBSSL_CANDIDATE in /usr/lib/*/libssl.so.3 /lib/*/libssl.so.3 /usr/lib64/libssl.so.3 /usr/lib/libssl.so.3 /usr/local/lib/libssl.so.3 /usr/local/lib/*/libssl.so.3; do
+ if [[ -e "$LIBSSL_CANDIDATE" ]]; then LIBSSL3="yes"; break; fi
+ done
+ fi
+ if [[ -n "$LIBSSL3" ]]; then
+ ASSET_CANDIDATES+=("coyote-${ARCH}-unknown-linux-gnu.tar.gz")
+ else
+ log "glibc detected but OpenSSL 3 (libssl.so.3) not found; using musl build"
+ fi
+ fi
+
+ ASSET_CANDIDATES+=("coyote-${ARCH}-unknown-linux-musl.tar.gz")
+ else
+ echo "Error: unsupported OS for this installer: $OS" >&2; exit 1
+ fi
+
+ DL_URLS=$(grep -oE '"browser_download_url":[[:space:]]*"[^"]+"' "$JSON" \
+ | sed -E 's/.*"browser_download_url":[[:space:]]*"//; s/"$//' \
+ || true)
+
+ INSTALLED=""
+ TRIED=()
+ ATTEMPT=0
+ for candidate in "${ASSET_CANDIDATES[@]}"; do
+ ASSET_URL=""
+ while IFS= read -r url; do
+ [[ -z "$url" ]] && continue
+ if [[ "$url" == */"$candidate" ]]; then
+ ASSET_URL="$url"
break
fi
- done
- if [[ -z "$LIBSSL3" ]]; then
- for LIBSSL_CANDIDATE in /usr/lib/*/libssl.so.3 /lib/*/libssl.so.3 /usr/lib64/libssl.so.3 /usr/lib/libssl.so.3 /usr/local/lib/libssl.so.3 /usr/local/lib/*/libssl.so.3; do
- if [[ -e "$LIBSSL_CANDIDATE" ]]; then LIBSSL3="yes"; break; fi
- done
+ done <<< "$DL_URLS"
+
+ if [[ -z "$ASSET_URL" ]]; then
+ TRIED+=("$candidate: no matching release asset")
+ continue
fi
- if [[ -n "$LIBSSL3" ]]; then
- ASSET_CANDIDATES+=("coyote-${ARCH}-unknown-linux-gnu.tar.gz")
+
+ ATTEMPT=$((ATTEMPT + 1))
+ WORK="$WORKDIR/attempt-$ATTEMPT"
+ mkdir -p "$WORK"
+
+ log "Selected asset: $candidate"
+ log "Download URL: $ASSET_URL"
+
+ ARCHIVE="$WORK/asset"
+ if [[ "$DL" == "curl" ]]; then
+ if ! curl -fL -H 'User-Agent: coyote-installer' "$ASSET_URL" -o "$ARCHIVE"; then
+ log "Failed to download $candidate; trying next candidate"
+ TRIED+=("$candidate: download failed")
+ continue
+ fi
else
- log "glibc detected but OpenSSL 3 (libssl.so.3) not found; using musl build"
+ if ! wget -q --header='User-Agent: coyote-installer' "$ASSET_URL" -O "$ARCHIVE"; then
+ log "Failed to download $candidate; trying next candidate"
+ TRIED+=("$candidate: download failed")
+ continue
+ fi
fi
- fi
- ASSET_CANDIDATES+=("coyote-${ARCH}-unknown-linux-musl.tar.gz")
-else
- echo "Error: unsupported OS for this installer: $OS" >&2; exit 1
-fi
+ EXTRACTED_DIR="$WORK/extracted"; mkdir -p "$EXTRACTED_DIR"
-DL_URLS=$(grep -oE '"browser_download_url":[[:space:]]*"[^"]+"' "$JSON" \
- | sed -E 's/.*"browser_download_url":[[:space:]]*"//; s/"$//' \
- || true)
-
-INSTALLED=""
-TRIED=()
-ATTEMPT=0
-for candidate in "${ASSET_CANDIDATES[@]}"; do
- ASSET_URL=""
- while IFS= read -r url; do
- [[ -z "$url" ]] && continue
- if [[ "$url" == */"$candidate" ]]; then
- ASSET_URL="$url"
- break
- fi
- done <<< "$DL_URLS"
-
- if [[ -z "$ASSET_URL" ]]; then
- TRIED+=("$candidate: no matching release asset")
- continue
- fi
-
- ATTEMPT=$((ATTEMPT + 1))
- WORK="$TMPDIR/attempt-$ATTEMPT"
- mkdir -p "$WORK"
-
- log "Selected asset: $candidate"
- log "Download URL: $ASSET_URL"
-
- ARCHIVE="$WORK/asset"
- if [[ "$DL" == "curl" ]]; then
- if ! curl -fL -H 'User-Agent: coyote-installer' "$ASSET_URL" -o "$ARCHIVE"; then
- log "Failed to download $candidate; trying next candidate"
- TRIED+=("$candidate: download failed")
- continue
- fi
- else
- if ! wget -q --header='User-Agent: coyote-installer' "$ASSET_URL" -O "$ARCHIVE"; then
- log "Failed to download $candidate; trying next candidate"
- TRIED+=("$candidate: download failed")
- continue
- fi
- fi
-
- EXTRACTED_DIR="$WORK/extracted"; mkdir -p "$EXTRACTED_DIR"
-
- if tar -tf "$ARCHIVE" >/dev/null 2>&1; then
- if ! tar -xzf "$ARCHIVE" -C "$EXTRACTED_DIR"; then
- log "Failed to extract $candidate; trying next candidate"
- TRIED+=("$candidate: extract failed")
- continue
- fi
- else
- if command -v unzip >/dev/null 2>&1; then
- if ! unzip -q "$ARCHIVE" -d "$EXTRACTED_DIR"; then
+ if tar -tf "$ARCHIVE" >/dev/null 2>&1; then
+ if ! tar -xzf "$ARCHIVE" -C "$EXTRACTED_DIR"; then
log "Failed to extract $candidate; trying next candidate"
TRIED+=("$candidate: extract failed")
continue
fi
else
- log "Unknown archive format for $candidate and 'unzip' is not available; trying next candidate"
- TRIED+=("$candidate: unknown archive format and 'unzip' unavailable")
+ if command -v unzip >/dev/null 2>&1; then
+ if ! unzip -q "$ARCHIVE" -d "$EXTRACTED_DIR"; then
+ log "Failed to extract $candidate; trying next candidate"
+ TRIED+=("$candidate: extract failed")
+ continue
+ fi
+ else
+ log "Unknown archive format for $candidate and 'unzip' is not available; trying next candidate"
+ TRIED+=("$candidate: unknown archive format and 'unzip' unavailable")
+ continue
+ fi
+ fi
+
+ BIN_PATH=""
+ while IFS= read -r -d '' f; do
+ base=$(basename "$f")
+ if [[ "$base" == "coyote" ]]; then
+ BIN_PATH="$f"
+ break
+ fi
+ done < <(find "$EXTRACTED_DIR" -type f -print0)
+
+ if [[ -z "$BIN_PATH" ]]; then
+ log "Could not find 'coyote' binary in $candidate; trying next candidate"
+ TRIED+=("$candidate: no 'coyote' binary in archive")
continue
fi
- fi
- BIN_PATH=""
- while IFS= read -r -d '' f; do
- base=$(basename "$f")
- if [[ "$base" == "coyote" ]]; then
- BIN_PATH="$f"
- break
+ chmod +x "$BIN_PATH"
+ if ! smoke_test "$BIN_PATH"; then
+ log "Downloaded $candidate but it failed to run on this system; trying next candidate"
+ TRIED+=("$candidate: binary failed to run on this system")
+ continue
fi
- done < <(find "$EXTRACTED_DIR" -type f -print0)
- if [[ -z "$BIN_PATH" ]]; then
- log "Could not find 'coyote' binary in $candidate; trying next candidate"
- TRIED+=("$candidate: no 'coyote' binary in archive")
- continue
+ install -m 0755 "$BIN_PATH" "${BIN_DIR}/coyote"
+ INSTALLED="$candidate"
+ break
+ done
+
+ if [[ -z "$INSTALLED" ]]; then
+ echo "Error: no usable asset found for ${OS}-${ARCH}. Tried:" >&2
+ for t in "${TRIED[@]}"; do echo " - $t" >&2; done
+ exit 1
fi
- chmod +x "$BIN_PATH"
- if ! "$BIN_PATH" --version >/dev/null 2>&1; then
- log "Downloaded $candidate but it failed to run on this system; trying next candidate"
- TRIED+=("$candidate: binary failed to run on this system")
- continue
- fi
+ log "Installed: ${BIN_DIR}/coyote"
- install -m 0755 "$BIN_PATH" "${BIN_DIR}/coyote"
- INSTALLED="$candidate"
- break
-done
+ case ":$PATH:" in
+ *":${BIN_DIR}:"*) ;;
+ *)
+ log "Note: ${BIN_DIR} is not in PATH. Add it, e.g.:"
+ log " export PATH=\"${BIN_DIR}:\$PATH\""
+ ;;
+ esac
-if [[ -z "$INSTALLED" ]]; then
- echo "Error: no usable asset found for ${OS}-${ARCH}. Tried:" >&2
- for t in "${TRIED[@]}"; do echo " - $t" >&2; done
- exit 1
-fi
-
-log "Installed: ${BIN_DIR}/coyote"
-
-case ":$PATH:" in
- *":${BIN_DIR}:"*) ;;
- *)
- log "Note: ${BIN_DIR} is not in PATH. Add it, e.g.:"
- log " export PATH=\"${BIN_DIR}:\$PATH\""
- ;;
-esac
-
-log "Done. Try: coyote --help"
+ log "Done. Try: coyote --help"
+}
+main "$@"