#!/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//dotfiles ~/dotfiles && cd ~/dotfiles && ./install.sh # or: # curl -fsSL https://raw.githubusercontent.com//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//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.. # --------------------------------------------------------------------------- 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 < -e / atuin login EOF