install.sh

7.6 KB · 271 lines · bash

1#!/usr/bin/env bash
2# brt installer — pipe via: curl -fsSL https://brt.sushii.dev/install.sh | sh
3[ -n "${BASH_VERSION:-}" ] || exec bash -s "$@"
4set -euo pipefail
5
6BRT_INSTALL_DIR="${BRT_INSTALL_DIR:-${HOME}/.local/share/brt}"
7BRT_BIN_DIR="${BRT_BIN_DIR:-${HOME}/.local/bin}"
8BRT_BASE_URL="${BRT_BASE_URL:-https://brt.sushii.dev}"
9
10info() { printf '\033[36m●\033[0m %s\n' "$*"; }
11ok() { printf '\033[32m✓\033[0m %s\n' "$*"; }
12warn() { printf '\033[33m!\033[0m %s\n' "$*" >&2; }
13error() { printf '\033[31m✗\033[0m %s\n' "$*" >&2; exit 1; }
14
15_brt_installer_os_windows() {
16 [[ "${OS:-}" == "Windows_NT" ]] && return 0
17 case "$(uname -s 2>/dev/null)" in
18 MINGW*|MSYS*|CYGWIN*) return 0 ;;
19 esac
20 return 1
21}
22
23if _brt_installer_os_windows; then
24 warn "This installer is for macOS and Linux."
25 info "On Windows, use:"
26 printf ' irm https://brt.sushii.dev/win/install.ps1 | iex\n'
27 exit 1
28fi
29
30load_progress_bar() {
31 local root="${1:-}"
32 if [[ -n "$root" && -f "${root}/lib/progress.sh" ]]; then
33 # shellcheck source=/dev/null
34 source "${root}/lib/progress.sh"
35 return 0
36 fi
37
38 local tmp
39 tmp=$(mktemp)
40 curl -fsSL \
41 -H 'Cache-Control: no-cache' \
42 -H 'Pragma: no-cache' \
43 "${BRT_BASE_URL}/lib/progress.sh" \
44 -o "$tmp" || error "Failed to download ${BRT_BASE_URL}/lib/progress.sh"
45 # shellcheck source=/dev/null
46 source "$tmp"
47 rm -f "$tmp"
48}
49
50BRT_LIB_MODULES="common progress deps ip dns portscan ping whois trace curl post headers ssl reverse edit json encode hash url grep uuid pass run serve watch listen killport du extract sysinfo publickey timestamp timer calc env which speed weather qr image video doom browse config access update uninstall"
51
52build_install_files() {
53 INSTALL_FILES=(brt)
54 local f
55 for f in ${BRT_LIB_MODULES}; do
56 INSTALL_FILES+=("lib/${f}.sh")
57 done
58 INSTALL_FILES+=(lib/access.py lib/media.py lib/browse.py version)
59}
60
61fetch() {
62 local path="$1"
63 local dest="${BRT_INSTALL_DIR}/${path}"
64 mkdir -p "$(dirname "$dest")"
65 curl -fsSL \
66 -H 'Cache-Control: no-cache' \
67 -H 'Pragma: no-cache' \
68 "${BRT_BASE_URL}/${path}" \
69 -o "$dest" || error "Failed to download ${BRT_BASE_URL}/${path}"
70}
71
72fetch_remote_version() {
73 curl -fsSL \
74 -H 'Cache-Control: no-cache' \
75 -H 'Pragma: no-cache' \
76 "${BRT_BASE_URL}/version" \
77 | tr -d '[:space:]'
78}
79
80read_installed_version() {
81 if [[ -f "${BRT_INSTALL_DIR}/version" ]]; then
82 tr -d '[:space:]' < "${BRT_INSTALL_DIR}/version"
83 else
84 grep '^_BRT_EMBEDDED_VERSION=' "${BRT_INSTALL_DIR}/brt" 2>/dev/null | head -1 | cut -d'"' -f2
85 fi
86}
87
88verify_install_version() {
89 local expected="$1"
90 local installed embedded
91
92 installed=$(read_installed_version)
93 embedded=$(grep '^_BRT_EMBEDDED_VERSION=' "${BRT_INSTALL_DIR}/brt" 2>/dev/null | head -1 | cut -d'"' -f2)
94
95 [[ -n "$installed" ]] || error "Install failed: version file missing"
96 [[ "$installed" == "$expected" ]] || error "Install failed: version file is ${installed}, expected ${expected}"
97 [[ "$embedded" == "$expected" ]] || error "Install failed: brt binary is ${embedded}, expected ${expected}"
98}
99
100install_from_local() {
101 local root="$1"
102 local path total i=0
103
104 build_install_files
105 total=${#INSTALL_FILES[@]}
106
107 info "Installing brt from local source..."
108 mkdir -p "$BRT_INSTALL_DIR/lib" "$BRT_BIN_DIR"
109 load_progress_bar "$root"
110
111 for path in "${INSTALL_FILES[@]}"; do
112 ((i++)) || true
113 progress_bar "$i" "$total" "$path"
114 mkdir -p "$(dirname "${BRT_INSTALL_DIR}/${path}")"
115 cp "${root}/${path}" "${BRT_INSTALL_DIR}/${path}"
116 done
117
118 chmod +x "$BRT_INSTALL_DIR/brt" "$BRT_INSTALL_DIR/lib/"*.sh "$BRT_INSTALL_DIR/lib/access.py" "$BRT_INSTALL_DIR/lib/media.py" "$BRT_INSTALL_DIR/lib/browse.py"
119}
120
121install_from_site() {
122 local remote_version="$1"
123 local path total i=0
124
125 build_install_files
126 total=${#INSTALL_FILES[@]}
127
128 info "Downloading brt ${remote_version} from ${BRT_BASE_URL}..."
129
130 rm -rf "$BRT_INSTALL_DIR"
131 mkdir -p "$BRT_INSTALL_DIR/lib" "$BRT_BIN_DIR"
132 load_progress_bar
133
134 for path in "${INSTALL_FILES[@]}"; do
135 ((i++)) || true
136 progress_bar "$i" "$total" "$path"
137 fetch "$path"
138 done
139
140 chmod +x "$BRT_INSTALL_DIR/brt" "$BRT_INSTALL_DIR/lib/"*.sh "$BRT_INSTALL_DIR/lib/access.py" "$BRT_INSTALL_DIR/lib/media.py" "$BRT_INSTALL_DIR/lib/browse.py"
141 verify_install_version "$remote_version"
142}
143
144BRT_PATH_MARKER='# brt PATH'
145
146shell_rc_file() {
147 case "${SHELL##*/}" in
148 zsh) printf '%s' "${HOME}/.zshrc" ;;
149 bash)
150 if [[ "$(uname -s)" == "Darwin" ]]; then
151 printf '%s' "${HOME}/.bash_profile"
152 else
153 printf '%s' "${HOME}/.bashrc"
154 fi
155 ;;
156 *) printf '%s' "${HOME}/.profile" ;;
157 esac
158}
159
160ensure_shell_path() {
161 local rc path_line
162 rc=$(shell_rc_file)
163 path_line="export PATH=\"${BRT_BIN_DIR}:\$PATH\""
164
165 if [[ -f "$rc" ]] && grep -qF "$BRT_PATH_MARKER" "$rc" 2>/dev/null; then
166 SHELL_RC_CONFIGURED="$rc"
167 return 0
168 fi
169
170 mkdir -p "$(dirname "$rc")"
171 touch "$rc"
172 {
173 echo ''
174 echo "$BRT_PATH_MARKER"
175 echo "$path_line"
176 } >> "$rc"
177 SHELL_RC_CONFIGURED="$rc"
178 ok "Added ${BRT_BIN_DIR} to PATH in ${rc}"
179}
180
181ensure_path_priority() {
182 mkdir -p "$BRT_BIN_DIR"
183 ln -sf "${BRT_INSTALL_DIR}/brt" "${BRT_BIN_DIR}/brt"
184
185 local dir p target
186 IFS=':' read -ra _path_dirs <<< "$PATH"
187 for dir in "${_path_dirs[@]}"; do
188 [[ -n "$dir" && -d "$dir" ]] || continue
189 p="${dir}/brt"
190 [[ -e "$p" ]] || continue
191 [[ "$p" == "${BRT_BIN_DIR}/brt" ]] && continue
192 if [[ -L "$p" ]]; then
193 target=$(readlink "$p" 2>/dev/null || true)
194 [[ "$target" == "${BRT_INSTALL_DIR}/brt" ]] && continue
195 fi
196 if [[ -w "$dir" ]]; then
197 rm -f "$p"
198 info "Removed conflicting brt at ${p}"
199 else
200 warn "Could not remove conflicting brt at ${p}"
201 fi
202 done
203
204 case ":${PATH}:" in
205 *":${BRT_BIN_DIR}:"*) ;;
206 *) ensure_shell_path ;;
207 esac
208
209 case ":${PATH}:" in
210 *":${BRT_BIN_DIR}:"*) ;;
211 *) export PATH="${BRT_BIN_DIR}:${PATH}" ;;
212 esac
213}
214
215ensure_startup_update_check() {
216 if [[ -x "${BRT_INSTALL_DIR}/brt" ]]; then
217 BRT_DIR="${BRT_INSTALL_DIR}" "${BRT_INSTALL_DIR}/brt" -update-check --install-hook 2>/dev/null || true
218 fi
219}
220
221install_media_dependencies() {
222 # shellcheck source=/dev/null
223 source "${BRT_INSTALL_DIR}/lib/deps.sh"
224 brt_install_media_dependencies
225}
226
227# Piped from curl — always install from site (never local copy)
228SCRIPT_PATH="${BASH_SOURCE[0]:-}"
229PIPED=false
230[[ -z "$SCRIPT_PATH" || "$SCRIPT_PATH" == "-" || "$SCRIPT_PATH" == /dev/fd/* || "$SCRIPT_PATH" == /dev/stdin ]] && PIPED=true
231
232LOCAL_ROOT=""
233if [[ "$PIPED" == false && -n "$SCRIPT_PATH" && -f "$SCRIPT_PATH" ]]; then
234 SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
235 if [[ -f "${SCRIPT_DIR}/brt" && -d "${SCRIPT_DIR}/lib" && -f "${SCRIPT_DIR}/install.sh" ]]; then
236 LOCAL_ROOT="$SCRIPT_DIR"
237 elif [[ -f "${SCRIPT_DIR}/../brt" && -d "${SCRIPT_DIR}/../lib" ]]; then
238 LOCAL_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
239 fi
240fi
241
242REMOTE_VERSION=""
243if [[ -z "$LOCAL_ROOT" ]]; then
244 REMOTE_VERSION=$(fetch_remote_version)
245 [[ -n "$REMOTE_VERSION" ]] || error "Could not fetch latest version from ${BRT_BASE_URL}/version"
246 info "Latest version: ${REMOTE_VERSION}"
247 install_from_site "$REMOTE_VERSION"
248else
249 install_from_local "$LOCAL_ROOT"
250 REMOTE_VERSION=$(read_installed_version)
251fi
252
253ensure_path_priority
254
255ensure_startup_update_check
256
257install_media_dependencies || true
258
259ok "brt ${REMOTE_VERSION} installed to ${BRT_INSTALL_DIR}"
260info "Binary: ${BRT_BIN_DIR}/brt"
261echo
262info "Verifying install..."
263"${BRT_BIN_DIR}/brt" -version
264echo
265if [[ -n "${SHELL_RC_CONFIGURED:-}" ]]; then
266 ok "PATH configured — open a new terminal, or run: source \"${SHELL_RC_CONFIGURED}\""
267else
268 ok "PATH ready (${BRT_BIN_DIR})"
269fi
270info "Quick test: brt -help"
271