install.sh RawHistoryFolder
1#!/usr/bin/env bash
2#
3# zut installer.
4#
5# Usage:
6# curl -fsSL https://git.bnema.dev/zut/raw/main/install.sh | bash
7# curl -fsSL https://git.bnema.dev/zut/raw/main/install.sh | bash -s -- v0.1.0 ~/bin
8#
9# Positional arguments:
10# $1 version — release tag (e.g. v0.1.0). Defaults to "latest".
11# $2 prefix — install directory. Defaults to the first writable
12# directory in: /usr/local/bin, $HOME/.local/bin,
13# $HOME/bin. Created if missing. Add it to your PATH
14# if it isn't already.
15#
16# Environment overrides:
17# ZUT_VERSION same as $1
18# ZUT_PREFIX same as $2
19#
20# The script detects your OS and architecture, downloads the matching
21# archive from the forge release, verifies the sha256 against the
22# release's checksums.txt, extracts the binary, and moves it into the
23# prefix directory. No sudo unless you explicitly pick a prefix that
24# needs it.
25
26set -euo pipefail
27
28REPO="zut"
29BINARY="zut"
30
31VERSION="${1:-${ZUT_VERSION:-latest}}"
32PREFIX="${2:-${ZUT_PREFIX:-}}"
33
34msg() { printf "\033[1m==>\033[0m %s\n" "$*"; }
35warn() { printf "\033[33mwarn:\033[0m %s\n" "$*" >&2; }
36die() { printf "\033[31merror:\033[0m %s\n" "$*" >&2; exit 1; }
37
38command -v curl >/dev/null 2>&1 || die "curl is required"
39command -v tar >/dev/null 2>&1 || die "tar is required"
40
41# ---- detect OS + arch ----
42
43uname_s=$(uname -s)
44uname_m=$(uname -m)
45
46case "$uname_s" in
47 Linux) OS=linux ;;
48 Darwin) OS=darwin ;;
49 MINGW*|MSYS*|CYGWIN*)
50 die "windows detected — use install.ps1 from powershell instead"
51 ;;
52 *) die "unsupported os: $uname_s" ;;
53esac
54
55case "$uname_m" in
56 x86_64|amd64) ARCH=amd64 ;;
57 arm64|aarch64) ARCH=arm64 ;;
58 *) die "unsupported arch: $uname_m" ;;
59esac
60
61# ---- resolve version ----
62
63if [ "$VERSION" = "latest" ]; then
64 # The forge answers /releases/latest with a redirect to
65 # /releases/tag/<tag>; read the tag from the final URL.
66 VERSION=$(curl -fsSLI -o /dev/null -w '%{url_effective}' \
67 "https://git.bnema.dev/${REPO}/releases/latest" \
68 | sed -E 's|.*/tag/([^/]+).*|\1|')
69 [ -n "$VERSION" ] || die "could not resolve latest version"
70fi
71
72case "$VERSION" in v*) ;; *) VERSION="v$VERSION" ;; esac
73VER_NUM="${VERSION#v}"
74
75# ---- pick an install prefix ----
76
77pick_prefix() {
78 local candidates=()
79 [ -n "$PREFIX" ] && { echo "$PREFIX"; return; }
80 candidates+=("/usr/local/bin")
81 [ -n "${HOME:-}" ] && candidates+=("$HOME/.local/bin" "$HOME/bin")
82 for d in "${candidates[@]}"; do
83 if [ -d "$d" ] && [ -w "$d" ]; then
84 echo "$d"
85 return
86 fi
87 done
88 # Nothing writable yet — create ~/.local/bin and use that.
89 if [ -n "${HOME:-}" ]; then
90 mkdir -p "$HOME/.local/bin"
91 echo "$HOME/.local/bin"
92 return
93 fi
94 die "no writable install prefix found; pass one as the second argument"
95}
96
97PREFIX=$(pick_prefix)
98mkdir -p "$PREFIX"
99
100# ---- download + verify + extract ----
101
102ARCHIVE="${BINARY}_${VER_NUM}_${OS}_${ARCH}.tar.gz"
103BASE_URL="https://git.bnema.dev/${REPO}/releases/download/${VERSION}"
104ARCHIVE_URL="${BASE_URL}/${ARCHIVE}"
105CHECKSUMS_URL="${BASE_URL}/checksums.txt"
106
107TMP=$(mktemp -d)
108trap 'rm -rf "$TMP"' EXIT
109
110msg "downloading ${ARCHIVE}"
111curl -fsSL -o "$TMP/$ARCHIVE" "$ARCHIVE_URL" \
112 || die "download failed: $ARCHIVE_URL"
113
114msg "verifying checksum"
115curl -fsSL -o "$TMP/checksums.txt" "$CHECKSUMS_URL" \
116 || die "download failed: $CHECKSUMS_URL"
117
118expected=$(grep " ${ARCHIVE}\$" "$TMP/checksums.txt" | awk '{print $1}' || true)
119[ -n "$expected" ] || die "no checksum for $ARCHIVE in checksums.txt"
120
121if command -v sha256sum >/dev/null 2>&1; then
122 actual=$(sha256sum "$TMP/$ARCHIVE" | awk '{print $1}')
123elif command -v shasum >/dev/null 2>&1; then
124 actual=$(shasum -a 256 "$TMP/$ARCHIVE" | awk '{print $1}')
125else
126 die "no sha256 tool found (sha256sum or shasum)"
127fi
128
129[ "$expected" = "$actual" ] \
130 || die "checksum mismatch: expected $expected, got $actual"
131
132msg "extracting"
133tar -xzf "$TMP/$ARCHIVE" -C "$TMP"
134
135[ -f "$TMP/$BINARY" ] || die "archive did not contain a '$BINARY' binary"
136
137msg "installing to $PREFIX/$BINARY"
138install -m 0755 "$TMP/$BINARY" "$PREFIX/$BINARY" 2>/dev/null \
139 || { cp "$TMP/$BINARY" "$PREFIX/$BINARY" && chmod 0755 "$PREFIX/$BINARY"; }
140
141# ---- PATH hint ----
142
143case ":$PATH:" in
144 *":$PREFIX:"*) ;;
145 *)
146 warn "$PREFIX is not on your PATH"
147 warn "add this to your shell rc file:"
148 warn " export PATH=\"$PREFIX:\$PATH\""
149 ;;
150esac
151
152msg "installed $("$PREFIX/$BINARY" --version 2>/dev/null || echo zut)"
153msg "run: zut (interactive tui)"
154msg "run: zut --help (all flags and subcommands)"