install.sh RawHistoryFolder
1#!/bin/sh
2# vev installer — Linux x86_64/arm64 and macOS arm64.
3# Usage: curl -fsSL https://raw.githubusercontent.com/bnema/vev/main/install.sh | sh
4# Env: VEV_VERSION=vX.Y.Z to pin a version (default: latest release).
5set -eu
6
7REPO="bnema/vev"
8VERSION="${VEV_VERSION:-latest}"
9
10info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
11err() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; }
12
13# --- Detect platform -------------------------------------------------------
14OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
15ARCH="$(uname -m)"
16case "$OS/$ARCH" in
17 linux/x86_64|linux/amd64) TARBALL="vev_linux_x86_64.tar.gz" ;;
18 linux/aarch64|linux/arm64) TARBALL="vev_linux_arm64.tar.gz" ;;
19 darwin/arm64) TARBALL="vev_darwin_arm64.tar.gz" ;;
20 *) err "unsupported platform: $OS/$ARCH (supported: linux/x86_64, linux/arm64, darwin/arm64)" ;;
21esac
22
23# --- Pick install dir ------------------------------------------------------
24if [ -d "$HOME/.local/bin" ]; then
25 INSTALL_DIR="$HOME/.local/bin"
26else
27 INSTALL_DIR="/usr/local/bin"
28fi
29
30# --- Resolve version -------------------------------------------------------
31if [ "$VERSION" = "latest" ]; then
32 VERSION="$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \
33 | grep '"tag_name"' | head -1 | cut -d '"' -f 4)"
34 [ -n "$VERSION" ] || err "could not resolve the latest release tag"
35fi
36info "installing vev ${VERSION} (${OS}/${ARCH}) to ${INSTALL_DIR}"
37
38# --- Download + verify -----------------------------------------------------
39TMP_DIR="$(mktemp -d)"
40trap 'rm -rf "$TMP_DIR"' EXIT
41
42BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"
43curl -fsSL -o "$TMP_DIR/$TARBALL" "$BASE_URL/$TARBALL" \
44 || err "download failed: $BASE_URL/$TARBALL"
45curl -fsSL -o "$TMP_DIR/checksums.txt" "$BASE_URL/checksums.txt" \
46 || err "download failed: $BASE_URL/checksums.txt"
47
48cd "$TMP_DIR"
49EXPECTED="$(grep " $TARBALL\$" checksums.txt | cut -d ' ' -f 1)"
50[ -n "$EXPECTED" ] || err "$TARBALL not found in checksums.txt"
51if command -v sha256sum >/dev/null 2>&1; then
52 ACTUAL="$(sha256sum "$TARBALL" | cut -d ' ' -f 1)"
53else
54 ACTUAL="$(shasum -a 256 "$TARBALL" | cut -d ' ' -f 1)"
55fi
56[ "$EXPECTED" = "$ACTUAL" ] || err "checksum mismatch for $TARBALL (expected $EXPECTED, got $ACTUAL)"
57info "checksum verified"
58
59# --- Extract + install -----------------------------------------------------
60tar -xzf "$TARBALL"
61
62# Keep this list in the archive's required installation order. All payloads
63# are checked and staged before a live file is replaced.
64PAYLOADS="vev scripts/vev-bar-top-right scripts/vev-bar-bottom-right"
65TRANSACTION=".vev-install-$$"
66
67path_exists() {
68 [ -e "$1" ] || [ -L "$1" ]
69}
70
71install_path() {
72 # Run every destination mutation through the same privilege path.
73 if [ -w "$INSTALL_DIR" ]; then
74 "$@"
75 else
76 sudo "$@"
77 fi
78}
79
80ensure_install_dir() {
81 [ -d "$INSTALL_DIR" ] && return 0
82
83 if [ -w "$(dirname "$INSTALL_DIR")" ]; then
84 mkdir -p "$INSTALL_DIR" || err "cannot create $INSTALL_DIR"
85 else
86 info "escalating with sudo to create $INSTALL_DIR"
87 sudo mkdir -p "$INSTALL_DIR" || err "cannot create $INSTALL_DIR"
88 fi
89}
90
91stage_path() {
92 printf '%s/%s-%s.new\n' "$INSTALL_DIR" "$TRANSACTION" "${1##*/}"
93}
94
95backup_path() {
96 printf '%s/%s-%s.bak\n' "$INSTALL_DIR" "$TRANSACTION" "${1##*/}"
97}
98
99remove_destination_file() {
100 install_path rm -f "$1"
101}
102
103cleanup_staging() {
104 for payload in $PAYLOADS; do
105 stage="$(stage_path "$payload")"
106 remove_destination_file "$stage" || return 1
107 done
108}
109
110rollback_install() {
111 rollback_ok=true
112
113 for payload in $PAYLOADS; do
114 name="${payload##*/}"
115 target="$INSTALL_DIR/$name"
116 stage="$(stage_path "$payload")"
117 backup="$(backup_path "$payload")"
118
119 if path_exists "$backup"; then
120 # A prior file was moved aside: remove the new one and put it back.
121 remove_destination_file "$target" || rollback_ok=false
122 install_path mv "$backup" "$target" || rollback_ok=false
123 elif ! path_exists "$stage"; then
124 # No backup means the original was absent. A missing stage was moved
125 # into place, so restore that original absence.
126 remove_destination_file "$target" || rollback_ok=false
127 fi
128 remove_destination_file "$stage" || rollback_ok=false
129 done
130
131 [ "$rollback_ok" = true ]
132}
133
134preflight_payloads() {
135 for payload in $PAYLOADS; do
136 [ -f "$payload" ] && [ -r "$payload" ] \
137 || err "archive is missing readable payload: $payload"
138 done
139}
140
141# Do not create or alter the destination until every archive payload exists.
142preflight_payloads
143ensure_install_dir
144
145# Stage every payload in the destination first, so each later mv is a
146# same-filesystem replacement. A staging failure leaves live files untouched.
147for source in $PAYLOADS; do
148 stage="$(stage_path "$source")"
149 if ! install_path install -m 755 "$source" "$stage"; then
150 cleanup_staging || err "failed to stage payloads and clean staging files"
151 err "failed to stage payload: $source"
152 fi
153done
154
155# Replace each live file only after all three staged copies are ready. Existing
156# files are retained as backups until the full transaction succeeds.
157for source in $PAYLOADS; do
158 name="${source##*/}"
159 target="$INSTALL_DIR/$name"
160 stage="$(stage_path "$source")"
161 backup="$(backup_path "$source")"
162
163 if path_exists "$target" && ! install_path mv "$target" "$backup"; then
164 rollback_install || err "failed to preserve $target and roll back installation"
165 err "failed to preserve existing file: $target"
166 fi
167 if ! install_path mv "$stage" "$target"; then
168 rollback_install || err "failed to replace $target and roll back installation"
169 err "failed to install payload: $source"
170 fi
171done
172
173for source in $PAYLOADS; do
174 backup="$(backup_path "$source")"
175 remove_destination_file "$backup" \
176 || err "installed payloads but failed to clean backup: $backup"
177done
178
179info "installed: $INSTALL_DIR/vev"
180case ":${PATH:-}:" in
181 *":$INSTALL_DIR:"*) "$INSTALL_DIR/vev" --version ;;
182 *) info "note: $INSTALL_DIR is not in your PATH" ;;
183esac