#!/bin/bash # ============================================================================= # 🚀 StianNOR — ZSH + NERD FONT SETUP v2.0 # Merges font_nerd_hack.sh + setup_zsh.sh # Supports: Arch/Manjaro, Debian/Ubuntu, Fedora, openSUSE, Alpine # ============================================================================= set -euo pipefail # --- Colors --- RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m' LINE="────────────────────────────────────────────────────────────────────────────" info() { echo -e "${CYAN}ℹ $*${RESET}"; } success() { echo -e "${GREEN}✅ $*${RESET}"; } warn() { echo -e "${YELLOW}⚠ $*${RESET}"; } error() { echo -e "${RED}❌ $*${RESET}"; } step() { echo -e "\n${BOLD}${BLUE}➤ $*${RESET}"; echo -e "${BLUE}${LINE}${RESET}"; } die() { error "$*"; exit 1; } # --- Error trap --- trap 'error "Failed at line $LINENO — command: $BASH_COMMAND"; exit 1' ERR # ============================================================================= # 1. DETECT PACKAGE MANAGER # ============================================================================= step "Detecting package manager" detect_pm() { if command -v pacman >/dev/null 2>&1; then PM="pacman" elif command -v apt >/dev/null 2>&1; then PM="apt" elif command -v dnf >/dev/null 2>&1; then PM="dnf" elif command -v zypper >/dev/null 2>&1; then PM="zypper" elif command -v apk >/dev/null 2>&1; then PM="apk" else die "No supported package manager found."; fi success "Package manager: $PM" } detect_pm pkg_install() { case "$PM" in pacman) sudo pacman -S --noconfirm --needed "$@" ;; apt) sudo apt install -y "$@" ;; dnf) sudo dnf install -y "$@" ;; zypper) sudo zypper install -y "$@" ;; apk) sudo apk add "$@" ;; esac } pkg_update() { case "$PM" in pacman) sudo pacman -Syu --noconfirm ;; apt) sudo apt update ;; dnf) sudo dnf check-update || true ;; zypper) sudo zypper refresh ;; apk) sudo apk update ;; esac } # ============================================================================= # 2. DETECT ENVIRONMENT # ============================================================================= step "Detecting desktop environment and terminals" get_distro() { [[ -f /etc/os-release ]] && { source /etc/os-release; echo "$NAME"; return; } uname -s } get_desktop() { local de="${XDG_CURRENT_DESKTOP,,}" [[ -z "$de" ]] && de="${DESKTOP_SESSION,,}" echo "$de" } get_terminals() { local terms=() for t in gnome-terminal konsole alacritty kitty wezterm xfce4-terminal mate-terminal tilix; do pgrep -x "$t" >/dev/null 2>&1 && terms+=("$t") done # Also check $TERM_PROGRAM [[ -n "${TERM_PROGRAM:-}" ]] && terms+=("${TERM_PROGRAM,,}") # Deduplicate printf '%s\n' "${terms[@]}" | sort -u | tr '\n' ' ' } DISTRO_NAME=$(get_distro) DESKTOP_ENV=$(get_desktop) TERMINALS=$(get_terminals) info "Distro: $DISTRO_NAME" info "Desktop: ${DESKTOP_ENV:-unknown}" info "Terminals: ${TERMINALS:-none detected}" # ============================================================================= # 3. INSTALL DEPENDENCIES # ============================================================================= step "Installing core dependencies" pkg_update case "$PM" in pacman) pkg_install curl git wget unzip ruby gcc make ;; apt) sudo apt install -y curl git wget unzip ruby ruby-dev gcc make build-essential ;; dnf) sudo dnf install -y curl git wget unzip ruby ruby-devel gcc make ;; zypper) sudo zypper install -y curl git wget unzip ruby ruby-devel gcc make ;; apk) sudo apk add curl git wget unzip ruby ruby-dev gcc make ;; esac success "Core dependencies installed" # ============================================================================= # 4. INSTALL HACK NERD FONT # ============================================================================= step "Installing Hack Nerd Font" FONT_DIR="/usr/local/share/fonts/nerd-fonts" FONT_ALIAS="Hack Nerd Font" FONT_TMPDIR=$(mktemp -d) FONT_VERSION="v3.4.0" # Ensure fontconfig tools available command -v fc-cache >/dev/null 2>&1 || pkg_install fontconfig cleanup_font_tmp() { rm -rf "$FONT_TMPDIR"; } trap 'cleanup_font_tmp; error "Failed at line $LINENO — command: $BASH_COMMAND"; exit 1' ERR trap 'cleanup_font_tmp' EXIT info "Downloading Hack Nerd Font $FONT_VERSION..." FONT_URL="https://github.com/ryanoasis/nerd-fonts/releases/download/${FONT_VERSION}/Hack.zip" if command -v curl >/dev/null 2>&1; then curl -fsSL "$FONT_URL" -o "$FONT_TMPDIR/Hack.zip" || die "Font download failed" else wget -q "$FONT_URL" -O "$FONT_TMPDIR/Hack.zip" || die "Font download failed" fi info "Extracting fonts..." unzip -q "$FONT_TMPDIR/Hack.zip" -d "$FONT_TMPDIR/Hack" || die "Font extraction failed" sudo mkdir -p "$FONT_DIR" # Only copy TTF/OTF — skip Windows-compat and licence files find "$FONT_TMPDIR/Hack" -name "*.ttf" -o -name "*.otf" | while read -r f; do [[ "$f" =~ (Windows|LICENSE|README) ]] && continue sudo cp "$f" "$FONT_DIR/" done sudo fc-cache -f "$FONT_DIR" success "Hack Nerd Font installed to $FONT_DIR" # --- Apply to Desktop Environment --- FONT_GENERAL="$FONT_ALIAS 12" FONT_MONO="$FONT_ALIAS 11" FONT_SMALL="$FONT_ALIAS 8" FONT_OTHER="$FONT_ALIAS 10" apply_kde_fonts() { local kwrite if command -v kwriteconfig6 >/dev/null 2>&1; then kwrite="kwriteconfig6" elif command -v kwriteconfig5 >/dev/null 2>&1; then kwrite="kwriteconfig5" else warn "kwriteconfig not found — KDE font config skipped"; return; fi $kwrite --file kdeglobals --group General --key font "$FONT_ALIAS,12,-1,5,50,0,0,0,0,0" $kwrite --file kdeglobals --group Fixed --key font "$FONT_ALIAS,11,-1,5,50,0,0,0,0,0" $kwrite --file kdeglobals --group Small --key font "$FONT_ALIAS,8,-1,5,50,0,0,0,0,0" $kwrite --file kdeglobals --group Toolbar --key font "$FONT_ALIAS,10,-1,5,50,0,0,0,0,0" $kwrite --file kdeglobals --group Menu --key font "$FONT_ALIAS,10,-1,5,50,0,0,0,0,0" $kwrite --file kdeglobals --group "Window Title" --key font "$FONT_ALIAS,10,-1,5,50,0,0,0,0,0" $kwrite --file kdeglobals --group General --key antialiasing "true" $kwrite --file kdeglobals --group General --key hinting "slight" $kwrite --file kdeglobals --group General --key subpixelRendering "rgb" $kwrite --file kdeglobals --group General --key forceFontDpi 96 # Konsole profiles find ~/.local/share/konsole -name "*.profile" 2>/dev/null \ -exec sed -i "s/^Font=.*/Font=$FONT_ALIAS,12,-1,5,50,0,0,0,0,0,Regular/" {} \; success "KDE fonts applied via $kwrite" } apply_gnome_fonts() { command -v gsettings >/dev/null 2>&1 || { warn "gsettings not found — GNOME font config skipped"; return; } gsettings set org.gnome.desktop.interface font-name "$FONT_GENERAL" gsettings set org.gnome.desktop.interface monospace-font-name "$FONT_MONO" gsettings set org.gnome.desktop.interface document-font-name "$FONT_GENERAL" # GNOME Terminal profiles via dconf if command -v dconf >/dev/null 2>&1; then while IFS= read -r profile; do [[ -z "$profile" ]] && continue dconf write "/org/gnome/terminal/legacy/profiles:/:${profile}/use-system-font" "false" dconf write "/org/gnome/terminal/legacy/profiles:/:${profile}/font" "'$FONT_MONO'" done < <(dconf list /org/gnome/terminal/legacy/profiles:/ 2>/dev/null | grep -oP '[^/:]+' || true) fi success "GNOME/Cinnamon fonts applied" } apply_xfce_fonts() { command -v xfconf-query >/dev/null 2>&1 || { warn "xfconf-query not found — XFCE font config skipped"; return; } xfconf-query -c xsettings -p /Gtk/FontName -s "$FONT_GENERAL" 2>/dev/null || true xfconf-query -c xfwm4 -p /general/font -s "$FONT_GENERAL" 2>/dev/null || true local xterm_conf="$HOME/.config/xfce4/terminal/terminalrc" if [[ -f "$xterm_conf" ]]; then sed -i "s/^FontName=.*/FontName=$FONT_MONO/" "$xterm_conf" fi success "XFCE fonts applied" } apply_mate_fonts() { command -v gsettings >/dev/null 2>&1 || { warn "gsettings not found — MATE font config skipped"; return; } gsettings set org.mate.interface font-name "$FONT_GENERAL" 2>/dev/null || true success "MATE fonts applied" } case "$DESKTOP_ENV" in *kde*|*plasma*) apply_kde_fonts ;; *gnome*|*cinnamon*|*unity*) apply_gnome_fonts ;; *xfce*) apply_xfce_fonts ;; *mate*) apply_mate_fonts ;; *) warn "Desktop '$DESKTOP_ENV' not specifically handled — font installed system-wide via fontconfig" ;; esac # --- Apply to detected terminal emulators --- apply_terminal_font() { local term="$1" case "$term" in alacritty) # Support both .yml (old) and .toml (new Alacritty >=0.13) local conf_toml="$HOME/.config/alacritty/alacritty.toml" local conf_yml="$HOME/.config/alacritty/alacritty.yml" if [[ -f "$conf_toml" ]]; then if grep -q '^\[font\]' "$conf_toml"; then sed -i '/^\[font\]/,/^\[/{s/^family = .*/family = "'"$FONT_ALIAS"'"/}' "$conf_toml" else printf '\n[font]\n[font.normal]\nfamily = "%s"\n[font.bold]\nfamily = "%s"\n[font.italic]\nfamily = "%s"\n\n[font.size]\nsize = 12.0\n' \ "$FONT_ALIAS" "$FONT_ALIAS" "$FONT_ALIAS" >> "$conf_toml" fi success "Alacritty TOML font updated" elif [[ -f "$conf_yml" ]]; then sed -i "/^ *family:/c\\ family: \"$FONT_ALIAS\"" "$conf_yml" sed -i "/^ *size:/c\\ size: 12" "$conf_yml" success "Alacritty YAML font updated" else warn "Alacritty config not found at $conf_toml or $conf_yml — skipping" fi ;; kitty) local kconf="$HOME/.config/kitty/kitty.conf" mkdir -p "$(dirname "$kconf")" if [[ -f "$kconf" ]]; then sed -i "/^font_family/c\\font_family $FONT_ALIAS" "$kconf" sed -i "/^font_size/c\\font_size 12.0" "$kconf" else printf 'font_family %s\nfont_size 12.0\n' "$FONT_ALIAS" > "$kconf" fi success "Kitty font configured" ;; wezterm) local wconf="$HOME/.config/wezterm/wezterm.lua" if [[ -f "$wconf" ]]; then warn "WezTerm config found at $wconf — add this manually:" echo " config.font = wezterm.font('$FONT_ALIAS')" fi ;; konsole) find ~/.local/share/konsole -name "*.profile" 2>/dev/null \ -exec sed -i "s/^Font=.*/Font=$FONT_ALIAS,12,-1,5,50,0,0,0,0,0,Regular/" {} \; success "Konsole profiles updated" ;; xfce4-terminal) local xterm_conf="$HOME/.config/xfce4/terminal/terminalrc" [[ -f "$xterm_conf" ]] && sed -i "s/^FontName=.*/FontName=$FONT_MONO/" "$xterm_conf" success "XFCE Terminal font updated" ;; gnome-terminal) ;; # handled by apply_gnome_fonts via dconf *) warn "Terminal '$term' font config not supported — set manually" ;; esac } for term in $TERMINALS; do apply_terminal_font "$term" done # --- fontconfig fallback (monospace alias) --- sudo tee /etc/fonts/local.conf > /dev/null < monospace ${FONT_ALIAS} Hack Nerd Font Mono ${FONT_ALIAS} FONTCONF sudo fc-cache -f success "fontconfig monospace alias set" # NOTE: TTY/vconsole fonts must be bitmap/psf fonts — Nerd Font TTFs # cannot be used in vconsole.conf. Skipping to avoid breaking TTY. # ============================================================================= # 5. INSTALL ZSH # ============================================================================= step "Installing Zsh" if ! command -v zsh >/dev/null 2>&1; then pkg_install zsh success "Zsh installed" else success "Zsh already installed: $(zsh --version)" fi # ============================================================================= # 6. UPDATE RUBYGEMS + INSTALL COLORLS # ============================================================================= step "Setting up Ruby gems (colorls)" export GEM_HOME="$HOME/.gem" export PATH="$PATH:$GEM_HOME/bin" USER_GEM_BIN="$(ruby -e 'puts Gem.user_dir' 2>/dev/null)/bin" # Update gems — skip system update on apt (breaks Debian policy) if [[ "$PM" != "apt" ]]; then gem update --system 2>/dev/null && success "RubyGems system updated" \ || warn "RubyGems system update failed — continuing" fi gem update 2>/dev/null && success "All gems updated" || warn "gem update had warnings — continuing" if gem list -i colorls >/dev/null 2>&1; then success "colorls already installed" else info "Installing colorls..." gem install --user-install colorls || die "colorls install failed" success "colorls installed" fi # Add gem bin to PATH in both .zshrc and .bashrc so it works during this session too for rc in "$HOME/.zshrc" "$HOME/.bashrc"; do if [[ -f "$rc" ]] && ! grep -q "$USER_GEM_BIN" "$rc" 2>/dev/null; then echo "export PATH=\"\$PATH:$USER_GEM_BIN\"" >> "$rc" info "Added gem bin to PATH in $rc" fi done export PATH="$PATH:$USER_GEM_BIN" # ============================================================================= # 7. INSTALL FASTFETCH # ============================================================================= step "Installing fastfetch" if command -v fastfetch >/dev/null 2>&1; then success "fastfetch already installed: $(fastfetch --version 2>/dev/null | head -1)" else info "Trying package manager first..." installed=false case "$PM" in pacman) sudo pacman -S --noconfirm --needed fastfetch 2>/dev/null && installed=true ;; apt) # fastfetch not in standard apt — try PPA or direct .deb download if sudo add-apt-repository -y ppa:zhangsongcui3371/fastfetch 2>/dev/null; then sudo apt update && sudo apt install -y fastfetch && installed=true fi ;; dnf) sudo dnf install -y fastfetch 2>/dev/null && installed=true ;; zypper) sudo zypper install -y fastfetch 2>/dev/null && installed=true ;; apk) sudo apk add fastfetch 2>/dev/null && installed=true ;; esac if [[ "$installed" == false ]]; then warn "Package manager install failed — downloading latest binary release..." ARCH=$(uname -m) [[ "$ARCH" == "x86_64" ]] && ARCH_NAME="amd64" || ARCH_NAME="$ARCH" FF_URL=$(curl -s https://api.github.com/repos/fastfetch-cli/fastfetch/releases/latest \ | grep "browser_download_url" \ | grep "linux-${ARCH_NAME}.tar.gz" \ | head -1 | cut -d'"' -f4) if [[ -n "$FF_URL" ]]; then FF_TMP=$(mktemp -d) curl -fsSL "$FF_URL" -o "$FF_TMP/fastfetch.tar.gz" tar -xzf "$FF_TMP/fastfetch.tar.gz" -C "$FF_TMP" sudo install -m755 "$FF_TMP"/fastfetch*/usr/bin/fastfetch /usr/local/bin/fastfetch 2>/dev/null \ || sudo cp "$(find "$FF_TMP" -name fastfetch -type f | head -1)" /usr/local/bin/fastfetch sudo chmod +x /usr/local/bin/fastfetch rm -rf "$FF_TMP" installed=true fi fi if [[ "$installed" == true ]] && command -v fastfetch >/dev/null 2>&1; then success "fastfetch installed: $(fastfetch --version 2>/dev/null | head -1)" else warn "fastfetch could not be installed automatically — install it manually" fi fi # ============================================================================= # 8. INSTALL OH MY ZSH # ============================================================================= step "Installing Oh My Zsh" if [[ -f "$HOME/.oh-my-zsh/oh-my-zsh.sh" ]]; then success "Oh My Zsh already installed" else info "Installing Oh My Zsh..." rm -rf "$HOME/.oh-my-zsh" RUNZSH=no KEEP_ZSHRC=yes \ sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" \ || die "Oh My Zsh install failed" success "Oh My Zsh installed" fi ZSH_CUSTOM="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}" # ============================================================================= # 9. INSTALL POWERLEVEL10K # ============================================================================= step "Installing Powerlevel10k theme" P10K_DIR="$ZSH_CUSTOM/themes/powerlevel10k" if [[ -d "$P10K_DIR" ]]; then info "Updating Powerlevel10k..." git -C "$P10K_DIR" pull --ff-only 2>/dev/null && success "Powerlevel10k updated" || warn "p10k update failed — skipping" else git clone --depth=1 https://github.com/romkatv/powerlevel10k.git "$P10K_DIR" \ || die "Powerlevel10k install failed" success "Powerlevel10k installed" fi # ============================================================================= # 10. INSTALL ZSH PLUGINS # ============================================================================= step "Installing Zsh plugins" declare -A ZSH_PLUGINS=( [zsh-autosuggestions]="https://github.com/zsh-users/zsh-autosuggestions" [zsh-syntax-highlighting]="https://github.com/zsh-users/zsh-syntax-highlighting" [zsh-completions]="https://github.com/zsh-users/zsh-completions" ) for plugin in "${!ZSH_PLUGINS[@]}"; do local_dir="$ZSH_CUSTOM/plugins/$plugin" if [[ -d "$local_dir" ]]; then git -C "$local_dir" pull --ff-only 2>/dev/null && success "$plugin updated" || warn "$plugin update skipped" else info "Installing $plugin..." git clone "${ZSH_PLUGINS[$plugin]}" "$local_dir" || warn "$plugin install failed — continuing" success "$plugin installed" fi done # ============================================================================= # 11. COPY .zshrc AND .p10k.zsh # ============================================================================= step "Applying .zshrc and .p10k.zsh" # Use the directory where THIS script lives as the source SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" copy_if_exists() { local src="$1" dst="$2" label="$3" if [[ -f "$src" ]]; then cp "$src" "$dst" success "$label → $dst" else warn "$src not found — $label not copied" fi } copy_if_exists "$SCRIPT_DIR/.zshrc" "$HOME/.zshrc" ".zshrc" copy_if_exists "$SCRIPT_DIR/.p10k.zsh" "$HOME/.p10k.zsh" ".p10k.zsh" # Ensure gem bin and zsh path are in the copied .zshrc for line in \ "export PATH=\"\$PATH:$USER_GEM_BIN\"" \ "export GEM_HOME=\"\$HOME/.gem\""; do grep -qF "$line" "$HOME/.zshrc" 2>/dev/null || echo "$line" >> "$HOME/.zshrc" done # ============================================================================= # 12. SET ZSH AS DEFAULT SHELL # ============================================================================= step "Setting Zsh as default shell" ZSH_PATH=$(command -v zsh) if [[ "$SHELL" == "$ZSH_PATH" ]]; then success "Zsh is already the default shell" else # Make sure zsh is in /etc/shells grep -qxF "$ZSH_PATH" /etc/shells || echo "$ZSH_PATH" | sudo tee -a /etc/shells if chsh -s "$ZSH_PATH" 2>/dev/null; then success "Default shell set to $ZSH_PATH" info "Log out and back in for the shell change to take effect" else warn "Could not change default shell — run manually: chsh -s $ZSH_PATH" fi fi # ============================================================================= # DONE # ============================================================================= echo "" echo -e "${LINE}" echo -e "${BOLD}${GREEN} ✅ StianNOR Zsh + Nerd Font Setup Complete!${RESET}" echo -e "${LINE}" echo -e " ${CYAN}Font:${RESET} Hack Nerd Font installed system-wide" echo -e " ${CYAN}Shell:${RESET} Zsh + Oh My Zsh + Powerlevel10k" echo -e " ${CYAN}Plugins:${RESET} autosuggestions, syntax-highlighting, completions" echo -e " ${CYAN}Tools:${RESET} colorls, fastfetch" echo -e "${LINE}" echo -e " ${YELLOW}Next steps:${RESET}" echo -e " 1. Log out and back in (shell change + font)" echo -e " 2. Run ${CYAN}p10k configure${RESET} to set up your prompt" echo -e " 3. In your terminal settings, select ${CYAN}Hack Nerd Font${RESET} manually if needed" echo -e "${LINE}" echo ""