#!/bin/sh set -eu RELEASE_KEY="RWQR3owV+nRhfgNJeUBqcRK868S52NFG2BOrIzKpkQ5ey6lCEfM9+3Eg" BASE="${FEARMINER_DOWNLOAD:-https://download.fearminer.com/latest}" GITHUB="https://github.com/fearminer/fearminer/releases/latest/download" code="" wallet="" pool="" worker="" system="" service=1 usage() { cat <<'EOF' FearMiner, installed in one line (Linux, macOS): curl -fsSL https://get.fearminer.com | sh # install, keep it running curl -fsSL https://get.fearminer.com | sh -s -- fm1_... # ... and join your cockpit's fleet curl -fsSL https://get.fearminer.com | sh -s -- --wallet ADDRESS --pool stratum+ssl://POOL:PORT --worker rig1 # ... and mine to it on your pool It downloads the latest release, checks it against the release key, and installs FearMiner as a service of your account (no sudo) that starts with the machine; on a Linux without systemd (a container on Vast.ai, RunPod, Docker) as a background process, which the machine's start script starts again after a restart (the welcome gives the line). What it mines comes from your cockpit (a mining sheet) or from --wallet with --pool: the pool is yours to choose, a wallet never goes without one. Options: fm1_... --wallet ADDRESS --pool URL [--worker NAME] --system (the machine's service, with sudo) --no-service (the binary only, in ~/.local/bin) --help The fearminer command, without sudo: when the binary's folder is not on PATH, a link to it goes in a folder of PATH this account can write to (/usr/local/bin as root, ~/bin), or else the folder is added to PATH in the profiles of your shells (bash, zsh, sh, fish) for the terminals to come. fearminer watch then follows the miner live. The check: the SHA256SUMS of the release is verified against the release key with minisign when it is installed, with openssl otherwise (an Ed25519 check of the same signature); without either, the SHA256SUMS of download.fearminer.com must be the same as GitHub's. Then the archive against SHA256SUMS. Nothing is run before all of it holds. EOF } say() { printf '%s\n' "fearminer: $*"; } die() { printf '%s\n' "fearminer: $*" >&2; exit 1; } on_path() { case ":$PATH:" in *":$1:"* | *":$1/:"*) return 0 ;; esac return 1 } with_home() { case "$1" in "$HOME"/*) printf '%s' "${2:-\$HOME}/${1#"$HOME"/}" ;; *) printf '%s' "$1" ;; esac } start_dir=$(pwd) path_entry() { case "$1" in "" | .) printf '%s' "$start_dir" ;; /) printf '/' ;; /*) printf '%s' "${1%/}" ;; *) printf '%s' "$start_dir/${1%/}" ;; esac } runs_here() { here_rest="$PATH:" while [ -n "$here_rest" ]; do here_dir=${here_rest%%:*} here_rest=${here_rest#*:} here_dir=$(path_entry "$here_dir") [ "$here_dir" != "$(dirname "$1")" ] || return 0 if [ -e "$here_dir/fearminer" ] || [ -L "$here_dir/fearminer" ]; then [ "$(readlink "$here_dir/fearminer" 2>/dev/null)" != "$1" ] || return 0 other="$here_dir/fearminer" return 1 fi done return 1 } first_at() { here_rest="$PATH:" while [ -n "$here_rest" ]; do here_dir=${here_rest%%:*} here_rest=${here_rest#*:} here_dir=$(path_entry "$here_dir") [ "$here_dir" != "$1" ] || return 0 if [ -e "$here_dir/fearminer" ] || [ -L "$here_dir/fearminer" ]; then return 1 fi done return 1 } linked="" other="" link_command() { runs_here "$1" && return 0 for d in /usr/local/bin ${HOME:+"$HOME/bin"}; do [ -d "$d" ] && [ -w "$d" ] && first_at "$d" || continue if [ -L "$d/fearminer" ]; then [ "$(readlink "$d/fearminer")" = "$1" ] || [ ! -e "$d/fearminer" ] || continue elif [ -e "$d/fearminer" ]; then continue fi ln -sfn "$1" "$d/fearminer" 2>/dev/null || continue linked="$d/fearminer" say "the fearminer command is $linked (a link to the binary)" return 0 done return 1 } add_to_profiles() { p=$(with_home "$1") line="case \":\$PATH:\" in *\":$p:\"*) ;; *) export PATH=\"$p:\$PATH\" ;; esac" shell=$(basename "${SHELL:-sh}") added="" for f in "$HOME/.profile" "$HOME/.bash_profile" "$HOME/.bash_login" "$HOME/.bashrc" "${ZDOTDIR:-$HOME}/.zshrc"; do if [ ! -f "$f" ]; then case "${f##*/}/$shell" in .profile/sh | .profile/dash | .profile/ash | .profile/ksh | .profile/bash) ;; .bashrc/bash | .zshrc/zsh) ;; *) continue ;; esac fi grep -qsF "$line" "$f" || printf '\n# FearMiner: the fearminer command\n%s\n' "$line" >> "$f" || continue added="$added $(with_home "$f" '~')" done fish="${XDG_CONFIG_HOME:-$HOME/.config}/fish" if [ "$shell" = fish ] || [ -d "$fish" ]; then f="$fish/conf.d/fearminer.fish" line="contains -- \"$p\" \$PATH; or set -gx PATH \"$p\" \$PATH" mkdir -p "$fish/conf.d" && { grep -qsF "$line" "$f" || printf '%s\n' "$line" >> "$f"; } && added="$added $(with_home "$f" '~')" fi if [ -n "$added" ]; then later=1 say "$(with_home "$1" '~') goes on PATH in new terminals (in$added)" fi } later="" last_step() { [ -z "$reachable" ] || return 0 d=$(dirname "$bin") p=$(with_home "$d") if on_path "$d"; then say "another fearminer comes first on PATH ($other); to use this one by name, run: export PATH=\"$p:\$PATH\"" elif [ -n "$later" ]; then say "to use fearminer in this terminal, open a new one or run: export PATH=\"$p:\$PATH\"" else say "to use fearminer by name, run: export PATH=\"$p:\$PATH\"" fi } while [ $# -gt 0 ]; do case "$1" in fm1_*) code="$1" ;; --wallet) [ $# -ge 2 ] || die "--wallet needs an address"; wallet="$2"; shift ;; --wallet=*) wallet="${1#--wallet=}" ;; --pool) [ $# -ge 2 ] || die "--pool needs a URL (stratum+ssl://POOL:PORT)"; pool="$2"; shift ;; --pool=*) pool="${1#--pool=}" ;; --worker) [ $# -ge 2 ] || die "--worker needs a name"; worker="$2"; shift ;; --worker=*) worker="${1#--worker=}" ;; --system) system=1 ;; --no-service) service="" ;; -h|--help) usage; exit 0 ;; *) die "unknown argument '$1' (--help)" ;; esac shift done if [ -n "$wallet" ] && [ -z "$pool" ]; then die "--wallet needs --pool: --wallet ADDRESS --pool stratum+ssl://POOL:PORT [--worker rig1] (the pool is yours to choose)" fi if [ -z "$wallet" ] && { [ -n "$pool" ] || [ -n "$worker" ]; }; then die "--pool and --worker go with --wallet: --wallet ADDRESS --pool stratum+ssl://POOL:PORT [--worker rig1]" fi if [ -d /hive ] && [ -f /hive-config/rig.conf ] 2>/dev/null; then die "this is HiveOS: the flight sheet runs FearMiner. Put --enroll fm1_... in its extra arguments instead." fi if [ -d /opt/mmp ] || [ -d /mmp-config ]; then die "this is mmpOS: a miner profile runs FearMiner, as a custom miner (the fearminer_mmpos package of the release). Put --enroll fm1_... in its Arguments instead." fi os=$(uname -s) arch=$(uname -m) case "$os/$arch" in Linux/x86_64 | Linux/amd64) asset="fearminer-linux-x86_64.tar.gz" ;; Darwin/arm64) asset="fearminer-macos-arm64.tar.gz" ;; *) die "no release for $os $arch yet (Linux x86_64, macOS Apple silicon, Windows: see https://fearminer.com)" ;; esac if command -v curl >/dev/null 2>&1; then fetch() { curl -fsSL --retry 3 -o "$2" "$1"; } elif command -v wget >/dev/null 2>&1; then fetch() { wget -q -O "$2" "$1"; } else die "curl or wget is needed" fi if command -v sha256sum >/dev/null 2>&1; then sha256() { sha256sum "$1" | cut -d' ' -f1; } elif command -v shasum >/dev/null 2>&1; then sha256() { shasum -a 256 "$1" | cut -d' ' -f1; } else die "sha256sum or shasum is needed" fi tmp=$(mktemp -d 2>/dev/null || mktemp -d -t fearminer) trap 'rm -rf "$tmp"' EXIT INT TERM cd "$tmp" say "downloading the latest release for $os $arch" fetch "$BASE/SHA256SUMS" SHA256SUMS || die "cannot reach $BASE" fetch "$BASE/SHA256SUMS.minisig" SHA256SUMS.minisig || die "the release has no signature at $BASE" verified="" if command -v minisign >/dev/null 2>&1; then minisign -q -Vm SHA256SUMS -P "$RELEASE_KEY" >/dev/null 2>&1 || die "the release signature does not hold (minisign): nothing installed" verified="minisign" elif command -v openssl >/dev/null 2>&1 && openssl version 2>/dev/null | grep -q '^OpenSSL [3-9]'; then printf '%s' "$RELEASE_KEY" | base64 -d > pub.bin 2>/dev/null || printf '%s' "$RELEASE_KEY" | base64 -D > pub.bin sed -n 2p SHA256SUMS.minisig | { base64 -d 2>/dev/null || base64 -D; } > sig.bin [ "$(head -c 2 sig.bin)" = "ED" ] || die "the release signature is not in the expected form: nothing installed" [ "$(tail -c +3 pub.bin | head -c 8 | od -An -tx1)" = "$(tail -c +3 sig.bin | head -c 8 | od -An -tx1)" ] || die "the release is signed by another key: nothing installed" { printf '\060\052\060\005\006\003\053\145\160\003\041\000'; tail -c 32 pub.bin; } > pub.der tail -c 64 sig.bin > s.bin openssl dgst -blake2b512 -binary SHA256SUMS > h.bin openssl pkeyutl -verify -pubin -inkey pub.der -keyform DER -rawin -in h.bin -sigfile s.bin >/dev/null 2>&1 || die "the release signature does not hold (openssl): nothing installed" verified="openssl" else fetch "$GITHUB/SHA256SUMS" SHA256SUMS.github || die "cannot reach GitHub to cross-check the release" a=$(grep " $asset\$" SHA256SUMS || true) b=$(grep " $asset\$" SHA256SUMS.github || true) [ -n "$a" ] && [ "$a" = "$b" ] || die "download.fearminer.com and GitHub disagree on $asset: nothing installed" verified="GitHub cross-check (install minisign or openssl 3 for the signature check)" fi expected=$(grep " $asset\$" SHA256SUMS | cut -d' ' -f1) [ -n "$expected" ] || die "SHA256SUMS does not list $asset" fetch "$BASE/$asset" "$asset" || die "cannot download $asset" [ "$(sha256 "$asset")" = "$expected" ] || die "$asset does not match its SHA-256: nothing installed" say "verified ($verified)" tar xzf "$asset" dir=$(find . -maxdepth 1 -type d -name 'fearminer-*' | head -n 1) [ -n "$dir" ] && [ -x "$dir/fearminer" ] || die "the archive has no fearminer binary" if [ "$os" = Darwin ]; then xattr -dr com.apple.quarantine "$dir" 2>/dev/null || true say "macOS: the quarantine flag is lifted from the checked download (the binary is not notarised yet)" fi version=$("$dir/fearminer" --version 2>/dev/null | head -n 1 || true) if [ -z "${HOME:-}" ]; then HOME=$(awk -F: -v uid="$(id -u)" '$3 == uid { print $6; exit }' /etc/passwd 2>/dev/null || true) case "$HOME" in /?*) ;; *) HOME="" ;; esac fi reachable="" if [ -n "$system" ] && [ -n "$service" ]; then bin=/usr/local/bin/fearminer elif [ -z "${HOME:-}" ]; then [ -n "$service" ] || die "--no-service needs a home folder, and HOME is not set" bin="" reachable=1 elif [ -n "$service" ] && [ "$os" = Darwin ]; then bin="$HOME/Library/Application Support/fearminer/bin/fearminer" else bin="$HOME/.local/bin/fearminer" fi if [ -z "$reachable" ] && link_command "$bin"; then reachable=1; fi [ -n "$reachable" ] || on_path "$(dirname "$bin")" || add_to_profiles "$(dirname "$bin")" if [ -z "$service" ]; then mkdir -p "$HOME/.local/bin" cp "$dir/fearminer" "$bin" [ -x "$dir/fearminer-helper" ] && cp "$dir/fearminer-helper" "$HOME/.local/bin/fearminer-helper" say "$version installed in ~/.local/bin" [ -z "$code" ] || "$bin" enroll "$code" last_step exit 0 fi set -- service install [ -n "$system" ] && set -- "$@" --system [ -n "$code" ] && set -- "$@" --enroll "$code" [ -n "$wallet" ] && set -- "$@" -o "$pool" -u "$wallet" [ -n "$worker" ] && set -- "$@" -w "$worker" st=0 if [ -n "$system" ] && [ "$(id -u)" != 0 ]; then sudo "$dir/fearminer" "$@" || st=$? else "$dir/fearminer" "$@" || st=$? fi if [ "$st" != 0 ]; then if [ -n "$linked" ] && [ ! -e "$linked" ]; then rm -f "$linked"; fi exit "$st" fi last_step