#!/bin/bash set -euo pipefail # ====================== Flags ====================== WITH_TIDE=0 KEEP_PROMPT=0 REMOVE_TIDE=0 VERBOSE=0 NO_PLUGINS=0 UPDATE_MODE="prompt" # prompt | yes | no while [[ $# -gt 0 ]]; do case "$1" in --with-tide) WITH_TIDE=1 ;; --keep-prompt) KEEP_PROMPT=1 ;; --remove-tide) REMOVE_TIDE=1 ;; --verbose) VERBOSE=1 ;; --no-plugins) NO_PLUGINS=1 ;; --update-check) UPDATE_MODE="yes" ;; --no-update-check) UPDATE_MODE="no" ;; --) shift; break ;; *) echo "Unknown option: $1"; exit 1 ;; esac shift done # Default plugin set (safe; no prompt change) PLUGINS=("jethrokuan/z" "jorgebucaran/nvm.fish") [[ $WITH_TIDE -eq 1 ]] && PLUGINS+=("ilancosman/tide") # ====================== UI Helpers ====================== log() { echo -e "• $*"; } ok() { echo -e "✓ $*"; } warn(){ echo -e "! $*" >&2; } padright() { printf "%-28s" "$1"; } draw_bar() { local cur="$1" total="$2" label="$3" width=40 (( total == 0 )) && total=1 local percent=$(( cur * 100 / total )) (( percent > 100 )) && percent=100 local filled=$(( width * cur / total )) (( filled > width )) && filled=$width local empty=$(( width - filled )) printf "\rInstalling: %s [%s%s] %3d%%" \ "$(padright "$label")" \ "$(printf '%0.s#' $(seq 1 $filled))" \ "$(printf '%0.s.' $(seq 1 $empty))" \ "$percent" } prompt_yn_tty() { local prompt="${1:-Proceed (y/N): }" local d="${2:-N}" ans="" if [ -r /dev/tty ] && [ -w /dev/tty ]; then read -r -p "$prompt" ans /dev/null; then log "Installing Homebrew…" /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile eval "$(/opt/homebrew/bin/brew shellenv)" ok "Homebrew installed" fi elif command -v apt &>/dev/null; then OS="debian"; PKG_MGR="apt" elif command -v dnf &>/dev/null; then OS="rhel"; PKG_MGR="dnf" elif command -v yum &>/dev/null; then OS="rhel"; PKG_MGR="yum" elif command -v pacman &>/dev/null; then OS="arch"; PKG_MGR="pacman" else warn "Unsupported platform"; exit 1; fi ok "Detected ${OS} (${PKG_MGR})" } # ====================== Debian repo/key ====================== install_gpg_if_needed() { if [[ "${OS}" == "debian" ]] && ! command -v gpg &>/dev/null; then log "Installing gnupg…" sudo apt update && sudo apt install -y gnupg ok "gnupg installed" fi } add_fish_repo_debian() { log "Adding Fish repo (Debian)…" echo 'deb http://download.opensuse.org/repositories/shells:/fish:/release:/4/Debian_12/ /' \ | sudo tee /etc/apt/sources.list.d/fish.list >/dev/null curl -fsSL https://download.opensuse.org/repositories/shells:fish:release:4/Debian_12/Release.key \ | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/fish.gpg >/dev/null sudo apt update ok "Repo added" } # ====================== Fish install/update ====================== install_fish() { log "Installing Fish…" case "$OS" in debian) add_fish_repo_debian; sudo apt install -y fish ;; rhel) sudo "$PKG_MGR" install -y fish ;; arch) sudo pacman -Sy --noconfirm fish ;; macos) brew install fish ;; esac ok "Fish installed" } should_check_updates() { case "$UPDATE_MODE" in yes) return 0 ;; no) return 1 ;; prompt) prompt_yn_tty "Check for Fish updates (may run a repo refresh)? (y/N): " "N" return $? ;; esac } update_fish_if_available() { log "Checking Fish updates…" case "$PKG_MGR" in apt) if should_check_updates; then sudo apt update apt list --upgradable 2>/dev/null | grep -q "^fish/" && sudo apt install -y fish || true else ok "Skipped update check" fi ;; dnf|yum) if should_check_updates; then sudo "$PKG_MGR" check-update || true "$PKG_MGR" list updates | grep -q "^fish" && sudo "$PKG_MGR" update -y fish || true else ok "Skipped update check" fi ;; pacman) if should_check_updates; then sudo pacman -Sy --noconfirm pacman -Qu | grep -q "^fish" && sudo pacman -S --noconfirm fish || true else ok "Skipped update check" fi ;; brew) if should_check_updates; then brew update brew outdated | grep -q "^fish" && brew upgrade fish || true else ok "Skipped update check" fi ;; esac ok "Fish up to date" } # ====================== Fisher & Plugins ====================== ensure_fisher() { if fish -c 'functions -q fisher' /dev/null 2>&1; then ok "Fisher already present" else log "Installing Fisher…" fish -c 'curl -sL https://git.io/fisher | source && fisher install jorgebucaran/fisher' /dev/null 2>&1 ok "Fisher installed" fi } install_plugins() { [[ $NO_PLUGINS -eq 1 ]] && { ok "Skipping plugins (per flag)"; return; } # Current installed plugins local installed installed="$(fish -c 'fisher list' /dev/null || true)" # Determine missing plugins local missing=() for p in "${PLUGINS[@]}"; do if ! grep -Fxq "$p" <<<"$installed"; then missing+=("$p") fi done # Nothing to do? if [[ ${#missing[@]} -eq 0 ]]; then ok "All requested plugins already installed" else if [[ $VERBOSE -eq 1 ]]; then for p in "${missing[@]}"; do echo "Installing (verbose): $p" fish -c "fisher install $p" /dev/null 2>&1 ) & pid=$! while kill -0 "$pid" 2>/dev/null; do draw_bar "$((i-1))" "$total" "$p" sleep 0.08 done draw_bar "$i" "$total" "$p" done echo ok "Plugins installed" fi fi # If Tide installed but user wants to keep default prompt if [[ $WITH_TIDE -eq 1 && $KEEP_PROMPT -eq 1 ]]; then log "Keeping default Fish prompt (disabling Tide init)…" rm -f ~/.config/fish/functions/fish_prompt.fish \ ~/.config/fish/conf.d/_tide_init.fish 2>/dev/null || true ok "Default prompt preserved" fi } remove_tide_if_requested() { [[ $REMOVE_TIDE -eq 0 ]] && return ensure_fisher # make sure fisher exists so removal works # Only remove if installed if fish -c 'fisher list' /dev/null | grep -Fxq "ilancosman/tide"; then log "Removing Tide…" if [[ $VERBOSE -eq 1 ]]; then fish -c "fisher remove ilancosman/tide" /dev/null 2>&1 fi fi # Clean any prompt overrides rm -f ~/.config/fish/functions/fish_prompt.fish \ ~/.config/fish/conf.d/_tide_init.fish 2>/dev/null || true ok "Tide removed and default prompt restored" } # ====================== Main ====================== detect_platform if command -v fish &>/dev/null; then ok "Fish already installed" update_fish_if_available else install_gpg_if_needed install_fish fi ensure_fisher install_plugins remove_tide_if_requested ok "Fish + Fisher + plugins ready on ${OS}"