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