Initial push to gitea
This commit is contained in:
480
scripts/install.sh
Executable file
480
scripts/install.sh
Executable 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
|
||||
Reference in New Issue
Block a user