This commit is contained in:
2025-08-09 16:59:55 +01:00
parent 2d222d4066
commit 9d4089f515

View File

@@ -1,261 +1,184 @@
#!/bin/bash #!/bin/bash
set -euo pipefail set -e
# ====================== Flags ====================== # 🎯 Detect OS & package manager
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/tty || true
[[ ${ans:-$d} =~ ^[Yy]$ ]]
else
[[ "$d" =~ ^[Yy]$ ]]
fi
}
# ====================== Platform Detect ======================
detect_platform() { detect_platform() {
if [[ "$OSTYPE" == "darwin"* ]]; then if [[ "$OSTYPE" == "darwin"* ]]; then
OS="macos"; PKG_MGR="brew" OS="macos"; PKG_MGR="brew"
if ! command -v brew &>/dev/null; then if ! command -v brew &>/dev/null; then
log "Installing Homebrew" echo "🍺 Installing Homebrew..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)" 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
echo "❌ Unsupported platform"; exit 1
fi fi
elif command -v apt &>/dev/null; then OS="debian"; PKG_MGR="apt" echo "✓ Detected $OS ($PKG_MGR)"
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 (Debian/Ubuntu)
install_gpg_if_needed() { install_gpg_if_needed() {
if [[ "${OS}" == "debian" ]] && ! command -v gpg &>/dev/null; then if [[ "$OS" == "debian" ]] && ! command -v gpg &>/dev/null; then
log "Installing gnupg…" echo "🔑 Installing gpg..."
sudo apt update && sudo apt install -y gnupg sudo apt update && sudo apt install -y gnupg
ok "gnupg installed" fi
fi
} }
# 📦 Add Fish repo (Debian/Ubuntu)
add_fish_repo_debian() { add_fish_repo_debian() {
log "Adding Fish repo (Debian)…" echo "Adding Fish repo..."
echo 'deb http://download.opensuse.org/repositories/shells:/fish:/release:/4/Debian_12/ /' \ echo 'deb http://download.opensuse.org/repositories/shells:/fish:/release:/4/Debian_12/ /' | \
| sudo tee /etc/apt/sources.list.d/fish.list >/dev/null 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 \ 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 gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/fish.gpg > /dev/null
sudo apt update sudo apt update
ok "Repo added"
} }
# ====================== Fish install/update ====================== # 🐟 Install Fish shell
install_fish() { install_fish() {
log "Installing Fish" echo "Installing Fish..."
case "$OS" in case "$OS" in
debian) add_fish_repo_debian; sudo apt install -y fish ;; debian) add_fish_repo_debian; sudo apt install -y fish ;;
rhel) sudo "$PKG_MGR" install -y fish ;; rhel) sudo $PKG_MGR install -y fish ;;
arch) sudo pacman -Sy --noconfirm fish ;; arch) sudo pacman -Sy --noconfirm fish ;;
macos) brew install fish ;; macos) brew install fish ;;
esac esac
ok "Fish installed"
} }
should_check_updates() { # 🔎 Detect if an update seems available (without doing a refresh)
case "$UPDATE_MODE" in fish_update_available() {
yes) return 0 ;; case "$PKG_MGR" in
no) return 1 ;; apt)
prompt) # Compare Installed vs Candidate using current cache (no apt update)
prompt_yn_tty "Check for Fish updates (may run a repo refresh)? (y/N): " "N" local installed candidate
return $? ;; installed="$(dpkg-query -W -f='${Version}\n' fish 2>/dev/null || true)"
esac candidate="$(apt-cache policy fish 2>/dev/null | awk '/Candidate:/ {print $2}')"
[[ -n "$installed" && -n "$candidate" && "$candidate" != "(none)" && "$candidate" != "$installed" ]]
;;
dnf|yum)
# Fast check; may contact repos briefly but doesn't install
$PKG_MGR -q check-update fish >/dev/null 2>&1
[[ $? -eq 100 ]] # 100 = updates available
;;
pacman)
# No refresh; just check queued upgrades
pacman -Qu 2>/dev/null | grep -q '^fish '
;;
brew)
# No brew update; check current outdated list
brew outdated --quiet 2>/dev/null | grep -q '^fish$'
;;
esac
} }
update_fish_if_available() { # ♻️ Update flow (only prompt if an update looks available)
log "Checking Fish updates…" maybe_update_fish() {
case "$PKG_MGR" in echo "• Checking Fish updates…"
apt) if fish_update_available; then
if should_check_updates; then read -rp "A newer Fish appears available. Refresh repos and update now? (y/N): " confirm
sudo apt update if [[ "$confirm" =~ ^[Yy]$ ]]; then
apt list --upgradable 2>/dev/null | grep -q "^fish/" && sudo apt install -y fish || true case "$PKG_MGR" in
else apt)
ok "Skipped update check" sudo apt update
fi sudo apt install -y fish && echo "✓ Fish updated"
;; ;;
dnf|yum) dnf|yum)
if should_check_updates; then sudo $PKG_MGR check-update || true
sudo "$PKG_MGR" check-update || true sudo $PKG_MGR update -y fish && echo "✓ Fish updated"
"$PKG_MGR" list updates | grep -q "^fish" && sudo "$PKG_MGR" update -y fish || true ;;
else pacman)
ok "Skipped update check" sudo pacman -Sy --noconfirm
fi sudo pacman -S --noconfirm fish && echo "✓ Fish updated"
;; ;;
pacman) brew)
if should_check_updates; then brew update
sudo pacman -Sy --noconfirm brew upgrade fish && echo "✓ Fish updated"
pacman -Qu | grep -q "^fish" && sudo pacman -S --noconfirm fish || true ;;
else esac
ok "Skipped update check" else
fi echo "✓ Skipped update"
;; fi
brew) else
if should_check_updates; then echo "✓ No update detected (based on current cache)"
brew update fi
brew outdated | grep -q "^fish" && brew upgrade fish || true
else
ok "Skipped update check"
fi
;;
esac
ok "Fish up to date"
} }
# ====================== Fisher & Plugins ====================== # 🎣 Install Fisher
ensure_fisher() { install_fisher() {
if fish -c 'functions -q fisher' </dev/null >/dev/null 2>&1; then if fish -c "functions -q fisher" &>/dev/null; then
ok "Fisher already present" echo "Fisher already present"
else else
log "Installing Fisher" echo "Installing Fisher..."
fish -c 'curl -sL https://git.io/fisher | source && fisher install jorgebucaran/fisher' </dev/null >/dev/null 2>&1 # isolate stdin so curl|bash doesnt leak into fish
ok "Fisher installed" fish -c 'curl -sL https://git.io/fisher | source && fisher install jorgebucaran/fisher' </dev/null
fi fi
} }
# 🧩 Install plugins (idempotent)
install_plugins() { install_plugins() {
[[ $NO_PLUGINS -eq 1 ]] && { ok "Skipping plugins (per flag)"; return; } local plugins=( jethrokuan/z ilancosman/tide jorgebucaran/nvm.fish )
local install_list=()
# Current installed plugins local installed
local installed installed="$(fish -c 'fisher list' </dev/null 2>/dev/null || true)"
installed="$(fish -c 'fisher list' </dev/null 2>/dev/null || true)" for p in "${plugins[@]}"; do
if ! grep -Fxq "$p" <<<"$installed"; then
# Determine missing plugins install_list+=("$p")
local missing=() fi
for p in "${PLUGINS[@]}"; do done
if ! grep -Fxq "$p" <<<"$installed"; then if [[ ${#install_list[@]} -gt 0 ]]; then
missing+=("$p") echo "• Installing plugins: ${install_list[*]}"
fi fish -c "fisher install ${install_list[*]}" </dev/null
done echo "✓ Plugins installed"
# 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
done
ok "Plugins installed"
else else
local total=${#missing[@]} echo "✓ All requested plugins already installed"
local i=0
for p in "${missing[@]}"; do
i=$((i+1))
draw_bar "$((i-1))" "$total" "$p"
( fish -c "fisher install $p" </dev/null >/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
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() { # 🐚 Ask to set Fish as default shell (only if not already)
[[ $REMOVE_TIDE -eq 0 ]] && return set_fish_default() {
local fish_path current_shell
fish_path="$(command -v fish)"
current_shell="$(getent passwd "$USER" | cut -d: -f7)"
ensure_fisher # make sure fisher exists so removal works if [[ "$current_shell" == "$fish_path" ]]; then
echo "✓ Fish is already your default shell"
# Only remove if installed return
if fish -c 'fisher list' </dev/null 2>/dev/null | grep -Fxq "ilancosman/tide"; then
log "Removing Tide…"
if [[ $VERBOSE -eq 1 ]]; then
fish -c "fisher remove ilancosman/tide" </dev/null
else
fish -c "fisher remove ilancosman/tide" </dev/null >/dev/null 2>&1
fi fi
fi
# Clean any prompt overrides read -rp "Set Fish as your default shell? (y/N): " confirm
rm -f ~/.config/fish/functions/fish_prompt.fish \ if [[ "$confirm" =~ ^[Yy]$ ]]; then
~/.config/fish/conf.d/_tide_init.fish 2>/dev/null || true if ! grep -q "^$fish_path$" /etc/shells; then
ok "Tide removed and default prompt restored" echo "$fish_path" | sudo tee -a /etc/shells >/dev/null
fi
chsh -s "$fish_path"
echo "✓ Fish set as default shell (applies to new sessions)"
else
echo "✓ Keeping current default shell"
fi
} }
# ====================== Main ====================== # 🚀 Main flow
detect_platform detect_platform
install_gpg_if_needed
if command -v fish &>/dev/null; then if command -v fish &>/dev/null; then
ok "Fish already installed" echo "Fish already installed"
update_fish_if_available maybe_update_fish
else else
install_gpg_if_needed install_fish
install_fish
fi fi
ensure_fisher install_fisher
install_plugins install_plugins
remove_tide_if_requested set_fish_default
ok "Fish + Fisher + plugins ready on ${OS}" echo "Fish + Fisher + plugins ready on $OS"