Files
dotfiles/install.sh
2026-05-28 18:23:35 -04:00

156 lines
6.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# install.sh — bootstrap zsh + oh-my-zsh + powerlevel10k + atuin and link dotfiles.
# Supports: Debian/Ubuntu (apt), Fedora/RHEL (dnf), Arch (pacman), macOS (brew).
# Idempotent: safe to re-run.
#
# Usage:
# git clone https://github.com/<you>/dotfiles ~/dotfiles && cd ~/dotfiles && ./install.sh
# or:
# curl -fsSL https://raw.githubusercontent.com/<you>/dotfiles/main/install.sh | bash
# (the curl form will git clone this repo to ~/dotfiles before running.)
set -euo pipefail
REPO_URL="${DOTFILES_REPO:-https://github.com/<you>/dotfiles}"
DOTFILES_DIR="${DOTFILES_DIR:-$HOME/dotfiles}"
log() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m!! \033[0m %s\n' "$*" >&2; }
die() { printf '\033[1;31mxx \033[0m %s\n' "$*" >&2; exit 1; }
# ---------------------------------------------------------------------------
# 0. If we're being curl|bashed, the script lives in /dev/stdin and there's no
# repo on disk. Clone, then re-exec from the checkout so $SCRIPT_DIR works.
# ---------------------------------------------------------------------------
if [ ! -t 0 ] && [ ! -f "${BASH_SOURCE[0]:-}" ] || [ "${BASH_SOURCE[0]:-}" = "/dev/stdin" ]; then
command -v git >/dev/null || die "git is required to clone the dotfiles repo"
if [ ! -d "$DOTFILES_DIR/.git" ]; then
log "Cloning $REPO_URL$DOTFILES_DIR"
git clone "$REPO_URL" "$DOTFILES_DIR"
fi
exec bash "$DOTFILES_DIR/install.sh" "$@"
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# ---------------------------------------------------------------------------
# 1. Detect package manager and install prerequisites.
# ---------------------------------------------------------------------------
SUDO=""
if [ "$(id -u)" -ne 0 ] && command -v sudo >/dev/null; then SUDO="sudo"; fi
install_pkgs() {
local pkgs=("$@")
if command -v apt-get >/dev/null; then
$SUDO apt-get update -y
$SUDO DEBIAN_FRONTEND=noninteractive apt-get install -y "${pkgs[@]}"
elif command -v dnf >/dev/null; then
$SUDO dnf install -y "${pkgs[@]}"
elif command -v pacman >/dev/null; then
$SUDO pacman -Sy --noconfirm --needed "${pkgs[@]}"
elif command -v brew >/dev/null; then
brew install "${pkgs[@]}"
else
die "No supported package manager found (apt/dnf/pacman/brew)."
fi
}
log "Installing prerequisites (zsh, git, curl)"
install_pkgs zsh git curl
# ---------------------------------------------------------------------------
# 2. Oh My Zsh.
# ---------------------------------------------------------------------------
export ZSH="$HOME/.oh-my-zsh"
if [ ! -d "$ZSH" ]; then
log "Installing oh-my-zsh"
RUNZSH=no CHSH=no KEEP_ZSHRC=yes \
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
else
log "oh-my-zsh already installed"
fi
ZSH_CUSTOM="${ZSH_CUSTOM:-$ZSH/custom}"
# ---------------------------------------------------------------------------
# 3. Powerlevel10k theme.
# ---------------------------------------------------------------------------
P10K_DIR="$ZSH_CUSTOM/themes/powerlevel10k"
if [ ! -d "$P10K_DIR" ]; then
log "Installing powerlevel10k"
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git "$P10K_DIR"
else
log "powerlevel10k already installed; pulling latest"
git -C "$P10K_DIR" pull --ff-only --quiet || warn "p10k pull failed (continuing)"
fi
# ---------------------------------------------------------------------------
# 4. zsh-autosuggestions plugin.
# ---------------------------------------------------------------------------
AUTOSUG_DIR="$ZSH_CUSTOM/plugins/zsh-autosuggestions"
if [ ! -d "$AUTOSUG_DIR" ]; then
log "Installing zsh-autosuggestions"
git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions "$AUTOSUG_DIR"
else
log "zsh-autosuggestions already installed"
fi
# ---------------------------------------------------------------------------
# 5. Atuin (shell history sync). Works on bash and zsh.
# ---------------------------------------------------------------------------
if ! command -v atuin >/dev/null && [ ! -x "$HOME/.atuin/bin/atuin" ]; then
log "Installing atuin"
curl --proto '=https' --tlsv1.2 -LsSf https://setup.atuin.sh | sh
else
log "atuin already installed"
fi
# ---------------------------------------------------------------------------
# 6. Link dotfiles. Backs up anything already there to *.backup.<timestamp>.
# ---------------------------------------------------------------------------
TS="$(date +%Y%m%d-%H%M%S)"
link_file() {
local src="$1" dest="$2"
if [ -e "$dest" ] && [ ! -L "$dest" ]; then
log "Backing up $dest$dest.backup.$TS"
mv "$dest" "$dest.backup.$TS"
elif [ -L "$dest" ]; then
rm "$dest"
fi
ln -s "$src" "$dest"
log "Linked $dest$src"
}
link_file "$SCRIPT_DIR/.zshrc" "$HOME/.zshrc"
link_file "$SCRIPT_DIR/.p10k.zsh" "$HOME/.p10k.zsh"
# ---------------------------------------------------------------------------
# 7. Change default shell to zsh (no-op if already zsh).
# ---------------------------------------------------------------------------
ZSH_BIN="$(command -v zsh)"
if [ -n "$ZSH_BIN" ] && [ "${SHELL:-}" != "$ZSH_BIN" ]; then
if grep -qx "$ZSH_BIN" /etc/shells 2>/dev/null || echo "$ZSH_BIN" | $SUDO tee -a /etc/shells >/dev/null; then
log "Setting login shell to $ZSH_BIN"
chsh -s "$ZSH_BIN" "$USER" || warn "chsh failed — run it manually: chsh -s $ZSH_BIN"
fi
else
log "Default shell already zsh"
fi
# ---------------------------------------------------------------------------
# 8. Done.
# ---------------------------------------------------------------------------
cat <<EOF
✔ Dotfiles installed.
Next steps:
• Start a new shell: exec zsh -l
• For machine-specific overrides (API keys, conda init, nvm, etc.),
drop them in: ~/.zshrc.local (already sourced by .zshrc, ignored by git)
• If powerlevel10k icons look wrong, install a Nerd Font:
https://github.com/romkatv/powerlevel10k#meslo-nerd-font-patched-for-powerlevel10k
• Atuin sync (optional): atuin register -u <user> -e <email> / atuin login
EOF