#!/bin/bash # # Berserk CLI installer # Usage: curl -fsSL https://go.bzrk.dev | bash # curl -fsSL https://go.bzrk.dev | bash -s v1.0.15 # # Berserk is pre-release (beta) software, provided AS IS with no warranty. # By installing and using it you agree to the Berserk Terms of Use: # https://www.bzrk.dev/legal/pre-release-license-terms/ set -e # release.yml pushes each release's binaries here, one artifact per platform # tagged --. Anonymous reads are plain HTTPS: the manifest # names the binary's sha256, the blob is the binary. REGISTRY_HOST="https://images.bzrk.dev" REGISTRY="${REGISTRY_HOST}/v2/release/bzrk" BINARY_NAME="bzrk" INSTALL_DIR="${BZRK_INSTALL_DIR:-$HOME/.local/bin}" # Parse command line arguments VERSION="$1" # Optional: specific version (e.g., v1.0.15) if [[ -n "$VERSION" ]] && [[ ! "$VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[^[:space:]]+)?$ ]]; then echo "Usage: $0 [VERSION]" >&2 echo " Examples: $0 v1.0.15" >&2 echo " $0 (installs latest)" >&2 exit 1 fi # Detect platform case "$(uname -s)" in Darwin) os="darwin" ;; Linux) os="linux" ;; MINGW*|MSYS*|CYGWIN*) echo "Windows is not supported by this script." >&2 exit 1 ;; *) echo "Unsupported operating system: $(uname -s)" >&2 exit 1 ;; esac case "$(uname -m)" in x86_64|amd64) arch="x86_64" ;; arm64|aarch64) arch="aarch64" ;; *) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;; esac if [ "$os" = "darwin" ] && [ "$arch" != "aarch64" ]; then echo "macOS is only supported on Apple Silicon (ARM64)." >&2 exit 1 fi # Check for required dependencies DOWNLOADER="" if command -v curl >/dev/null 2>&1; then DOWNLOADER="curl" elif command -v wget >/dev/null 2>&1; then DOWNLOADER="wget" else echo "Either curl or wget is required but neither is installed" >&2 exit 1 fi if command -v sha256sum >/dev/null 2>&1; then SHA256="sha256sum" elif command -v shasum >/dev/null 2>&1; then SHA256="shasum -a 256" else echo "Either sha256sum or shasum is required to verify the download" >&2 exit 1 fi # download_file [output] [accept] download_file() { local url="$1" local output="$2" local accept="${3:-*/*}" if [ "$DOWNLOADER" = "curl" ]; then if [ -n "$output" ]; then curl -fsSL -H "Accept: ${accept}" -o "$output" "$url" else curl -fsSL -H "Accept: ${accept}" "$url" fi else if [ -n "$output" ]; then wget -q --header="Accept: ${accept}" -O "$output" "$url" else wget -q --header="Accept: ${accept}" -O - "$url" fi fi } platform="${os}-${arch}" tmp_dir=$(mktemp -d) trap 'rm -rf "$tmp_dir"' EXIT # fetch_page fetch_page() { if [ "$DOWNLOADER" = "curl" ]; then curl -fsSL -D "$3" -o "$2" "$1" else wget -q -S -O "$2" "$1" 2> "$3" fi } # The rel="next" target in a response's Link header, if any. next_page() { tr -d '\r' < "$1" | sed -n 's/^[[:space:]]*[Ll]ink:.*<\([^>]*\)>[^,]*rel="next".*/\1/p' | head -1 } # The newest stable or release-candidate version published for this platform, # across every page of the tag list. One-off builds (e.g. v1.0.40-dropbox-v2) # are never picked as newest. newest_release() { local url="${REGISTRY}/tags/list?n=1000" local tags="$tmp_dir/tags" page="$tmp_dir/page" headers="$tmp_dir/headers" next : > "$tags" while [ -n "$url" ]; do fetch_page "$url" "$page" "$headers" || return 1 tr ',' '\n' < "$page" | sed -n 's/.*"\(v[0-9][^"]*\)".*/\1/p' >> "$tags" next=$(next_page "$headers") case "$next" in "") url="" ;; http://* | https://*) url="$next" ;; /*) url="${REGISTRY_HOST}${next}" ;; *) echo "Unexpected Link header target: ${next}" >&2; return 1 ;; esac done sed -n "s/-${platform}\$//p" "$tags" \ | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$' \ | awk '{ v = $0; sub(/^v/, "", v) split(v, parts, "-"); split(parts[1], core, ".") rc = parts[2] if (rc == "") { stable = 1; n = 0 } else { stable = 0; sub(/^rc\./, "", rc); n = rc } printf "%d %d %d %d %d %s\n", core[1], core[2], core[3], stable, n, $0 }' \ | sort -n -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ | tail -1 \ | awk '{ print $6 }' } # Resolve version if [ -z "$VERSION" ]; then echo "Fetching latest release..." if ! VERSION=$(newest_release) || [ -z "$VERSION" ]; then echo "Failed to determine the latest version from ${REGISTRY}" >&2 exit 1 fi else [[ "$VERSION" =~ ^v ]] || VERSION="v${VERSION}" fi manifest_url="${REGISTRY}/manifests/${VERSION}-${platform}" if ! manifest=$(download_file "$manifest_url" "" "application/vnd.oci.image.manifest.v1+json" 2>/dev/null); then echo "There is no bzrk release ${VERSION} for ${os}/${arch}." >&2 exit 1 fi layer=$(echo "$manifest" | tr -d '\n ' | sed -n 's/.*"layers":\[\({[^]]*}\)\].*/\1/p') digest=$(echo "$layer" | sed -n 's/.*"digest":"\(sha256:[0-9a-f]\{64\}\)".*/\1/p') size=$(echo "$layer" | sed -n 's/.*"size":\([0-9][0-9]*\).*/\1/p') if [ -z "$digest" ] || [ -z "$size" ]; then echo "The ${VERSION} manifest names no binary: ${manifest}" >&2 exit 1 fi download_url="${REGISTRY}/blobs/${digest}" echo "Installing ${BINARY_NAME} ${VERSION} (${os}/${arch})..." # Download binary to temp dir binary_path="${tmp_dir}/${BINARY_NAME}" if ! download_file "$download_url" "$binary_path"; then echo "Downloading ${download_url} failed." >&2 exit 1 fi actual_size=$(wc -c < "$binary_path" | tr -d ' ') actual="sha256:$($SHA256 "$binary_path" | cut -d' ' -f1)" if [ "$actual_size" != "$size" ] || [ "$actual" != "$digest" ]; then echo "The download is corrupt: got ${actual_size} bytes (${actual}), expected ${size} bytes (${digest})." >&2 exit 1 fi chmod +x "$binary_path" # Remove macOS quarantine attribute (prevents Gatekeeper blocking unsigned binaries) if [ "$os" = "darwin" ]; then xattr -d com.apple.quarantine "$binary_path" 2>/dev/null || true fi # Verify the binary runs if ! "$binary_path" --version >/dev/null 2>&1; then echo "Downloaded binary failed to execute. Your platform may not be supported yet." >&2 exit 1 fi # Install mkdir -p "$INSTALL_DIR" mv "$binary_path" "${INSTALL_DIR}/${BINARY_NAME}" echo "" echo "Installed ${BINARY_NAME} ${VERSION} to ${INSTALL_DIR}/${BINARY_NAME}" # Check if install dir is in PATH if ! echo "$PATH" | tr ':' '\n' | grep -qx "$INSTALL_DIR"; then echo "" echo "Add ${INSTALL_DIR} to your PATH:" shell_name=$(basename "${SHELL:-/bin/bash}") case "$shell_name" in zsh) echo " echo 'export PATH=\"${INSTALL_DIR}:\$PATH\"' >> ~/.zshrc && source ~/.zshrc" ;; bash) echo " echo 'export PATH=\"${INSTALL_DIR}:\$PATH\"' >> ~/.bashrc && source ~/.bashrc" ;; fish) echo " fish_add_path ${INSTALL_DIR}" ;; *) echo " echo 'export PATH=\"${INSTALL_DIR}:\$PATH\"' >> ~/.profile" ;; esac fi echo "" echo "Run '${BINARY_NAME} --help' to get started." echo "" echo "Berserk is pre-release (beta) software, provided AS IS with no warranty." echo "By using it you agree to the Terms of Use: https://www.bzrk.dev/legal/pre-release-license-terms/" echo ""