Initial push to gitea

This commit is contained in:
2026-05-10 13:37:17 -06:00
commit 54629aecad
20 changed files with 2381 additions and 0 deletions

480
scripts/install.sh Executable file
View File

@@ -0,0 +1,480 @@
#!/usr/bin/env bash
# install.sh — interactive setup for `publish`.
#
# Detects OS + GPU, walks through:
# 1. system dependencies (ffmpeg, cmake, git, go, GPU runtime)
# 2. whisper.cpp checkout + build for the chosen backend
# 3. ggml model download
# 4. publish binary build + symlink into ~/.local/bin
#
# Re-runnable. Each step is idempotent and skippable.
#
# Pass --doctor to print detection info and exit.
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PREFIX="${PREFIX:-$HOME/.local}"
BINDIR="$PREFIX/bin"
MODELDIR="$HOME/.cache/whisper.cpp"
WHISPER_REPO="${WHISPER_REPO:-$HOME/Git Repos/whisper.cpp}"
WHISPER_GIT="https://github.com/ggerganov/whisper.cpp"
MODEL_BASE_URL="https://huggingface.co/ggerganov/whisper.cpp/resolve/main"
DOCTOR_ONLY=0
[[ "${1:-}" == "--doctor" ]] && DOCTOR_ONLY=1
bold() { printf '\033[1m%s\033[0m\n' "$*"; }
info() { printf ' %s\n' "$*"; }
warn() { printf '\033[33m warn: %s\033[0m\n' "$*" >&2; }
err() { printf '\033[31m error: %s\033[0m\n' "$*" >&2; }
hr() { printf -- '----------------------------------------\n'; }
# Portable CPU count: nproc on Linux, sysctl on macOS, fallback 4.
ncpu() {
if command -v nproc >/dev/null 2>&1; then nproc
elif command -v sysctl >/dev/null 2>&1; then sysctl -n hw.ncpu 2>/dev/null || echo 4
else echo 4; fi
}
ask_yn() {
# ask_yn "prompt" default(Y|N)
local prompt="$1" default="${2:-Y}" reply
local hint="[Y/n]"; [[ "$default" == "N" ]] && hint="[y/N]"
while true; do
read -r -p " $prompt $hint " reply || true
reply="${reply:-$default}"
case "$reply" in
[Yy]*) return 0 ;;
[Nn]*) return 1 ;;
esac
done
}
ask_choice() {
# ask_choice "prompt" default option1 option2 ...
local prompt="$1" default="$2"; shift 2
local options=("$@") reply
local opts_joined; opts_joined="$(IFS='/'; echo "${options[*]}")"
while true; do
read -r -p " $prompt [$opts_joined] (default: $default): " reply || true
reply="${reply:-$default}"
for opt in "${options[@]}"; do
[[ "$reply" == "$opt" ]] && { echo "$reply"; return 0; }
done
warn "must be one of: $opts_joined"
done
}
# ---------- detection ----------
detect_os() {
case "$(uname -s)" in
Linux*)
if command -v pacman >/dev/null 2>&1; then echo "arch"
elif command -v apt-get >/dev/null 2>&1; then echo "debian"
elif command -v dnf >/dev/null 2>&1; then echo "fedora"
else echo "linux-other"; fi ;;
Darwin*) echo "macos" ;;
*) echo "unknown" ;;
esac
}
detect_gpu() {
# apple silicon
if [[ "$(uname -s)" == "Darwin" ]]; then
[[ "$(uname -m)" == "arm64" ]] && { echo "apple"; return; }
echo "none"; return
fi
# nvidia
if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null 2>&1; then
echo "nvidia"; return
fi
if command -v lspci >/dev/null 2>&1; then
local vga; vga="$(lspci 2>/dev/null | grep -iE 'vga|3d|display' || true)"
if echo "$vga" | grep -iq 'nvidia'; then echo "nvidia"; return; fi
if echo "$vga" | grep -iqE 'amd|ati|radeon'; then echo "amd"; return; fi
if echo "$vga" | grep -iq 'intel'; then echo "intel"; return; fi
fi
echo "none"
}
default_backend_for() {
case "$1" in
nvidia) echo "cuda" ;;
amd) echo "vulkan" ;; # easier to set up than rocm; user can pick rocm
apple) echo "metal" ;;
intel) echo "vulkan" ;;
*) echo "cpu" ;;
esac
}
OS="$(detect_os)"
GPU="$(detect_gpu)"
DEFAULT_BACKEND="$(default_backend_for "$GPU")"
# ---------- doctor ----------
print_detection() {
bold "=== publish — environment ==="
info "OS: $OS ($(uname -s) $(uname -r))"
info "Arch: $(uname -m)"
info "GPU: $GPU"
info "Default backend: $DEFAULT_BACKEND"
info "Repo dir: $REPO_DIR"
info "Install prefix: $PREFIX"
info "Whisper repo: $WHISPER_REPO"
info "Model dir: $MODELDIR"
hr
bold "Dependencies"
local deps=(go ffmpeg cmake git curl)
case "$DEFAULT_BACKEND" in
cuda) deps+=(nvidia-smi nvcc) ;;
rocm) deps+=(rocminfo hipcc) ;;
vulkan) deps+=(vulkaninfo glslc) ;;
esac
case "$OS" in
macos) deps+=(brew xcode-select pbcopy) ;;
*) deps+=(claude wl-copy xclip) ;;
esac
# Known off-PATH locations for tools that linux distros tuck away.
extra_path_for() {
case "$1" in
nvcc) echo /opt/cuda/bin/nvcc ;;
hipcc) echo /opt/rocm/bin/hipcc ;;
rocminfo) echo /opt/rocm/bin/rocminfo ;;
*) echo "" ;;
esac
}
for d in "${deps[@]}"; do
if command -v "$d" >/dev/null 2>&1; then
info "$(printf '%-14s %s' "$d:" "$(command -v "$d")")"
else
extra="$(extra_path_for "$d")"
if [[ -n "$extra" && -x "$extra" ]]; then
info "$(printf '%-14s %s' "$d:" "$extra (not on PATH)")"
else
info "$(printf '%-14s %s' "$d:" "MISSING")"
fi
fi
done
}
print_detection
if [[ $DOCTOR_ONLY -eq 1 ]]; then
exit 0
fi
hr
echo
# ---------- pick backend ----------
bold "Step 1 — pick whisper.cpp backend"
echo
echo " Detected default: $DEFAULT_BACKEND"
echo " Options: cuda | rocm | vulkan | metal | cpu | skip"
echo " cuda — NVIDIA GPU, fastest if you have one"
echo " rocm — AMD GPU via ROCm/HIP, fastest on supported AMD cards"
echo " vulkan — any GPU with a Vulkan driver (good cross-vendor fallback)"
echo " metal — Apple Silicon"
echo " cpu — no GPU, use the system whisper.cpp package"
echo " skip — leave whisper.cpp alone (e.g. you've already built it)"
echo
BACKEND="$(ask_choice "Backend" "$DEFAULT_BACKEND" cuda rocm vulkan metal cpu skip)"
# ---------- macOS preflight ----------
if [[ "$OS" == "macos" ]]; then
hr
bold "macOS preflight — Xcode Command Line Tools + Homebrew"
echo
if ! xcode-select -p >/dev/null 2>&1; then
warn "Xcode Command Line Tools not installed."
echo " Run: xcode-select --install"
echo " Then re-run 'make install'."
exit 1
else
info "Xcode CLT present at $(xcode-select -p)"
fi
if ! command -v brew >/dev/null 2>&1; then
warn "Homebrew not installed."
echo
echo " Install with:"
echo ' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
echo
if ask_yn "Install Homebrew now?" Y; then
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Make brew available for the rest of this run on Apple Silicon (default prefix /opt/homebrew).
if [[ -x /opt/homebrew/bin/brew ]]; then
eval "$(/opt/homebrew/bin/brew shellenv)"
elif [[ -x /usr/local/bin/brew ]]; then
eval "$(/usr/local/bin/brew shellenv)"
fi
else
err "Homebrew is required for the macOS install path. Install it and re-run."
exit 1
fi
else
info "Homebrew present at $(command -v brew)"
fi
fi
# ---------- system deps ----------
hr
bold "Step 2 — system dependencies"
echo
base_pkgs_arch=(go ffmpeg cmake git curl)
base_pkgs_debian=(golang-go ffmpeg cmake git curl build-essential)
base_pkgs_fedora=(golang ffmpeg cmake git curl gcc-c++)
base_pkgs_macos=(go ffmpeg cmake git curl)
backend_pkgs_arch_cuda=(cuda gcc15)
backend_pkgs_arch_rocm=(rocm-hip-sdk rocm-hip-runtime hipblas rocblas)
backend_pkgs_arch_vulkan=(vulkan-headers vulkan-icd-loader shaderc)
backend_pkgs_arch_cpu=(whisper.cpp)
backend_pkgs_macos_metal=() # xcode-select on macOS provides Metal toolchain
backend_pkgs_macos_cpu=(whisper-cpp)
pkgs_to_install=()
case "$OS" in
arch)
pkgs_to_install+=("${base_pkgs_arch[@]}")
case "$BACKEND" in
cuda) pkgs_to_install+=("${backend_pkgs_arch_cuda[@]}") ;;
rocm) pkgs_to_install+=("${backend_pkgs_arch_rocm[@]}") ;;
vulkan) pkgs_to_install+=("${backend_pkgs_arch_vulkan[@]}") ;;
cpu) pkgs_to_install+=("${backend_pkgs_arch_cpu[@]}") ;;
esac
;;
macos)
pkgs_to_install+=("${base_pkgs_macos[@]}")
[[ "$BACKEND" == "cpu" ]] && pkgs_to_install+=("${backend_pkgs_macos_cpu[@]}")
;;
debian)
pkgs_to_install+=("${base_pkgs_debian[@]}")
warn "Debian/Ubuntu: GPU runtime install for $BACKEND is distro-specific; you'll need to handle it manually."
;;
fedora)
pkgs_to_install+=("${base_pkgs_fedora[@]}")
warn "Fedora: GPU runtime install for $BACKEND is distro-specific; you'll need to handle it manually."
;;
*)
warn "Unknown OS '$OS' — install $BACKEND runtime, ffmpeg, cmake, git, and Go manually."
;;
esac
missing=()
for p in "${pkgs_to_install[@]}"; do
case "$p" in
# arch package -> binary mapping for "is it installed?" checks
go) command -v go >/dev/null 2>&1 || missing+=("$p") ;;
golang-go|golang) command -v go >/dev/null 2>&1 || missing+=("$p") ;;
ffmpeg) command -v ffmpeg >/dev/null 2>&1 || missing+=("$p") ;;
cmake) command -v cmake >/dev/null 2>&1 || missing+=("$p") ;;
git) command -v git >/dev/null 2>&1 || missing+=("$p") ;;
curl) command -v curl >/dev/null 2>&1 || missing+=("$p") ;;
cuda) command -v nvcc >/dev/null 2>&1 || missing+=("$p") ;;
gcc15) [[ -x /usr/bin/g++-15 ]] || missing+=("$p") ;;
rocm-hip-sdk) command -v hipcc >/dev/null 2>&1 || missing+=("$p") ;;
rocm-hip-runtime|hipblas|rocblas) [[ -d /opt/rocm ]] || missing+=("$p") ;;
vulkan-headers|vulkan-icd-loader) command -v vulkaninfo >/dev/null 2>&1 || missing+=("$p") ;;
shaderc) command -v glslc >/dev/null 2>&1 || missing+=("$p") ;;
whisper.cpp|whisper-cpp) command -v whisper-cli >/dev/null 2>&1 || command -v whisper-cpp >/dev/null 2>&1 || missing+=("$p") ;;
build-essential|gcc-c++) command -v g++ >/dev/null 2>&1 || missing+=("$p") ;;
*) missing+=("$p") ;;
esac
done
if (( ${#missing[@]} == 0 )); then
info "All system dependencies present."
else
info "Missing: ${missing[*]}"
case "$OS" in
arch) cmd="sudo pacman -S --needed ${missing[*]}" ;;
debian) cmd="sudo apt-get update && sudo apt-get install -y ${missing[*]}" ;;
fedora) cmd="sudo dnf install -y ${missing[*]}" ;;
macos) cmd="brew install ${missing[*]}" ;;
*) cmd="" ;;
esac
if [[ -n "$cmd" ]]; then
echo
echo " Suggested install command:"
echo " $cmd"
echo
if ask_yn "Run it now?" Y; then
bash -c "$cmd"
else
warn "Skipped. Re-run after installing manually."
fi
fi
fi
# ---------- whisper.cpp build ----------
build_whisper() {
local backend="$1"
if [[ "$backend" == "cpu" ]]; then
if command -v whisper-cli >/dev/null 2>&1 || command -v whisper-cpp >/dev/null 2>&1; then
info "Using system whisper.cpp; no build needed."
return 0
fi
err "No system whisper.cpp found and 'cpu' chosen; install the package or pick another backend."
return 1
fi
if [[ "$backend" == "skip" ]]; then
info "Skipping whisper.cpp."
return 0
fi
if [[ ! -d "$WHISPER_REPO/.git" ]]; then
info "Cloning whisper.cpp into $WHISPER_REPO"
mkdir -p "$(dirname "$WHISPER_REPO")"
git clone --depth=1 "$WHISPER_GIT" "$WHISPER_REPO"
else
if ask_yn "whisper.cpp already at $WHISPER_REPO — git pull latest?" N; then
git -C "$WHISPER_REPO" pull --ff-only || warn "git pull failed; continuing with current checkout"
fi
fi
local build_dir="$WHISPER_REPO/build-$backend"
info "Configuring $backend build in $build_dir"
case "$backend" in
cuda)
local host_cxx=""
[[ -x /usr/bin/g++-15 ]] && host_cxx="-DCMAKE_CUDA_HOST_COMPILER=/usr/bin/g++-15"
local arch="86"
if command -v nvidia-smi >/dev/null 2>&1; then
local cap; cap="$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader 2>/dev/null | head -1 | tr -d '.')"
[[ -n "$cap" ]] && arch="$cap"
fi
info " CUDA arch: sm_$arch"
PATH="/opt/cuda/bin:$PATH" cmake -S "$WHISPER_REPO" -B "$build_dir" \
-DGGML_CUDA=1 \
-DCMAKE_CUDA_ARCHITECTURES="$arch" \
$host_cxx \
-DCMAKE_BUILD_TYPE=Release
PATH="/opt/cuda/bin:$PATH" cmake --build "$build_dir" -j"$(ncpu)" --config Release
;;
rocm)
local gpu_arch="gfx1102"
if command -v rocminfo >/dev/null 2>&1; then
local detected; detected="$(rocminfo 2>/dev/null | awk '/Name:[[:space:]]+gfx/ {print $2; exit}')"
[[ -n "$detected" ]] && gpu_arch="$detected"
fi
info " AMDGPU target: $gpu_arch"
HIPCXX="${HIPCXX:-/opt/rocm/llvm/bin/clang++}" \
cmake -S "$WHISPER_REPO" -B "$build_dir" \
-DGGML_HIP=1 \
-DAMDGPU_TARGETS="$gpu_arch" \
-DCMAKE_BUILD_TYPE=Release
cmake --build "$build_dir" -j"$(ncpu)"
;;
vulkan)
cmake -S "$WHISPER_REPO" -B "$build_dir" \
-DGGML_VULKAN=1 \
-DCMAKE_BUILD_TYPE=Release
cmake --build "$build_dir" -j"$(ncpu)"
;;
metal)
# Metal is on by default on Apple Silicon; no special flag.
cmake -S "$WHISPER_REPO" -B "$build_dir" -DCMAKE_BUILD_TYPE=Release
cmake --build "$build_dir" -j"$(ncpu)"
;;
*)
err "Unknown backend: $backend"; return 1 ;;
esac
mkdir -p "$BINDIR"
local link="$BINDIR/whisper-cli-$backend"
ln -sf "$build_dir/bin/whisper-cli" "$link"
info "linked $link -> $build_dir/bin/whisper-cli"
}
hr
bold "Step 3 — whisper.cpp"
echo
build_whisper "$BACKEND"
# ---------- model download ----------
hr
bold "Step 4 — whisper model"
echo
mkdir -p "$MODELDIR"
existing_models=()
while IFS= read -r m; do existing_models+=("$(basename "$m")"); done < <(ls "$MODELDIR"/ggml-*.bin 2>/dev/null || true)
if (( ${#existing_models[@]} > 0 )); then
info "Existing models in $MODELDIR:"
for m in "${existing_models[@]}"; do info " - $m"; done
fi
if ask_yn "Download a model now?" $([ ${#existing_models[@]} -eq 0 ] && echo Y || echo N); then
echo
echo " Sizes (English-only suffix .en is faster on English audio):"
echo " tiny.en ~75MB"
echo " base.en ~142MB (default; good speed/quality balance)"
echo " small.en ~466MB"
echo " medium.en ~1.5GB"
echo " large-v3 ~2.9GB (multilingual, highest quality)"
echo
SIZE="$(ask_choice "Model" "base.en" tiny.en base.en small.en medium.en large-v3)"
target="$MODELDIR/ggml-$SIZE.bin"
if [[ -f "$target" ]]; then
info "$target already exists; skipping download."
else
info "Downloading ggml-$SIZE.bin ..."
curl -L --fail -o "$target" "$MODEL_BASE_URL/ggml-$SIZE.bin"
info "saved to $target"
fi
fi
# ---------- publish build + symlink ----------
hr
bold "Step 5 — publish binary"
echo
if ! command -v go >/dev/null 2>&1; then
err "Go is not installed; cannot build publish."
exit 1
fi
(cd "$REPO_DIR" && go build -o publish .)
info "built $REPO_DIR/publish"
mkdir -p "$BINDIR"
ln -sf "$REPO_DIR/publish" "$BINDIR/publish"
info "linked $BINDIR/publish -> $REPO_DIR/publish"
# ---------- summarizer hint ----------
hr
bold "Step 6 — summarizer"
echo
if command -v claude >/dev/null 2>&1; then
info "Found 'claude' CLI — default --summarizer claude-cli will work."
elif [[ -n "${ANTHROPIC_API_KEY:-}" ]]; then
info "ANTHROPIC_API_KEY set — use --summarizer claude-api."
else
warn "Neither 'claude' CLI nor ANTHROPIC_API_KEY found. Install Claude Code or export ANTHROPIC_API_KEY before running summaries."
fi
# ---------- done ----------
hr
bold "Done."
echo
echo " Quick sanity check:"
echo " publish --help"
echo " publish --doctor # via this script: bash scripts/install.sh --doctor"
echo
echo " Make sure $BINDIR is on your PATH."
case ":$PATH:" in
*":$BINDIR:"*) ;;
*) warn "$BINDIR is not currently on your PATH." ;;
esac