fix: Harden install scripts: detect libssl3 without ldconfig on PATH, survive noexec tmp dirs, and guard against partial curl|bash execution
This commit is contained in:
+41
-17
@@ -16,6 +16,8 @@ param(
|
|||||||
[string]$BinDir = $env:BIN_DIR
|
[string]$BinDir = $env:BIN_DIR
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if ($Version -and $Version -match '^[0-9]') { $Version = "v$Version" }
|
||||||
|
|
||||||
$Repo = 'Dark-Alex-17/coyote'
|
$Repo = 'Dark-Alex-17/coyote'
|
||||||
|
|
||||||
function Write-Info($msg) { Write-Host "[coyote-install] $msg" }
|
function Write-Info($msg) { Write-Host "[coyote-install] $msg" }
|
||||||
@@ -89,13 +91,14 @@ if ($os -eq 'windows') {
|
|||||||
|
|
||||||
$tmp = New-Item -ItemType Directory -Force -Path ([IO.Path]::Combine([IO.Path]::GetTempPath(), "coyote-$(Get-Random)"))
|
$tmp = New-Item -ItemType Directory -Force -Path ([IO.Path]::Combine([IO.Path]::GetTempPath(), "coyote-$(Get-Random)"))
|
||||||
|
|
||||||
$exec = if ($isWin) { 'coyote.exe' } else { 'coyote' }
|
try {
|
||||||
$dest = Join-Path $BinDir $exec
|
$exec = if ($isWin) { 'coyote.exe' } else { 'coyote' }
|
||||||
|
$dest = Join-Path $BinDir $exec
|
||||||
|
|
||||||
$installed = $false
|
$installed = $false
|
||||||
$tried = @()
|
$tried = @()
|
||||||
$attempt = 0
|
$attempt = 0
|
||||||
foreach ($c in $candidates) {
|
foreach ($c in $candidates) {
|
||||||
$asset = $release.assets | Where-Object { $_.name -eq $c } | Select-Object -First 1
|
$asset = $release.assets | Where-Object { $_.name -eq $c } | Select-Object -First 1
|
||||||
if (-not $asset) {
|
if (-not $asset) {
|
||||||
$tried += "${c}: no matching release asset"
|
$tried += "${c}: no matching release asset"
|
||||||
@@ -155,6 +158,19 @@ foreach ($c in $candidates) {
|
|||||||
|
|
||||||
$works = $false
|
$works = $false
|
||||||
try { & $bin --version *> $null; if ($LASTEXITCODE -eq 0) { $works = $true } } catch { }
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
if (-not $works) {
|
if (-not $works) {
|
||||||
Write-Info "Downloaded $c but it failed to run on this system; trying next candidate"
|
Write-Info "Downloaded $c but it failed to run on this system; trying next candidate"
|
||||||
$tried += "${c}: binary failed to run on this system"
|
$tried += "${c}: binary failed to run on this system"
|
||||||
@@ -165,29 +181,37 @@ foreach ($c in $candidates) {
|
|||||||
Write-Info "Installed: $dest"
|
Write-Info "Installed: $dest"
|
||||||
$installed = $true
|
$installed = $true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-not $installed) {
|
if (-not $installed) {
|
||||||
Write-Error "No usable asset found for $os-$arch. Tried:"
|
Write-Error "No usable asset found for $os-$arch. Tried:"
|
||||||
$tried | ForEach-Object { Write-Error " - $_" }
|
$tried | ForEach-Object { Write-Error " - $_" }
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($isWin) {
|
if ($isWin) {
|
||||||
$pathParts = ($env:Path -split ';') | Where-Object { $_ -ne '' }
|
$pathParts = ($env:Path -split ';') | Where-Object { $_ -ne '' }
|
||||||
if ($pathParts -notcontains $BinDir) {
|
if ($pathParts -notcontains $BinDir) {
|
||||||
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User'); if (-not $userPath) { $userPath = '' }
|
# Read/write the User PATH via the registry directly: the [Environment]
|
||||||
if (-not ($userPath -split ';' | Where-Object { $_ -eq $BinDir })) {
|
# 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 }
|
$newUserPath = if ($userPath.Trim().Length -gt 0) { "$userPath;$BinDir" } else { $BinDir }
|
||||||
[Environment]::SetEnvironmentVariable('Path', $newUserPath, 'User')
|
$regKey.SetValue('Path', $newUserPath, [Microsoft.Win32.RegistryValueKind]::ExpandString)
|
||||||
Write-Info "Added to User PATH: $BinDir (restart shell to take effect)"
|
Write-Info "Added to User PATH: $BinDir (restart shell to take effect)"
|
||||||
}
|
}
|
||||||
|
$regKey.Close()
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
} else {
|
||||||
if (-not ($env:PATH -split ':' | Where-Object { $_ -eq $BinDir })) {
|
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 "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"
|
|
||||||
|
|
||||||
|
|||||||
+101
-81
@@ -13,8 +13,6 @@ set -euo pipefail
|
|||||||
# --bin-dir <dir> Install directory (default: /usr/local/bin or ~/.local/bin). Or set BIN_DIR.
|
# --bin-dir <dir> Install directory (default: /usr/local/bin or ~/.local/bin). Or set BIN_DIR.
|
||||||
|
|
||||||
REPO="Dark-Alex-17/coyote"
|
REPO="Dark-Alex-17/coyote"
|
||||||
VERSION="${COYOTE_VERSION:-}"
|
|
||||||
BIN_DIR="${BIN_DIR:-}"
|
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
echo "coyote installer (Linux/macOS)"
|
echo "coyote installer (Linux/macOS)"
|
||||||
@@ -25,24 +23,6 @@ usage() {
|
|||||||
echo " -h, --help Show help"
|
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() {
|
log() {
|
||||||
echo "[coyote-install] $*"
|
echo "[coyote-install] $*"
|
||||||
}
|
}
|
||||||
@@ -54,42 +34,6 @@ need_cmd() {
|
|||||||
fi
|
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() {
|
http_get() {
|
||||||
if [[ "$DL" == "curl" ]]; then
|
if [[ "$DL" == "curl" ]]; then
|
||||||
curl -fsSL -H 'User-Agent: coyote-installer' "$1"
|
curl -fsSL -H 'User-Agent: coyote-installer' "$1"
|
||||||
@@ -98,24 +42,98 @@ http_get() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
TMPDIR="$(mktemp -d)"
|
smoke_test() {
|
||||||
trap 'rm -rf "$TMPDIR"' EXIT
|
# 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
|
||||||
|
rm -f "$probe"
|
||||||
|
return "$ok"
|
||||||
|
}
|
||||||
|
|
||||||
log "Fetching release metadata from $RELEASE_URL"
|
main() {
|
||||||
JSON="$TMPDIR/release.json"
|
VERSION="${COYOTE_VERSION:-}"
|
||||||
if ! http_get "$RELEASE_URL" > "$JSON"; then
|
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
|
echo "Error: failed to fetch release metadata. Check version tag." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ASSET_CANDIDATES=()
|
ASSET_CANDIDATES=()
|
||||||
if [[ "$OS" == "darwin" ]]; then
|
if [[ "$OS" == "darwin" ]]; then
|
||||||
if [[ "$ARCH" == "x86_64" ]]; then
|
if [[ "$ARCH" == "x86_64" ]]; then
|
||||||
ASSET_CANDIDATES+=("coyote-x86_64-apple-darwin.tar.gz")
|
ASSET_CANDIDATES+=("coyote-x86_64-apple-darwin.tar.gz")
|
||||||
else
|
else
|
||||||
ASSET_CANDIDATES+=("coyote-aarch64-apple-darwin.tar.gz")
|
ASSET_CANDIDATES+=("coyote-aarch64-apple-darwin.tar.gz")
|
||||||
fi
|
fi
|
||||||
elif [[ "$OS" == "linux" ]]; then
|
elif [[ "$OS" == "linux" ]]; then
|
||||||
LIBC="musl"
|
LIBC="musl"
|
||||||
if command -v getconf >/dev/null 2>&1 && getconf GNU_LIBC_VERSION >/dev/null 2>&1; then LIBC="gnu"; fi
|
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 ldd --version 2>&1 | grep -qi glibc; then LIBC="gnu"; fi
|
||||||
@@ -144,18 +162,18 @@ elif [[ "$OS" == "linux" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
ASSET_CANDIDATES+=("coyote-${ARCH}-unknown-linux-musl.tar.gz")
|
ASSET_CANDIDATES+=("coyote-${ARCH}-unknown-linux-musl.tar.gz")
|
||||||
else
|
else
|
||||||
echo "Error: unsupported OS for this installer: $OS" >&2; exit 1
|
echo "Error: unsupported OS for this installer: $OS" >&2; exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
DL_URLS=$(grep -oE '"browser_download_url":[[:space:]]*"[^"]+"' "$JSON" \
|
DL_URLS=$(grep -oE '"browser_download_url":[[:space:]]*"[^"]+"' "$JSON" \
|
||||||
| sed -E 's/.*"browser_download_url":[[:space:]]*"//; s/"$//' \
|
| sed -E 's/.*"browser_download_url":[[:space:]]*"//; s/"$//' \
|
||||||
|| true)
|
|| true)
|
||||||
|
|
||||||
INSTALLED=""
|
INSTALLED=""
|
||||||
TRIED=()
|
TRIED=()
|
||||||
ATTEMPT=0
|
ATTEMPT=0
|
||||||
for candidate in "${ASSET_CANDIDATES[@]}"; do
|
for candidate in "${ASSET_CANDIDATES[@]}"; do
|
||||||
ASSET_URL=""
|
ASSET_URL=""
|
||||||
while IFS= read -r url; do
|
while IFS= read -r url; do
|
||||||
[[ -z "$url" ]] && continue
|
[[ -z "$url" ]] && continue
|
||||||
@@ -171,7 +189,7 @@ for candidate in "${ASSET_CANDIDATES[@]}"; do
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
ATTEMPT=$((ATTEMPT + 1))
|
ATTEMPT=$((ATTEMPT + 1))
|
||||||
WORK="$TMPDIR/attempt-$ATTEMPT"
|
WORK="$WORKDIR/attempt-$ATTEMPT"
|
||||||
mkdir -p "$WORK"
|
mkdir -p "$WORK"
|
||||||
|
|
||||||
log "Selected asset: $candidate"
|
log "Selected asset: $candidate"
|
||||||
@@ -230,7 +248,7 @@ for candidate in "${ASSET_CANDIDATES[@]}"; do
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
chmod +x "$BIN_PATH"
|
chmod +x "$BIN_PATH"
|
||||||
if ! "$BIN_PATH" --version >/dev/null 2>&1; then
|
if ! smoke_test "$BIN_PATH"; then
|
||||||
log "Downloaded $candidate but it failed to run on this system; trying next candidate"
|
log "Downloaded $candidate but it failed to run on this system; trying next candidate"
|
||||||
TRIED+=("$candidate: binary failed to run on this system")
|
TRIED+=("$candidate: binary failed to run on this system")
|
||||||
continue
|
continue
|
||||||
@@ -239,23 +257,25 @@ for candidate in "${ASSET_CANDIDATES[@]}"; do
|
|||||||
install -m 0755 "$BIN_PATH" "${BIN_DIR}/coyote"
|
install -m 0755 "$BIN_PATH" "${BIN_DIR}/coyote"
|
||||||
INSTALLED="$candidate"
|
INSTALLED="$candidate"
|
||||||
break
|
break
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -z "$INSTALLED" ]]; then
|
if [[ -z "$INSTALLED" ]]; then
|
||||||
echo "Error: no usable asset found for ${OS}-${ARCH}. Tried:" >&2
|
echo "Error: no usable asset found for ${OS}-${ARCH}. Tried:" >&2
|
||||||
for t in "${TRIED[@]}"; do echo " - $t" >&2; done
|
for t in "${TRIED[@]}"; do echo " - $t" >&2; done
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Installed: ${BIN_DIR}/coyote"
|
log "Installed: ${BIN_DIR}/coyote"
|
||||||
|
|
||||||
case ":$PATH:" in
|
case ":$PATH:" in
|
||||||
*":${BIN_DIR}:"*) ;;
|
*":${BIN_DIR}:"*) ;;
|
||||||
*)
|
*)
|
||||||
log "Note: ${BIN_DIR} is not in PATH. Add it, e.g.:"
|
log "Note: ${BIN_DIR} is not in PATH. Add it, e.g.:"
|
||||||
log " export PATH=\"${BIN_DIR}:\$PATH\""
|
log " export PATH=\"${BIN_DIR}:\$PATH\""
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
log "Done. Try: coyote --help"
|
log "Done. Try: coyote --help"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
|
|||||||
Reference in New Issue
Block a user