#!/bin/sh set -eu default_release_base="https://forge.tionis.dev/eric/geth/releases/download" version="" install_dir="${GETH_INSTALL_DIR:-${HOME}/.local/bin}" release_base="${GETH_RELEASE_BASE_URL:-$default_release_base}" print_asset=false archive_path="" checksum_path="" modify_path=true usage() { cat <<'EOF' Install a checksum-verified geth release for the current user. Usage: install.sh --version [--install-dir ] [--release-base-url ] install.sh --version --archive [--checksum ] install.sh --version --print-asset The installer only places the single geth executable. It never initializes a geth home or starts a daemon. GETH_INSTALL_DIR and GETH_RELEASE_BASE_URL provide the corresponding option defaults. EOF } while [ "$#" -gt 0 ]; do case "$1" in --version) [ "$#" -ge 2 ] || { echo "error: --version requires a value" >&2; exit 2; } version=$2 shift 2 ;; --install-dir) [ "$#" -ge 2 ] || { echo "error: --install-dir requires a value" >&2; exit 2; } install_dir=$2 shift 2 ;; --release-base-url) [ "$#" -ge 2 ] || { echo "error: --release-base-url requires a value" >&2; exit 2; } release_base=$2 shift 2 ;; --archive) [ "$#" -ge 2 ] || { echo "error: --archive requires a value" >&2; exit 2; } archive_path=$2 shift 2 ;; --checksum) [ "$#" -ge 2 ] || { echo "error: --checksum requires a value" >&2; exit 2; } checksum_path=$2 shift 2 ;; --no-modify-path) modify_path=false shift ;; --print-asset) print_asset=true shift ;; -h|--help) usage exit 0 ;; *) echo "error: unknown argument: $1" >&2 usage >&2 exit 2 ;; esac done [ -n "$version" ] || { echo "error: --version is required" >&2; usage >&2; exit 2; } if printf '%s' "$install_dir" | LC_ALL=C grep '[[:cntrl:]]' >/dev/null 2>&1; then echo "error: install directory must not contain control characters" >&2 exit 2 fi case "$version" in v[0-9]*[!0-9A-Za-z._-]*|v[0-9]*/*) echo "error: version contains unsupported characters" >&2 exit 2 ;; v[0-9]*) ;; *) echo "error: version must be an explicit tag such as v0.1.0" >&2; exit 2 ;; esac case "$install_dir" in /*) ;; *) echo "error: install directory must be an absolute path" >&2; exit 2 ;; esac os=$(uname -s) arch=$(uname -m) case "$os:$arch" in Linux:x86_64|Linux:amd64) platform=linux-x86_64 ;; Darwin:x86_64|Darwin:amd64) platform=macos-x86_64 ;; Darwin:arm64|Darwin:aarch64) platform=macos-aarch64 ;; *) echo "error: no geth release artifact for $os $arch" >&2 exit 1 ;; esac asset="geth-${version}-${platform}.tar.gz" if [ "$print_asset" = true ]; then printf '%s\n' "$asset" exit 0 fi [ -n "$archive_path" ] || command -v curl >/dev/null 2>&1 || { echo "error: curl is required" >&2; exit 1; } if command -v sha256sum >/dev/null 2>&1; then hash_command=sha256sum elif command -v shasum >/dev/null 2>&1; then hash_command='shasum -a 256' else echo "error: sha256sum or shasum is required" >&2 exit 1 fi tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/geth-install.XXXXXX") staged_binary="" cleanup() { rm -rf "$tmp_dir" if [ -n "$staged_binary" ] && [ -f "$staged_binary" ]; then rm -f "$staged_binary" fi } trap cleanup EXIT HUP INT TERM if [ -n "$archive_path" ]; then archive=$archive_path checksum=${checksum_path:-$archive_path.sha256} [ -f "$archive" ] || { echo "error: archive not found: $archive" >&2; exit 1; } [ -f "$checksum" ] || { echo "error: checksum not found: $checksum" >&2; exit 1; } echo "using local archive $archive" else [ -z "$checksum_path" ] || { echo "error: --checksum requires --archive" >&2; exit 2; } archive="$tmp_dir/$asset" checksum="$archive.sha256" url="${release_base%/}/${version}/${asset}" echo "downloading $url" curl -fL --proto '=https' --proto-redir '=https' --tlsv1.2 -o "$archive" "$url" curl -fL --proto '=https' --proto-redir '=https' --tlsv1.2 -o "$checksum" "$url.sha256" fi expected=$(awk 'NR == 1 { print tolower($1) }' "$checksum") case "$expected" in *[!0-9a-f]*|'') echo "error: invalid SHA-256 file for $asset" >&2; exit 1 ;; esac [ "${#expected}" -eq 64 ] || { echo "error: invalid SHA-256 length for $asset" >&2; exit 1; } if [ "$hash_command" = sha256sum ]; then actual=$(sha256sum "$archive" | awk '{ print tolower($1) }') else actual=$(shasum -a 256 "$archive" | awk '{ print tolower($1) }') fi [ "$actual" = "$expected" ] || { echo "error: checksum mismatch for $asset" >&2; exit 1; } echo "verified SHA-256: $actual" if [ -n "$archive_path" ]; then package=$(basename "$archive") package=${package%.tar.gz} else package=${asset%.tar.gz} fi tar -xzf "$archive" -C "$tmp_dir" source_binary="$tmp_dir/$package/geth" [ -f "$source_binary" ] || { echo "error: archive does not contain $package/geth" >&2; exit 1; } mkdir -p "$install_dir" staged_binary="$install_dir/.geth.install.$$" cp "$source_binary" "$staged_binary" chmod 755 "$staged_binary" mv -f "$staged_binary" "$install_dir/geth" echo "installed geth to $install_dir/geth" case ":${PATH}:" in *":${install_dir}:"*) ;; *) if [ "$modify_path" = true ]; then case "${SHELL:-}" in */zsh) path_profile=${GETH_PATH_PROFILE:-$HOME/.zprofile} ;; *) path_profile=${GETH_PATH_PROFILE:-$HOME/.profile} ;; esac escaped_install_dir=$(printf '%s' "$install_dir" | sed 's/[\\`"$]/\\&/g') path_marker="# geth installer PATH: $install_dir" path_added=false path_update_failed=false if [ ! -f "$path_profile" ] || ! grep -F "$path_marker" "$path_profile" >/dev/null 2>&1; then if printf '\n%s\nexport PATH="%s:\044PATH"\n' "$path_marker" "$escaped_install_dir" >> "$path_profile"; then path_added=true else path_update_failed=true fi fi if [ "$path_added" = true ]; then echo "added $install_dir to PATH in $path_profile; open a new terminal to use it" elif [ "$path_update_failed" = true ]; then echo "warning: could not update $path_profile; add this directory to PATH:" >&2 echo " export PATH=\"$install_dir:\$PATH\"" >&2 else echo "$install_dir is already configured in $path_profile; open a new terminal to use it" fi else echo "add this directory to PATH before using geth:" echo " export PATH=\"$install_dir:\$PATH\"" fi ;; esac echo "the daemon was not started; run 'geth daemon install' when ready"