#!/bin/sh # PkgFire CLI installer. # # curl -fsSL https://pkgfire.org/install.sh | sh # # You are about to pipe a script from the internet into a shell, to install a # tool whose entire purpose is to stop you running code you have not checked. # That is not lost on us. So: # # - read it first: curl -fsSL https://pkgfire.org/install.sh | less # - every binary is verified against the published SHA256SUMS before it is # installed, and this script refuses to continue on a mismatch # - or skip it entirely and download from /downloads/ by hand # # POSIX sh, no bashisms: macOS ships an ancient bash and some people use dash. set -eu BASE_URL="${PKGFIRE_BASE_URL:-https://pkgfire.org}" INSTALL_DIR="${PKGFIRE_INSTALL_DIR:-}" red() { printf '\033[31m%s\033[0m\n' "$1" >&2; } green() { printf '\033[32m%s\033[0m\n' "$1"; } dim() { printf '\033[2m%s\033[0m\n' "$1"; } bold() { printf '\033[1m%s\033[0m\n' "$1"; } die() { red "error: $1"; exit 1; } need() { command -v "$1" >/dev/null 2>&1 || die "$1 is required but not installed" } # ------------------------------------------------------------------ platform detect_platform() { os=$(uname -s | tr '[:upper:]' '[:lower:]') arch=$(uname -m) case "$os" in linux) os=linux ;; darwin) os=darwin ;; msys*|mingw*|cygwin*) die "Windows is supported, but not by this script. Download ${BASE_URL}/downloads/pkgfire-windows-amd64.exe and put it somewhere on your PATH." ;; *) die "unsupported operating system: $os" ;; esac case "$arch" in x86_64|amd64) arch=amd64 ;; arm64|aarch64) arch=arm64 ;; *) die "unsupported architecture: $arch" ;; esac PLATFORM="${os}-${arch}" } # --------------------------------------------------------------- destination # Prefer a directory already on PATH that we can write to without sudo. # Silently needing root, or installing somewhere not on PATH, are both worse # than asking. choose_dir() { if [ -n "$INSTALL_DIR" ]; then return fi for candidate in "$HOME/.local/bin" /usr/local/bin "$HOME/bin"; do if [ -d "$candidate" ] && [ -w "$candidate" ]; then INSTALL_DIR="$candidate" return fi done # Nothing suitable exists yet; ~/.local/bin is the least surprising to make. INSTALL_DIR="$HOME/.local/bin" mkdir -p "$INSTALL_DIR" || die "could not create $INSTALL_DIR" } on_path() { case ":${PATH}:" in *":$1:"*) return 0 ;; *) return 1 ;; esac } # -------------------------------------------------------------------- install main() { need curl need uname need mktemp detect_platform choose_dir bold "PkgFire CLI" dim " platform ${PLATFORM}" dim " destination ${INSTALL_DIR}/pkgfire" dim " source ${BASE_URL}/downloads/" printf '\n' binary="pkgfire-${PLATFORM}" tmp=$(mktemp -d) || die "could not create a temporary directory" # Clean up on any exit, including an interrupt part-way through a download. trap 'rm -rf "$tmp"' EXIT INT TERM printf 'Downloading %s... ' "$binary" curl -fsSL "${BASE_URL}/downloads/${binary}" -o "$tmp/pkgfire" \ || die "download failed. Is ${BASE_URL} reachable?" curl -fsSL "${BASE_URL}/downloads/SHA256SUMS" -o "$tmp/SHA256SUMS" \ || die "could not fetch the checksum file" printf 'done\n' # Verify before anything is made executable or moved into place. A security # tool that installs unverified bytes is not making a serious argument. printf 'Verifying checksum... ' expected=$(grep " ${binary}\$" "$tmp/SHA256SUMS" | awk '{print $1}') [ -n "$expected" ] || die "no checksum published for ${binary}" if command -v sha256sum >/dev/null 2>&1; then actual=$(sha256sum "$tmp/pkgfire" | awk '{print $1}') elif command -v shasum >/dev/null 2>&1; then actual=$(shasum -a 256 "$tmp/pkgfire" | awk '{print $1}') else die "need sha256sum or shasum to verify the download" fi if [ "$actual" != "$expected" ]; then red "CHECKSUM MISMATCH" red " expected $expected" red " got $actual" die "refusing to install. The download was corrupted, or tampered with." fi printf 'ok\n' chmod 755 "$tmp/pkgfire" # Sanity-check that it runs on this machine before replacing anything. if ! "$tmp/pkgfire" version >/dev/null 2>&1; then die "the downloaded binary does not run on this machine" fi version=$("$tmp/pkgfire" version 2>/dev/null || echo "pkgfire") if ! mv "$tmp/pkgfire" "${INSTALL_DIR}/pkgfire" 2>/dev/null; then die "could not write to ${INSTALL_DIR}. Re-run with a writable destination: PKGFIRE_INSTALL_DIR=\$HOME/.local/bin sh -c \"\$(curl -fsSL ${BASE_URL}/install.sh)\"" fi printf '\n' green "Installed ${version} to ${INSTALL_DIR}/pkgfire" if ! on_path "$INSTALL_DIR"; then printf '\n' red "${INSTALL_DIR} is not on your PATH." printf 'Add it, then reopen your shell:\n\n' printf ' echo '\''export PATH="%s:$PATH"'\'' >> ~/.zshrc\n\n' "$INSTALL_DIR" fi # Pre-fill the API URL from wherever this script was fetched, so the only # thing left to enter is the key itself. config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/pkgfire" if [ ! -f "${config_dir}/config.toml" ]; then mkdir -p "$config_dir" 2>/dev/null && chmod 700 "$config_dir" 2>/dev/null || true if [ -d "$config_dir" ]; then umask 077 printf '# PkgFire CLI configuration.\n# Written by install.sh. Sign in with `pkgfire auth login`.\napi_url = "%s"\n' \ "$BASE_URL" > "${config_dir}/config.toml" 2>/dev/null || true chmod 600 "${config_dir}/config.toml" 2>/dev/null || true dim "Wrote ${config_dir}/config.toml with api_url = ${BASE_URL}" fi fi printf '\n' bold "Next:" printf ' 1. pkgfire auth login (approve this machine in the dashboard)\n' printf ' 2. pkgfire scan . (in any npm, pnpm, Bun, Yarn, Go, Cargo or Python project)\n' printf ' In CI, create a key at %s/api-keys and set PKGFIRE_API_KEY instead.\n' "$BASE_URL" printf '\n' dim "Then wrap your installs: pkgfire npm install, pkgfire pnpm add, pkgfire bun install, pkgfire go get" } main "$@"