#!/bin/sh # BowlerKit CLI installer. # # Usage: # curl -fsSL https://cli.bowlerkit.com/ | sh # # Detects OS/arch (linux & darwin, amd64 & arm64), downloads the matching # release from cli.bowlerkit.com, and installs it WITHOUT requiring root: # /usr/local/bin when writable, otherwise ~/.local/bin (PATH guidance is # printed when needed). Override the install directory with # BOWLER_INSTALL_DIR, or the download origin with BOWLER_DOWNLOAD_BASE. # # Releasing a new version: # 1. Build release binaries (stamps the version AND the production API # server from cli/.env.prod into each binary): # cd cli && make release VERSION= # Produces bin/bowler-{darwin-amd64,darwin-arm64,linux-amd64, # linux-arm64,windows-amd64[.exe]} + SHA256SUMS. # Manual equivalent of one platform (both stamps required): # CGO_ENABLED=0 GOOS= GOARCH= go build -trimpath \ # -ldflags="-s -w \ # -X soewidiputra/bowler/services.Version= \ # -X soewidiputra/bowler/services.DefaultAPIServer=$BOWLER_API_SERVER" \ # -o bowler-- . # where BOWLER_API_SERVER comes from cli/.env.prod # (https://cli.bowlerkit.com/api). Binaries built WITHOUT the # DefaultAPIServer stamp fall back to http://localhost:8080/api. # 2. SHA256SUMS are generated next to the binaries by `make release`; # manually: cd && sha256sum bowler-* > SHA256SUMS # 3. Upload binaries + SHA256SUMS to /opt/bowlerkit-dl// on # server_tencent_new # 4. Write the version (e.g. v1.0.1) to /opt/bowlerkit-dl/latest.txt set -e BASE_URL="${BOWLER_DOWNLOAD_BASE:-https://cli.bowlerkit.com/dl}" printf '\033[1;34mInstalling BowlerKit CLI...\033[0m\n' # --- detect platform ------------------------------------------------------- OS=$(uname -s) ARCH=$(uname -m) case "$OS" in Linux) OS=linux ;; Darwin) OS=darwin ;; *) printf '\033[31mError: unsupported operating system "%s".\033[0m\n' "$OS" >&2 exit 1 ;; esac case "$ARCH" in x86_64 | amd64) ARCH=amd64 ;; aarch64 | arm64) ARCH=arm64 ;; *) printf '\033[31mError: unsupported architecture "%s".\033[0m\n' "$ARCH" >&2 exit 1 ;; esac # --- pick a downloader ----------------------------------------------------- if command -v curl >/dev/null 2>&1; then fetch() { curl -fsSL -o "$2" "$1"; } elif command -v wget >/dev/null 2>&1; then fetch() { wget -q -O "$2" "$1"; } else printf '\033[31mError: curl or wget is required to run this installer.\033[0m\n' >&2 exit 1 fi TMP_DIR=$(mktemp -d) trap 'rm -rf "$TMP_DIR"' EXIT INT TERM # --- resolve the latest version -------------------------------------------- fetch "$BASE_URL/latest.txt" "$TMP_DIR/latest.txt" VERSION=$(tr -d '[:space:]' <"$TMP_DIR/latest.txt") case "$VERSION" in v*) : ;; *) printf '\033[31mError: could not determine the latest version.\033[0m\n' >&2 exit 1 ;; esac # --- download --------------------------------------------------------------- DOWNLOAD_URL="$BASE_URL/$VERSION/bowler-$OS-$ARCH" printf 'Downloading BowlerKit %s for %s/%s...\n' "$VERSION" "$OS" "$ARCH" fetch "$DOWNLOAD_URL" "$TMP_DIR/bowler" if [ ! -s "$TMP_DIR/bowler" ]; then printf '\033[31mError: downloaded binary is empty — check %s\033[0m\n' "$DOWNLOAD_URL" >&2 exit 1 fi chmod +x "$TMP_DIR/bowler" # --- verify checksum -------------------------------------------------------- # Confirms the downloaded binary matches the published build byte-for-byte. fetch "$BASE_URL/$VERSION/SHA256SUMS" "$TMP_DIR/SHA256SUMS" WANT_FILE="bowler-$OS-$ARCH" EXPECTED=$(awk -v f="$WANT_FILE" '$2 == f { print $1 }' "$TMP_DIR/SHA256SUMS") if [ -z "$EXPECTED" ]; then printf '\033[31mError: no checksum published for %s — refusing to install.\033[0m\n' "$WANT_FILE" >&2 exit 1 fi if command -v sha256sum >/dev/null 2>&1; then ACTUAL=$(sha256sum "$TMP_DIR/bowler" | awk '{print $1}') elif command -v shasum >/dev/null 2>&1; then ACTUAL=$(shasum -a 256 "$TMP_DIR/bowler" | awk '{print $1}') elif command -v openssl >/dev/null 2>&1; then ACTUAL=$(openssl dgst -sha256 "$TMP_DIR/bowler" | awk '{print $NF}') else printf '\033[33mWarning: no sha256 tool found (sha256sum/shasum/openssl); skipping integrity check.\033[0m\n' >&2 ACTUAL=$EXPECTED fi if [ "$ACTUAL" != "$EXPECTED" ]; then printf '\033[31mError: checksum mismatch for %s!\033[0m\n' "$WANT_FILE" >&2 printf ' expected: %s\n actual: %s\n' "$EXPECTED" "$ACTUAL" >&2 printf 'The download may be corrupted or tampered with. Installation aborted.\n' >&2 exit 1 fi printf 'Checksum verified (%s).\n' "$ACTUAL" # --- install ---------------------------------------------------------------- # Root-free by default: prefer /usr/local/bin when writable, else ~/.local/bin. # An explicit BOWLER_INSTALL_DIR always wins (sudo used only if you aim it at # a root-owned directory). INSTALL_DIR="${BOWLER_INSTALL_DIR:-}" if [ -z "$INSTALL_DIR" ]; then if [ -w /usr/local/bin ] || [ "$(id -u)" = "0" ]; then INSTALL_DIR=/usr/local/bin elif [ -n "$HOME" ]; then INSTALL_DIR="$HOME/.local/bin" else printf '\033[31mError: cannot determine an install directory — set BOWLER_INSTALL_DIR.\033[0m\n' >&2 exit 1 fi fi if [ ! -d "$INSTALL_DIR" ]; then mkdir -p "$INSTALL_DIR" 2>/dev/null || sudo mkdir -p "$INSTALL_DIR" fi if [ -w "$INSTALL_DIR" ]; then mv "$TMP_DIR/bowler" "$INSTALL_DIR/bowler" elif command -v sudo >/dev/null 2>&1; then printf 'Writing to %s requires sudo...\n' "$INSTALL_DIR" sudo mv "$TMP_DIR/bowler" "$INSTALL_DIR/bowler" else printf '\033[33mCannot write to %s and sudo is unavailable.\033[0m\n' "$INSTALL_DIR" >&2 printf 'Install manually:\n mv %s ~/.local/bin/bowler\n' "$TMP_DIR/bowler" >&2 exit 1 fi printf '\033[1;32mBowlerKit CLI %s installed successfully!\033[0m\n' "$VERSION" # PATH guidance — only when the target is not already on PATH. case ":$PATH:" in *":$INSTALL_DIR:"*) : ;; *) printf '\n\033[33mNote: %s is not on your PATH yet.\033[0m\n' "$INSTALL_DIR" printf 'Make it available now:\n' printf ' export PATH="%s:$PATH"\n' "$INSTALL_DIR" case "$(basename "${SHELL:-}")" in zsh) RC="$HOME/.zshrc" ;; bash) RC="$HOME/.bashrc" ;; fish) printf 'For fish: fish_add_path %s\n' "$INSTALL_DIR" RC="" ;; *) RC="your shell rc file" ;; esac [ -n "$RC" ] && printf 'To make it permanent, add that line to %s.\n' "$RC" ;; esac printf '\nNext steps:\n' printf ' 1. \033[1mbowler doctor\033[0m — verify your environment\n' printf ' 2. \033[1mbowler create my-app\033[0m — scaffold your project (asks for your license key)\n'