#!/usr/bin/env bash # # Lantern installer โ€” pure-Go Filecoin light node. # # curl -fsSL https://get.lantern.reiers.io | bash # # Or, from a local checkout: # # bash install.sh # # Environment variables: # LANTERN_VERSION Tag to install (default: latest) # LANTERN_HOME Data + binary directory (default: ~/.lantern) # LANTERN_PREFIX Where to symlink the binary (default: /usr/local/bin) # LANTERN_YES=1 Non-interactive; assume defaults (background service) # LANTERN_NO_SERVICE=1 Skip the OS service installation step # # Exit codes: 0 success, anything else fatal. set -euo pipefail # ---------- UI helpers ---------- CLR_RESET='\033[0m'; CLR_BOLD='\033[1m' CLR_RED='\033[0;31m'; CLR_GREEN='\033[0;32m'; CLR_YELLOW='\033[0;33m' CLR_BLUE='\033[0;34m'; CLR_CYAN='\033[0;36m'; CLR_DIM='\033[2m' if [[ "${TERM:-}" == "dumb" || ! -t 1 ]]; then CLR_RESET=''; CLR_BOLD=''; CLR_RED=''; CLR_GREEN=''; CLR_YELLOW='' CLR_BLUE=''; CLR_CYAN=''; CLR_DIM='' fi banner() { cat <&2; exit 1; } info() { printf " ${CLR_DIM}ยท${CLR_RESET} %s\n" "$*"; } ask() { # ask "question" default_yes_or_no local q="$1" def="${2:-y}" reply if [[ "${LANTERN_YES:-}" == "1" ]]; then echo "$def"; return; fi if [[ "$def" == "y" ]]; then printf " ? %s [Y/n] " "$q"; else printf " ? %s [y/N] " "$q"; fi read -r reply || reply="$def" reply="${reply:-$def}" echo "$reply" } # ---------- Preflight ---------- preflight() { step "Preflight" case "$(uname -s)" in Darwin) OS=darwin ;; Linux) OS=linux ;; *) fail "Unsupported OS: $(uname -s). Lantern supports macOS and Linux." ;; esac case "$(uname -m)" in arm64|aarch64) ARCH=arm64 ;; x86_64|amd64) ARCH=amd64 ;; *) fail "Unsupported arch: $(uname -m). Lantern supports arm64 and amd64." ;; esac ok "Detected ${OS}/${ARCH}" for cmd in curl tar shasum sha256sum mktemp; do if command -v "$cmd" >/dev/null 2>&1; then : elif [[ "$cmd" == "shasum" || "$cmd" == "sha256sum" ]]; then # we need at least one of them continue else fail "Missing required command: $cmd" fi done if ! command -v shasum >/dev/null 2>&1 && ! command -v sha256sum >/dev/null 2>&1; then fail "Need either shasum or sha256sum for SHA-256 verification" fi ok "Tools: curl, tar, shasum/sha256sum available" LANTERN_HOME="${LANTERN_HOME:-$HOME/.lantern}" LANTERN_PREFIX="${LANTERN_PREFIX:-/usr/local/bin}" mkdir -p "$LANTERN_HOME" ok "Data directory: $LANTERN_HOME" } # ---------- Download ---------- sha256_of() { if command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | awk '{print $1}' else sha256sum "$1" | awk '{print $1}' fi } download_binary() { step "Download Lantern binary" LANTERN_VERSION="${LANTERN_VERSION:-latest}" if [[ "$LANTERN_VERSION" == "latest" ]]; then url_base="https://github.com/Reiers/lantern/releases/latest/download" else url_base="https://github.com/Reiers/lantern/releases/download/${LANTERN_VERSION}" fi bin_name="lantern-${OS}-${ARCH}" bin_url="${url_base}/${bin_name}" sha_url="${bin_url}.sha256" target="${LANTERN_HOME}/lantern" # Idempotence: if a binary already exists and matches the latest sha, # skip download. if [[ -x "$target" && "${LANTERN_REINSTALL:-0}" != "1" ]]; then if existing_sha=$(sha256_of "$target" 2>/dev/null); then info "Existing binary at $target (sha256 $(echo "$existing_sha" | cut -c1-12)...)" info "Skipping download (LANTERN_REINSTALL=1 to force)" ok "Using existing binary" return fi fi tmp_dir=$(mktemp -d) trap 'rm -rf "$tmp_dir"' EXIT info "Fetching ${bin_url}" http_code=$(curl -fsSL -o "$tmp_dir/$bin_name" -w "%{http_code}" "$bin_url" || echo "000") if [[ "$http_code" != "200" ]]; then warn "Binary not yet published at ${bin_url} (HTTP $http_code)" info "Falling back to local source build (requires Go 1.25+)" build_from_source "$target" return fi info "Fetching SHA-256 manifest" if curl -fsSL -o "$tmp_dir/$bin_name.sha256" "$sha_url"; then expected=$(cut -d' ' -f1 < "$tmp_dir/$bin_name.sha256") actual=$(sha256_of "$tmp_dir/$bin_name") if [[ "$expected" != "$actual" ]]; then fail "SHA-256 mismatch! expected=$expected actual=$actual" fi ok "SHA-256 verified ($expected)" else warn "SHA-256 file not available; skipping integrity check" fi chmod +x "$tmp_dir/$bin_name" mv "$tmp_dir/$bin_name" "$target" ok "Installed binary to $target" install_symlink "$target" } build_from_source() { local target="$1" if ! command -v go >/dev/null 2>&1; then fail "go is not installed and no release binary available. Install Go 1.25+ from https://go.dev/dl" fi # If install.sh is being run from inside a clone, build from here. if [[ -f "go.mod" && -d "cmd/lantern" ]]; then info "Building from $(pwd)" CGO_ENABLED=0 go build -o "$target" ./cmd/lantern chmod +x "$target" ok "Built and installed to $target" install_symlink "$target" return fi fail "No release artifact and no source tree found to build from" } install_symlink() { local target="$1" local link="${LANTERN_PREFIX}/lantern" if [[ -L "$link" || -e "$link" ]]; then local existing existing=$(readlink "$link" 2>/dev/null || echo "$link") if [[ "$existing" == "$target" ]]; then ok "Symlink already in place: $link โ†’ $target" return fi info "Replacing existing $link (was: $existing)" fi if [[ -w "$(dirname "$link")" ]]; then ln -sf "$target" "$link" ok "Symlink: $link โ†’ $target" else info "Need sudo to write $link" if sudo ln -sf "$target" "$link"; then ok "Symlink: $link โ†’ $target" else warn "Could not create $link. Add $LANTERN_HOME to PATH or run with sudo." fi fi } # ---------- Trust bootstrap ---------- trust_bootstrap() { step "Trust bootstrap โ€” multi-source quorum" info "This runs once. Asking 5+ independent sources for the current" info "F3-finalized chain head; refuses to continue if they disagree." local q="${LANTERN_BOOTSTRAP_QUORUM:-5}" local t="${LANTERN_BOOTSTRAP_TIMEOUT:-90s}" local extra_peers="" if [[ -n "${LANTERN_PEERS:-}" ]]; then # comma-separated list โ†’ repeated --peer flags IFS=',' read -r -a _peers <<< "$LANTERN_PEERS" for p in "${_peers[@]}"; do extra_peers+=" --peer $p"; done fi # Detect whether an anchor already exists; offer to skip if so. if [[ -s "${LANTERN_HOME}/bootstrap-anchor.json" && "${LANTERN_REANCHOR:-0}" != "1" ]]; then info "Existing bootstrap anchor found:" sed 's/^/ /' "${LANTERN_HOME}/bootstrap-anchor.json" info "Re-run with LANTERN_REANCHOR=1 to refresh." ok "Skipping quorum probe (anchor exists)" return fi # Run `lantern init` in --no-wallet mode; we handle wallet creation # ourselves below so the installer can ask the user about it. local bin="${LANTERN_HOME}/lantern" if ! LANTERN_HOME="$LANTERN_HOME" \ "$bin" init \ --bootstrap-quorum="$q" \ --bootstrap-timeout="$t" \ --no-wallet $extra_peers; then fail "Quorum bootstrap failed. Try 'lantern doctor' for per-source diagnostics." fi } # ---------- Wallet ---------- wallet_setup() { step "Wallet" local bin="${LANTERN_HOME}/lantern" if "$bin" wallet list 2>/dev/null | grep -q '^\*'; then ok "Wallet already configured" return fi ans=$(ask "Create a fresh BLS wallet now?" y) if [[ "$ans" =~ ^[Yy]$ ]]; then "$bin" wallet new --type=bls ok "Wallet created" else info "Skipping wallet creation. Use 'lantern wallet new --type=bls' later." fi } # ---------- Service ---------- service_setup() { if [[ "${LANTERN_NO_SERVICE:-}" == "1" ]]; then info "LANTERN_NO_SERVICE=1 set; skipping OS service installation" return fi step "Daemon lifecycle" local default_choice if [[ "${LANTERN_YES:-}" == "1" ]]; then default_choice=background else default_choice=foreground fi printf " How should Lantern run?\n" printf " ${CLR_BOLD}b${CLR_RESET}) Background service (launchd / systemd user)\n" printf " ${CLR_BOLD}f${CLR_RESET}) Foreground only (start manually with 'lantern daemon')\n" printf " ${CLR_BOLD}s${CLR_RESET}) Skip โ€” I'll decide later\n" if [[ "${LANTERN_YES:-}" == "1" ]]; then choice="b" else printf " Choice [default: ${default_choice:0:1}]: " read -r choice || choice="" choice="${choice:-${default_choice:0:1}}" fi case "$choice" in b|B|background) "${LANTERN_HOME}/lantern" service install ok "Service installed and started" ;; f|F|foreground) ok "Foreground only. Start with: lantern daemon" ;; s|S|skip|*) info "Skipped service setup. Run 'lantern service install' later." ;; esac } # ---------- Closing summary ---------- closing() { step "Done" local bin="${LANTERN_HOME}/lantern" local token="" if [[ -s "${LANTERN_HOME}/token" ]]; then token=$(cat "${LANTERN_HOME}/token") fi cat <