From 8b94b860a76c6b9ffa3f0830d9105f8e748956d7 Mon Sep 17 00:00:00 2001 From: Abhisek Datta Date: Mon, 4 May 2026 21:46:58 +0530 Subject: [PATCH] chore: Add install.sh based installation option (#234) --- README.md | 21 ++++++++ install.sh | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100755 install.sh diff --git a/README.md b/README.md index 7a68339..2badde1 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,12 @@ Get protected in seconds. ### 1. Install +**MacOS / Linux (Install Script)** + +```bash +curl -fsSL https://raw.githubusercontent.com/safedep/pmg/main/install.sh | sh +``` + **MacOS / Linux (Homebrew)** ```bash @@ -66,6 +72,8 @@ brew install safedep/tap/pmg npm install -g @safedep/pmg ``` +> **Note:** NPM-based installs can be fragile when Node.js is managed by version managers like [`mise`](https://mise.jdx.dev/) or [`asdf`](https://asdf-vm.com/), since the global `npm` bin path changes with the active Node version. Prefer the install script or Homebrew in those setups. + > See [Installation](#installation) for additional methods. ### 2. Setup @@ -140,6 +148,17 @@ PMG supports the tools you already use: ## Installation +
+Install Script (MacOS/Linux) + +Downloads the latest release from GitHub, verifies its SHA-256 checksum, and installs to `$HOME/.local/bin` (if on `PATH`) or `/usr/local/bin`. + +```bash +curl -fsSL https://raw.githubusercontent.com/safedep/pmg/main/install.sh | sh +``` + +
+
Homebrew (MacOS/Linux) @@ -157,6 +176,8 @@ brew install safedep/tap/pmg npm install -g @safedep/pmg ``` +> **Note:** NPM-based installs can be fragile when Node.js is managed by version managers like [`mise`](https://mise.jdx.dev/) or [`asdf`](https://asdf-vm.com/). The global `npm` bin path changes with the active Node version, so switching versions can leave `pmg` unavailable on `PATH` (or pointing to an old install). For these setups, prefer the install script or Homebrew. +
diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..8054171 --- /dev/null +++ b/install.sh @@ -0,0 +1,148 @@ +#!/bin/sh +set -eu + +do_install() { + REPO="safedep/pmg" + BINARY="pmg" + + # Prefer $HOME/.local/bin if it exists and is in PATH. + INSTALL_DIR="/usr/local/bin" + if [ -n "${HOME:-}" ]; then + local_bin="$HOME/.local/bin" + case ":${PATH}:" in + *":${local_bin}:"*) + INSTALL_DIR="$local_bin" + mkdir -p "$INSTALL_DIR" + ;; + esac + fi + + # Detect OS and architecture. + OS="$(uname -s)" + ARCH="$(uname -m)" + + case "$OS" in + Linux) os="Linux" ;; + Darwin) os="Darwin" ;; + MINGW* | MSYS* | CYGWIN*) os="Windows" ;; + *) + echo "Error: unsupported operating system: $OS" >&2 + exit 1 + ;; + esac + + case "$ARCH" in + x86_64 | amd64) arch="x86_64" ;; + aarch64 | arm64) arch="arm64" ;; + i386 | i686) arch="i386" ;; + *) + echo "Error: unsupported architecture: $ARCH" >&2 + exit 1 + ;; + esac + + # macOS ships a universal binary. + if [ "$os" = "Darwin" ]; then + arch="all" + fi + + if [ "$os" = "Windows" ]; then + BINARY="pmg.exe" + fi + + # Verify install directory exists. + if [ ! -d "$INSTALL_DIR" ]; then + echo "Error: install directory ${INSTALL_DIR} does not exist" >&2 + echo "Create it with: sudo mkdir -p ${INSTALL_DIR}" >&2 + exit 1 + fi + + # Fetch latest release tag via redirect (avoids JSON parsing). + echo "Fetching latest release..." + tag=$(curl -fsSI -o /dev/null -w '%{redirect_url}' "https://github.com/${REPO}/releases/latest" | sed 's|.*/||') + if [ -z "$tag" ]; then + echo "Error: could not determine latest release" >&2 + exit 1 + fi + echo "Latest release: $tag" + + # Build download URL. + # Assets follow the pattern: pmg_{OS}_{arch}.{tar.gz|zip} + if [ "$os" = "Windows" ]; then + asset="pmg_${os}_${arch}.zip" + else + asset="pmg_${os}_${arch}.tar.gz" + fi + url="https://github.com/${REPO}/releases/download/${tag}/${asset}" + + # Download archive and checksums. + echo "Downloading ${asset}..." + tmpdir=$(mktemp -d) + trap 'rm -rf "$tmpdir"' EXIT + checksums_url="https://github.com/${REPO}/releases/download/${tag}/checksums.txt" + if ! curl -fsSL -o "${tmpdir}/${asset}" "$url"; then + echo "Error: archive not available for ${os}/${arch}" >&2 + echo "Check available assets at https://github.com/${REPO}/releases/tag/${tag}" >&2 + exit 1 + fi + if ! curl -fsSL -o "${tmpdir}/checksums.txt" "$checksums_url"; then + echo "Error: could not download checksums.txt from ${checksums_url}" >&2 + exit 1 + fi + + # Verify SHA-256 checksum. + expected=$(grep " ${asset}$" "${tmpdir}/checksums.txt" | cut -d' ' -f1) + if [ -z "$expected" ]; then + echo "Error: no checksum entry found for ${asset} in checksums.txt" >&2 + exit 1 + fi + if command -v sha256sum >/dev/null 2>&1; then + actual=$(sha256sum "${tmpdir}/${asset}" | cut -d' ' -f1) + elif command -v shasum >/dev/null 2>&1; then + actual=$(shasum -a 256 "${tmpdir}/${asset}" | cut -d' ' -f1) + else + echo "Error: neither sha256sum nor shasum found; cannot verify download" >&2 + echo " Install one of them and re-run, or download manually from:" >&2 + echo " https://github.com/${REPO}/releases/tag/${tag}" >&2 + exit 1 + fi + if [ "$actual" != "$expected" ]; then + echo "Error: checksum mismatch for ${asset}" >&2 + echo " expected: $expected" >&2 + echo " actual: $actual" >&2 + exit 1 + fi + echo "Checksum verified." + + if [ "$os" = "Windows" ]; then + unzip -q -o "${tmpdir}/${asset}" "${BINARY}" -d "${tmpdir}" + else + tar -xzf "${tmpdir}/${asset}" -C "${tmpdir}" "${BINARY}" + fi + + if [ ! -f "${tmpdir}/${BINARY}" ]; then + echo "Error: ${BINARY} not found in archive" >&2 + exit 1 + fi + + # Install. + if [ -w "$INSTALL_DIR" ]; then + install -m 755 "${tmpdir}/${BINARY}" "${INSTALL_DIR}/${BINARY}" + else + echo "Installing to ${INSTALL_DIR} (requires sudo)..." + sudo install -m 755 "${tmpdir}/${BINARY}" "${INSTALL_DIR}/${BINARY}" + fi + + echo "Installed pmg ${tag} to ${INSTALL_DIR}/${BINARY}" + + # Verify the install directory is in PATH. + case ":${PATH}:" in + *":${INSTALL_DIR}:"*) ;; + *) + echo "Warning: ${INSTALL_DIR} is not in your PATH. Add it with:" >&2 + echo " export PATH=\"${INSTALL_DIR}:\$PATH\"" >&2 + ;; + esac +} + +do_install