Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions .github/workflows/install-script.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: Install Script

on:
push:
branches: [main]
paths:
- install.sh
- .github/workflows/install-script.yml
pull_request:
paths:
- install.sh
- .github/workflows/install-script.yml

permissions:
contents: read

concurrency:
group: install-script-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
name: Shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Shellcheck
run: shellcheck --shell=sh install.sh

install:
name: Install (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
defaults:
run:
shell: bash
env:
PUP_INSTALL_DIR: ${{ github.workspace }}/pup-bin
PUP_NO_BREW: "1"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install latest via pipe
run: sh < install.sh
- name: Verify latest version
run: |
latest="$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/DataDog/pup/releases/latest)"
latest="${latest##*/tag/v}"
"$PUP_INSTALL_DIR/pup" --version | tee version.txt
grep -F "$latest" version.txt
- name: Install pinned version with v prefix
run: PUP_VERSION=v1.27.0 sh install.sh
- name: Verify pinned version
run: |
"$PUP_INSTALL_DIR/pup" --version | tee version.txt
grep -F "1.27.0" version.txt
- name: Nonexistent version fails
run: |
if PUP_VERSION=0.0.0 sh install.sh; then
echo "expected install of a nonexistent version to fail"
exit 1
fi

install-brew:
name: Install via Homebrew (macOS)
runs-on: macos-latest
env:
HOMEBREW_NO_AUTO_UPDATE: "1"
HOMEBREW_NO_INSTALL_CLEANUP: "1"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install via pipe
run: sh < install.sh 2>&1 | tee install.log
- name: Verify Homebrew was used
run: |
grep -F "Homebrew detected" install.log
brew list --formula pup
pup --version
- name: Overrides skip Homebrew
run: |
PUP_VERSION=1.27.0 PUP_INSTALL_DIR="$RUNNER_TEMP/pup-bin" sh install.sh 2>&1 | tee override.log
if grep -F "Homebrew detected" override.log; then
echo "expected PUP_VERSION/PUP_INSTALL_DIR to bypass Homebrew"
exit 1
fi
"$RUNNER_TEMP/pup-bin/pup" --version | grep -F "1.27.0"
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,24 @@ list of commands as built.

## Installation

### Install Script (macOS/Linux/Windows)

```bash
curl -fsSL https://raw.githubusercontent.com/DataDog/pup/main/install.sh | sh
```

On macOS with Homebrew installed, the script runs `brew install datadog-labs/pack/pup`, unless `PUP_VERSION` or `PUP_INSTALL_DIR` is set. Otherwise (Linux, macOS without Homebrew, and Windows via Git Bash, MSYS2, or Cygwin) it downloads the release archive, verifies its SHA-256 checksum, and installs the binary to `~/.local/bin`. Environment variables override the defaults:

| Variable | Default | Description |
|----------|---------|-------------|
| `PUP_VERSION` | latest release | Version to install (e.g. `1.27.0`) |
| `PUP_INSTALL_DIR` | `~/.local/bin` | Directory to install the binary into |
| `PUP_NO_BREW` | unset | Set to `1` on macOS to skip Homebrew and download directly |

```bash
curl -fsSL https://raw.githubusercontent.com/DataDog/pup/main/install.sh | PUP_VERSION=1.27.0 PUP_INSTALL_DIR=/usr/local/bin sh
```

### Homebrew (macOS/Linux)

```bash
Expand Down
172 changes: 172 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#!/bin/sh
# Install the pup CLI (https://github.com/DataDog/pup).
#
# curl -fsSL https://raw.githubusercontent.com/DataDog/pup/main/install.sh | sh
#
# On macOS with Homebrew available, installs via `brew install datadog-labs/pack/pup`
# unless PUP_VERSION or PUP_INSTALL_DIR is set, since Homebrew cannot honor them.
# Everywhere else (Linux, macOS without Homebrew, Windows via Git Bash/MSYS2/Cygwin),
# downloads the release archive, verifies its SHA-256 checksum, and installs the binary.
#
# Environment variables:
# PUP_VERSION Version to install, e.g. 1.27.0 (default: latest release)
# PUP_INSTALL_DIR Directory for the binary (default: $HOME/.local/bin)
# PUP_NO_BREW Set to 1 to skip Homebrew on macOS and download directly

set -eu

REPO="DataDog/pup"
BREW_FORMULA="datadog-labs/pack/pup"

say() { printf 'pup-install: %s\n' "$*" >&2; }
die() {
say "error: $*"
exit 1
}

need() {
command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}

detect_os() {
case "$(uname -s)" in
Darwin) echo Darwin ;;
Linux) echo Linux ;;
MINGW* | MSYS* | CYGWIN*) echo Windows ;;
*) die "unsupported operating system: $(uname -s)" ;;
esac
}

detect_arch() {
os="$1"
arch="$(uname -m)"
case "$arch" in
x86_64 | amd64) arch=x86_64 ;;
arm64 | aarch64) arch=arm64 ;;
*) die "unsupported architecture: $arch" ;;
esac
# A shell running under Rosetta reports x86_64 on Apple Silicon.
if [ "$os" = Darwin ] && [ "$arch" = x86_64 ] &&
[ "$(sysctl -n sysctl.proc_translated 2>/dev/null || true)" = 1 ]; then
arch=arm64
fi
# Only an x86_64 Windows build is published; Windows on ARM runs it under emulation.
if [ "$os" = Windows ]; then
arch=x86_64
fi
echo "$arch"
}

latest_version() {
# Follow the /releases/latest redirect instead of the rate-limited GitHub API.
url="$(curl -fsSLI -o /dev/null -w '%{url_effective}' "https://github.com/${REPO}/releases/latest")" ||
die "could not resolve the latest release"
case "$url" in
*/tag/v*) echo "${url##*/tag/v}" ;;
*) die "unexpected latest release URL: $url" ;;
esac
}

sha256_of() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$1" | awk '{print $1}'
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$1" | awk '{print $1}'
else
die "sha256sum or shasum is required to verify the download"
fi
}

verify_checksum() {
file="$1"
checksums="$2"
name="$(basename "$file")"
expected="$(awk -v f="$name" '$2 == f {print $1}' "$checksums")"
[ -n "$expected" ] || die "no checksum listed for $name"
actual="$(sha256_of "$file")"
[ "$expected" = "$actual" ] || die "checksum mismatch for $name (expected $expected, got $actual)"
}

extract() {
archive="$1"
dest="$2"
case "$archive" in
*.tar.gz) tar -xzf "$archive" -C "$dest" ;;
*.zip)
if command -v unzip >/dev/null 2>&1; then
unzip -q "$archive" -d "$dest"
else
powershell.exe -NoProfile -Command \
"Expand-Archive -LiteralPath '$(cygpath -w "$archive")' -DestinationPath '$(cygpath -w "$dest")'"
fi
;;
esac
}

install_with_brew() {
say "Homebrew detected; running: brew install $BREW_FORMULA"
brew install "$BREW_FORMULA" </dev/null
say "installed $(command -v pup)"
}

install_direct() {
os="$1"
arch="$(detect_arch "$os")"
version="${PUP_VERSION:-$(latest_version)}"
version="${version#v}"
ext=tar.gz
bin=pup
if [ "$os" = Windows ]; then
ext=zip
bin=pup.exe
fi
asset="pup_${version}_${os}_${arch}.${ext}"
base="https://github.com/${REPO}/releases/download/v${version}"
install_dir="${PUP_INSTALL_DIR:-$HOME/.local/bin}"

tmp="$(mktemp -d)"
# shellcheck disable=SC2064 # expand $tmp now, not when the trap fires
trap "rm -rf '$tmp'" EXIT

say "downloading $asset"
curl -fsSL -o "$tmp/$asset" "$base/$asset" ||
die "download failed: $base/$asset (does version $version exist for $os/$arch?)"
curl -fsSL -o "$tmp/checksums.txt" "$base/pup_${version}_checksums.txt" ||
die "download failed: $base/pup_${version}_checksums.txt"
verify_checksum "$tmp/$asset" "$tmp/checksums.txt"

mkdir -p "$tmp/extract"
extract "$tmp/$asset" "$tmp/extract"
[ -f "$tmp/extract/$bin" ] || die "$bin not found in $asset"

mkdir -p "$install_dir"
# Replace via mv so a running pup binary is not overwritten in place.
cp "$tmp/extract/$bin" "$install_dir/$bin.tmp"
chmod 755 "$install_dir/$bin.tmp"
mv -f "$install_dir/$bin.tmp" "$install_dir/$bin"
say "installed pup $version to $install_dir/$bin"

case ":$PATH:" in
*":$install_dir:"*) ;;
*) say "note: $install_dir is not on your PATH; add it with: export PATH=\"$install_dir:\$PATH\"" ;;
esac
}

main() {
need uname
os="$(detect_os)"
if [ "$os" = Darwin ] && [ "${PUP_NO_BREW:-0}" != 1 ] &&
[ -z "${PUP_VERSION:-}" ] && [ -z "${PUP_INSTALL_DIR:-}" ] &&
command -v brew >/dev/null 2>&1; then
install_with_brew
return
fi
need curl
need tar
need awk
install_direct "$os"
}

# POSIX sh: no `local`, so function variables are script-global.
# Everything runs from main so a truncated `curl | sh` download executes nothing.
main
Loading